Loading...
Searching...
No Matches
ServeMode.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
26
27#include "eon/ServeMode.h"
28#include "eon/EonLogger.h"
29#include "eon/Potential.h"
30#include "eon/ServeRpcServer.h"
31
32#include <algorithm>
33#include <cctype>
34#include <ranges>
35#include <sstream>
36#include <thread>
37#include <vector>
38
39namespace eonc {
40namespace {
41
43ForceCallback makeForceCallback(std::shared_ptr<Potential> pot) {
44 return [pot = std::move(pot)](long nAtoms, const double *positions,
45 const int *atomicNrs, double *forces,
46 double *energy, const double *box) {
47 double variance = 0.0;
48 pot->force(nAtoms, positions, atomicNrs, forces, energy, &variance, box);
49 };
50}
51
52} // anonymous namespace
53
54// ---------------------------------------------------------------------------
55// Single-model serve
56// ---------------------------------------------------------------------------
57
58void serveMode(const Parameters &params, const std::string &host,
59 uint16_t port) {
60 auto pot_type = params.potential_options().potential;
61 EONC_LOG_INFO("Creating potential: {}",
62 std::string(magic_enum::enum_name(pot_type)));
63
64 auto eon_pot = eonc::helpers::makePotential(params);
65 if (!eon_pot) {
66 EONC_LOG_ERROR("Failed to create potential of type {}",
67 std::string(magic_enum::enum_name(pot_type)));
68 return;
69 }
70
71 auto callback = makeForceCallback(std::move(eon_pot));
72
73 // Blocks until killed (runs Cap'n Proto event loop)
74 startRpcServer(std::move(callback), host, port);
75}
76
77// ---------------------------------------------------------------------------
78// Multi-model concurrent serve
79// ---------------------------------------------------------------------------
80
81void serveMultiple(const std::vector<ServeEndpoint> &endpoints,
82 const Parameters &base_params) {
83 if (endpoints.empty()) {
84 EONC_LOG_ERROR("No serve endpoints specified");
85 return;
86 }
87
88 // Single endpoint: run in the main thread (no extra overhead)
89 if (endpoints.size() == 1) {
90 auto params = base_params;
92 endpoints[0].potential;
93 serveMode(params, endpoints[0].host, endpoints[0].port);
94 return;
95 }
96
97 // Multiple endpoints: one thread per server
98 EONC_LOG_INFO("Starting {} concurrent RPC servers", endpoints.size());
99
100 std::vector<std::thread> threads;
101 threads.reserve(endpoints.size());
102
103 for (const auto &ep : endpoints) {
104 threads.emplace_back([&base_params, ep]() {
105 auto params = base_params;
107 auto pot_name = std::string(magic_enum::enum_name(ep.potential));
108
109 EONC_LOG_INFO("[{}:{}] Creating potential: {}", ep.host, ep.port,
110 pot_name);
111
112 auto eon_pot = eonc::helpers::makePotential(params);
113 if (!eon_pot) {
114 EONC_LOG_ERROR("[{}:{}] Failed to create potential {}", ep.host,
115 ep.port, pot_name);
116 return;
117 }
118
119 auto callback = makeForceCallback(std::move(eon_pot));
120 startRpcServer(std::move(callback), ep.host, ep.port);
121 });
122 }
123
124 // Wait for all threads (they block until killed)
125 for (auto &t : threads) {
126 if (t.joinable()) {
127 t.join();
128 }
129 }
130}
131
132// ---------------------------------------------------------------------------
133// Replicated serve: N copies of same potential on sequential ports
134// ---------------------------------------------------------------------------
135
136void serveReplicated(const Parameters &params, const std::string &host,
137 uint16_t base_port, size_t replicas) {
138 if (replicas == 0) {
139 EONC_LOG_ERROR("Replicas must be >= 1");
140 return;
141 }
142 if (replicas == 1) {
143 serveMode(params, host, base_port);
144 return;
145 }
146
147 EONC_LOG_INFO("Starting {} replicated servers on ports {}-{}", replicas,
148 base_port, base_port + replicas - 1);
149
150 std::vector<std::thread> threads;
151 threads.reserve(replicas);
152
153 for (size_t i = 0; i < replicas; ++i) {
154 uint16_t port = static_cast<uint16_t>(base_port + i);
155 threads.emplace_back(
156 [&params, &host, port]() { serveMode(params, host, port); });
157 }
158
159 for (auto &t : threads) {
160 if (t.joinable()) {
161 t.join();
162 }
163 }
164}
165
166// ---------------------------------------------------------------------------
167// Gateway serve: single port backed by a pool of potential instances
168// ---------------------------------------------------------------------------
169
170void serveGateway(const Parameters &params, const std::string &host,
171 uint16_t port, size_t pool_size) {
172 if (pool_size == 0) {
173 EONC_LOG_ERROR("Pool size must be >= 1");
174 return;
175 }
176
177 auto pot_type = params.potential_options().potential;
178 EONC_LOG_INFO("Creating pool of {} {} instances for gateway on {}:{}",
179 pool_size, std::string(magic_enum::enum_name(pot_type)), host,
180 port);
181
182 std::vector<ForceCallback> pool;
183 pool.reserve(pool_size);
184
185 for (size_t i = 0; i < pool_size; ++i) {
186 auto eon_pot = eonc::helpers::makePotential(params);
187 if (!eon_pot) {
188 EONC_LOG_ERROR("Failed to create potential instance {}/{}", i + 1,
189 pool_size);
190 return;
191 }
192 pool.push_back(makeForceCallback(std::move(eon_pot)));
193 }
194
195 EONC_LOG_INFO("Pool ready, starting gateway server");
196 startPooledRpcServer(std::move(pool), host, port);
197}
198
199// ---------------------------------------------------------------------------
200// Config-driven dispatch
201// ---------------------------------------------------------------------------
202
203void serveFromConfig(const Parameters &params) {
204 const auto &opts = params.serve_options();
205
206 // Multi-model endpoints take priority
207 if (!opts.endpoints.empty()) {
208 auto endpoints = parseServeSpec(opts.endpoints);
209 if (endpoints.empty()) {
210 EONC_LOG_ERROR("No valid endpoints in spec: {}", opts.endpoints);
211 return;
212 }
213 serveMultiple(endpoints, params);
214 return;
215 }
216
217 // Gateway mode
218 if (opts.gateway_port > 0) {
219 size_t pool = (opts.replicas > 0) ? opts.replicas : 1;
220 serveGateway(params, opts.host, opts.gateway_port, pool);
221 return;
222 }
223
224 // Replicated mode (default)
225 serveReplicated(params, opts.host, opts.port, opts.replicas);
226}
227
228// ---------------------------------------------------------------------------
229// Spec parser: "pot:port,pot:host:port,..."
230// ---------------------------------------------------------------------------
231
232std::vector<ServeEndpoint> parseServeSpec(const std::string &spec) {
233 std::vector<ServeEndpoint> endpoints;
234 std::istringstream stream(spec);
235 std::string token;
236
237 while (std::getline(stream, token, ',')) {
238 // Trim whitespace
239 token.erase(0, token.find_first_not_of(" \t"));
240 token.erase(token.find_last_not_of(" \t") + 1);
241 if (token.empty())
242 continue;
243
244 // Parse "potential:port" or "potential:host:port"
245 size_t first_colon = token.find(':');
246 if (first_colon == std::string::npos) {
247 EONC_LOG_ERROR("Invalid serve spec '{}': expected 'potential:port'",
248 token);
249 continue;
250 }
251
252 std::string pot_str = token.substr(0, first_colon);
253 std::string rest = token.substr(first_colon + 1);
254
255 // Trim parts after colon split
256 pot_str.erase(0, pot_str.find_first_not_of(" \t"));
257 pot_str.erase(pot_str.find_last_not_of(" \t") + 1);
258 rest.erase(0, rest.find_first_not_of(" \t"));
259 rest.erase(rest.find_last_not_of(" \t") + 1);
260
261 // Lowercase the potential name
262 std::ranges::transform(pot_str, pot_str.begin(), [](unsigned char c) {
263 return static_cast<char>(std::tolower(c));
264 });
265
266 ServeEndpoint ep;
267 ep.potential =
268 magic_enum::enum_cast<PotType>(pot_str, magic_enum::case_insensitive)
269 .value_or(PotType::UNKNOWN);
270
271 if (ep.potential == PotType::UNKNOWN) {
272 EONC_LOG_ERROR("Unknown potential type '{}'", pot_str);
273 continue;
274 }
275
276 size_t second_colon = rest.find(':');
277 if (second_colon != std::string::npos) {
278 // "host:port" format
279 ep.host = rest.substr(0, second_colon);
280 ep.host.erase(0, ep.host.find_first_not_of(" \t"));
281 ep.host.erase(ep.host.find_last_not_of(" \t") + 1);
282 std::string port_str = rest.substr(second_colon + 1);
283 port_str.erase(0, port_str.find_first_not_of(" \t"));
284 port_str.erase(port_str.find_last_not_of(" \t") + 1);
285 ep.port = static_cast<uint16_t>(std::stoi(port_str));
286 } else {
287 // "port" only
288 ep.host = "localhost";
289 ep.port = static_cast<uint16_t>(std::stoi(rest));
290 }
291
292 QUILL_LOG_INFO(eonc::log::get(), "Parsed endpoint: {} on {}:{}",
293 std::string(magic_enum::enum_name(ep.potential)), ep.host,
294 ep.port);
295 endpoints.push_back(ep);
296 }
297
298 return endpoints;
299}
300
301} // namespace eonc
#define EONC_LOG_ERROR(...)
Definition EonLogger.h:261
#define EONC_LOG_INFO(...)
Definition EonLogger.h:249
const potential_options_t & potential_options() const
const serve_options_t & serve_options() const
std::shared_ptr< Potential > makePotential(const Parameters &params)
quill::Logger * get() noexcept
Get or create the default "combi" logger.
Definition EonLogger.h:44
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.
std::function< void(long nAtoms, const double *positions, const int *atomicNrs, double *forces, double *energy, const double *box)> ForceCallback
Callback type for potential energy/force evaluation.
void startPooledRpcServer(std::vector< ForceCallback > pool, const std::string &host, uint16_t port)
Start a blocking Cap'n Proto RPC server backed by a pool of force callbacks dispatched round-robin.
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.
void startRpcServer(ForceCallback callback, const std::string &host, uint16_t port)
Start a blocking Cap'n Proto RPC server using a force callback.
static potential_options_t & potential_options(Parameters &p)
Configuration for a single serve endpoint.
Definition ServeMode.h:27
std::string host
Definition ServeMode.h:29