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
33namespace {
34constexpr auto colorScheme = Argum::basicDefaultColorScheme<char>;
35Argum::BasicColorizer<char> colorizer(colorScheme);
36} // namespace
37
38void singlePoint(std::unique_ptr<eonc::Matter> matter) {
39 std::cout << "Energy: " << std::fixed << std::setprecision(15)
40 << matter->getPotentialEnergy() << std::endl;
41 std::cout << "(free) Forces: \n" << matter->getForcesFree() << "\n";
42 std::cout << "Max atom force: " << std::scientific << matter->maxForce()
43 << std::endl;
44}
45
46void minimize(std::unique_ptr<eonc::Matter> matter,
47 const std::string &confileout) {
48 matter->relax(false, false);
49 if (confileout.empty()) {
50 std::cout << "No output file specified, not saving" << std::endl;
51 return;
52 }
53 std::cout << "Saving relaxed structure to " << confileout << std::endl;
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
101#ifdef WITH_SERVE_MODE
102 std::optional<std::string> config_path;
103 std::optional<std::string> serve_spec;
104 // Empty until the matching flag is passed. A value here makes has_value()
105 // true, so -p alone takes the serve path and never reads a structure file.
106 std::optional<std::string> serve_host;
107 std::optional<uint16_t> serve_port;
108 std::optional<size_t> replicas;
109 std::optional<bool> gateway;
110#endif
111
112 auto params = Parameters{};
113
114 const char *progname = (argc ? argv[0] : "eonclient");
115
116 Argum::Parser parser;
117
118 parser.add(Argum::Option("--help", "-h")
119 .help("show this help message and exit")
120 .handler([&]() {
121 // Format help with color
122 auto helpText = parser.formatHelp(progname);
123 std::cout << colorizer.heading("eOn Client - Help")
124 << "\n\n";
125 std::cout << helpText;
126 std::exit(EXIT_SUCCESS);
127 }));
128
129 parser.add(Argum::Option("--version", "-v")
130 .help("Print version information")
131 .handler([&]() {
132 std::cout << VERSION_STRING << std::endl;
133 std::exit(EXIT_SUCCESS);
134 }));
135
136 parser.add(Argum::Option("--features")
137 .help("Print compile-time features")
138 .handler([&]() {
140 std::exit(EXIT_SUCCESS);
141 }));
142
143 parser.add(Argum::Option("--minimize", "-m")
144 .help("Minimization of inputConfile saves to outputConfile")
145 .handler([&]() { mflag = true; }));
146
147 parser.add(Argum::Option("--single", "-s")
148 .help("Single point energy of inputConfile")
149 .handler([&]() { sflag = true; }));
150
151 parser.add(Argum::Option("--compare", "-c")
152 .help("Compare structures of inputConfile to outputConfile")
153 .handler([&]() { cflag = true; }));
154
155 parser.add(
156 Argum::Option("--optimizer", "-o")
157 .argName("METHOD")
158 .help("Optimization method")
159 .handler([&](const std::string_view &value) { optimizer = value; }));
160
161 parser.add(Argum::Option("--force", "-f")
162 .argName("VALUE")
163 .help("Convergence force")
164 .handler([&](const std::string_view &value) {
165 optConvergedForce = Argum::parseFloatingPoint<double>(value);
166 }));
167
168 parser.add(
169 Argum::Option("--tolerance", "-t")
170 .argName("VALUE")
171 .help("Distance tolerance")
172 .handler([&](const std::string_view &value) {
174 .distance_difference = Argum::parseFloatingPoint<double>(value);
175 }));
176
177 parser.add(Argum::Option("--potential", "-p")
178 .argName("POTENTIAL")
179 .help("The potential (e.g. qsc, lj, eam_al)")
180 .handler([&](const std::string_view &value) {
181 pflag = true;
182 potential = value;
183 }));
184
185#ifdef WITH_SERVE_MODE
186 parser.add(
187 Argum::Option("--serve")
188 .argName("SPEC")
189 .help("Serve potential(s) over rgpot Cap'n Proto RPC. "
190 "Spec: 'potential:port' or 'pot1:port1,pot2:port2'")
191 .handler([&](const std::string_view &value) { serve_spec = value; }));
192
193 parser.add(
194 Argum::Option("--serve-host")
195 .argName("HOST")
196 .help("Host to bind RPC server(s) to")
197 .handler([&](const std::string_view &value) { serve_host = value; }));
198
199 parser.add(Argum::Option("--serve-port")
200 .argName("PORT")
201 .help("Port for single-potential serve mode (used with -p)")
202 .handler([&](const std::string_view &value) {
203 serve_port = Argum::parseIntegral<uint16_t>(value);
204 }));
205
206 parser.add(Argum::Option("--replicas")
207 .argName("N")
208 .help("Number of replicated server instances (used with -p)")
209 .handler([&](const std::string_view &value) {
210 replicas = Argum::parseIntegral<size_t>(value);
211 }));
212
213 parser.add(Argum::Option("--gateway")
214 .help("Run a single gateway port backed by N pool instances "
215 "(use with -p and --replicas)")
216 .handler([&]() { gateway = true; }));
217
218 parser.add(Argum::Option("--config")
219 .argName("FILE")
220 .help("Config file for potential parameters (INI format, "
221 "e.g. [Metatomic] model_path=model.pt)")
222 .handler([&](const std::string_view &value) {
223 config_path = value;
224 }));
225#endif
226
227 // One value each: the partitioner hands a greedy zeroOrMoreTimes positional
228 // every remaining argument, which leaves confileout empty and overwrites
229 // confile with the last one.
230 parser.add(
231 Argum::Positional("confile")
232 .help("Input structure file")
233 .occurs(Argum::zeroOrOneTime)
234 .handler([&](const std::string_view &value) { confile = value; }));
235
236 parser.add(
237 Argum::Positional("confileout")
238 .help("Output structure file (optional)")
239 .occurs(Argum::zeroOrOneTime)
240 .handler([&](const std::string_view &value) { confileout = value; }));
241
242 try {
243 parser.parse(argc, argv);
244 } catch (const Argum::ParsingException &ex) {
245 std::cerr << colorizer.error(ex.message()) << '\n';
246 std::cerr << colorizer.warning(parser.formatUsage(progname)) << '\n';
247 std::exit(EXIT_FAILURE);
248 }
249
250 if (sflag && mflag) {
251 std::cerr << colorizer.error(
252 "Cannot specify both minimization and single point\n");
253 std::exit(EXIT_FAILURE);
254 }
255
256 if (!pflag && (sflag || mflag)) {
257 std::cerr << colorizer.error("Must specify a potential\n");
258 std::exit(EXIT_FAILURE);
259 }
260
261 if (cflag && confileout.empty()) {
262 std::cerr << colorizer.error(
263 "Comparison needs two structure files: the input con file and the "
264 "one to compare it against\n");
265 std::cerr << colorizer.warning(parser.formatUsage(progname)) << '\n';
266 std::exit(EXIT_FAILURE);
267 }
268
269#ifdef WITH_SERVE_MODE
270 // Load config file if provided (for potential-specific parameters
271 // like model_path, device, length_unit, etc.)
272 if (config_path.has_value()) {
273 std::ifstream config_file(config_path.value());
274 if (!config_file.is_open()) {
275 std::cerr << colorizer.error("Cannot open config file: ")
276 << config_path.value() << '\n';
277 std::exit(EXIT_FAILURE);
278 }
279 params.load(config_path.value());
280 }
281
282 // Handle --serve mode (does not require a con file)
283 if (serve_spec.has_value()) {
284 auto endpoints = parseServeSpec(serve_spec.value());
285 if (endpoints.empty()) {
286 std::cerr << colorizer.error("No valid serve endpoints in spec: ")
287 << serve_spec.value() << '\n';
288 std::exit(EXIT_FAILURE);
289 }
290 serveMultiple(endpoints, params);
291 std::exit(EXIT_SUCCESS);
292 }
293
294 // Handle -p with serve flags (single potential serve mode).
295 // Defaults are applied below with value_or; has_value() is the flag.
296 if (pflag && !sflag && !mflag && !cflag &&
297 (serve_host.has_value() || serve_port.has_value() ||
298 replicas.has_value() || gateway.has_value())) {
299 for (auto &ch : potential) {
300 ch = std::tolower(static_cast<unsigned char>(ch));
301 }
303 magic_enum::enum_cast<PotType>(potential, magic_enum::case_insensitive)
304 .value_or(PotType::UNKNOWN);
305 auto host = serve_host.value_or("localhost");
306 auto port = serve_port.value_or(12345);
307 auto reps = replicas.value_or(1);
308 bool gw = gateway.value_or(false);
309
310 if (gw) {
311 serveGateway(params, host, port, reps);
312 } else if (reps > 1) {
313 serveReplicated(params, host, port, reps);
314 } else {
315 serveMode(params, host, port);
316 }
317 std::exit(EXIT_SUCCESS);
318 }
319
320 // Config-driven serve (no -p or --serve, just --config with [Serve])
321 if (!pflag && !sflag && !mflag && !cflag && config_path.has_value() &&
322 !serve_spec.has_value() &&
323 (!eonc::ParametersLoadAccess::serve_options(params).endpoints.empty() ||
324 eonc::ParametersLoadAccess::serve_options(params).gateway_port > 0 ||
325 eonc::ParametersLoadAccess::serve_options(params).replicas > 1)) {
326 serveFromConfig(params);
327 std::exit(EXIT_SUCCESS);
328 }
329#endif
330
331 // Serve modes take no structure file and have already exited above.
332 if (confile.empty()) {
333 std::cerr << colorizer.error(
334 "At least one non-option argument is required: the con file\n");
335 std::cerr << colorizer.warning(parser.formatUsage(progname)) << '\n';
336 std::exit(EXIT_FAILURE);
337 }
338
339 if (!cflag) {
340 for (auto &ch : potential) {
341 ch = std::tolower(static_cast<unsigned char>(ch));
342 }
343 }
344
345 if (!cflag) {
347 magic_enum::enum_cast<PotType>(potential, magic_enum::case_insensitive)
348 .value_or(PotType::UNKNOWN);
349 }
350
351 if (!sflag) {
353 magic_enum::enum_cast<OptType>(optimizer, magic_enum::case_insensitive)
354 .value_or(OptType::CG);
356 optConvergedForce;
357 }
358
359 if (cflag) {
360 // Matter copies structure_comparison_options into its own structComp in
361 // the constructor, so the flag has to be set before the two below.
363 .check_rotation = true;
364 }
365
366 auto pot = eonc::helpers::makePotential(params);
367 auto matter = std::make_unique<Matter>(pot, params);
368 auto matter2 = std::make_unique<Matter>(pot, params);
369 if (!eonc::io::io_ok(matter->con2matter(confile))) {
370 std::cerr << "Failed to load " << confile << std::endl;
371 std::exit(EXIT_FAILURE);
372 }
373
374 if (sflag) {
375 singlePoint(std::move(matter));
376 } else if (mflag) {
377 minimize(std::move(matter), confileout);
378 } else if (cflag) {
379 if (!eonc::io::io_ok(matter2->con2matter(confileout))) {
380 std::cerr << "Failed to load " << confileout << std::endl;
381 std::exit(EXIT_FAILURE);
382 }
383 Matter probe(*matter);
384 if (probe.compare(*matter2, true)) {
385 std::cout << "Structures match\n";
386 } else {
387 std::cout << colorizer.error("Structures do not match\n");
388 }
389 }
390}
391
392} // namespace eonc
void printFeatures()
void minimize(std::unique_ptr< eonc::Matter > matter, const std::string &confileout)
void singlePoint(std::unique_ptr< eonc::Matter > matter)
bool compare(const Matter &matter, bool indistinguishable=false)
Definition Matter.cpp:184
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:58
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:81
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.
static structure_comparison_options_t & structure_comparison_options(Parameters &p)
static optimizer_options_t & optimizer_options(Parameters &p)
static serve_options_t & serve_options(Parameters &p)
static potential_options_t & potential_options(Parameters &p)