Loading...
Searching...
No Matches
CommandLine.cpp
Go to the documentation of this file.
1/*
2** This file is part of eOn.
3**
4** SPDX-License-Identifier: BSD-3-Clause
5**
6** Copyright (c) 2010--present, eOn Development Team
7** All rights reserved.
8**
9** Repo:
10** https://github.com/TheochemUI/eOn
11*/
12#include "eon/CommandLine.h"
13#include "eon/Matter.h"
14#include "eon/Parameters.h"
15#include "eon/Potential.h"
16#include "version.h"
17
18#ifdef WITH_SERVE_MODE
19#include "eon/ServeMode.h"
20#endif
21
22#include <argum.h>
23
24#include <cstdlib>
25#include <fstream>
26#include <iomanip>
27#include <iostream>
28#include <memory>
29#include <optional>
30#include <sstream>
31#include <string>
32
33using namespace Argum;
34
35// Create a colorizer with default color scheme
36constexpr auto colorScheme = basicDefaultColorScheme<char>;
37BasicColorizer<char> colorizer(colorScheme);
38
39void singlePoint(std::unique_ptr<Matter> matter) {
40 std::cout << "Energy: " << std::fixed << std::setprecision(15)
41 << matter->getPotentialEnergy() << std::endl;
42 std::cout << "(free) Forces: \n" << matter->getForcesFree() << "\n";
43 std::cout << "Max atom force: " << std::scientific << matter->maxForce()
44 << std::endl;
45}
46
47void minimize(std::unique_ptr<Matter> matter, const std::string &confileout) {
48 matter->relax(false, false);
49 if (!confileout.empty()) {
50 std::cout << "Saving relaxed structure to " << confileout << std::endl;
51 } else {
52 std::cout << "No output file specified, not saving" << std::endl;
53 }
54 if (!eonc::io::io_ok(matter->matter2con(confileout))) {
55 std::cerr << "Failed to write " << confileout << std::endl;
56 }
57}
58
60 std::cout << colorizer.heading("Compile-time features:") << std::endl;
61 // Parse FEATURES_STRING and colorize enabled/disabled
62 std::istringstream stream(FEATURES_STRING);
63 std::string line;
64 while (std::getline(stream, line)) {
65 if (line.find(": enabled") != std::string::npos) {
66 // Extract feature name and colorize "enabled" with warning (yellow)
67 size_t colonPos = line.find(": ");
68 if (colonPos != std::string::npos) {
69 std::string featureName = line.substr(0, colonPos + 2);
70 std::string status = line.substr(colonPos + 2);
71 std::cout << featureName << colorizer.warning(status) << std::endl;
72 } else {
73 std::cout << line << std::endl;
74 }
75 } else if (line.find(": disabled") != std::string::npos) {
76 // Extract feature name and colorize "disabled" with error (red)
77 size_t colonPos = line.find(": ");
78 if (colonPos != std::string::npos) {
79 std::string featureName = line.substr(0, colonPos + 2);
80 std::string status = line.substr(colonPos + 2);
81 std::cout << featureName << colorizer.error(status) << std::endl;
82 } else {
83 std::cout << line << std::endl;
84 }
85 } else {
86 std::cout << line << std::endl;
87 }
88 }
89}
90
91namespace eonc {
92
93void commandLine(int argc, char **argv) {
94 bool sflag = false, mflag = false, pflag = false, cflag = false;
95 double optConvergedForce = 0.001;
96 std::string potential;
97 std::string confile;
98 std::string confileout;
99 std::string optimizer("cg");
100 std::optional<std::string> config_path;
101
102#ifdef WITH_SERVE_MODE
103 std::optional<std::string> serve_spec;
104 std::optional<std::string> serve_host("localhost");
105 std::optional<uint16_t> serve_port(12345);
106 std::optional<size_t> replicas(1);
107 std::optional<bool> gateway(false);
108#endif
109
110 auto params = Parameters{};
111
112 const char *progname = (argc ? argv[0] : "eonclient");
113
114 Parser parser;
115
116 parser.add(Option("--help", "-h")
117 .help("show this help message and exit")
118 .handler([&]() {
119 // Format help with color
120 auto helpText = parser.formatHelp(progname);
121 std::cout << colorizer.heading("eOn Client - Help")
122 << "\n\n";
123 std::cout << helpText;
124 std::exit(EXIT_SUCCESS);
125 }));
126
127 parser.add(Option("--version", "-v")
128 .help("Print version information")
129 .handler([&]() {
130 std::cout << VERSION_STRING << std::endl;
131 std::exit(EXIT_SUCCESS);
132 }));
133
134 parser.add(
135 Option("--features").help("Print compile-time features").handler([&]() {
137 std::exit(EXIT_SUCCESS);
138 }));
139
140 parser.add(Option("--minimize", "-m")
141 .help("Minimization of inputConfile saves to outputConfile")
142 .handler([&]() { mflag = true; }));
143
144 parser.add(Option("--single", "-s")
145 .help("Single point energy of inputConfile")
146 .handler([&]() { sflag = true; }));
147
148 parser.add(Option("--compare", "-c")
149 .help("Compare structures of inputConfile to outputConfile")
150 .handler([&]() { cflag = true; }));
151
152 parser.add(
153 Option("--optimizer", "-o")
154 .argName("METHOD")
155 .help("Optimization method")
156 .handler([&](const std::string_view &value) { optimizer = value; }));
157
158 parser.add(Option("--force", "-f")
159 .argName("VALUE")
160 .help("Convergence force")
161 .handler([&](const std::string_view &value) {
162 optConvergedForce = parseFloatingPoint<double>(value);
163 }));
164
165 parser.add(Option("--tolerance", "-t")
166 .argName("VALUE")
167 .help("Distance tolerance")
168 .handler([&](const std::string_view &value) {
169 params.structure_comparison_options.distance_difference =
170 parseFloatingPoint<double>(value);
171 }));
172
173 parser.add(Option("--potential", "-p")
174 .argName("POTENTIAL")
175 .help("The potential (e.g. qsc, lj, eam_al)")
176 .handler([&](const std::string_view &value) {
177 pflag = true;
178 potential = value;
179 }));
180
181#ifdef WITH_SERVE_MODE
182 parser.add(
183 Option("--serve")
184 .argName("SPEC")
185 .help("Serve potential(s) over rgpot Cap'n Proto RPC. "
186 "Spec: 'potential:port' or 'pot1:port1,pot2:port2'")
187 .handler([&](const std::string_view &value) { serve_spec = value; }));
188
189 parser.add(
190 Option("--serve-host")
191 .argName("HOST")
192 .help("Host to bind RPC server(s) to")
193 .handler([&](const std::string_view &value) { serve_host = value; }));
194
195 parser.add(Option("--serve-port")
196 .argName("PORT")
197 .help("Port for single-potential serve mode (used with -p)")
198 .handler([&](const std::string_view &value) {
199 serve_port = parseIntegral<uint16_t>(value);
200 }));
201
202 parser.add(Option("--replicas")
203 .argName("N")
204 .help("Number of replicated server instances (used with -p)")
205 .handler([&](const std::string_view &value) {
206 replicas = parseIntegral<size_t>(value);
207 }));
208
209 parser.add(Option("--gateway")
210 .help("Run a single gateway port backed by N pool instances "
211 "(use with -p and --replicas)")
212 .handler([&]() { gateway = true; }));
213
214 parser.add(Option("--config")
215 .argName("FILE")
216 .help("Config file for potential parameters (INI format, "
217 "e.g. [Metatomic] model_path=model.pt)")
218 .handler([&](const std::string_view &value) {
219 config_path = value;
220 }));
221#endif
222
223 // One value each: the partitioner hands a greedy zeroOrMoreTimes positional
224 // every remaining argument, which leaves confileout empty and overwrites
225 // confile with the last one.
226 parser.add(
227 Positional("confile")
228 .help("Input structure file")
229 .occurs(zeroOrOneTime)
230 .handler([&](const std::string_view &value) { confile = value; }));
231
232 parser.add(
233 Positional("confileout")
234 .help("Output structure file (optional)")
235 .occurs(zeroOrOneTime)
236 .handler([&](const std::string_view &value) { confileout = value; }));
237
238 try {
239 parser.parse(argc, argv);
240 } catch (const ParsingException &ex) {
241 std::cerr << colorizer.error(ex.message()) << '\n';
242 std::cerr << colorizer.warning(parser.formatUsage(progname)) << '\n';
243 std::exit(EXIT_FAILURE);
244 }
245
246 if (sflag && mflag) {
247 std::cerr << colorizer.error(
248 "Cannot specify both minimization and single point\n");
249 std::exit(EXIT_FAILURE);
250 }
251
252 if (!pflag && (sflag || mflag)) {
253 std::cerr << colorizer.error("Must specify a potential\n");
254 std::exit(EXIT_FAILURE);
255 }
256
257 if (cflag && confileout.empty()) {
258 std::cerr << colorizer.error(
259 "Comparison needs two structure files: the input con file and the "
260 "one to compare it against\n");
261 std::cerr << colorizer.warning(parser.formatUsage(progname)) << '\n';
262 std::exit(EXIT_FAILURE);
263 }
264
265#ifdef WITH_SERVE_MODE
266 // Load config file if provided (for potential-specific parameters
267 // like model_path, device, length_unit, etc.)
268 if (config_path.has_value()) {
269 std::ifstream config_file(config_path.value());
270 if (!config_file.is_open()) {
271 std::cerr << colorizer.error("Cannot open config file: ")
272 << config_path.value() << '\n';
273 std::exit(EXIT_FAILURE);
274 }
275 params.load(config_path.value());
276 }
277
278 // Handle --serve mode (does not require a con file)
279 if (serve_spec.has_value()) {
280 auto endpoints = parseServeSpec(serve_spec.value());
281 if (endpoints.empty()) {
282 std::cerr << colorizer.error("No valid serve endpoints in spec: ")
283 << serve_spec.value() << '\n';
284 std::exit(EXIT_FAILURE);
285 }
286 serveMultiple(endpoints, params);
287 std::exit(EXIT_SUCCESS);
288 }
289
290 // Handle -p with serve flags (single potential serve mode)
291 if (pflag && !sflag && !mflag && !cflag &&
292 (serve_port.has_value() || replicas.has_value() || gateway.has_value())) {
293 for (auto &ch : potential) {
294 ch = std::tolower(static_cast<unsigned char>(ch));
295 }
296 params.potential_options.potential =
297 magic_enum::enum_cast<PotType>(potential, magic_enum::case_insensitive)
298 .value_or(PotType::UNKNOWN);
299 auto host = serve_host.value_or("localhost");
300 auto port = serve_port.value_or(12345);
301 auto reps = replicas.value_or(1);
302 bool gw = gateway.value_or(false);
303
304 if (gw) {
305 serveGateway(params, host, port, reps);
306 } else if (reps > 1) {
307 serveReplicated(params, host, port, reps);
308 } else {
309 serveMode(params, host, port);
310 }
311 std::exit(EXIT_SUCCESS);
312 }
313
314 // Config-driven serve (no -p or --serve, just --config with [Serve])
315 if (!pflag && !sflag && !mflag && !cflag && config_path.has_value() &&
316 !serve_spec.has_value() &&
317 (!params.serve_options.endpoints.empty() ||
318 params.serve_options.gateway_port > 0 ||
319 params.serve_options.replicas > 1)) {
320 serveFromConfig(params);
321 std::exit(EXIT_SUCCESS);
322 }
323#endif
324
325 // Serve modes take no structure file and have already exited above.
326 if (confile.empty()) {
327 std::cerr << colorizer.error(
328 "At least one non-option argument is required: the con file\n");
329 std::cerr << colorizer.warning(parser.formatUsage(progname)) << '\n';
330 std::exit(EXIT_FAILURE);
331 }
332
333 if (!cflag) {
334 for (auto &ch : potential) {
335 ch = std::tolower(static_cast<unsigned char>(ch));
336 }
337 }
338
339 if (!cflag) {
340 params.potential_options.potential =
341 magic_enum::enum_cast<PotType>(potential, magic_enum::case_insensitive)
342 .value_or(PotType::UNKNOWN);
343 }
344
345 if (!sflag) {
346 params.optimizer_options.method =
347 magic_enum::enum_cast<OptType>(optimizer, magic_enum::case_insensitive)
348 .value_or(OptType::CG);
349 params.optimizer_options.converged_force = optConvergedForce;
350 }
351
352 if (cflag) {
353 // Matter copies structure_comparison_options into its own structComp in
354 // the constructor, so the flag has to be set before the two below.
355 params.structure_comparison_options.check_rotation = true;
356 }
357
358 auto pot = eonc::helpers::makePotential(params);
359 auto matter = std::make_unique<Matter>(pot, params);
360 auto matter2 = std::make_unique<Matter>(pot, params);
361 if (!eonc::io::io_ok(matter->con2matter(confile))) {
362 std::cerr << "Failed to load " << confile << std::endl;
363 std::exit(EXIT_FAILURE);
364 }
365
366 if (sflag) {
367 singlePoint(std::move(matter));
368 } else if (mflag) {
369 minimize(std::move(matter), confileout);
370 } else if (cflag) {
371 if (!eonc::io::io_ok(matter2->con2matter(confileout))) {
372 std::cerr << "Failed to load " << confileout << std::endl;
373 std::exit(EXIT_FAILURE);
374 }
375 if (matter->compare(*matter2, true)) {
376 std::cout << "Structures match\n";
377 } else {
378 std::cout << colorizer.error("Structures do not match\n");
379 }
380 }
381}
382
383} // namespace eonc
void minimize(std::unique_ptr< Matter > matter, const std::string &confileout)
void printFeatures()
BasicColorizer< char > colorizer(colorScheme)
void singlePoint(std::unique_ptr< Matter > matter)
constexpr auto colorScheme
std::shared_ptr< Potential > makePotential(const Parameters &params)
constexpr bool io_ok(IoStatus s) noexcept
Definition ConFileIO.h:38
RAII resource manager for the ARTn C library with global synchronization.
std::vector< ServeEndpoint > parseServeSpec(const std::string &spec)
Parse a serve configuration string into endpoints.
void serveGateway(const Parameters &params, const std::string &host, uint16_t port, size_t pool_size)
Start a gateway server backed by a pool of potential instances.
void serveMode(const Parameters &params, const std::string &host, uint16_t port)
Start a single rgpot-compatible Cap'n Proto RPC server.
Definition ServeMode.cpp:56
void serveFromConfig(const Parameters &params)
Start serve mode from config-file parameters.
void commandLine(int argc, char **argv)
void serveMultiple(const std::vector< ServeEndpoint > &endpoints, const Parameters &base_params)
Serve multiple potentials concurrently on different ports.
Definition ServeMode.cpp:79
void serveReplicated(const Parameters &params, const std::string &host, uint16_t base_port, size_t replicas)
Serve N replicas of the same potential across sequential ports.