LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
sage_rest_engine.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_API_SYM_SAGE_REST_ENGINE_H
6#define LINE_API_SYM_SAGE_REST_ENGINE_H
7
8/**
9 * @file
10 * @ingroup api_sym
11 * SymEngine backed by the line-sage-rest service.
12 *
13 * Port of jline.api.sym.SageRestEngine. The service is SageMath behind the JSON
14 * protocol in io/sage/server.py. Every request is a single POST carrying the whole
15 * problem, so nothing is bind-mounted and the client works against a container,
16 * a remote host or a hand-started server alike.
17 *
18 * NUMERIC COEFFICIENTS ARE SENT AS DECIMAL STRINGS and read server side as
19 * exact rationals, which is what keeps the solve exact: a double coerced by the
20 * CAS would carry the binary rational nearest the decimal instead, and the
21 * difference survives all the way into the printed normal form. The same rule
22 * applies to the assignment eval() sends.
23 *
24 * The server enforces the timeout too, so a runaway symbolic solve is killed
25 * there rather than merely abandoned here.
26 */
27
28#include <cmath>
29#include <cstddef>
30#include <cstdio>
31#include <cstdlib>
32#include <iostream>
33#include <map>
34#include <mutex>
35#include <string>
36#include <vector>
37
38#include "json.hpp"
40#include "line/util/error.h"
41#include "line/util/http.h"
42
43namespace line {
44namespace sym {
45
46namespace detail {
47
48using Json = nlohmann::json;
49
50/**
51 * Canary for SageRestEngine::isUsable: the weighted-average softmin form, the
52 * smallest expression observed to kill a worker whose FLINT wants BMI2/ADX on
53 * a CPU that has neither. Its argument is a 17-digit decimal so the exact
54 * rational is multi-limb, which is what reaches the offending routine.
55 */
56inline const char* canary_expr() {
57 return "(x*exp(-x) + exp(-1))/(exp(-x) + exp(-1))";
58}
59
60/** @return the canary's argument */
61inline const char* canary_arg() { return "0.68999999999999995"; }
62
63/** The canary's value. */
64const double CANARY_VALUE = 0.82116556904906557;
65
66/** @return the usability verdicts, keyed by base URL */
67inline std::map<std::string, bool>& usable_cache() {
68 static std::map<std::string, bool> cache;
69 return cache;
70}
71
72/** @return the mutex guarding usable_cache */
73inline std::mutex& usable_mutex() {
74 static std::mutex m;
75 return m;
76}
77
78/**
79 * The shortest decimal literal that round-trips to v, i.e. what Java's
80 * Double.toString sends. Twin of line::reg::shortest_decimal, repeated here so
81 * that api/sym stays independent of the numeric core it never otherwise uses.
82 */
83inline std::string decimal_string(double v) {
84 char buf[64];
85 for (int prec = 1; prec <= 17; ++prec) {
86 std::snprintf(buf, sizeof(buf), "%.*g", prec, v);
87 if (std::strtod(buf, nullptr) == v) return std::string(buf);
88 }
89 std::snprintf(buf, sizeof(buf), "%.17g", v);
90 return std::string(buf);
91}
92
93/** Reads a string array field, empty when the field is absent or null. */
94inline std::vector<std::string> to_string_list(const Json& obj, const std::string& field) {
95 std::vector<std::string> out;
96 if (!obj.contains(field) || obj[field].is_null()) return out;
97 for (const Json& el : obj[field]) out.push_back(el.get<std::string>());
98 return out;
99}
100
101/** Reads a string field, or the fallback when it is absent or null. */
102inline std::string opt_string(const Json& obj, const std::string& field,
103 const std::string& fallback) {
104 if (obj.contains(field) && !obj[field].is_null()) return obj[field].get<std::string>();
105 return fallback;
106}
107
108/** Encodes a square expression matrix, refusing a ragged one. */
109inline Json to_json_matrix(const std::vector<std::vector<std::string>>& Q) {
110 if (Q.empty()) throw InputError("SageRestEngine: Q must not be empty");
111 Json rows = Json::array();
112 for (std::size_t i = 0; i < Q.size(); ++i) {
113 if (Q[i].size() != Q.size())
114 throw InputError("SageRestEngine: Q must be square, row " + std::to_string(i) +
115 " has " + std::to_string(Q[i].size()) + " entries but Q has " +
116 std::to_string(Q.size()) + " rows");
117 Json row = Json::array();
118 for (std::size_t j = 0; j < Q[i].size(); ++j)
119 row.push_back(Q[i][j].empty() ? std::string("0") : Q[i][j]);
120 rows.push_back(row);
121 }
122 return rows;
123}
124
125/**
126 * Encodes a string list, dropping empty entries. An empty symbol marks an event
127 * with no positive rate, as symbolicGeneratorResult.symbols does in the JAR; it
128 * contributes nothing and must not reach the server as "".
129 */
130inline Json to_json_array(const std::vector<std::string>& items) {
131 Json arr = Json::array();
132 for (std::size_t i = 0; i < items.size(); ++i)
133 if (!items[i].empty()) arr.push_back(items[i]);
134 return arr;
135}
136
137/**
138 * A possibly NON-SQUARE block, e.g. a weight block.
139 *
140 * to_json_matrix is for the generator and insists on a square matrix; a weight
141 * block is (number of measures) by (number of states) and is rarely square. The
142 * column count is still checked, because a short row would otherwise be a
143 * silently wrong measure rather than an error.
144 */
145inline Json to_json_rows(const std::vector<std::vector<std::string>>& rows, std::size_t width) {
146 Json out = Json::array();
147 for (std::size_t i = 0; i < rows.size(); ++i) {
148 if (rows[i].size() != width)
149 throw InputError("SageRestEngine: weight row " + std::to_string(i) + " has " +
150 std::to_string(rows[i].size()) + " entries but the generator has " +
151 std::to_string(width) + " states");
152 Json row = Json::array();
153 for (std::size_t j = 0; j < rows[i].size(); ++j)
154 row.push_back(rows[i][j].empty() ? std::string("0") : rows[i][j]);
155 out.push_back(row);
156 }
157 return out;
158}
159
160/**
161 * A vector whose LENGTH IS PART OF THE CONTRACT, with an empty entry meaning
162 * zero rather than absent.
163 *
164 * Deliberately not to_json_array: that one DROPS empty entries, which is right
165 * for a symbol list (an event with no positive rate contributes no symbol) and
166 * catastrophic for s0 or alpha, where dropping an entry shifts every state
167 * after it and the server would see a shorter vector than the matrix.
168 */
169inline Json to_json_vector(const std::vector<std::string>& items) {
170 Json arr = Json::array();
171 for (std::size_t i = 0; i < items.size(); ++i)
172 arr.push_back(items[i].empty() ? std::string("0") : items[i]);
173 return arr;
174}
175
176inline std::vector<CtmcMeasure> to_measure_list(const Json& obj, const std::string& field) {
177 std::vector<CtmcMeasure> out;
178 if (!obj.contains(field) || obj[field].is_null()) return out;
179 for (const Json& el : obj[field]) {
180 CtmcMeasure m;
181 m.name = opt_string(el, "name", "");
182 m.expr = opt_string(el, "expr", "");
183 m.num = opt_string(el, "num", "");
184 m.den = opt_string(el, "den", "");
185 m.reason = opt_string(el, "reason", "");
186 out.push_back(m);
187 }
188 return out;
189}
190
191inline std::vector<int> to_int_list(const Json& obj, const std::string& field) {
192 std::vector<int> out;
193 if (!obj.contains(field) || obj[field].is_null()) return out;
194 for (const Json& el : obj[field]) out.push_back(el.get<int>());
195 return out;
196}
197
198} // namespace detail
199
200/** Client of the line-sage-rest service. */
201class SageRestEngine : public SymEngine {
202public:
203 /** Default per-request timeout, in seconds. */
204 static constexpr int DEFAULT_TIMEOUT_SECONDS = 300;
205
206 /**
207 * @param baseUrl base URL of the service, e.g. "http://localhost:8080"
208 */
209 explicit SageRestEngine(const std::string& baseUrl)
210 : timeoutSeconds_(DEFAULT_TIMEOUT_SECONDS) {
211 std::string u = baseUrl;
212 const std::size_t b = u.find_first_not_of(" \t\r\n");
213 const std::size_t e = u.find_last_not_of(" \t\r\n");
214 u = b == std::string::npos ? std::string() : u.substr(b, e - b + 1);
215 while (!u.empty() && u[u.size() - 1] == '/') u.erase(u.size() - 1);
216 if (u.empty()) throw InputError("SageRestEngine: baseUrl must not be empty");
217 baseUrl_ = u;
218 }
219
220 /**
221 * Sets the per-request timeout.
222 *
223 * @param seconds timeout in seconds; not positive disables it
224 * @return this engine
225 */
227 timeoutSeconds_ = seconds;
228 return *this;
229 }
230
231 /** @return the per-request timeout in seconds */
232 int getTimeoutSeconds() const { return timeoutSeconds_; }
233
234 /** @return the base URL this engine posts to */
235 const std::string& getBaseUrl() const { return baseUrl_; }
236
237 std::string name() const override { return "sage"; }
238
239 bool isAvailable() const override {
240 try {
241 const detail::Json health = get("/api/v1/health", 5000);
242 return detail::opt_string(health, "status", "") == "ok";
243 } catch (const Error&) {
244 return false;
245 }
246 }
247
248 /**
249 * Checks that the service can actually EVALUATE, not merely that it
250 * answers.
251 *
252 * The line-sage-rest image ships a FLINT built for CPUs that have BMI2 and
253 * ADX. On an older host the first multi-limb exact operation raises
254 * SIGILL, the worker dies mid-request and the call returns no bytes at
255 * all; /api/v1/health is pure Python and keeps answering, so it cannot see
256 * this. The canary is the weighted-average softmin form, which is what the
257 * fluid export actually sends, and is the smallest expression observed to
258 * trigger it. Verdicts are cached per URL, so this costs one small request
259 * the first time a service is considered and nothing after. See
260 * _kb/11-conventions-and-gotchas.md.
261 *
262 * @return true if the service returned the canary's value
263 */
264 bool isUsable() const {
265 {
266 std::lock_guard<std::mutex> guard(detail::usable_mutex());
267 std::map<std::string, bool>& cache = detail::usable_cache();
268 const std::map<std::string, bool>::const_iterator it = cache.find(baseUrl_);
269 if (it != cache.end()) return it->second;
270 }
271 bool ok = false;
272 try {
273 detail::Json request;
274 request["exprs"] = detail::Json::array({std::string(detail::canary_expr())});
275 detail::Json values = detail::Json::object();
276 values["x"] = std::string(detail::canary_arg());
277 request["values"] = values;
278 request["timeout_s"] = 30;
279 const detail::Json response =
280 parse(http::post_json(baseUrl_ + "/api/v1/eval", request.dump(), 60000));
281 checkStatus("/api/v1/eval", response);
282 if (response.contains("values") && response["values"].is_array() &&
283 response["values"].size() == 1 && !response["values"][0].is_null()) {
284 const double v = response["values"][0].get<double>();
285 ok = std::fabs(v - detail::CANARY_VALUE) < 1e-9;
286 }
287 } catch (const Error&) {
288 // A dead worker closes the connection without a reply, which
289 // surfaces as a transport error rather than a service one. Either
290 // way the backend cannot serve us.
291 ok = false;
292 }
293 if (!ok) {
294 std::cerr << "[LINE] Ignoring symbolic backend at " << baseUrl_
295 << ": it did not return the usability canary. On a CPU without "
296 << "BMI2/ADX the image's FLINT raises SIGILL mid-request." << std::endl;
297 }
298 {
299 std::lock_guard<std::mutex> guard(detail::usable_mutex());
300 detail::usable_cache()[baseUrl_] = ok;
301 }
302 return ok;
303 }
304
305 /**
306 * Reads the service identity, used to tell a line-sage-rest server apart
307 * from another line-*-rest service on the same conventional port.
308 *
309 * @return the /api/v1/info document
310 */
311 detail::Json info() const { return get("/api/v1/info", 5000); }
312
313 /**
314 * Names the routes this client calls that the service does not serve.
315 *
316 * A SERVICE CAN BE HEALTHY AND STILL TOO OLD, and that case used to reach
317 * the caller as a request failure rather than as a resolution failure. The
318 * roster ran a line-sage-rest image predating the ctmc/measures and
319 * ctmc/passage routes on 2026-09-11: it answered /api/v1/health, passed the
320 * `isUsable` arithmetic canary, and then failed every call to those routes
321 * with `unknown endpoint /api/v1/ctmc/measures` -- an error that reads as a
322 * defect in the solver rather than as an out-of-date container.
323 * /api/v1/info lists what the running server actually routes, so asking it
324 * turns that into a named refusal at resolution time.
325 *
326 * @return the missing routes; empty when the service serves them all, and
327 * empty when it is too old to report its route list at all, which
328 * leaves the request itself to fail as before
329 */
330 std::vector<std::string> missingRoutes() const {
331 static const char* kRequired[] = {
332 "/api/v1/ctmc/solve", "/api/v1/ctmc/sensitivity", "/api/v1/ctmc/measures",
333 "/api/v1/ctmc/passage", "/api/v1/simplify", "/api/v1/diff", "/api/v1/eval",
334 "/api/v1/fluid/odes"};
335 std::vector<std::string> missing;
336 const detail::Json doc = info();
337 if (!doc.contains("endpoints") || !doc["endpoints"].is_array()) return missing;
338 const detail::Json& served = doc["endpoints"];
339 for (std::size_t i = 0; i < sizeof(kRequired) / sizeof(kRequired[0]); ++i) {
340 bool found = false;
341 for (detail::Json::const_iterator it = served.begin(); it != served.end(); ++it) {
342 if (it->is_string() && it->get<std::string>() == kRequired[i]) {
343 found = true;
344 break;
345 }
346 }
347 if (!found) missing.push_back(kRequired[i]);
348 }
349 return missing;
350 }
351
352 CtmcSolution solveCTMC(const std::vector<std::vector<std::string>>& Q,
353 const std::vector<std::string>& symbols) override {
354 detail::Json request;
355 request["Q"] = detail::to_json_matrix(Q);
356 request["symbols"] = detail::to_json_array(symbols);
357 request["normalize"] = true;
358 const detail::Json response = post("/api/v1/ctmc/solve", request);
359
360 CtmcSolution sol;
361 sol.pi = detail::to_string_list(response, "pi");
362 sol.num = detail::to_string_list(response, "num");
363 sol.den = detail::opt_string(response, "den", "1");
364 sol.nConnComp = response.contains("nConnComp") ? response["nConnComp"].get<int>() : 1;
365 if (response.contains("connComp") && !response["connComp"].is_null())
366 for (const detail::Json& el : response["connComp"]) sol.connComp.push_back(el.get<int>());
367 return sol;
368 }
369
370 CtmcMeasures ctmcMeasures(const std::vector<std::vector<std::string>>& Q,
371 const std::vector<std::string>& symbols,
372 const std::vector<std::vector<std::string>>& weights,
373 const std::vector<std::string>& names,
374 const std::vector<RatioSpec>& ratios) override {
375 detail::Json request;
376 request["Q"] = detail::to_json_matrix(Q);
377 request["symbols"] = detail::to_json_array(symbols);
378 request["weights"] = detail::to_json_rows(weights, Q.size());
379 request["normalize"] = true;
380 if (!names.empty()) request["names"] = detail::to_json_vector(names);
381 if (!ratios.empty()) {
382 detail::Json arr = detail::Json::array();
383 for (std::size_t i = 0; i < ratios.size(); ++i) {
384 detail::Json o = detail::Json::object();
385 o["name"] = ratios[i].name;
386 o["num"] = ratios[i].num;
387 o["den"] = ratios[i].den;
388 arr.push_back(o);
389 }
390 request["ratios"] = arr;
391 }
392 const detail::Json response = post("/api/v1/ctmc/measures", request);
393
394 CtmcMeasures out;
395 out.pi = detail::to_string_list(response, "pi");
396 out.num = detail::to_string_list(response, "num");
397 out.den = detail::opt_string(response, "den", "1");
398 out.nConnComp = response.contains("nConnComp") ? response["nConnComp"].get<int>() : 1;
399 out.connComp = detail::to_int_list(response, "connComp");
400 out.measures = detail::to_measure_list(response, "measures");
401 out.ratios = detail::to_measure_list(response, "ratios");
402 return out;
403 }
404
405 Passage ctmcPassage(const std::vector<std::vector<std::string>>& S,
406 const std::vector<std::string>& s0,
407 const std::vector<std::string>& alpha, const std::string& atom,
408 const std::vector<std::string>& symbols, const std::string& svar,
409 const std::vector<std::string>& want, int nmax) override {
410 detail::Json request;
411 request["S"] = detail::to_json_matrix(S);
412 request["s0"] = detail::to_json_vector(s0);
413 request["alpha"] = detail::to_json_vector(alpha);
414 request["atom"] = atom.empty() ? std::string("0") : atom;
415 request["symbols"] = detail::to_json_array(symbols);
416 request["svar"] = svar.empty() ? std::string("s") : svar;
417 request["want"] = detail::to_json_vector(want);
418 request["nmax"] = nmax < 1 ? 1 : nmax;
419 const detail::Json response = post("/api/v1/ctmc/passage", request);
420
421 Passage out;
422 out.lst = detail::opt_string(response, "lst", "");
423 out.lstNum = detail::opt_string(response, "lst_num", "");
424 out.lstDen = detail::opt_string(response, "lst_den", "");
425 out.lstAll = detail::to_string_list(response, "lstall");
426 out.moments = detail::to_string_list(response, "moments");
427 if (response.contains("momall") && !response["momall"].is_null())
428 for (const detail::Json& row : response["momall"]) {
429 std::vector<std::string> r;
430 for (const detail::Json& el : row) r.push_back(el.get<std::string>());
431 out.momAll.push_back(r);
432 }
433 out.unreachable = detail::to_int_list(response, "unreachable");
434 return out;
435 }
436
437 SymSensitivity ctmcSensitivity(const std::vector<std::vector<std::string>>& Q,
438 const std::vector<std::string>& symbols,
439 const std::string& theta,
440 const std::vector<std::string>& reward) override {
441 detail::Json request;
442 request["Q"] = detail::to_json_matrix(Q);
443 request["symbols"] = detail::to_json_array(symbols);
444 request["theta"] = theta;
445 if (!reward.empty()) request["reward"] = detail::to_json_array(reward);
446 const detail::Json response = post("/api/v1/ctmc/sensitivity", request);
447
449 s.pi = detail::to_string_list(response, "pi");
450 s.dpi = detail::to_string_list(response, "dpi");
451 s.Er = detail::opt_string(response, "Er", "");
452 s.S = detail::opt_string(response, "S", "");
453 s.SS = detail::opt_string(response, "SS", "");
454 s.hasReward = !reward.empty();
455 return s;
456 }
457
458 std::vector<std::string> simplify(const std::vector<std::string>& exprs,
459 const std::string& form) override {
460 detail::Json request;
461 request["exprs"] = detail::to_json_array(exprs);
462 request["form"] = form.empty() ? std::string("cancel") : form;
463 return detail::to_string_list(post("/api/v1/simplify", request), "results");
464 }
465
466 std::vector<std::string> diff(const std::vector<std::string>& exprs,
467 const std::string& variable, int order) override {
468 detail::Json request;
469 request["exprs"] = detail::to_json_array(exprs);
470 request["var"] = variable;
471 request["order"] = order;
472 return detail::to_string_list(post("/api/v1/diff", request), "results");
473 }
474
475 std::vector<double> eval(const std::vector<std::string>& exprs,
476 const std::map<std::string, double>& assignment) override {
477 detail::Json request;
478 request["exprs"] = detail::to_json_array(exprs);
479 detail::Json values = detail::Json::object();
480 for (std::map<std::string, double>::const_iterator it = assignment.begin();
481 it != assignment.end(); ++it) {
482 // sent as text so the server reads the decimal exactly, see the
483 // header comment
484 values[it->first] = detail::decimal_string(it->second);
485 }
486 request["values"] = values;
487 const detail::Json response = post("/api/v1/eval", request);
488
489 std::vector<double> out;
490 if (!response.contains("values") || response["values"].is_null()) return out;
491 for (const detail::Json& el : response["values"])
492 out.push_back(el.is_null() ? std::nan("") : el.get<double>());
493 return out;
494 }
495
496 FluidODEs fluidODEs(const std::vector<std::string>& rhs, const std::vector<std::string>& vars,
497 const std::vector<std::string>& want) override {
498 detail::Json request;
499 request["rhs"] = detail::to_json_array(rhs);
500 request["vars"] = detail::to_json_array(vars);
501 request["want"] = detail::to_json_array(want);
502 const detail::Json response = post("/api/v1/fluid/odes", request);
503
504 FluidODEs odes;
505 if (response.contains("jacobian") && !response["jacobian"].is_null()) {
506 odes.hasJacobian = true;
507 for (const detail::Json& row : response["jacobian"]) {
508 std::vector<std::string> r;
509 for (const detail::Json& el : row) r.push_back(el.get<std::string>());
510 odes.jacobian.push_back(r);
511 }
512 }
513 if (response.contains("latex") && !response["latex"].is_null()) {
514 odes.hasLatex = true;
515 odes.latex = detail::to_string_list(response, "latex");
516 }
517 if (response.contains("equilibria") && !response["equilibria"].is_null()) {
518 odes.hasEquilibria = true;
519 for (const detail::Json& sol : response["equilibria"]) {
520 std::map<std::string, std::string> m;
521 for (detail::Json::const_iterator it = sol.begin(); it != sol.end(); ++it)
522 m[it.key()] = it.value().get<std::string>();
523 odes.equilibria.push_back(m);
524 }
525 }
526 return odes;
527 }
528
529private:
530 detail::Json post(const std::string& path, detail::Json request) const {
531 const int millis = timeoutSeconds_ > 0 ? timeoutSeconds_ * 1000 : 0;
532 if (timeoutSeconds_ > 0) request["timeout_s"] = timeoutSeconds_;
533 const detail::Json response = parse(
534 http::post_json(baseUrl_ + path, request.dump(), millis));
535 checkStatus(path, response);
536 return response;
537 }
538
539 detail::Json get(const std::string& path, int millis) const {
540 return parse(http::get(baseUrl_ + path, millis));
541 }
542
543 static detail::Json parse(const http::Response& response) {
544 if (response.body.empty())
545 throw SymEngineError("line-sage-rest returned HTTP " +
546 std::to_string(response.status) + " with no body");
547 detail::Json parsed = detail::Json::parse(response.body, nullptr, false);
548 if (parsed.is_discarded() || !parsed.is_object())
549 throw SymEngineError("line-sage-rest returned a non-JSON body: " + response.body);
550 return parsed;
551 }
552
553 static void checkStatus(const std::string& path, const detail::Json& response) {
554 const std::string status = detail::opt_string(response, "status", "");
555 if (status == "ok") return;
556 throw SymEngineError("line-sage-rest " + path + " failed [" +
557 detail::opt_string(response, "code", "error") +
558 "]: " + detail::opt_string(response, "message", "unspecified error"));
559 }
560
561 std::string baseUrl_;
562 int timeoutSeconds_;
563};
564
565} // namespace sym
566} // namespace line
567
568#endif // LINE_API_SYM_SAGE_REST_ENGINE_H
Base error for the multiprecision C++ port.
Definition error.h:31
InputError(const std::string &what)
Definition error.h:39
SageRestEngine(const std::string &baseUrl)
static constexpr int DEFAULT_TIMEOUT_SECONDS
Default per-request timeout, in seconds.
std::vector< double > eval(const std::vector< std::string > &exprs, const std::map< std::string, double > &assignment) override
Substitutes values for symbols and evaluates.
detail::Json info() const
Reads the service identity, used to tell a line-sage-rest server apart from another line-*-rest servi...
std::vector< std::string > missingRoutes() const
Names the routes this client calls that the service does not serve.
CtmcSolution solveCTMC(const std::vector< std::vector< std::string > > &Q, const std::vector< std::string > &symbols) override
Symbolic stationary distribution of a CTMC, pi Q = 0 with sum(pi) = 1.
FluidODEs fluidODEs(const std::vector< std::string > &rhs, const std::vector< std::string > &vars, const std::vector< std::string > &want) override
Jacobian, LaTeX form and equilibria of a fluid vector field.
bool isUsable() const
Checks that the service can actually EVALUATE, not merely that it answers.
std::string name() const override
Name of the backing engine, e.g.
bool isAvailable() const override
True if the engine answers a health probe.
std::vector< std::string > diff(const std::vector< std::string > &exprs, const std::string &variable, int order) override
Differentiates expressions.
SageRestEngine & setTimeoutSeconds(int seconds)
Sets the per-request timeout.
const std::string & getBaseUrl() const
SymSensitivity ctmcSensitivity(const std::vector< std::vector< std::string > > &Q, const std::vector< std::string > &symbols, const std::string &theta, const std::vector< std::string > &reward) override
Exact parametric sensitivity of a steady-state reward.
Passage ctmcPassage(const std::vector< std::vector< std::string > > &S, const std::vector< std::string > &s0, const std::vector< std::string > &alpha, const std::string &atom, const std::vector< std::string > &symbols, const std::string &svar, const std::vector< std::string > &want, int nmax) override
First passage time into a target set: the transform and the moments.
std::vector< std::string > simplify(const std::vector< std::string > &exprs, const std::string &form) override
CtmcMeasures ctmcMeasures(const std::vector< std::vector< std::string > > &Q, const std::vector< std::string > &symbols, const std::vector< std::vector< std::string > > &weights, const std::vector< std::string > &names, const std::vector< RatioSpec > &ratios) override
Rewrites expressions into a normal form.
SymEngineError(const std::string &what)
Definition sym_engine.h:43
A computer algebra backend.
Definition sym_engine.h:131
The exception types the port throws.
Minimal HTTP/1.1 client, enough to talk to a line-*-rest service.
Response post_json(const std::string &url, const std::string &json, int timeoutMillis)
POST a JSON document.
Definition http.h:361
Response get(const std::string &url, int timeoutMillis)
GET a URL.
Definition http.h:349
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
Symbolic stationary distribution of a CTMC.
Definition sym_engine.h:48
std::string name
Caller supplied name.
Definition sym_engine.h:49
std::string num
Numerator of expr, empty when undefined.
Definition sym_engine.h:51
std::string reason
Why the value is undefined, empty otherwise.
Definition sym_engine.h:61
std::string expr
The value as a rational function, or "undefined".
Definition sym_engine.h:50
std::string den
Denominator of expr, empty when undefined.
Definition sym_engine.h:52
Stationary distribution together with the measures taken from it.
Definition sym_engine.h:75
std::vector< CtmcMeasure > ratios
One per requested ratio, in order.
Definition sym_engine.h:82
std::string den
Common denominator of the whole vector.
Definition sym_engine.h:78
std::vector< CtmcMeasure > measures
One per weight vector, in order.
Definition sym_engine.h:81
int nConnComp
Weakly connected components.
Definition sym_engine.h:79
std::vector< std::string > num
Numerator of each entry over den.
Definition sym_engine.h:77
std::vector< int > connComp
Component index of each state, one based.
Definition sym_engine.h:80
std::vector< std::string > pi
Stationary probability of each state.
Definition sym_engine.h:76
std::vector< std::string > pi
Stationary probability of each state, as an expression.
Definition sym_engine.h:97
std::vector< int > connComp
Component index of each state, one based.
Definition sym_engine.h:101
std::vector< std::string > num
Numerator of each entry over the common denominator.
Definition sym_engine.h:98
int nConnComp
Weakly connected components of the generator.
Definition sym_engine.h:100
std::string den
Common denominator of the whole vector.
Definition sym_engine.h:99
Symbolic analysis of a fluid vector field.
Definition sym_engine.h:121
std::vector< std::string > latex
LaTeX form of each right hand side.
Definition sym_engine.h:123
std::vector< std::map< std::string, std::string > > equilibria
variable -> expression
Definition sym_engine.h:124
std::vector< std::vector< std::string > > jacobian
d f_i / d x_j
Definition sym_engine.h:122
First passage time transform and moments.
Definition sym_engine.h:86
std::vector< std::vector< std::string > > momAll
Per start state moments.
Definition sym_engine.h:92
std::vector< std::string > lstAll
Per start state transform.
Definition sym_engine.h:90
std::string lstNum
Numerator of lst.
Definition sym_engine.h:88
std::string lst
The transform L(s), empty if not requested.
Definition sym_engine.h:87
std::vector< std::string > moments
Moments 1..nmax for the initial law.
Definition sym_engine.h:91
std::vector< int > unreachable
States that cannot reach the target.
Definition sym_engine.h:93
std::string lstDen
Denominator of lst.
Definition sym_engine.h:89
Exact parametric sensitivity, following Trivedi and Bobbio (2017), Sec.
Definition sym_engine.h:111
bool hasReward
Whether Er, S and SS were computed.
Definition sym_engine.h:117
std::vector< std::string > dpi
Derivative of the distribution with respect to theta.
Definition sym_engine.h:113
std::vector< std::string > pi
Stationary distribution.
Definition sym_engine.h:112
std::string SS
Scaled sensitivity (theta/E[r]) d(E[r])/dtheta, Eq. (9.80).
Definition sym_engine.h:116
std::string Er
Mean reward, empty if no reward was given.
Definition sym_engine.h:114
std::string S
Unscaled sensitivity d(E[r])/dtheta, Eq. (9.79).
Definition sym_engine.h:115
Computer algebra operations LINE needs, as seen by this port.