1function sens = ctmcSensitivities(model, options)
2% ctmcSensitivities d(metric)/d(rate)
for an arbitrary Markovian model, via
3%
the generator-derivative equation of Trivedi and Bobbio (2017), Eq. (9.81).
5% This
is the fallback
for the cases
the differentiated-MVA primitive cannot
6% reach: open, multiserver, and non-unit-visit networks, for which
7% opt.sens.openSensitivities and opt.sens.closedSensitivities return []. It
8%
is exact wherever SolverCTMC
is exact, and correspondingly it
is limited by
9%
the state-space size rather than by
the product-
form assumptions.
11% Only queue-length sensitivities are produced. Utilization, response time,
12% and throughput are reward rates whose definition involves
the solved
13% metrics themselves, so they need
the reward-derivative term of Eq. (9.83)
14% and are not covered here.
16% Returns [] if
the model has no CTMC representation of tractable size.
18% Copyright (c) 2012-2026, Imperial College London
22if nargin < 2 || isempty(options)
23 options = SolverCTMC.defaultOptions();
26sn = model.getStruct();
30solver = SolverCTMC(model, options);
32 Q = full(solver.getGenerator());
33 spaceAggr = solver.getStateSpaceAggr();
35 return; % state space unavailable or over
the memory gate
43% One parameter per finite positive exponential service rate.
45% The rate setter substitutes an Exp of
the perturbed rate, which
is a
46% perturbation of theta only where
the nominal process
is itself exponential.
47% For an Erlang or Coxian service
the substitution would change
the
48% distribution family, and
the resulting difference quotient would not be
49% dQ/dtheta at all, so those stations are skipped rather than reported wrong.
53 rate = sn.rates(ist, k);
54 if ~isfinite(rate) || rate <= 0
57 if isempty(sn.procid) || size(sn.procid, 1) < ist || size(sn.procid, 2) < k
60 if sn.procid(ist, k) ~= ProcessType.EXP
63 nodeIdx = sn.stationToNode(ist);
64 params{end+1} =
struct(...
68 'pkey', opt.SensitivityData.paramKey(sn.nodenames{nodeIdx}, sn.classnames{k})); %#ok<AGROW>
72sens = opt.SensitivityData();
73for p = 1:numel(params)
76 param.value = pr.value;
77 param.set = makeRateSetter(pr.node, pr.class);
80 [~, ~, dpi] = solver.getSensitivity(param);
82 continue; % perturbation changed
the state space; skip
this parameter
86 nameI = sn.nodenames{sn.stationToNode(ist)};
88 col = (ist - 1) * K + r;
89 if col > size(spaceAggr, 2)
92 rvec = spaceAggr(:, col);
93 if any(~isfinite(rvec))
94 continue; % Source stations carry an infinite population
96 mkey = opt.SensitivityData.metricKey(nameI, sn.classnames{r});
97 sens.add(
'QLen', mkey, pr.pkey, dpi * rvec);
103function h = makeRateSetter(nodeIdx, k)
104% h = makeRateSetter(nodeIdx, k)
105% Handle setting
the service rate of node NODEIDX
class K on a model copy.
106% The distribution
is replaced by an exponential of
the requested rate, so
107%
this is only meaningful where
the nominal service
is itself exponential.
108% Node and
class are resolved inside
the copy, since
the objects of
the
109% original model
do not belong to it.
111h = @(m, value) setRate(m, nodeIdx, k, value);
114function setRate(m, nodeIdx, k, value)
116classes = m.getClasses();
117nodes{nodeIdx}.setService(classes{k}, Exp(value));