LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
sensitivity.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_OPT_SENSITIVITY_H
6#define LINE_OPT_SENSITIVITY_H
7
8/**
9 * @file
10 * @ingroup line_opt
11 * Parametric sensitivities of a solved network, as optimizer input.
12 *
13 * `compute_model_sensitivities` dispatches on what the model admits:
14 * `open_sensitivities` for an open network, `closed_sensitivities` for a closed
15 * one, and `ctmc_sensitivities` where the product-form derivatives do not apply
16 * and the chain has to be differentiated instead. Each returns a
17 * `SensitivityData` or nothing, so an unavailable sensitivity is absent rather
18 * than zero -- a caller must not read "no derivative" as "the derivative is
19 * zero".
20 */
21
22#include <cmath>
23#include <map>
24#include <optional>
25#include <string>
26#include <vector>
27
30#include "line/opt/results.h"
33
34namespace line {
35namespace opt {
36
37inline std::optional<SensitivityData> open_sensitivities(qn::Network<double>& model) {
39 if (sn.classes.empty()) return std::nullopt;
40 for (const qn::JobClass& jobclass : sn.classes)
41 if (!std::isinf(jobclass.population)) return std::nullopt;
42
44 SensitivityData sensitivity;
45 for (std::size_t i = 0; i < sn.nstations; ++i) {
46 const qn::Station<double>& station = sn.stations[i];
47 if (station.sched == lang::SchedStrategy::EXT) continue;
48 const bool delay = station.sched == lang::SchedStrategy::INF;
49 if (!delay && station.nservers > 1.0) return std::nullopt;
50
51 std::vector<double> demand(sn.nclasses, 0.0), rho(sn.nclasses, 0.0);
52 for (std::size_t r = 0; r < sn.nclasses; ++r) {
53 const double rate = sn.rates(i, r);
54 const double visits = pf.V(i, r);
55 if (std::isfinite(rate) && rate > 0.0 && visits > 0.0) {
56 demand[r] = visits / rate;
57 rho[r] = pf.lambda[r] * demand[r];
58 }
59 }
60 double utilization = 0.0;
61 if (!delay)
62 for (double value : rho) utilization += value;
63 const double denominator = 1.0 - utilization;
64 if (!delay && denominator <= 0.0) return std::nullopt;
65
66 const std::string& station_name = station.name;
67 for (std::size_t s = 0; s < sn.nclasses; ++s) {
68 const double rate = sn.rates(i, s);
69 if (std::isfinite(rate) && rate > 0.0 && pf.V(i, s) > 0.0)
70 sensitivity.add("Util", station_name,
71 SensitivityData::parameter_key(station_name, sn.classes[s].name),
72 -rho[s] / rate);
73 }
74
75 for (std::size_t r = 0; r < sn.nclasses; ++r) {
76 const double rate_r = sn.rates(i, r);
77 if (pf.V(i, r) <= 0.0 || !std::isfinite(rate_r) || rate_r <= 0.0) continue;
78 const std::string metric = SensitivityData::metric_key(
79 station_name, sn.classes[r].name);
80 for (std::size_t s = 0; s < sn.nclasses; ++s) {
81 const double rate_s = sn.rates(i, s);
82 if (!std::isfinite(rate_s) || rate_s <= 0.0 || pf.V(i, s) <= 0.0)
83 continue;
84 const std::string parameter = SensitivityData::parameter_key(
85 station_name, sn.classes[s].name);
86 const double d_utilization = delay ? 0.0 : -rho[s] / rate_s;
87 const double d_demand = s == r ? -demand[r] / rate_s : 0.0;
88 const double d_rho = s == r ? -rho[r] / rate_s : 0.0;
89 double d_response, d_queue;
90 if (delay) {
91 d_response = d_demand;
92 d_queue = d_rho;
93 } else {
94 d_response = (d_demand * denominator +
95 demand[r] * d_utilization) /
96 (denominator * denominator);
97 d_queue = (d_rho * denominator + rho[r] * d_utilization) /
98 (denominator * denominator);
99 }
100 sensitivity.add("RespT", metric, parameter, d_response);
101 sensitivity.add("QLen", metric, parameter, d_queue);
102 }
103 sensitivity.add("Tput", metric,
104 SensitivityData::parameter_key(station_name, sn.classes[r].name), 0.0);
105 }
106 }
107 return sensitivity;
108}
109
110inline std::optional<SensitivityData> closed_sensitivities(qn::Network<double>& model) {
111 const qn::NetworkStruct<double>& sn = model.get_struct();
112 if (sn.classes.empty()) return std::nullopt;
113 std::vector<int> population(sn.nclasses, 0);
114 for (std::size_t r = 0; r < sn.nclasses; ++r) {
115 if (!std::isfinite(sn.classes[r].population)) return std::nullopt;
116 population[r] = static_cast<int>(std::llround(sn.classes[r].population));
117 }
118
120 for (double servers : pf.S)
121 if (std::isfinite(servers) && servers > 1.0) return std::nullopt;
122 std::vector<double> think_time(sn.nclasses, 0.0);
123 for (std::size_t z = 0; z < pf.Z.rows(); ++z)
124 for (std::size_t r = 0; r < sn.nclasses; ++r)
125 think_time[r] += pf.Z(z, r);
127 pf.D, population, think_time);
128
129 struct Parameter {
130 std::size_t index;
131 double chain;
132 std::string key;
133 };
134 std::vector<Parameter> parameters;
135 for (std::size_t j = 0; j < pf.queue_stations.size(); ++j) {
136 const std::size_t station = pf.queue_stations[j] - 1;
137 for (std::size_t s = 0; s < sn.nclasses; ++s) {
138 const double rate = sn.rates(station, s);
139 if (!std::isfinite(rate) || rate <= 0.0 || pf.D(j, s) <= 0.0) continue;
140 parameters.push_back({j * sn.nclasses + s, -pf.D(j, s) / rate,
141 SensitivityData::parameter_key(sn.stations[station].name,
142 sn.classes[s].name)});
143 }
144 }
145
146 SensitivityData sensitivity;
147 for (std::size_t i = 0; i < pf.queue_stations.size(); ++i) {
148 const std::size_t station = pf.queue_stations[i] - 1;
149 const std::string& station_name = sn.stations[station].name;
150 for (std::size_t r = 0; r < sn.nclasses; ++r) {
151 if (pf.D(i, r) <= 0.0) continue;
152 const std::string metric = SensitivityData::metric_key(
153 station_name, sn.classes[r].name);
154 for (const Parameter& parameter : parameters) {
155 sensitivity.add("RespT", metric, parameter.key,
156 result.dR[parameter.index](i, r) * parameter.chain);
157 sensitivity.add("QLen", metric, parameter.key,
158 result.dQ[parameter.index](i, r) * parameter.chain);
159 sensitivity.add("Tput", metric, parameter.key,
160 result.dX(r, parameter.index) * parameter.chain);
161 sensitivity.add("Util", station_name, parameter.key,
162 result.dU[parameter.index](i, r) * parameter.chain);
163 }
164 }
165 }
166 return sensitivity;
167}
168
169inline std::optional<SensitivityData> ctmc_sensitivities(qn::Network<double>& model,
170 ctmc::CtmcOptions options = {}) {
171 const qn::NetworkStruct<double>& sn = model.get_struct();
173 Matrix<double> state_space;
174 try {
175 base = ctmc::solver_ctmc_analyzer(sn, options);
176 state_space = ctmc::ctmc_state_space_aggr(sn, base.chain.space);
177 } catch (const std::exception&) {
178 return std::nullopt;
179 }
180 if (state_space.empty()) return std::nullopt;
181
182 SensitivityData sensitivity;
183 for (std::size_t pstation = 0; pstation < sn.nstations; ++pstation) {
184 for (std::size_t pclass = 0; pclass < sn.nclasses; ++pclass) {
185 const double rate = sn.rates(pstation, pclass);
186 if (!std::isfinite(rate) || rate <= 0.0 ||
187 !sn.has_service_law(pstation, pclass) ||
188 sn.service[pstation][pclass].type != lang::ProcessType::EXP)
189 continue;
190 const std::string parameter = SensitivityData::parameter_key(
191 sn.stations[pstation].name, sn.classes[pclass].name);
192 ctmc::CtmcSensParam<double> specification;
193 specification.name = parameter;
194 specification.value = rate;
195 specification.set = [pstation, pclass](qn::NetworkStruct<double>& changed,
196 double value) {
197 changed.service[pstation][pclass] = lang::Distrib<double>::exp_rate(value);
198 changed.refresh_rates();
199 };
200 ctmc::CtmcSens<double> derivative;
201 try {
202 derivative = ctmc::solver_ctmc_sensitivity(sn, options, specification);
203 } catch (const std::exception&) {
204 continue;
205 }
206 for (std::size_t i = 0; i < sn.nstations; ++i) {
207 const std::string& station = sn.stations[i].name;
208 for (std::size_t r = 0; r < sn.nclasses; ++r) {
209 const std::size_t column = i * sn.nclasses + r;
210 if (column >= state_space.cols()) continue;
211 double value = 0.0;
212 bool finite = true;
213 for (std::size_t state = 0; state < state_space.rows(); ++state) {
214 if (!std::isfinite(state_space(state, column))) {
215 finite = false;
216 break;
217 }
218 value += derivative.dpi[state] * state_space(state, column);
219 }
220 if (finite)
221 sensitivity.add("QLen", SensitivityData::metric_key(
222 station, sn.classes[r].name), parameter, value);
223 }
224 }
225 }
226 }
227 return sensitivity;
228}
229
230inline std::optional<SensitivityData> compute_model_sensitivities(
231 qn::Network<double> model, bool use_ctmc = false) {
232 try {
233 std::optional<SensitivityData> result = open_sensitivities(model);
234 if (result) return result;
235 result = closed_sensitivities(model);
236 if (result) return result;
237 } catch (const std::exception&) {
238 }
239 return use_ctmc ? ctmc_sensitivities(model) : std::nullopt;
240}
241
242} // namespace opt
243} // namespace line
244
245#endif
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
bool empty() const
Definition matrix.h:92
void add(const std::string &kind, const std::string &metric, const std::string &parameter, double value)
Definition results.h:46
static std::string metric_key(const std::string &station, const std::string &jobclass)
Definition results.h:56
static std::string parameter_key(const std::string &station, const std::string &jobclass)
Definition results.h:60
A network plus its refreshed NetworkStruct.
std::vector< std::vector< Distrib< T > > > service
service[i][r], 0-based station and class; a disabled entry marks a pair never visited.
bool has_service_law(std::size_t i, std::size_t r) const
Does (station i, class r) have a service law an analyzer may convert?
std::vector< JobClass > classes
std::vector< Station< T > > stations
stations[k-1] is the k-th station
Matrix< T > rates
(nstations x nclasses) service rates and SCVs, with a PARALLEL disabled flag instead of MATLAB's NaN ...
A queueing network under construction.
const NetworkStruct< T > & get_struct()
The refreshed struct, MATLAB's model.getStruct().
CtmcSens< T > solver_ctmc_sensitivity(const NetworkStruct< T > &sn, const CtmcOptions &opt, const CtmcSensParam< T > &param, const std::vector< T > &reward=std::vector< T >(), const std::string &method="fd", const CtmcSymbolicOptions &symopt=CtmcSymbolicOptions())
Port of @@SolverCTMC/getSensitivity.
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....
Matrix< T > ctmc_state_space_aggr(const NetworkStruct< T > &sn, const std::vector< NetState< T > > &space)
Port of StateSpaceAggr: the per-(station, class) job counts of every state, as an (nstates x nstation...
PfParams< T > sn_get_product_form_params(const qn::NetworkStruct< T > &sn)
Port of sn_get_product_form_params.
std::optional< SensitivityData > open_sensitivities(qn::Network< double > &model)
Definition sensitivity.h:37
std::optional< SensitivityData > closed_sensitivities(qn::Network< double > &model)
std::optional< SensitivityData > ctmc_sensitivities(qn::Network< double > &model, ctmc::CtmcOptions options={})
std::optional< SensitivityData > compute_model_sensitivities(qn::Network< double > model, bool use_ctmc=false)
SensResult< T > pfqn_sens(const Matrix< T > &L, const std::vector< int > &N, const std::vector< T > &Z, const std::vector< int > &mi)
Exact analytic derivatives of the mean performance measures {X,Q,U,R} of a closed product-form (BCMP)...
Definition pfqn_sens.h:348
The Network constructor API: Queue, Delay, Source, Sink, Router, ClassSwitch, Cache,...
Exact analytic derivatives of the mean performance measures {X,Q,U,R} of a closed product-form (BCMP)...
What an evaluation and a solve return.
Port of matlab/src/api/sn/sn_get_product_form_params.m: the CLASS-level product-form parameters.
Port of @@SolverCTMC/getSensitivity and getSensitivityRanking: the parametric sensitivity of a steady...
The SolverCTMC knobs this port honours.
Everything one CTMC solve produces.
static Distrib exp_rate(const T &r)
Definition lang_types.h:814
The [lambda,D,N,Z,mu,S,V] of the reference.
std::vector< double > S
(Mq) server counts
Matrix< T > Z
(max(1,Mz) x R) demand at the delay stations
Matrix< T > V
(M x R) visits summed over chains
Matrix< T > D
(Mq x R) demand at the queueing stations
std::vector< std::size_t > queue_stations
(Mq) 1-based station indices
std::vector< T > lambda
(R) arrival rate, zero on a closed class
std::vector< Matrix< T > > dR
(P) matrices M x R
Definition pfqn_sens.h:75
std::vector< Matrix< T > > dU
(P) matrices M x R
Definition pfqn_sens.h:74
Matrix< T > dX
(R x P) dX(r)/dparam(p)
Definition pfqn_sens.h:72
std::vector< Matrix< T > > dQ
(P) matrices M x R, dQ[p](i,r)
Definition pfqn_sens.h:73
One job class of the network.
double population
infinite for an open class
One station of the network.
SchedStrategy sched
double nservers
may be infinite (a Delay, or an inf-scheduled task)