5#ifndef LINE_SOLVERS_CTMC_SOLVER_CTMC_SENS_H
6#define LINE_SOLVERS_CTMC_SOLVER_CTMC_SENS_H
85namespace sens_detail {
94 std::vector<double> rate;
95 std::vector<Matrix<T> > shape;
96 std::vector<bool> active;
100EventRates<T> event_rates(
const std::vector<
Matrix<T> >& F) {
102 out.rate.assign(F.size(), 0.0);
103 out.shape.resize(F.size());
104 out.active.assign(F.size(),
false);
105 for (std::size_t e = 0; e < F.size(); ++e) {
106 const symbolic_detail::MinPositive<T> m = symbolic_detail::min_positive(F[e]);
107 if (!m.has)
continue;
110 for (std::size_t i = 0; i < S.rows(); ++i)
111 for (std::size_t j = 0; j < S.cols(); ++j) S(i, j) = T(F[e](i, j) / m.value);
113 out.active[e] =
true;
120bool same_shape(
const Matrix<T>& a,
const Matrix<T>& b,
double tol) {
121 if (a.rows() != b.rows() || a.cols() != b.cols())
return false;
122 for (std::size_t i = 0; i < a.rows(); ++i)
123 for (std::size_t j = 0; j < a.cols(); ++j)
124 if (std::fabs(num_traits<T>::to_double(a(i, j)) - num_traits<T>::to_double(b(i, j))) >
142void symbolic_sensitivity(
const NetworkStruct<T>& sn,
const CtmcOptions& opt,
143 const CtmcSensParam<T>& param,
double theta,
double h,
144 const CtmcSymbolicOptions& symopt, std::vector<T>& pi_out,
145 std::vector<T>& dpi_out) {
147 const std::size_t n = g.space.size();
148 const std::size_t ne = g.symbols.size();
150 std::vector<double> rate0(ne, 0.0);
151 for (std::size_t e = 0; e < ne; ++e) rate0[e] = num_traits<T>::to_double(g.rate0[e]);
153 const double hrate = std::max(std::fabs(theta), 1.0) * 1e-3;
154 NetworkStruct<T> up = sn, dn = sn;
155 param.set(up, theta + hrate);
156 param.set(dn, theta - hrate);
159 if (gu.space.size() != n || gd.space.size() != n)
161 "solver_ctmc_sensitivity: perturbing the parameter changed the state-space size, so "
162 "the generators cannot be differenced; this happens when theta switches a transition "
163 "on or off (a zero rate, or an immediate transition appearing)");
164 if (gu.filt.size() != ne || gd.filt.size() != ne)
166 "solver_ctmc_sensitivity: perturbing the parameter changed the number of events");
168 EventRates<T> ru = event_rates(gu.filt), rd = event_rates(gd.filt);
169 for (std::size_t e = 0; e < ne; ++e) {
170 if (g.symbols[e].empty())
continue;
171 if (!ru.active[e] || !rd.active[e] || !same_shape(ru.shape[e], g.filt[e], 1e-8) ||
172 !same_shape(rd.shape[e], g.filt[e], 1e-8))
174 "solver_ctmc_sensitivity: perturbing the parameter reshapes the filtration of "
176 std::to_string(e + 1) +
177 " rather than scaling it, so the generator is not linear in a single rate per "
178 "event and the symbolic chain rule does not apply; use method 'fd'");
181 double curvature = 0.0, scale = 1.0;
182 for (std::size_t e = 0; e < ne; ++e) {
183 curvature = std::max(curvature, std::fabs(ru.rate[e] + rd.rate[e] - 2.0 * rate0[e]));
184 scale = std::max(scale, std::fabs(rate0[e]));
187 if (curvature > 1e-9 * scale) {
188 NetworkStruct<T> up2 = sn, dn2 = sn;
189 param.set(up2, theta + h);
190 param.set(dn2, theta - h);
195 std::vector<double> drate(ne, 0.0);
196 for (std::size_t e = 0; e < ne; ++e) drate[e] = (ru.rate[e] - rd.rate[e]) / (2.0 * step);
198 const std::shared_ptr<sym::SymEngine> engine =
199 symbolic_detail::require_engine(symopt,
"the 'symbolic' sensitivity");
200 const std::vector<std::string> pi_expr = engine->solveCTMC(g.Q, g.active_symbols()).pi;
201 if (pi_expr.size() != n)
202 throw sym::SymEngineError(
"solver_ctmc_sensitivity: backend '" + engine->name() +
203 "' returned " + std::to_string(pi_expr.size()) +
204 " entries for a " + std::to_string(n) +
" state chain");
206 std::map<std::string, double> assignment;
207 for (std::size_t e = 0; e < ne; ++e)
208 if (!g.symbols[e].empty()) assignment[g.symbols[e]] = rate0[e];
210 const std::vector<double> pi = engine->eval(pi_expr, assignment);
211 pi_out.assign(n, num_traits<T>::from_int(0));
212 for (std::size_t s = 0; s < n && s < pi.size(); ++s)
213 pi_out[s] = num_traits<T>::from_double(pi[s]);
215 dpi_out.assign(n, num_traits<T>::from_int(0));
216 for (std::size_t e = 0; e < ne; ++e) {
217 if (g.symbols[e].empty())
continue;
220 if (drate[e] == 0.0)
continue;
221 const std::vector<double> dvals =
222 engine->eval(engine->diff(pi_expr, g.symbols[e], 1), assignment);
223 if (dvals.size() != n)
224 throw sym::SymEngineError(
"solver_ctmc_sensitivity: backend '" + engine->name() +
225 "' returned " + std::to_string(dvals.size()) +
226 " derivative values for a " + std::to_string(n) +
228 for (std::size_t s = 0; s < n; ++s)
229 dpi_out[s] += num_traits<T>::from_double(drate[e] * dvals[s]);
248 const std::vector<T>& reward = std::vector<T>(),
249 const std::string& method =
"fd",
251 if (method !=
"fd" && method !=
"symbolic")
252 throw InputError(
"solver_ctmc_sensitivity: unknown method '" + method +
253 "'; expected 'fd' or 'symbolic'");
255 throw InputError(
"solver_ctmc_sensitivity: the parameter carries no setter, so theta "
256 "cannot be applied to the model");
258 const double theta = param.
value;
259 const double h = param.
step > 0 ? param.
step : std::max(std::fabs(theta), 1.0) * 1e-6;
263 if (method ==
"symbolic") {
264 sens_detail::symbolic_sensitivity(
sn,
opt, param, theta, h, symopt, out.
pi, out.
dpi);
268 n = base.
chain.space.size();
271 param.
set(up, theta + h);
272 param.
set(dn, theta - h);
275 if (su.
chain.space.size() != n || sd.
chain.space.size() != n)
277 "solver_ctmc_sensitivity: perturbing the parameter changed the state-space size, "
278 "so the generators cannot be differenced; this happens when theta switches a "
279 "transition on or off (a zero rate, or an immediate transition appearing)");
283 for (std::size_t a = 0; a < n; ++a)
284 for (std::size_t b = 0; b < n; ++b)
285 dQ(a, b) = T((su.
chain.Q(a, b) - sd.
chain.Q(a, b)) / twoh);
290 if (reward.empty())
return out;
291 if (reward.size() != n)
292 throw InputError(
"solver_ctmc_sensitivity: the reward must have one entry per state");
297 for (std::size_t s = 0; s < n; ++s) {
298 out.
S += T(out.
dpi[s] * reward[s]);
299 er += T(out.
pi[s] * reward[s]);
329 const std::vector<
CtmcSensParam<T>>& params,
const std::vector<T>& reward) {
331 throw InputError(
"solver_ctmc_sensitivity_ranking: a reward is required to rank "
333 std::vector<CtmcSensRank<T>> rows;
334 for (std::size_t l = 0; l < params.size(); ++l) {
337 row.parameter = params[l].name.empty() ?
"theta" + std::to_string(l + 1) : params[l].name;
338 row.value = params[l].value;
347 std::stable_sort(rows.begin(), rows.end(),
349 if (a.scaled_valid != b.scaled_valid) return a.scaled_valid;
350 if (!a.scaled_valid) return false;
351 return std::fabs(num_traits<T>::to_double(a.SS)) >
352 std::fabs(num_traits<T>::to_double(b.SS));
UnsupportedError(const std::string &what)
A network plus its refreshed NetworkStruct.
Sensitivity of the steady-state distribution of a CTMC to a scalar parameter.
The exception types the port throws.
Dense matrix and non-owning view.
CtmcSens< T > solver_ctmc_sensitivity(const NetworkStruct< T > &sn, const CtmcOptions &opt, const CtmcSensParam< T > ¶m, const std::vector< T > &reward=std::vector< T >(), const std::string &method="fd", const CtmcSymbolicOptions &symopt=CtmcSymbolicOptions())
Port of @@SolverCTMC/getSensitivity.
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...
std::vector< CtmcSensRank< T > > solver_ctmc_sensitivity_ranking(const NetworkStruct< T > &sn, const CtmcOptions &opt, const std::vector< CtmcSensParam< T > > ¶ms, const std::vector< T > &reward)
Port of @@SolverCTMC/getSensitivityRanking: rank parameters by influence.
CtmcSolution< T > solver_ctmc_analyzer(const NetworkStruct< T > &sn_in, const CtmcOptions &opt)
Port of solver_ctmc_analyzer.m plus the fork-join wrapper of @@SolverCTMC/runAnalyzer....
CtmcSymbolicGenerator< T > ctmc_symbolic_generator(const NetworkStruct< T > &sn, const CtmcOptions &opt, bool invert_symbol=false)
Port of @@SolverCTMC/getSymbolicGenerator.m.
std::vector< T > ctmc_sens(const Matrix< T > &Q, const Matrix< T > &dQ, const std::vector< T > &pi)
Sensitivity of the steady-state distribution of a CTMC to a scalar parameter.
A queueing network and its refreshed NetworkStruct.
Port of solver_ctmc_analyzer.m and the parts of @@SolverCTMC/runAnalyzer.m that surround one solve: t...
Port of @@SolverCTMC/getSymbolicGenerator and getSymbolicSolution.
The SolverCTMC knobs this port honours.
The scalar parameter a sensitivity is taken with respect to.
double step
Central-difference step; <= 0 takes the reference's max(|theta|,1)*1e-6.
std::function< void(NetworkStruct< T > &, double)> set
Apply theta to a COPY of the struct; the original is never mutated.
One row of the ranking table.
What one sensitivity computation returns.
T S
d E[r] / d theta, Eq. (9.79)
T SS
(theta/E[r]) d E[r] / d theta, Eq. (9.80)
bool scaled_valid
false when E[r] is zero, MATLAB's NaN
Everything one CTMC solve produces.
std::vector< T > pi
stationary distribution over chain.space
Backend selection, mirroring options.config.symbolic and its timeout.
static constexpr double Zero
Computer algebra operations LINE needs, as seen by this port.