← Documentation
ADS-IDAC Engine 5.0.0
RestApiSolver.cpp
Go to the documentation of this file.
1
4
5#include "RestApiSolver.h"
6#include "../Global/fmodule.h"
7
8#include <httplib.h>
9#include <json.hpp>
10#include <iostream>
11#include <fstream>
12#include <stdexcept>
13#include <cstring>
14using namespace std;
15
17
18RestApiSolver::RestApiSolver(const std::string& base_url)
19 : base_url_(base_url) {}
20
21// ---- HTTP helpers ----
22
23std::string RestApiSolver::httpPost(const std::string& path, const std::string& body) {
24 // Parse base_url_ into host:port
25 std::string host = base_url_;
26 int port = 80;
27
28 // Strip http://
29 auto pos = host.find("://");
30 if (pos != std::string::npos) host = host.substr(pos + 3);
31 // Extract port
32 pos = host.find(':');
33 if (pos != std::string::npos) {
34 port = std::stoi(host.substr(pos + 1));
35 host = host.substr(0, pos);
36 }
37
38 httplib::Client cli(host, port);
39 cli.set_connection_timeout(5);
40 cli.set_read_timeout(30);
41
42 auto res = cli.Post(path, body, "application/json");
43 if (!res) {
44 throw std::runtime_error("HTTP POST failed: " + path + " error=" + httplib::to_string(res.error()));
45 }
46 if (res->status >= 400) {
47 throw std::runtime_error("HTTP POST " + path + " returned " + std::to_string(res->status) + ": " + res->body);
48 }
49 return res->body;
50}
51
52std::string RestApiSolver::httpGet(const std::string& path) {
53 std::string host = base_url_;
54 int port = 80;
55 auto pos = host.find("://");
56 if (pos != std::string::npos) host = host.substr(pos + 3);
57 pos = host.find(':');
58 if (pos != std::string::npos) {
59 port = std::stoi(host.substr(pos + 1));
60 host = host.substr(0, pos);
61 }
62
63 httplib::Client cli(host, port);
64 cli.set_connection_timeout(5);
65 cli.set_read_timeout(30);
66
67 auto res = cli.Get(path);
68 if (!res) {
69 throw std::runtime_error("HTTP GET failed: " + path);
70 }
71 return res->body;
72}
73
74std::string RestApiSolver::httpDelete(const std::string& path) {
75 std::string host = base_url_;
76 int port = 80;
77 auto pos = host.find("://");
78 if (pos != std::string::npos) host = host.substr(pos + 3);
79 pos = host.find(':');
80 if (pos != std::string::npos) {
81 port = std::stoi(host.substr(pos + 1));
82 host = host.substr(0, pos);
83 }
84
85 httplib::Client cli(host, port);
86 auto res = cli.Delete(path);
87 if (!res) return "{}";
88 return res->body;
89}
90
91// ---- Session management ----
92
94 json body = {{"mode", fresh ? "fresh" : "restart"}};
95 auto resp_str = httpPost("/api/v1/sessions", body.dump());
96 auto resp = json::parse(resp_str);
97 session_id_ = resp["session_id"].get<std::string>();
98 std::cout << "[RestApiSolver] Created session: " << session_id_ << std::endl;
99}
100
102 if (!session_id_.empty()) {
103 httpDelete("/api/v1/sessions/" + session_id_);
104 session_id_.clear();
105 }
106}
107
108// ---- Populate r5pars_ from JSON ----
109
110void RestApiSolver::populateR5Pars(const std::string& json_str) {
111 auto resp = json::parse(json_str);
112
113 r5pars_.timesno = resp["time"].get<double>();
114
115 // Volumes
116 if (resp.contains("volumes")) {
117 for (auto& v : resp["volumes"]) {
118 int i = v["index"].get<int>();
119 if (i >= 0 && i < 500) {
120 r5pars_.liq_t[i] = v["liq_t"].get<double>();
121 r5pars_.vap_t[i] = v["vap_t"].get<double>();
122 r5pars_.sat_t[i] = v["sat_t"].get<double>();
123 r5pars_.sys_pressure[i] = v["pressure"].get<double>();
124 r5pars_.boron_d[i] = v["boron_d"].get<double>();
125 r5pars_.quality[i] = v["quality"].get<double>();
126 r5pars_.flow_reg[i] = v["flow_reg"].get<double>();
127 }
128 }
129 }
130
131 // Junctions
132 if (resp.contains("junctions")) {
133 for (auto& j : resp["junctions"]) {
134 int i = j["index"].get<int>();
135 if (i >= 0 && i < 500) {
136 r5pars_.mass_fr[i] = j["mass_fr"].get<double>();
137 r5pars_.liq_vel[i] = j["liq_vel"].get<double>();
138 r5pars_.vap_vel[i] = j["vap_vel"].get<double>();
139 }
140 }
141 }
142
143 // Variable trips
144 if (resp.contains("variable_trips")) {
145 for (auto& t : resp["variable_trips"]) {
146 int i = t["index"].get<int>();
147 if (i >= 0 && i < 500) {
148 r5pars_.vartrip_t[i] = t["trip_time"].get<double>();
149 }
150 }
151 }
152
153 // Logical trips
154 if (resp.contains("logical_trips")) {
155 for (auto& t : resp["logical_trips"]) {
156 int i = t["index"].get<int>();
157 if (i >= 0 && i < 500) {
158 r5pars_.logtrip_t[i] = t["trip_time"].get<double>();
159 }
160 }
161 }
162
163 // Control variables
164 if (resp.contains("control_variables")) {
165 for (auto& c : resp["control_variables"]) {
166 int i = c["index"].get<int>();
167 if (i >= 0 && i < 500) {
168 r5pars_.ctrl_var_val[i] = c["value"].get<double>();
169 }
170 }
171 }
172
173 // Stop flag
174 if (resp.contains("stop_flag")) {
175 r5pars_.csnostop = resp["stop_flag"].get<int>();
176 }
177}
178
179// ---- Send interventions ----
180
182 // Compare current inter_val[] with last snapshot to find changes
183 json actions = json::array();
184 int n = r5pars_.n_interact;
185 for (int i = 0; i < n; i++) {
186 double current = r5pars_.inter_val[i];
187 auto it = last_inter_val_.find(i);
188 if (it == last_inter_val_.end() || std::abs(it->second - current) > 1e-10) {
189 actions.push_back({{"index", i}, {"value", current}});
190 last_inter_val_[i] = current;
191 }
192 }
193
194 if (!actions.empty()) {
195 json body = {{"actions", actions}};
196 httpPost("/api/v1/sessions/" + session_id_ + "/intervene", body.dump());
197 }
198}
199
200// ---- Sequence execution ----
201
203 // Create session on first call; reuse for subsequent sequences
204 if (session_id_.empty()) {
205 createSession(true);
206 } else if (!restore_checkpoints_.empty()) {
207 // Restore from checkpoint for branching restart.
208 // Read ncount from indeck.i (written by restartForNewSequence)
209 // to select the correct checkpoint from the map.
210 int ncount = parseNcountFromIndeck("indeck.i");
211 std::string ckpt_id;
212 auto it = restore_checkpoints_.find(ncount);
213 if (it != restore_checkpoints_.end()) {
214 ckpt_id = it->second;
215 } else if (!restore_checkpoints_.empty()) {
216 // Fallback: use the last checkpoint in the map
217 ckpt_id = restore_checkpoints_.rbegin()->second;
218 std::cerr << "[RestApiSolver] WARNING: ncount=" << ncount
219 << " not found in checkpoint map, using fallback" << std::endl;
220 }
221 if (!ckpt_id.empty()) {
222 json restore_body = {{"checkpoint_id", ckpt_id}};
223 auto resp = httpPost("/api/v1/sessions/" + session_id_ + "/restore",
224 restore_body.dump());
225 auto rj = json::parse(resp);
226 double restored_time = rj["time"].get<double>();
227 // Reset r5pars_ state that RELAP5's resetini.for would normally clear.
228 // Without this, csnostop/timesno from the previous sequence persist
229 // and the while loop never enters.
230 r5pars_.csnostop = 0;
231 r5pars_.timesno = restored_time;
232 r5pars_.cgennode = false;
233 r5pars_.cnbranch = 0;
234 std::cout << "[RestApiSolver] Restored checkpoint " << ckpt_id
235 << " (ncount=" << ncount << ") t=" << restored_time << std::endl;
236 }
237 restore_checkpoints_.clear();
238 }
239
240 // Callbacks: first-init on first sequence, scenario start always
241 if (first_sequence_) {
242 cb.onFirstInit(*this);
243 first_sequence_ = false;
244 }
245 cb.onScenarioStart(*this);
246
247 // Clear per-sequence state
248 last_inter_val_.clear();
249 seq_checkpoints_.clear();
250
251 // Main simulation loop — advance 0.5s per iteration
252 while (r5pars_.csnostop == 0 && !r5pars_.terminateall) {
253 // Advance the remote solver
254 json advance_body = {{"dt", 0.5}};
255 auto resp = httpPost("/api/v1/sessions/" + session_id_ + "/advance",
256 advance_body.dump());
257 populateR5Pars(resp);
258
259 // Set cncount before onEvaluationPoint so BranchCluster records it
260 // (mirrors RELAP5 setting cncount on restart record writes)
261 r5pars_.cncount = ++checkpoint_counter_;
262
263 // Engine evaluation point (crew scanning, branching decisions)
264 cb.onEvaluationPoint(*this);
265
266 // Send any crew actions that changed during evaluation
268
269 // Handle branching checkpoint request
270 if (r5pars_.cgennode && r5pars_.cnbranch > 1) {
271 auto ckpt_resp = httpPost("/api/v1/sessions/" + session_id_ + "/checkpoint");
272 auto ckpt = json::parse(ckpt_resp);
273 std::string ckpt_id = ckpt["checkpoint_id"].get<std::string>();
274
275 // Map this ncount to the checkpoint ID for later restore
276 seq_checkpoints_[r5pars_.cncount] = ckpt_id;
277 last_checkpoint_id_ = ckpt_id;
278 r5pars_.cgennode = false;
279
280 std::cout << "[RestApiSolver] Checkpoint " << ckpt_id
281 << " ncount=" << r5pars_.cncount
282 << " t=" << r5pars_.timesno << std::endl;
283
284 cb.onBranchingPoint(*this);
285 }
286 }
287
288 // Session stays alive for potential checkpoint restore on next sequence
289}
290
291// ---- State accessors ----
292
293double RestApiSolver::currentTime() const { return r5pars_.timesno; }
294int RestApiSolver::stopFlag() const { return r5pars_.csnostop; }
295bool RestApiSolver::isTerminateAll() const { return r5pars_.terminateall; }
296void RestApiSolver::setTerminateAll(bool v) { r5pars_.terminateall = v; }
297
298// ---- Branch control ----
299
300void RestApiSolver::setBranchCount(int n) { r5pars_.cnbranch = n; }
301void RestApiSolver::setGenNode(bool v) { r5pars_.cgennode = v; }
302
303// ---- Restart lifecycle ----
304
305void RestApiSolver::setNewRestart(bool v) { r5pars_.cnewrst = v; }
306
307void RestApiSolver::setInputDeck(const std::string& path) {
308 // REST solver doesn't use input deck files; the mock generates its own state.
309 // We still store the path in r5pars_ for compatibility.
310 strncpy(r5pars_.carg2, path.c_str(), sizeof(r5pars_.carg2) - 1);
311 r5pars_.carg2[sizeof(r5pars_.carg2) - 1] = '\0';
312 r5pars_.carg2len = static_cast<int>(path.size());
313}
314
315// ---- Checkpoint/restore ----
316
317std::vector<char> RestApiSolver::checkpoint() {
318 // Serialize the ncount→checkpoint_id map as JSON.
319 // restartForNewSequence stores this per source-sequence, and restore()
320 // later parses it to select the right checkpoint by ncount.
321 if (seq_checkpoints_.empty()) return {};
322 json j;
323 for (auto& [ncount, ckpt_id] : seq_checkpoints_) {
324 j[std::to_string(ncount)] = ckpt_id;
325 }
326 std::string s = j.dump();
327 return std::vector<char>(s.begin(), s.end());
328}
329
330void RestApiSolver::restore(const std::vector<char>& data) {
331 if (data.empty()) return;
332 // Parse the ncount→checkpoint_id map from JSON buffer.
333 // The actual server-side restore happens in runSequence() after
334 // reading the target ncount from indeck.i.
335 std::string s(data.begin(), data.end());
336 auto j = json::parse(s);
337 restore_checkpoints_.clear();
338 for (auto& [key, val] : j.items()) {
339 restore_checkpoints_[std::stoi(key)] = val.get<std::string>();
340 }
341}
342
343// ---- Configuration ----
344
345void RestApiSolver::setThermoCacheMode(int) { /* no-op for REST solver */ }
346void RestApiSolver::setMembufMode(int) { /* no-op for REST solver */ }
347
348// ---- Helpers ----
349
350int RestApiSolver::parseNcountFromIndeck(const std::string& path) {
351 // indeck.i format:
352 // 100 restart transnt
353 // 101 run
354 // 103 <ncount>
355 // .
356 std::ifstream ifs(path);
357 std::string line;
358 while (std::getline(ifs, line)) {
359 if (line.size() >= 4 && line.substr(0, 3) == "103") {
360 auto pos = line.find_first_of("0123456789", 3);
361 if (pos != std::string::npos) {
362 return std::stoi(line.substr(pos));
363 }
364 }
365 }
366 return -1;
367}
nlohmann::json json
REST API implementation of ThermalHydraulicSolver.
document e
nlohmann::json json
void setInputDeck(const std::string &path) override
Store path in r5pars_.carg2 / carg2len. The remote solver does not read input decks; runSequence() al...
void setTerminateAll(bool v) override
Sets r5pars_.terminateall.
std::map< int, std::string > restore_checkpoints_
void setThermoCacheMode(int mode) override
No-op.
RestApiSolver(const std::string &base_url)
bool isTerminateAll() const override
r5pars_.terminateall
static int parseNcountFromIndeck(const std::string &path)
std::string httpDelete(const std::string &path)
int checkpoint_counter_
Definition: RestApiSolver.h:96
std::string httpPost(const std::string &path, const std::string &body="{}")
bool first_sequence_
Definition: RestApiSolver.h:95
std::map< int, std::string > seq_checkpoints_
Definition: RestApiSolver.h:99
std::map< int, double > last_inter_val_
std::string session_id_
Definition: RestApiSolver.h:92
void runSequence(SolverCallback &cb) override
Run one sequence against the remote solver. Blocks until it ends.
std::string httpGet(const std::string &path)
void sendInterventions()
void setBranchCount(int n) override
Sets r5pars_.cnbranch.
void createSession(bool fresh)
std::vector< char > checkpoint() override
JSON object mapping restart count (as a string key) to the server-side checkpoint id,...
double currentTime() const override
r5pars_.timesno [s]
int stopFlag() const override
r5pars_.csnostop
std::string base_url_
Definition: RestApiSolver.h:91
void populateR5Pars(const std::string &json_str)
void restore(const std::vector< char > &data) override
Parse a buffer produced by checkpoint() and keep its map for the next runSequence(),...
void setMembufMode(int mode) override
No-op.
std::string last_checkpoint_id_
Definition: RestApiSolver.h:93
void setGenNode(bool v) override
Sets r5pars_.cgennode.
void setNewRestart(bool v) override
Sets r5pars_.cnewrst.
Callback interface that the engine implements to receive notifications from the solver.
virtual void onBranchingPoint(ThermalHydraulicSolver &solver)=0
Called when a branching point is reached during simulation. RelapSolver routes the Fortran initiate_b...
virtual void onFirstInit(ThermalHydraulicSolver &solver)=0
Called once for the entire simulation during first initialization. RelapSolver routes the Fortran act...
virtual void onScenarioStart(ThermalHydraulicSolver &solver)=0
Called once at the start of each scenario/sequence. RelapSolver routes the Fortran secini_() callback...
virtual void onEvaluationPoint(ThermalHydraulicSolver &solver)=0
Called at each evaluation point (~0.5s simulation time intervals). RelapSolver routes the Fortran int...
struct @0 r5pars_
Element parse(const std::string &text)
Definition: xml_lite.cpp:259