LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
solver_ctmc_transient_analyzer.m
1function [t,pit,QNt,UNt,RNt,TNt,CNt,XNt,InfGen,StateSpace,StateSpaceAggr,EventFiltration,runtime,fname] = solver_ctmc_transient_analyzer(sn, options)
2% [T,PIT,QNT,UNT,RNT,TNT,CNT,XNT,INFGEN,STATESPACE,STATESPACEAGGR,EVENTFILTRATION,RUNTIME,FNAME] = SOLVER_CTMC_TRANSIENT_ANALYZER(QN, OPTIONS)
3%
4% Copyright (c) 2012-2026, Imperial College London
5% All rights reserved.
6
7RNt=[]; CNt=[]; XNt=[];
8
9M = sn.nstations; %number of stations
10K = sn.nclasses; %number of classes
11fname = '';
12Tstart = tic;
13S = sn.nservers;
14sched = sn.sched;
15PH = sn.proc;
16
17line_debug('CTMC transient analyzer starting: nstations=%d, nclasses=%d', M, K);
18
19[InfGen,StateSpace,StateSpaceAggr,EventFiltration,~,depRates,sn] = solver_ctmc(sn, options); % sn is updated with the state space
20
21if options.keep
22 fname = lineTempName;
23 save([fname,'.mat'],'InfGen','StateSpace','StateSpaceAggr','EventFiltration')
24 line_printf('\nCTMC infinitesimal generator and state space saved in: ');
25 line_printf([fname, '.mat'])
26end
27
28state = [];
29for ist=1:sn.nnodes
30 if sn.isstateful(ist)
31 isf = sn.nodeToStateful(ist);
32 state = [state,zeros(1,size(sn.space{isf},2)-length(sn.state{isf})),sn.state{isf}];
33 end
34end
35pi0 = zeros(1,length(InfGen));
36
37state0 = matchrow(StateSpace, state);
38if state0 == -1
39 state0 = matchrow(StateSpace, round(state));
40 state = round(state);
41 if state0 == -1
42 line_error(mfilename,'Initial state not contained in the state space.');
43 end
44end
45pi0(state0) = 1; % find initial state and set it to probability 1
46
47% Time-varying generator support. When options.config.rate_sched is supplied
48% (per-(station,class) rate trajectories, e.g. injected by the coupled LN
49% transient), the CTMC becomes time-inhomogeneous: the transitions attributable
50% to a scaled (station,class) service are modulated by m(t)=rate(t)/nominal.
51% The generator is linear in sn.rates(ist,r), so each scaled component
52% Qhat_sc = (Q(scaled) - Q_base)/(probe-1) is extracted with one extra
53% solver_ctmc build, and Q(t) = Q_base + sum_sc (m_sc(t)-1) Qhat_sc is
54% integrated by non-homogeneous uniformization over piecewise-constant segments.
55rate_sched = [];
56if isfield(options,'config') && isfield(options.config,'rate_sched') && ~isempty(options.config.rate_sched)
57 rate_sched = options.config.rate_sched;
58end
59if isempty(rate_sched)
60 [pit,t] = ctmc_transient(InfGen,pi0,options.timespan(1),options.timespan(2),options.stiff,[],options.timestep);
61 mscale = [];
62else
63 [pit,t,mscale] = local_ctmc_timevarying(sn, options, InfGen, StateSpace, pi0, rate_sched, M, K);
64end
65pit(pit<GlobalConstants.Zero)=0;
66
67QNt = cell(M,K);
68UNt = cell(M,K);
69%XNt = cell(1,K);
70TNt = cell(M,K);
71
72if t(1) == 0
73 t(1) = GlobalConstants.Zero;
74end
75for k=1:K
76 % XNt(k) = pi*arvRates(:,sn.refstat(k),k);
77 for ist=1:M
78 %occupancy_t = cumsum(pit.*[0;diff(t)],1)./t;
79 occupancy_t = pit;
80 TNt{ist,k} = occupancy_t*depRates(:,ist,k);
81 % Under a time-varying rate schedule, the throughput of a scaled
82 % (station,class) is modulated by its multiplier m(t); depRates is the
83 % nominal-rate departure rate, so scale it pointwise in time.
84 if ~isempty(mscale)
85 TNt{ist,k} = TNt{ist,k} .* squeeze(mscale(ist,k,:));
86 end
87 % A Source station holds an unbounded (Inf) population, so its
88 % StateSpaceAggr column is Inf and QNt/UNt are undefined
89 % (pit*Inf yields Inf/NaN). Mirror the steady-state analyzer, which
90 % leaves the Source QN/UN at 0 and reports only its throughput.
91 if sn.nodetype(sn.stationToNode(ist)) == NodeType.Source
92 QNt{ist,k} = zeros(size(pit,1),1);
93 UNt{ist,k} = zeros(size(pit,1),1);
94 continue
95 end
96 qlenAt_t = pit*StateSpaceAggr(:,(ist-1)*K+k);
97 %QNt{i,k} = cumsum(qlenAt_t.*[0;diff(t)])./t;
98 QNt{ist,k} = qlenAt_t;
99 switch sched(ist)
100 case SchedStrategy.INF
101 UNt{ist,k} = QNt{ist,k};
102 case {SchedStrategy.FCFS, SchedStrategy.HOL, SchedStrategy.SIRO, SchedStrategy.SEPT, SchedStrategy.LEPT, SchedStrategy.SJF}
103 if ~isempty(PH{ist}{k})
104 UNt{ist,k} = occupancy_t*min(StateSpaceAggr(:,(ist-1)*K+k),S(ist))/S(ist);
105 end
106 case SchedStrategy.PS
107 uik = min(StateSpaceAggr(:,(ist-1)*K+k),S(ist)) .* StateSpaceAggr(:,(ist-1)*K+k) ./ sum(StateSpaceAggr(:,((ist-1)*K+1):(ist*K)),2);
108 uik(isnan(uik))=0;
109 utilAt_t = pit * uik / S(ist);
110 %UNt{i,k} = cumsum(utilAt_t.*[0;diff(t)])./t;
111 UNt{ist,k} = utilAt_t;
112 case SchedStrategy.DPS
113 w = sn.schedparam(ist,:);
114 nik = S(ist) * w(k) * StateSpaceAggr(:,(ist-1)*K+k) ./ sum(repmat(w,size(StateSpaceAggr,1),1).*StateSpaceAggr(:,((ist-1)*K+1):(ist*K)),2);
115 nik(isnan(nik))=0;
116 UNt{ist,k} = occupancy_t*nik;
117 otherwise
118 if ~isempty(PH{ist}{k})
119 ind = sn.stationToNode(ist);
120 line_warning(mfilename,'Transient utilization not support yet for station %s, returning an approximation.\n',sn.nodenames{ind});
121 UNt{ist,k} = occupancy_t*min(StateSpaceAggr(:,(ist-1)*K+k),S(ist))/S(ist);
122 end
123 end
124 end
125end
126runtime = toc(Tstart);
127
128%if options.verbose
129% line_printf('\nCTMC analysis completed. Runtime: %f seconds.\n',runtime);
130%end
131end
132
133function [pit, t, mscale] = local_ctmc_timevarying(sn, options, Qbase, StateSpace, pi0, rate_sched, M, K)
134% Integrate the time-inhomogeneous forward equation dpi/dt = pi Q(t) by
135% non-homogeneous uniformization over a piecewise-constant grid. The generator
136% is Q(t) = Qbase + sum_sc (m_sc(t)-1) Qhat_sc, where Qhat_sc is the linear
137% component of Qbase attributable to the scaled (station,class) rate,
138% extracted by a single probe rebuild (Q is linear in sn.rates).
139ts = options.timespan;
140Ngrid = 100;
141if isfield(options,'config') && isfield(options.config,'ctmc_tv_ngrid') && ~isempty(options.config.ctmc_tv_ngrid)
142 Ngrid = options.config.ctmc_tv_ngrid;
143end
144t = linspace(ts(1), ts(2), Ngrid)';
145nt = numel(t);
146nS = size(Qbase,1);
147
148% Build the per-schedule generator component and multiplier trajectory.
149probe = 2.0;
150nsc = numel(rate_sched);
151Qhat = cell(1,nsc);
152mtraj = ones(nt, nsc);
153scStation = zeros(1,nsc); scClass = zeros(1,nsc);
154for s = 1:nsc
155 ist = rate_sched(s).station;
156 r = rate_sched(s).class;
157 scStation(s) = ist; scClass(s) = r;
158 % probe rebuild with the (ist,r) service process time-scaled by `probe`;
159 % the reachable state space is rate-independent, so ordering matches Qbase.
160 % solver_ctmc builds transitions from the process representation (sn.proc
161 % MAP D0/D1 and sn.mu), NOT from sn.rates, so all rate-carrying fields must
162 % be scaled. Time-scaling a MAP/PH by f multiplies D0 and D1 (and the phase
163 % rates mu) by f, scaling every completion/phase rate by f.
164 snp = sn;
165 snp.rates(ist,r) = snp.rates(ist,r) * probe;
166 if iscell(snp.proc) && numel(snp.proc) >= ist && iscell(snp.proc{ist}) && numel(snp.proc{ist}) >= r
167 pr = snp.proc{ist}{r};
168 if iscell(pr)
169 for z = 1:numel(pr)
170 if isnumeric(pr{z})
171 pr{z} = pr{z} * probe;
172 end
173 end
174 snp.proc{ist}{r} = pr;
175 end
176 end
177 if iscell(snp.mu) && numel(snp.mu) >= ist && iscell(snp.mu{ist}) && numel(snp.mu{ist}) >= r
178 snp.mu{ist}{r} = snp.mu{ist}{r} * probe;
179 end
180 Qp = solver_ctmc(snp, options);
181 if size(Qp,1) ~= nS
182 line_error(mfilename, 'rate_sched probe changed the CTMC state-space size; cannot build time-varying generator.');
183 end
184 Qhat{s} = (Qp - Qbase) / (probe - 1);
185 % multiplier m(t) = rate(t)/nominal (nominal defaults to sn.rates(ist,r))
186 if isfield(rate_sched(s),'nominal') && ~isempty(rate_sched(s).nominal)
187 nominal = rate_sched(s).nominal;
188 else
189 nominal = sn.rates(ist,r);
190 end
191 seg_t = rate_sched(s).tgrid(:)';
192 seg_r = rate_sched(s).rates(:)';
193 mtraj(:,s) = interp1(seg_t, seg_r, min(max(t,seg_t(1)),seg_t(end)), 'linear') / nominal;
194end
195
196% Propagate one segment per grid interval, with the multiplier frozen at the
197% interval midpoint (second-order in dt). Over a segment the generator Qk is
198% constant, so the exact forward solution is pi(t+dt) = pi(t) expm(Qk dt).
199% We deliberately use the matrix exponential rather than ctmc_uniformization:
200% an LN layer can carry a near-instantaneous reply-signal sentinel rate (~1e9),
201% making the generator stiff (q dt ~ 1e8); uniformization then splits the step
202% into >1e5 sub-segments and leaks all probability mass to zero. expm of a
203% valid generator is exactly stochastic, so mass is conserved for any
204% stiffness. For a large sentinel rate the corresponding transient state simply
205% empties within the segment, which is the intended near-instantaneous limit.
206pit = zeros(nt, nS);
207pit(1,:) = pi0(:)';
208Qfull = full(Qbase);
209QhatFull = cell(1,nsc);
210for s = 1:nsc
211 QhatFull{s} = full(Qhat{s});
212end
213for k = 1:nt-1
214 dt = t(k+1) - t(k);
215 Qk = Qfull;
216 for s = 1:nsc
217 mk = 0.5*(mtraj(k,s) + mtraj(k+1,s));
218 Qk = Qk + (mk - 1) * QhatFull{s};
219 end
220 pit(k+1,:) = pit(k,:) * expm(Qk * dt);
221end
222
223% Per-(station,class) throughput multiplier over time (1 where not scaled).
224mscale = ones(M, K, nt);
225for s = 1:nsc
226 mscale(scStation(s), scClass(s), :) = reshape(mtraj(:,s), 1, 1, nt);
227end
228end
Definition Station.m:245