LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
ctmcSensitivities.m
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).
4%
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.
10%
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.
15%
16% Returns [] if the model has no CTMC representation of tractable size.
17%
18% Copyright (c) 2012-2026, Imperial College London
19% All rights reserved.
20
21sens = [];
22if nargin < 2 || isempty(options)
23 options = SolverCTMC.defaultOptions();
24end
25
26sn = model.getStruct();
27M = sn.nstations;
28K = sn.nclasses;
29
30solver = SolverCTMC(model, options);
31try
32 Q = full(solver.getGenerator());
33 spaceAggr = solver.getStateSpaceAggr();
34catch
35 return; % state space unavailable or over the memory gate
36end
37if isempty(spaceAggr)
38 return;
39end
40
41pi = ctmc_solve(Q);
42
43% One parameter per finite positive exponential service rate.
44%
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.
50params = {};
51for ist = 1:M
52 for k = 1:K
53 rate = sn.rates(ist, k);
54 if ~isfinite(rate) || rate <= 0
55 continue;
56 end
57 if isempty(sn.procid) || size(sn.procid, 1) < ist || size(sn.procid, 2) < k
58 continue;
59 end
60 if sn.procid(ist, k) ~= ProcessType.EXP
61 continue;
62 end
63 nodeIdx = sn.stationToNode(ist);
64 params{end+1} = struct(...
65 'value', rate, ...
66 'node', nodeIdx, ...
67 'class', k, ...
68 'pkey', opt.SensitivityData.paramKey(sn.nodenames{nodeIdx}, sn.classnames{k})); %#ok<AGROW>
69 end
70end
71
72sens = opt.SensitivityData();
73for p = 1:numel(params)
74 pr = params{p};
75 param = struct();
76 param.value = pr.value;
77 param.set = makeRateSetter(pr.node, pr.class);
78
79 try
80 [~, ~, dpi] = solver.getSensitivity(param);
81 catch
82 continue; % perturbation changed the state space; skip this parameter
83 end
84
85 for ist = 1:M
86 nameI = sn.nodenames{sn.stationToNode(ist)};
87 for r = 1:K
88 col = (ist - 1) * K + r;
89 if col > size(spaceAggr, 2)
90 continue;
91 end
92 rvec = spaceAggr(:, col);
93 if any(~isfinite(rvec))
94 continue; % Source stations carry an infinite population
95 end
96 mkey = opt.SensitivityData.metricKey(nameI, sn.classnames{r});
97 sens.add('QLen', mkey, pr.pkey, dpi * rvec);
98 end
99 end
100end
101end
102
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.
110
111h = @(m, value) setRate(m, nodeIdx, k, value);
112end
113
114function setRate(m, nodeIdx, k, value)
115nodes = m.getNodes();
116classes = m.getClasses();
117nodes{nodeIdx}.setService(classes{k}, Exp(value));
118end
Definition fjtag.m:157
Definition Station.m:265