LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
NetworkSolver.m
1classdef NetworkSolver < Solver
2 % NetworkSolver Abstract base class for queueing network solvers
3 %
4 % NetworkSolver provides the common interface and functionality for all
5 % solvers that can analyze Network models. It handles performance metric
6 % computation, language switching between MATLAB and Java implementations,
7 % and provides standardized solver initialization and execution patterns.
8 %
9 % @brief Abstract base class for all queueing network analysis solvers
10 %
11 % Key characteristics:
12 % - Abstract base for all network-specific solvers
13 % - Manages performance metric handles and computation
14 % - Supports both MATLAB native and Java/JLINE implementations
15 % - Provides standardized solver options and configuration
16 % - Handles model language switching and delegation
17 %
18 % NetworkSolver serves as the foundation for:
19 % - Analytical solvers (MVA, NC, CTMC, etc.)
20 % - Simulation solvers (JMT, SSA)
21 % - Approximation methods (Fluid, MAM, NN)
22 % - Automatic solver selection (AUTO)
23 %
24 % Example usage pattern:
25 % @code
26 % solver = SolverMVA(model, 'MyMVASolver');
27 % solver.getAvg(); % Get average performance metrics
28 % @endcode
29 %
30 % Copyright (c) 2012-2026, Imperial College London
31 % All rights reserved.
32
33
34 properties (Access = protected)
35 handles; % performance metric handles
36 end
37
38 properties (Hidden)
39 % Cached MMT fork-join transformation, reused across SolverLN outer
40 % iterations (tagged by structVersion, NOT cleared by reset()).
41 % see _kb/05-solvers-overview.md for rationale
42 mmtCache = [];
43 end
44
45 properties (Hidden)
46 % Percentile extraction method of the last getPerctRespT call
47 % ('cdf', 'forktail', ...), so that citations() can report the paper
48 % behind a tail estimate the user actually asked for.
49 lastPerctMethod = '';
50 end
51
52 properties (Access = public)
53 % Auxiliary-class arrival rates of the fork-join (MMT) fixed point,
54 % retained across runAnalyzer calls so that an outer iteration (e.g.
55 % SolverLN) restarts the MMT loop from the previous converged point
56 % instead of from GlobalConstants.FineTol. Like options.init_sol it
57 % survives reset(); use resetForkWarmStart to discard it.
58 fjForkLambda = [];
59 end
60
61 methods
62
63 function self = NetworkSolver(model, name, options)
64 % NETWORKSOLVER Create a NetworkSolver instance
65 %
66 % @brief Creates a NetworkSolver with the specified model and options
67 % @param model Network model to be analyzed
68 % @param name String identifier for the solver instance
69 % @param options Optional SolverOptions structure for configuration
70 % @return self NetworkSolver instance ready for analysis
71 %
72 % The constructor initializes the solver with the provided model,
73 % validates inputs, sets options, and prepares performance metric handles.
74 self@Solver(model, name);
75 if isempty(model)
76 line_error(mfilename,'The model supplied in input is empty');
77 end
78 if nargin>=3 %exist('options','var'),
79 self.setOptions(options);
80 end
81 self.result = [];
82
83 if isempty(model.obj) % not a Java object
84 initHandles(self)
85 end
86 end
87
88 function resetForkWarmStart(self)
89 % RESETFORKWARMSTART()
90 % Discard the retained MMT fixed point. Mirrors options.init_sol:
91 % the iterate survives reset() and is invalidated explicitly by the
92 % caller when the chain basis changes.
93 self.fjForkLambda = [];
94 end
95
96 function setLang(self)
97 % NOTE: self.model is passed by reference so it affects the
98 % user model content
99 switch self.options.lang %
100 case 'matlab' % matlab solver
101 if self.model.isJavaNative() % java model
102 matlab_model = JLINE.jline_to_line(self.model.obj);
103 matlab_model.obj = self.model.obj;
104 self.model = matlab_model;
105 self.initHandles;
106 else % matlab model
107 % no-op
108 end
109 case 'java' % solver lang
110 joptions = self.options;
111 if ~isempty(self.model.obj) % java model
112 % no-op
113 else % matlab model
114 self.model.obj = JLINE.line_to_jline(self.model);
115 end
116 switch self.name
117 case 'SolverCTMC'
118 self.obj = JLINE.SolverCTMC(self.model.obj, joptions);
119 case 'SolverLDES'
120 self.obj = JLINE.SolverLDES(self.model.obj, joptions);
121 case {'SolverFluid','SolverFLD'}
122 self.obj = JLINE.SolverFluid(self.model.obj, joptions);
123 case 'SolverJMT'
124 self.obj = JLINE.SolverJMT(self.model.obj, joptions);
125 case 'SolverMAM'
126 self.obj = JLINE.SolverMAM(self.model.obj, joptions);
127 case 'SolverMVA'
128 self.obj = JLINE.SolverMVA(self.model.obj, joptions);
129 case 'SolverNC'
130 self.obj = JLINE.SolverNC(self.model.obj, joptions);
131 case 'SolverSSA'
132 self.obj = JLINE.SolverSSA(self.model.obj, joptions);
133 case 'SolverQNS'
134 self.obj = JLINE.SolverQNS(self.model.obj, joptions);
135 end
136 end
137 end
138
139 function initHandles(self)
140 [Q,U,R,T,A,W] = self.model.getAvgHandles();
141
142 % Get tardiness handles if available
143 if ismethod(self.model, 'getAvgTardHandles')
144 Tard = self.model.getAvgTardHandles();
145 else
146 Tard = [];
147 end
148 if ismethod(self.model, 'getAvgSysTardHandles')
149 SysTard = self.model.getAvgSysTardHandles();
150 else
151 SysTard = [];
152 end
153
154 self.setAvgHandles(Q,U,R,T,A,W,Tard,SysTard);
155
156 [Qt,Ut,Tt] = self.model.getTranHandles;
157 self.setTranHandles(Qt,Ut,Tt);
158
159 % refreshStruct is called lazily by model.getStruct() when first needed
160 end
161
162 function self = runAnalyzerChecks(self, options)
163 % Single, method-aware feature gate shared by every solver.
164 % It resolves the concrete method that will actually run (for
165 % options.method='default' this may map to a specific method via
166 % feature-driven selection), then gates the model against that
167 % method's per-method feature set rather than the solver-level
168 % union. Solvers whose methods are all equivalent inherit the base
169 % behavior transparently (getMethodFeatureSet defaults to the
170 % coarse solver set), so this replaces the previous bespoke
171 % per-solver overrides without changing their behavior.
172
173 % Propagate solver verbose level to global so that model-level
174 % messages (e.g., priority info in refreshStruct) respect it
175 GlobalConstants.setVerbose(options.verbose);
176 if ~self.enableChecks
177 return;
178 end
179 if ~any(cellfun(@(s) strcmp(s,options.method),self.listValidMethods))
180 line_error(mfilename,sprintf('The ''%s'' method is unsupported by this solver.\n',options.method));
181 end
182 method = self.resolveMethod(options);
183 [bool, reason] = self.supportsModelMethod(method);
184 if ~bool
185 if strcmp(method, options.method)
186 line_error(mfilename, sprintf('This model contains features not supported by the solver. %s', reason));
187 else
188 line_error(mfilename, sprintf('This model contains features not supported by the solver''s ''%s'' method. %s', method, reason));
189 end
190 end
191 end
192
193 function [bool, reason] = supportsModelMethod(self, method)
194 % Fine, method-aware gate: does the model fit the concrete METHOD?
195 % Base behavior derives the answer from getMethodFeatureSet(method):
196 % when that is empty the solver does not diverge per method and the
197 % solver's own supports(model) is used (preserving structural checks
198 % such as LQNS layers / LDES LayeredNetwork special-casing).
199 % Solvers with non-feature-set structural per-method rules (e.g. NC
200 % 'mem' via solver_nc_mem_supports) override this.
201 featSupported = self.getMethodFeatureSet(method);
202 if isempty(featSupported)
203 bool = self.supports(self.model);
204 reason = '';
205 else
206 featUsed = self.model.getUsedLangFeatures();
207 [bool, reason] = SolverFeatureSet.supports(featSupported, featUsed);
208 end
209 end
210
211 function method = resolveMethod(self, options)
212 % Resolve the concrete method that will run. Base behavior is a
213 % no-op (returns options.method unchanged). Solvers that perform
214 % feature-driven selection for options.method='default' override
215 % this (typically via selectMethod).
216 method = options.method;
217 end
218
219 function featSupported = getMethodFeatureSet(self, method) %#ok<INUSD>
220 % Per-method feature set. Returning empty ([]) signals "this solver
221 % does not diverge per method": the gate then uses the solver's own
222 % supports(model), preserving any structural checks it carries.
223 % Divergent solvers (e.g. MVA, MAM, NC) override this to return the
224 % base envelope with per-method add/remove deltas applied.
225 featSupported = [];
226 end
227
228 function method = selectMethod(self, preferenceList)
229 % Feature-driven method selection: returns the first method in
230 % PREFERENCELIST whose per-method feature set covers the model.
231 % Falls back to the last entry when none fully covers (the base
232 % gate then reports the precise unsupported features).
233 method = preferenceList{end};
234 for k = 1:numel(preferenceList)
235 cand = preferenceList{k};
236 [ok, ~] = self.supportsModelMethod(cand);
237 if ok
238 method = cand;
239 return;
240 end
241 end
242 end
243
244 function self = setTranHandles(self,Qt,Ut,Tt)
245 self.handles.Qt = Qt;
246 self.handles.Ut = Ut;
247 self.handles.Tt = Tt;
248 end
249
250 function self = setAvgHandles(self,Q,U,R,T,A,W,Tard,SysTard)
251 self.handles.Q = Q;
252 self.handles.U = U;
253 self.handles.R = R;
254 self.handles.T = T;
255 self.handles.A = A;
256 self.handles.W = W;
257 if nargin >= 8
258 self.handles.Tard = Tard;
259 end
260 if nargin >= 9
261 self.handles.SysTard = SysTard;
262 end
263 end
264
265 function [Qt,Ut,Tt] = getTranHandles(self)
266 Qt = self.handles.Qt;
267 Ut = self.handles.Ut;
268 Tt = self.handles.Tt;
269 end
270
271 function [Q,U,R,T,A,W] = getAvgHandles(self)
272 if isempty(self.handles)
273 self.handles.Q = [];
274 self.handles.U = [];
275 self.handles.R = [];
276 self.handles.T = [];
277 self.handles.A = [];
278 self.handles.W = [];
279 self.handles.Tard = [];
280 self.handles.SysTard = [];
281 initHandles(self);
282 end
283 Q = self.handles.Q;
284 U = self.handles.U;
285 R = self.handles.R;
286 T = self.handles.T;
287 A = self.handles.A;
288 W = self.handles.W;
289 end
290
291 function Q = getAvgQLenHandles(self)
292 if isempty(self.handles) || ~isstruct(self.handles)
293 self.getAvgHandles();
294 end
295 Q = self.handles.Q;
296 end
297
298 function U = getAvgUtilHandles(self)
299 if isempty(self.handles) || ~isstruct(self.handles)
300 self.getAvgHandles();
301 end
302 U = self.handles.U;
303 end
304
305 function R = getAvgRespTHandles(self)
306 if isempty(self.handles) || ~isstruct(self.handles)
307 self.getAvgHandles();
308 end
309 R = self.handles.R;
310 end
311
312 function T = getAvgTputHandles(self)
313 if isempty(self.handles) || ~isstruct(self.handles)
314 self.getAvgHandles();
315 end
316 T = self.handles.T;
317 end
318
319 function A = getAvgArvRHandles(self)
320 if isempty(self.handles) || ~isstruct(self.handles)
321 self.getAvgHandles();
322 end
323 A = self.handles.A;
324 end
325
326 function W = getAvgResidTHandles(self)
327 if isempty(self.handles) || ~isstruct(self.handles)
328 self.getAvgHandles();
329 end
330 W = self.handles.W;
331 end
332 end
333
334
335 methods (Access = 'protected')
336 function bool = hasAvgResults(self)
337 % BOOL = HASAVGRESULTS()
338
339 % Returns true if the solver has computed steady-state average metrics.
340 bool = false;
341 if self.hasResults
342 if isfield(self.result,'Avg')
343 bool = true;
344 end
345 end
346 end
347
348 function bool = hasTranResults(self)
349 % BOOL = HASTRANRESULTS()
350
351 % Return true if the solver has computed transient average metrics.
352 bool = false;
353 if self.hasResults
354 if isfield(self.result,'Tran')
355 if isfield(self.result.Tran,'Avg')
356 bool = isfield(self.result.Tran.Avg,'Q');
357 end
358 end
359 end
360 end
361
362 function bool = hasDistribResults(self)
363 % BOOL = HASDISTRIBRESULTS()
364
365 % Return true if the solver has computed steady-state distribution metrics.
366 bool = false;
367 if self.hasResults
368 bool = isfield(self.result.Distribution,'C');
369 end
370 end
371 end
372
373 methods (Sealed)
374
375 function self = setModel(self, model)
376 % SELF = SETMODEL(MODEL)
377
378 % Assign the model to be solved.
379 self.model = model;
380 end
381
382 function QN = getAvgQLen(self)
383 % QN = GETAVGQLEN()
384
385 % Compute average queue-lengths at steady-state
386 Q = getAvgQLenHandles(self);
387 [QN,~,~,~] = self.getAvg(Q,[],[],[],[],[]);
388 end
389
390 function UN = getAvgUtil(self)
391 % UN = GETAVGUTIL()
392
393 % Compute average utilizations at steady-state
394 U = getAvgUtilHandles(self);
395 [~,UN,~,~] = self.getAvg([],U,[],[],[],[]);
396 end
397
398 function RN = getAvgRespT(self)
399 % RN = GETAVGRESPT()
400
401 % Compute average response times at steady-state
402 R = getAvgRespTHandles(self);
403 [~,~,RN,~] = self.getAvg([],[],R,[],[],[]);
404 end
405
406 function WN = getAvgResidT(self)
407 % WN = GETAVGRESIDT()
408
409 % Compute average residence times at steady-state
410 R = getAvgRespTHandles(self);
411 W = getAvgResidTHandles(self);
412 [~,~,~,~,~,WN] = self.getAvg([],[],R,[],[],W);
413 end
414
415 function WT = getAvgWaitT(self)
416 % RN = GETAVGWAITT()
417 % Compute average waiting time in queue excluding service
418 R = getAvgRespTHandles(self);
419 [~,~,RN,~] = self.getAvg([],[],R,[],[],[]);
420 if isempty(RN)
421 WT = [];
422 return
423 end
424 sn = self.model.getStruct;
425 WT = RN - 1./ sn.rates(:);
426 WT(sn.nodetype==NodeType.Source) = 0;
427 end
428
429 function TN = getAvgTput(self)
430 % TN = GETAVGTPUT()
431
432 % Compute average throughputs at steady-state
433 T = getAvgTputHandles(self);
434 [~,~,~,TN] = self.getAvg([],[],[],T,[],[]);
435 end
436
437 function AN = getAvgArvR(self)
438 % AN = GETAVGARVR()
439 sn = self.model.getStruct();
440
441 % Compute average arrival rate at steady-state
442 TH = getAvgTputHandles(self);
443 [~,~,~,TN] = self.getAvg([],[],[],TH,[],[]);
444 AN = sn_get_arvr_from_tput(sn, TN, TH);
445 end
446
447 % also accepts a cell array with the handlers in it
448 [QN,UN,RN,TN,AN,WN] = getAvg(self,Q,U,R,T,A,W);
449 [QN,UN,RN,TN,AN,WN] = getAvgNode(self,Q,U,R,T,A,W);
450
451 [AvgTable,QT,UT,RT,WT,AT,TT] = getAvgTable(self,Q,U,R,T,A,keepDisabled);
452
453 [AvgTable,QT] = getAvgQLenTable(self,Q,keepDisabled);
454 [AvgTable,UT] = getAvgUtilTable(self,U,keepDisabled);
455 [AvgTable,RT] = getAvgRespTTable(self,R,keepDisabled);
456 [AvgTable,TT] = getAvgTputTable(self,T,keepDisabled);
457
458 [NodeAvgTable,QTn,UTn,RTn,WTn,ATn,TTn] = getAvgNodeTable(self,Q,U,R,T,A,keepDisabled);
459 [CacheAvgTable] = getAvgCacheTable(self);
460 [ItemAvgTable] = getAvgItemTable(self);
461 [AvgChain,QTc,UTc,RTc,WTc,ATc,TTc] = getAvgChainTable(self,Q,U,R,T);
462 [AvgChain,QTc,UTc,RTc,WTc,ATc,TTc] = getAvgNodeChainTable(self,Q,U,R,T);
463
464 [QNc,UNc,RNc,WNc,ANc,TNc] = getAvgChain(self,Q,U,R,T);
465 [QNc] = getAvgQLenChain(self,Q);
466 [UNc] = getAvgUtilChain(self,U);
467 [RNc] = getAvgRespTChain(self,R);
468 [WNc] = getAvgResidTChain(self,W);
469 [TNc] = getAvgTputChain(self,T);
470 [ANc] = getAvgArvRChain(self,A);
471 [QNc] = getAvgNodeQLenChain(self,Q);
472 [UNc] = getAvgNodeUtilChain(self,U);
473 [RNc] = getAvgNodeRespTChain(self,R);
474 [WNc] = getAvgNodeResidTChain(self,W);
475 [TNc] = getAvgNodeTputChain(self,T);
476 [ANc] = getAvgNodeArvRChain(sef,A);
477
478 [CNc,XNc] = getAvgSys(self,R,T);
479 [CT,XT] = getAvgSysTable(self,R,T);
480 [RN] = getAvgSysRespT(self,R);
481 [TN] = getAvgSysTput(self,T);
482
483
484 function self = setAvgResults(self,Q,U,R,T,A,W,C,X,runtime,method,iter)
485 % SELF = SETAVGRESULTS(SELF,Q,U,R,T,A,W,C,X,RUNTIME,METHOD,ITER)
486 % Store average metrics at steady-state
487 self.result.('solver') = getName(self);
488 if nargin<11 %~exist('method','var')
489 method = getOptions(self).method;
490 end
491 if nargin<12 %~exist('iter','var')
492 iter = NaN;
493 end
494 self.result.Avg.('method') = method;
495 self.result.Avg.('iter') = iter;
496 if isnan(Q), Q=[]; end
497 if isnan(R), R=[]; end
498 if isnan(T), T=[]; end
499 if isnan(U), U=[]; end
500 if isnan(X), X=[]; end
501 if isnan(C), C=[]; end
502 if isnan(A), A=[]; end
503 if isnan(W), W=[]; end
504 self.result.Avg.Q = real(Q);
505 self.result.Avg.R = real(R);
506 self.result.Avg.X = real(X);
507 self.result.Avg.U = real(U);
508 self.result.Avg.T = real(T);
509 self.result.Avg.C = real(C);
510 self.result.Avg.A = real(A);
511 self.result.Avg.W = real(W);
512 self.result.Avg.runtime = runtime;
513 if ~isfield(self.result.Avg,'timedOut')
514 % Wall-clock time-budget flag; solvers that stop early on
515 % options.timeout overwrite this with true after calling setAvgResults.
516 self.result.Avg.timedOut = false;
517 end
518 if getOptions(self).verbose
519 try
520 solvername = erase(self.result.solver,'Solver');
521 catch
522 solvername = self.result.solver(7:end);
523 end
524 if isnan(iter) || iter==1 || strcmp(solvername,'LDES') || strcmp(solvername,'SSA')
525 line_printf('%s analysis [method: %s, lang: %s, env: %s] completed in %fs.',solvername,self.result.Avg.method,self.options.lang,version("-release"),runtime);
526 else
527 line_printf('%s analysis [method: %s, lang: %s, env: %s] completed in %fs. Iterations: %d.',solvername,self.result.Avg.method,self.options.lang,version("-release"),runtime,iter);
528 end
529 line_printf('\n');
530 end
531 end
532
533 function self = setAvgResultsCI(self, QCI, UCI, RCI, TCI, ACI, WCI, CCI, XCI)
534 % SELF = SETAVGRESULTSCI(SELF, QCI, UCI, RCI, TCI, ACI, WCI, CCI, XCI)
535 % Store confidence interval bounds for average metrics
536 % Each CI parameter is an [M x K x 2] array with lower/upper bounds
537 % or an [M x K] array with half-widths (mean ± halfwidth)
538 if nargin >= 2 && ~isempty(QCI)
539 self.result.Avg.QCI = QCI;
540 end
541 if nargin >= 3 && ~isempty(UCI)
542 self.result.Avg.UCI = UCI;
543 end
544 if nargin >= 4 && ~isempty(RCI)
545 self.result.Avg.RCI = RCI;
546 end
547 if nargin >= 5 && ~isempty(TCI)
548 self.result.Avg.TCI = TCI;
549 end
550 if nargin >= 6 && ~isempty(ACI)
551 self.result.Avg.ACI = ACI;
552 end
553 if nargin >= 7 && ~isempty(WCI)
554 self.result.Avg.WCI = WCI;
555 end
556 if nargin >= 8 && ~isempty(CCI)
557 self.result.Avg.CCI = CCI;
558 end
559 if nargin >= 9 && ~isempty(XCI)
560 self.result.Avg.XCI = XCI;
561 end
562 end
563
564 function self = setDistribResults(self,Cd,runtime)
565 % SELF = SETDISTRIBRESULTS(SELF,CD,RUNTIME)
566
567 % Store distribution metrics at steady-state
568 self.result.('solver') = getName(self);
569 self.result.Distribution.('method') = getOptions(self).method;
570 self.result.Distribution.C = Cd;
571 self.result.Distribution.runtime = runtime;
572 end
573
574 function self = setTranProb(self,t,pi_t,SS,runtimet)
575 % SELF = SETTRANPROB(SELF,T,PI_T,SS,RUNTIMET)
576
577 % Store transient average metrics
578 self.result.('solver') = getName(self);
579 self.result.Tran.Prob.('method') = getOptions(self).method;
580 self.result.Tran.Prob.t = t;
581 self.result.Tran.Prob.pi_t = pi_t;
582 self.result.Tran.Prob.SS = SS;
583 self.result.Tran.Prob.runtime = runtimet;
584 end
585
586 function self = setTranAvgResults(self,Qt,Ut,Rt,Tt,Ct,Xt,runtimet)
587 % SELF = SETTRANAVGRESULTS(SELF,QT,UT,RT,TT,CT,XT,RUNTIMET)
588
589 % Store transient average metrics
590 self.result.('solver') = getName(self);
591 self.result.Tran.Avg.('method') = getOptions(self).method;
592 % Clear individual cells that are scalar NaN (not entire array)
593 for i=1:size(Qt,1), for r=1:size(Qt,2), if isscalar(Qt{i,r}) && any(isnan(Qt{i,r})), Qt{i,r}=[]; end, end, end
594 for i=1:size(Rt,1), for r=1:size(Rt,2), if isscalar(Rt{i,r}) && any(isnan(Rt{i,r})), Rt{i,r}=[]; end, end, end
595 for i=1:size(Ut,1), for r=1:size(Ut,2), if isscalar(Ut{i,r}) && any(isnan(Ut{i,r})), Ut{i,r}=[]; end, end, end
596 for i=1:size(Tt,1), for r=1:size(Tt,2), if isscalar(Tt{i,r}) && any(isnan(Tt{i,r})), Tt{i,r}=[]; end, end, end
597 for i=1:size(Xt,1), for r=1:size(Xt,2), if isscalar(Xt{i,r}) && any(isnan(Xt{i,r})), Xt{i,r}=[]; end, end, end
598 for i=1:size(Ct,1), for r=1:size(Ct,2), if isscalar(Ct{i,r}) && any(isnan(Ct{i,r})), Ct{i,r}=[]; end, end, end
599 self.result.Tran.Avg.Q = Qt;
600 self.result.Tran.Avg.R = Rt;
601 self.result.Tran.Avg.U = Ut;
602 self.result.Tran.Avg.T = Tt;
603 self.result.Tran.Avg.X = Xt;
604 self.result.Tran.Avg.C = Ct;
605 self.result.Tran.Avg.runtime = runtimet;
606 end
607
608 end
609
610 methods
611 [QNt,UNt,TNt] = getTranAvg(self,Qt,Ut,Tt);
612
613 function [lNormConst] = getProbNormConstAggr(self)
614 % [LNORMCONST] = GETPROBNORMCONST()
615
616 % Return normalizing constant of state probabilities
617 line_error(mfilename,sprintf('getProbNormConstAggr is not supported by %s',class(self)));
618 end
619
620 function Pstate = getProb(self, node, state)
621 % PSTATE = GETPROBSTATE(NODE, STATE)
622
623 % Return marginal state probability for station ist state
624 line_error(mfilename,sprintf('getProb is not supported by %s',class(self)));
625 end
626
627 function Psysstate = getProbSys(self)
628 % PSYSSTATE = GETPROBSYSSTATE()
629
630 % Return joint state probability
631 line_error(mfilename,sprintf('getProbSys is not supported by %s',class(self)));
632 end
633
634 function Pnir = getProbAggr(self, node, state_a)
635 % PNIR = GETPROBSTATEAGGR(NODE, STATE_A)
636
637 % Return marginal state probability for station ist state
638 line_error(mfilename,sprintf('getProbAggr is not supported by %s',class(self)));
639 end
640
641 function Pnjoint = getProbSysAggr(self)
642 % PNJOINT = GETPROBSYSSTATEAGGR()
643
644 % Return joint state probability
645 line_error(mfilename,sprintf('getProbSysAggr is not supported by %s',class(self)));
646 end
647
648 function Pmarg = getProbMarg(self, node, jobclass, state_m)
649 % PMARG = GETPROBMARG(NODE, JOBCLASS, STATE_M)
650
651 % Return marginalized state probability for station and class
652 % This computes probabilities marginalized on a given class, in contrast to
653 % getProbAggr which aggregates over all classes.
654 line_error(mfilename,sprintf('getProbMarg is not supported by %s',class(self)));
655 end
656
657 function tstate = sample(self, node, numEvents)
658 % TSTATE = SAMPLE(NODE, numEvents)
659
660 % Return marginal state probability for station ist state
661 line_error(mfilename,sprintf('sample is not supported by %s',class(self)));
662 end
663
664 function tstate = sampleAggr(self, node, numEvents)
665 % TSTATE = SAMPLEAGGR(NODE, numEvents)
666
667 % Return marginal state probability for station ist state
668 line_error(mfilename,sprintf('sampleAggr is not supported by %s',class(self)));
669 end
670
671 function tstate = sampleSys(self, numEvents)
672 % TSTATE = SAMPLESYS(numEvents)
673
674 % Return joint state probability
675 line_error(mfilename,sprintf('sampleSys is not supported by %s',class(self)));
676 end
677
678 function tstate = sampleSysAggr(self, numEvents)
679 % TSTATE = SAMPLESYSAGGR(numEvents)
680
681 % Return joint state probability
682 line_error(mfilename,sprintf('sampleSysAggr is not supported by %s',class(self)));
683 end
684
685 function RD = getCdfRespT(self, R)
686 % RD = GETCDFRESPT(R)
687
688 % Return cumulative distribution of response times at steady-state
689 % This uses a trivial approximation that assumes exponential
690 % distributions everywhere with mean as RN(i,r)
691
692 sn = self.getStruct;
693 RD = cell(sn.nstations,sn.nclasses);
694 if GlobalConstants.DummyMode
695 return
696 end
697 T0 = tic;
698 if nargin<2 %~exist('R','var')
699 R = self.getAvgRespTHandles;
700 % to do: check if some R are disabled
701 end
702 if ~self.hasAvgResults
703 self.getAvg; % get steady-state solution
704 end
705 for i=1:sn.nstations
706 if sn.nodetype(sn.stationToNode(i)) ~= NodeType.Source
707 for c=1:sn.nclasses
708 if isfinite(self.result.Avg.R(i,c)) && self.result.Avg.R(i,c)>0
709 lambda = 1/self.result.Avg.R(i,c);
710 n = 100; % number of points
711 quantiles = linspace(0.001, 0.999, n);
712 RD{i,c} = [quantiles;-log(1 - quantiles) / lambda]';
713 else
714 RD{i,c} = [1,0];
715 end
716 end
717 end
718 end
719 runtime = toc(T0);
720 self.setDistribResults(RD, runtime);
721 end
722
723 function RD = getTranCdfRespT(self, R)
724 % RD = GETTRANCDFRESPT(R)
725
726 % Return cumulative distribution of response times during transient
727 line_error(mfilename,sprintf('getTranCdfRespT is not supported by %s',class(self)));
728 end
729
730 function RD = getCdfPassT(self, R)
731 % RD = GETCDFPASST(R)
732
733 % Return cumulative distribution of passage times at steady-state
734 line_error(mfilename,sprintf('getCdfPassT is not supported by %s',class(self)));
735 end
736
737 function RD = getTranCdfPassT(self, R)
738 % RD = GETTRANCDFPASST(R)
739
740 % Return cumulative distribution of passage times during transient
741 line_error(mfilename,sprintf('getTranCdfPassT is not supported by %s',class(self)));
742 end
743
744 % Kotlin-style aliases for getAvg* methods
745 function avg_table = avgTable(self)
746 % AVGTABLE Kotlin-style alias for getAvgTable
747 avg_table = self.getAvgTable();
748 end
749
750 function avg_sys_table = avgSysTable(self)
751 % AVGSYSTABLE Kotlin-style alias for getAvgSysTable
752 avg_sys_table = self.getAvgSysTable();
753 end
754
755 function avg_node_table = avgNodeTable(self)
756 % AVGNODETABLE Kotlin-style alias for getAvgNodeTable
757 avg_node_table = self.getAvgNodeTable();
758 end
759
760 function avg_chain_table = avgChainTable(self)
761 % AVGCHAINTABLE Kotlin-style alias for getAvgChainTable
762 avg_chain_table = self.getAvgChainTable();
763 end
764
765 function avg_node_chain_table = avgNodeChainTable(self)
766 % AVGNODECHAINTABLE Kotlin-style alias for getAvgNodeChainTable
767 avg_node_chain_table = self.getAvgNodeChainTable();
768 end
769
770 % Table -> T aliases
771 function avg_table = avgT(self)
772 % AVGT Short alias for avgTable
773 avg_table = self.avgTable();
774 end
775
776 function avg_sys_table = avgSysT(self)
777 % AVGSYST Short alias for avgSysTable
778 avg_sys_table = self.avgSysTable();
779 end
780
781 function avg_node_table = avgNodeT(self)
782 % AVGNODET Short alias for avgNodeTable
783 avg_node_table = self.avgNodeTable();
784 end
785
786 function avg_chain_table = avgChainT(self)
787 % AVGCHAINT Short alias for avgChainTable
788 avg_chain_table = self.avgChainTable();
789 end
790
791 function avg_node_chain_table = avgNodeChainT(self)
792 % AVGNODECHAINT Short alias for avgNodeChainTable
793 avg_node_chain_table = self.avgNodeChainTable();
794 end
795
796 function varargout = avgChain(self, varargin)
797 % AVGCHAIN Kotlin-style alias for getAvgChain
798 [varargout{1:nargout}] = self.getAvgChain(varargin{:});
799 end
800
801 function varargout = avgSys(self, varargin)
802 % AVGSYS Kotlin-style alias for getAvgSys
803 [varargout{1:nargout}] = self.getAvgSys(varargin{:});
804 end
805
806 function varargout = avgNode(self, varargin)
807 % AVGNODE Kotlin-style alias for getAvgNode
808 [varargout{1:nargout}] = self.getAvgNode(varargin{:});
809 end
810
811 function sys_resp_time = avgSysRespT(self, varargin)
812 % AVGSYSRESPT Kotlin-style alias for getAvgSysRespT
813 sys_resp_time = self.getAvgSysRespT(varargin{:});
814 end
815
816 function sys_tput = avgSysTput(self, varargin)
817 % AVGSYSTPUT Kotlin-style alias for getAvgSysTput
818 sys_tput = self.getAvgSysTput(varargin{:});
819 end
820
821 function arvr_chain = avgArvRChain(self, varargin)
822 % AVGARVCHAIN Kotlin-style alias for getAvgArvRChain
823 arvr_chain = self.getAvgArvRChain(varargin{:});
824 end
825
826 function qlen_chain = avgQLenChain(self, varargin)
827 % AVGQLENCHAIN Kotlin-style alias for getAvgQLenChain
828 qlen_chain = self.getAvgQLenChain(varargin{:});
829 end
830
831 function util_chain = avgUtilChain(self, varargin)
832 % AVGUTILCHAIN Kotlin-style alias for getAvgUtilChain
833 util_chain = self.getAvgUtilChain(varargin{:});
834 end
835
836 function resp_t_chain = avgRespTChain(self, varargin)
837 % AVGRESPTCHAIN Kotlin-style alias for getAvgRespTChain
838 resp_t_chain = self.getAvgRespTChain(varargin{:});
839 end
840
841 function resid_t_chain = avgResidTChain(self, varargin)
842 % AVGRESIDTCHAIN Kotlin-style alias for getAvgResidTChain
843 resid_t_chain = self.getAvgResidTChain(varargin{:});
844 end
845
846 function tput_chain = avgTputChain(self, varargin)
847 % AVGTPUTCHAIN Kotlin-style alias for getAvgTputChain
848 tput_chain = self.getAvgTputChain(varargin{:});
849 end
850
851 function node_arvr_chain = avgNodeArvRChain(self, varargin)
852 % AVGNODERVRCHAIN Kotlin-style alias for getAvgNodeArvRChain
853 node_arvr_chain = self.getAvgNodeArvRChain(varargin{:});
854 end
855
856 function node_qlen_chain = avgNodeQLenChain(self, varargin)
857 % AVGNODEQLENCHAIN Kotlin-style alias for getAvgNodeQLenChain
858 node_qlen_chain = self.getAvgNodeQLenChain(varargin{:});
859 end
860
861 function node_util_chain = avgNodeUtilChain(self, varargin)
862 % AVGNODEUTILCHAIN Kotlin-style alias for getAvgNodeUtilChain
863 node_util_chain = self.getAvgNodeUtilChain(varargin{:});
864 end
865
866 function node_resp_t_chain = avgNodeRespTChain(self, varargin)
867 % AVGNODERESPTCHAIN Kotlin-style alias for getAvgNodeRespTChain
868 node_resp_t_chain = self.getAvgNodeRespTChain(varargin{:});
869 end
870
871 function node_resid_t_chain = avgNodeResidTChain(self, varargin)
872 % AVGNODERESIDTCHAIN Kotlin-style alias for getAvgNodeResidTChain
873 node_resid_t_chain = self.getAvgNodeResidTChain(varargin{:});
874 end
875
876 function node_tput_chain = avgNodeTputChain(self, varargin)
877 % AVGNODETPUTCHAIN Kotlin-style alias for getAvgNodeTputChain
878 node_tput_chain = self.getAvgNodeTputChain(varargin{:});
879 end
880
881 % Kotlin-style aliases for getTran* methods
882 function varargout = tranAvg(self, varargin)
883 % TRANAVG Kotlin-style alias for getTranAvg
884 [varargout{1:nargout}] = self.getTranAvg(varargin{:});
885 end
886
887 function rd = tranCdfRespT(self, varargin)
888 % TRANCDFRESPT Kotlin-style alias for getTranCdfRespT
889 rd = self.getTranCdfRespT(varargin{:});
890 end
891
892 function rd = tranCdfPassT(self, varargin)
893 % TRANCDFPASST Kotlin-style alias for getTranCdfPassT
894 rd = self.getTranCdfPassT(varargin{:});
895 end
896
897 % Kotlin-style aliases for getCdf* methods
898 function rd = cdfRespT(self, varargin)
899 % CDFRESPT Kotlin-style alias for getCdfRespT
900 rd = self.getCdfRespT(varargin{:});
901 end
902
903 function rd = cdfPassT(self, varargin)
904 % CDFPASST Kotlin-style alias for getCdfPassT
905 rd = self.getCdfPassT(varargin{:});
906 end
907
908 % Kotlin-style aliases for getProb* methods
909 function pstate = prob(self, varargin)
910 % PROB Kotlin-style alias for getProb
911 pstate = self.getProb(varargin{:});
912 end
913
914 function psysstate = probSys(self)
915 % PROBSYS Kotlin-style alias for getProbSys
916 psysstate = self.getProbSys();
917 end
918
919 function tf = supportsExactSensitivity(self) %#ok<MANU>
920 % TF = SUPPORTSEXACTSENSITIVITY()
921 % True when the solver evaluates a product-form recursion that
922 % getSensitivityTable can differentiate analytically. False here,
923 % so that a solver reaching this base implementation obtains its
924 % sensitivities by finite differences on its own predictions.
925 % Overridden by SolverMVA and SolverNC.
926 tf = false;
927 end
928
929 function pnir = probAggr(self, varargin)
930 % PROBAGGR Kotlin-style alias for getProbAggr
931 pnir = self.getProbAggr(varargin{:});
932 end
933
934 function pnjoint = probSysAggr(self)
935 % PROBSYSAGGR Kotlin-style alias for getProbSysAggr
936 pnjoint = self.getProbSysAggr();
937 end
938
939 function pmarg = probMarg(self, varargin)
940 % PROBMARG Kotlin-style alias for getProbMarg
941 pmarg = self.getProbMarg(varargin{:});
942 end
943
944 function lnormconst = probNormConstAggr(self)
945 % PROBNORMCONSTAGGR Kotlin-style alias for getProbNormConstAggr
946 lnormconst = self.getProbNormConstAggr();
947 end
948
949 % Table -> T shorthand aliases for the auxiliary result tables
950 % (moment/sensitivity/cache/item/orbit), mirroring aT for getAvgTable.
951 function varargout = momentT(self, varargin)
952 % MOMENTT Alias for getMomentTable
953 [varargout{1:nargout}] = self.getMomentTable(varargin{:});
954 end
955 function varargout = mT(self, varargin)
956 % MT Alias for getMomentTable
957 [varargout{1:nargout}] = self.getMomentTable(varargin{:});
958 end
959 function varargout = getMomentT(self, varargin)
960 % GETMOMENTT Alias for getMomentTable
961 [varargout{1:nargout}] = self.getMomentTable(varargin{:});
962 end
963 function varargout = momentChainT(self, varargin)
964 % MOMENTCHAINT Alias for getMomentChainTable
965 [varargout{1:nargout}] = self.getMomentChainTable(varargin{:});
966 end
967 function varargout = mCT(self, varargin)
968 % MCT Alias for getMomentChainTable
969 [varargout{1:nargout}] = self.getMomentChainTable(varargin{:});
970 end
971 function varargout = getMomentChainT(self, varargin)
972 % GETMOMENTCHAINT Alias for getMomentChainTable
973 [varargout{1:nargout}] = self.getMomentChainTable(varargin{:});
974 end
975 function varargout = momentStationT(self, varargin)
976 % MOMENTSTATIONT Alias for getMomentStationTable
977 [varargout{1:nargout}] = self.getMomentStationTable(varargin{:});
978 end
979 function varargout = mST(self, varargin)
980 % MST Alias for getMomentStationTable
981 [varargout{1:nargout}] = self.getMomentStationTable(varargin{:});
982 end
983 function varargout = getMomentStationT(self, varargin)
984 % GETMOMENTSTATIONT Alias for getMomentStationTable
985 [varargout{1:nargout}] = self.getMomentStationTable(varargin{:});
986 end
987 function varargout = sensitivityT(self, varargin)
988 % SENSITIVITYT Alias for getSensitivityTable
989 [varargout{1:nargout}] = self.getSensitivityTable(varargin{:});
990 end
991 function varargout = sT(self, varargin)
992 % ST Alias for getSensitivityTable
993 [varargout{1:nargout}] = self.getSensitivityTable(varargin{:});
994 end
995 function varargout = getSensitivityT(self, varargin)
996 % GETSENSITIVITYT Alias for getSensitivityTable
997 [varargout{1:nargout}] = self.getSensitivityTable(varargin{:});
998 end
999 function varargout = cacheAvgT(self, varargin)
1000 % CACHEAVGT Alias for getAvgCacheTable
1001 [varargout{1:nargout}] = self.getAvgCacheTable(varargin{:});
1002 end
1003 function varargout = aCaT(self, varargin)
1004 % ACAT Alias for getAvgCacheTable
1005 [varargout{1:nargout}] = self.getAvgCacheTable(varargin{:});
1006 end
1007 function varargout = getAvgCacheT(self, varargin)
1008 % GETAVGCACHET Alias for getAvgCacheTable
1009 [varargout{1:nargout}] = self.getAvgCacheTable(varargin{:});
1010 end
1011 function varargout = itemAvgT(self, varargin)
1012 % ITEMAVGT Alias for getAvgItemTable
1013 [varargout{1:nargout}] = self.getAvgItemTable(varargin{:});
1014 end
1015 function varargout = aIT(self, varargin)
1016 % AIT Alias for getAvgItemTable
1017 [varargout{1:nargout}] = self.getAvgItemTable(varargin{:});
1018 end
1019 function varargout = getAvgItemT(self, varargin)
1020 % GETAVGITEMT Alias for getAvgItemTable
1021 [varargout{1:nargout}] = self.getAvgItemTable(varargin{:});
1022 end
1023 function varargout = orbitAvgT(self, varargin)
1024 % ORBITAVGT Alias for getAvgOrbitTable
1025 [varargout{1:nargout}] = self.getAvgOrbitTable(varargin{:});
1026 end
1027 function varargout = aOT(self, varargin)
1028 % AOT Alias for getAvgOrbitTable
1029 [varargout{1:nargout}] = self.getAvgOrbitTable(varargin{:});
1030 end
1031 function varargout = getAvgOrbitT(self, varargin)
1032 % GETAVGORBITT Alias for getAvgOrbitTable
1033 [varargout{1:nargout}] = self.getAvgOrbitTable(varargin{:});
1034 end
1035 function varargout = lossAvgT(self, varargin)
1036 % LOSSAVGT Alias for getAvgLossTable
1037 [varargout{1:nargout}] = self.getAvgLossTable(varargin{:});
1038 end
1039 function varargout = aLT(self, varargin)
1040 % ALT Alias for getAvgLossTable
1041 [varargout{1:nargout}] = self.getAvgLossTable(varargin{:});
1042 end
1043 function varargout = getAvgLossT(self, varargin)
1044 % GETAVGLOSST Alias for getAvgLossTable
1045 [varargout{1:nargout}] = self.getAvgLossTable(varargin{:});
1046 end
1047 function varargout = regionLossAvgT(self, varargin)
1048 % REGIONLOSSAVGT Alias for getAvgRegionLossTable
1049 [varargout{1:nargout}] = self.getAvgRegionLossTable(varargin{:});
1050 end
1051 function varargout = aRLT(self, varargin)
1052 % ARLT Alias for getAvgRegionLossTable
1053 [varargout{1:nargout}] = self.getAvgRegionLossTable(varargin{:});
1054 end
1055 function varargout = getAvgRegionLossT(self, varargin)
1056 % GETAVGREGIONLOSST Alias for getAvgRegionLossTable
1057 [varargout{1:nargout}] = self.getAvgRegionLossTable(varargin{:});
1058 end
1059
1060 % Kotlin-style aliases for get*Handles methods
1061 function varargout = avgHandles(self)
1062 % AVGHANDLES Kotlin-style alias for getAvgHandles
1063 [varargout{1:nargout}] = self.getAvgHandles();
1064 end
1065
1066 function varargout = tranHandles(self)
1067 % TRANHANDLES Kotlin-style alias for getTranHandles
1068 [varargout{1:nargout}] = self.getTranHandles();
1069 end
1070
1071 function q = avgQLenHandles(self)
1072 % AVGQLENHANDLES Kotlin-style alias for getAvgQLenHandles
1073 q = self.getAvgQLenHandles();
1074 end
1075
1076 function u = avgUtilHandles(self)
1077 % AVGUTILHANDLES Kotlin-style alias for getAvgUtilHandles
1078 u = self.getAvgUtilHandles();
1079 end
1080
1081 function r = avgRespTHandles(self)
1082 % AVGRESPTHANDLES Kotlin-style alias for getAvgRespTHandles
1083 r = self.getAvgRespTHandles();
1084 end
1085
1086 function t = avgTputHandles(self)
1087 % AVGTPUTHANDLES Kotlin-style alias for getAvgTputHandles
1088 t = self.getAvgTputHandles();
1089 end
1090
1091 function a = avgArvRHandles(self)
1092 % AVGARVRHANDLES Kotlin-style alias for getAvgArvRHandles
1093 a = self.getAvgArvRHandles();
1094 end
1095
1096 function w = avgResidTHandles(self)
1097 % AVGRESIDTHANDLES Kotlin-style alias for getAvgResidTHandles
1098 w = self.getAvgResidTHandles();
1099 end
1100
1101 % Kotlin-style aliases for basic get* methods
1102 function qn = avgQLen(self)
1103 % AVGQLEN Kotlin-style alias for getAvgQLen
1104 qn = self.getAvgQLen();
1105 end
1106
1107 function un = avgUtil(self)
1108 % AVGUTIL Kotlin-style alias for getAvgUtil
1109 un = self.getAvgUtil();
1110 end
1111
1112 function rn = avgRespT(self)
1113 % AVGRESPT Kotlin-style alias for getAvgRespT
1114 rn = self.getAvgRespT();
1115 end
1116
1117 function wn = avgResidT(self)
1118 % AVGRESIDT Kotlin-style alias for getAvgResidT
1119 wn = self.getAvgResidT();
1120 end
1121
1122 function wt = avgWaitT(self)
1123 % AVGWAITT Kotlin-style alias for getAvgWaitT
1124 wt = self.getAvgWaitT();
1125 end
1126
1127 function tn = avgTput(self)
1128 % AVGTPUT Kotlin-style alias for getAvgTput
1129 tn = self.getAvgTput();
1130 end
1131
1132 function an = avgArvR(self)
1133 % AVGARVR Kotlin-style alias for getAvgArvR
1134 an = self.getAvgArvR();
1135 end
1136
1137 end
1138
1139 methods (Static)
1140 % Integer placement decided by an auxiliary solver steady state
1141 placement = warmStartPlacement(initSolver, sn)
1142
1143 function [bool, reason] = checkBindingCapacity(model, solverName)
1144 % [BOOL, REASON] = CHECKBINDINGCAPACITY(MODEL, SOLVERNAME)
1145 % Shared structural gate for finite station capacity
1146 % (setCapacity) and finite per-class buffers (classCap), used by
1147 % the product-form solvers (MVA, NC). A product-form solver has no
1148 % representation of a finite buffer, so without this gate it
1149 % silently returns the UNCONSTRAINED answer (e.g. QLen=4 instead
1150 % of the M/M/1/2 value 0.8525). There is no registry feature name
1151 % for plain capacity, hence the structural test; this mirrors the
1152 % native Python check in solvers/solver_mva/solver_mva.py.
1153 %
1154 % The test reads the node-level cap/classCap set by the user, NOT
1155 % sn.cap/sn.classcap: refreshCapacity derives a FINITE sn.classcap
1156 % (= the chain population) for every closed model, so an sn-level
1157 % test would reject every closed model.
1158 %
1159 % Only a capacity that can actually BIND is rejected. A closed
1160 % model whose station capacity is at least the total population
1161 % can never block a job, so the declaration is a no-op and the
1162 % product-form answer stays exact (a common idiom: setCap(N) on an
1163 % order-independent station of an N-job closed model). njobs is Inf
1164 % for an open class, so any finite capacity reachable by an open
1165 % class binds.
1166 %
1167 % Cache models are exempt: Cache.m sets classCap=1 on the
1168 % retrieval queues it builds, and MVA/NC solve those through their
1169 % dedicated cache/retrieval analyzers rather than as a buffer
1170 % constraint.
1171 bool = true;
1172 reason = '';
1173 if ~isa(model, 'Network')
1174 return
1175 end
1176 nodes = model.getNodes();
1177 for i = 1:numel(nodes)
1178 if isa(nodes{i}, 'Cache')
1179 return
1180 end
1181 end
1182 njobs = model.getStruct().njobs(:)';
1183 totalJobs = sum(njobs); % Inf as soon as one class is open
1184 for i = 1:numel(nodes)
1185 node = nodes{i};
1186 if ~isa(node, 'Station') || isa(node, 'Source') || isa(node, 'Sink')
1187 continue
1188 end
1189 if ~isempty(node.cap) && ~isinf(node.cap) && node.cap >= 0 && node.cap < totalJobs
1190 bool = false;
1191 reason = sprintf('Finite station capacity (setCapacity=%g) at station ''%s'' is not supported by %s. Use SolverCTMC, SolverJMT or SolverLDES.', node.cap, node.getName(), solverName);
1192 return
1193 end
1194 ccap = node.classCap;
1195 for r = 1:min(numel(ccap), numel(njobs))
1196 if ~isinf(ccap(r)) && ccap(r) > 0 && ccap(r) < njobs(r)
1197 bool = false;
1198 reason = sprintf('Finite per-class capacity (classCap=%g for class %d) at station ''%s'' is not supported by %s. Use SolverCTMC, SolverJMT or SolverLDES.', ccap(r), r, node.getName(), solverName);
1199 return
1200 end
1201 end
1202 end
1203 end
1204
1205 function solvers = getAllSolvers(model, options)
1206 % SOLVERS = GETALLSOLVERS(MODEL, OPTIONS)
1207
1208 % Return a cell array with all Network solvers
1209 if nargin<2 %~exist('options','var')
1210 options = Solver.defaultOptions;
1211 end
1212 solvers = {};
1213 solvers{end+1} = SolverCTMC(model, options);
1214 solvers{end+1} = SolverFluid(model, options);
1215 solvers{end+1} = SolverJMT(model, options);
1216 solvers{end+1} = SolverMAM(model, options);
1217 solvers{end+1} = SolverMVA(model, options);
1218 solvers{end+1} = SolverNC(model, options);
1219 solvers{end+1} = SolverSSA(model, options);
1220 end
1221 end
1222
1223end
Definition fjtag.m:161