5#ifndef LINE_SOLVERS_CTMC_SOLVER_CTMC_SYMBOLIC_H
6#define LINE_SOLVERS_CTMC_SOLVER_CTMC_SYMBOLIC_H
88 std::vector<std::vector<std::string> >
Q;
92 std::vector<Matrix<T> >
filt;
97 std::vector<NetState<T> >
space;
98 std::vector<Sync<T> >
sync;
103 std::vector<std::string> out;
104 for (std::size_t e = 0; e <
symbols.size(); ++e)
110namespace symbolic_detail {
117inline std::string coeff_string(
double c) {
118 if (c == std::floor(c) && std::fabs(c) < 1e15) {
120 std::snprintf(buf,
sizeof(buf),
"%lld",
static_cast<long long>(c));
121 return std::string(buf);
123 return sym::detail::decimal_string(c);
135std::string symbolic_entry(
const CtmcSymbolicGenerator<T>& g, std::size_t i, std::size_t j) {
137 for (std::size_t e = 0; e < g.symbols.size(); ++e) {
138 if (g.symbols[e].empty())
continue;
139 const double c = num_traits<T>::to_double(g.terms[e](i, j));
140 if (c == 0.0)
continue;
142 if (c < 0.0) s +=
"-";
144 s += c < 0.0 ?
" - " :
" + ";
146 const double a = std::fabs(c);
147 if (g.invert_symbol) {
148 s += coeff_string(a) +
"/" + g.symbols[e];
150 if (a != 1.0) s += coeff_string(a) +
"*";
154 return s.empty() ? std::string(
"0") : s;
160 T value = num_traits<T>::from_int(0);
165MinPositive<T> min_positive(
const Matrix<T>& F) {
167 const T zero = num_traits<T>::from_int(0);
168 for (std::size_t i = 0; i < F.rows(); ++i)
169 for (std::size_t j = 0; j < F.cols(); ++j) {
170 if (!(F(i, j) > zero))
continue;
171 if (!out.has || F(i, j) < out.value) {
180inline void apply_timeout(
const std::shared_ptr<sym::SymEngine>& engine,
int timeout_s) {
181 if (timeout_s <= 0)
return;
182 sym::SageRestEngine* rest =
dynamic_cast<sym::SageRestEngine*
>(engine.get());
183 if (rest !=
nullptr) rest->setTimeoutSeconds(timeout_s);
187inline std::string backend_missing(
const std::string& what) {
188 return std::string(
"SolverCTMC: ") + what +
189 " needs a computer-algebra backend, and none is available. Start one with\n docker "
190 "run -d -p 8080:8080 " +
192 " environment variable at a running service, or set the backend to its URL";
202inline std::shared_ptr<sym::SymEngine> require_engine(
const CtmcSymbolicOptions& opt,
203 const std::string& what) {
205 if (!engine)
throw sym::SymEngineError(backend_missing(what));
206 apply_timeout(engine, opt.timeout_s);
228 bool invert_symbol =
false) {
235 const std::size_t n = gen.
Q.rows();
236 const std::size_t ne = gen.
filt.size();
242 for (std::size_t e = 0; e < ne; ++e) {
243 const symbolic_detail::MinPositive<T> m = symbolic_detail::min_positive(gen.
filt[e]);
244 if (!m.has)
continue;
245 g.
rate0[e] = m.value;
247 for (std::size_t i = 0; i < F.
rows(); ++i)
248 for (std::size_t j = 0; j < F.
cols(); ++j)
250 F(i, j) = T(gen.
filt[e](i, j) / m.value);
255 g.
symbols[e] =
"x" + std::to_string(e + 1);
258 g.
Q.assign(n, std::vector<std::string>(n,
"0"));
259 for (std::size_t i = 0; i < n; ++i)
260 for (std::size_t j = 0; j < n; ++j) g.
Q[i][j] = symbolic_detail::symbolic_entry(g, i, j);
277 if (x.size() != g.
symbols.size())
278 throw InputError(
"ctmc_symbolic_eval_infgen: expected " +
279 std::to_string(g.
symbols.size()) +
" symbol values, got " +
280 std::to_string(x.size()));
281 const std::size_t n = g.
Q.size();
283 for (std::size_t e = 0; e < g.
symbols.size(); ++e) {
284 if (g.
symbols[e].empty())
continue;
287 " is inverted in the generator and cannot be zero");
289 for (std::size_t i = 0; i < n; ++i)
290 for (std::size_t j = 0; j < n; ++j) Q(i, j) += T(c * g.
terms[e](i, j));
298 std::vector<std::string>
pi;
299 std::vector<std::string>
num;
321 const std::shared_ptr<sym::SymEngine> engine =
322 symbolic_detail::require_engine(symopt,
"the symbolic stationary distribution");
332 out.
engine = engine->name();
333 if (out.
pi.size() != g.
space.size())
335 "' returned " + std::to_string(out.
pi.size()) +
336 " entries for a " + std::to_string(g.
space.size()) +
A network plus its refreshed NetworkStruct.
The symbolic backend is unreachable, or rejected the request.
Steady-state distribution of a continuous-time Markov chain.
The exception types the port throws.
Dense matrix and non-owning view.
CtmcGenerator< T > ctmc_get_generator(const NetworkStruct< T > &sn, const CtmcSolution< T > &d)
Port of @@SolverCTMC/getGenerator.m: the generator, its event filtration and the synchronization list...
Matrix< T > ctmc_symbolic_eval_infgen(const CtmcSymbolicGenerator< T > &g, const std::vector< T > &x)
Evaluates the symbolic generator at a symbol assignment, the twin of the JAR's evalInfGen.
CtmcSymbolicSolution< T > ctmc_symbolic_solution(const NetworkStruct< T > &sn, const CtmcOptions &opt, const CtmcSymbolicOptions &symopt=CtmcSymbolicOptions())
Port of @@SolverCTMC/getSymbolicSolution.m: pi Q = 0 with sum(pi) = 1 over the field of rational func...
CtmcSymbolicGenerator< T > ctmc_symbolic_generator(const NetworkStruct< T > &sn, const CtmcOptions &opt, bool invert_symbol=false)
Port of @@SolverCTMC/getSymbolicGenerator.m.
Matrix< T > ctmc_makeinfgen(const Matrix< T > &Q)
Set the diagonal so that every row sums to zero (ctmc_makeinfgen).
const char *const SYM_URL_ENV
Environment variable naming a service to use.
std::shared_ptr< SymEngine > sym_resolve(const std::string &requested="auto")
Resolves an engine.
const char *const SYM_DOCKER_IMAGE
Image serving the symbolic REST API.
A queueing network and its refreshed NetworkStruct.
SymEngine backed by the line-sage-rest service.
Port of solver_ctmc_analyzer.m and the parts of @@SolverCTMC/runAnalyzer.m that surround one solve: t...
The remaining @@SolverCTMC accessors: getGenerator / getInfGen, getStateSpace / getStateSpaceAggr and...
Port of the MATLAB +State package: the encoding that turns a station's state row into marginal job co...
Port of the event half of MATLAB's +State package: the successor states an event produces at one node...
[infGen, eventFilt, ev] of @@SolverCTMC/getGenerator.m.
std::vector< NetState< T > > space
row i of Q is space[i]
std::vector< Matrix< T > > filt
eventFilt: filt[a] holds only what synchronization sync[a] contributed, so sum_a filt[a] is the off-d...
std::vector< Sync< T > > sync
ev, the reference's sn.sync
Matrix< T > Q
the infinitesimal generator
The SolverCTMC knobs this port honours.
The outputs of @@SolverCTMC/getSymbolicGenerator.
std::vector< Matrix< T > > terms
ctmc_makeinfgen(filt[e]), the numeric term symbol e scales; empty if inactive.
std::vector< T > rate0
Minimum positive rate of each event, i.e.
std::vector< std::string > symbols
x1..xE, empty for an event with no positive rate.
bool invert_symbol
entries carry c/x_e instead of c*x_e
std::vector< std::string > active_symbols() const
The symbols that actually occur, i.e.
std::vector< std::vector< std::string > > Q
The generator as expression strings, row major; "0" where the entry is zero.
std::vector< Matrix< T > > filt
Event filtration divided by its minimum positive rate; empty if inactive.
std::vector< Sync< T > > sync
what filt is indexed by
std::vector< NetState< T > > space
row i of Q is space[i]
Backend selection, mirroring options.config.symbolic and its timeout.
int timeout_s
options.config.symbolic_timeout, seconds; the reference defaults to 300.
std::string backend
auto to search, a URL, an image name, or none to stay local.
What @@SolverCTMC/getSymbolicSolution.m returns, plus the engine that answered.
std::string engine
backend that solved it, e.g. sage
int nconncomp
weakly connected components of the generator
std::vector< NetState< T > > space
entry i of pi is space[i]
std::vector< std::string > num
numerator of each entry over den
std::vector< int > conncomp
component index of each state, one based
std::vector< std::string > pi
stationary probability of each state
std::string den
common denominator of the vector
Symbolic stationary distribution of a CTMC.
std::vector< std::string > pi
Stationary probability of each state, as an expression.
std::vector< int > connComp
Component index of each state, one based.
std::vector< std::string > num
Numerator of each entry over the common denominator.
int nConnComp
Weakly connected components of the generator.
std::string den
Common denominator of the whole vector.
Computer algebra operations LINE needs, as seen by this port.
Resolves the symbolic backend to use, and owns the container that serves it.