1classdef NetworkSolver < Solver
2 % NetworkSolver Abstract base
class for queueing network
solvers
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.
9 % @brief Abstract base
class for all queueing network analysis
solvers
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
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)
24 % Example usage pattern:
26 % solver = SolverMVA(model,
'MyMVASolver');
27 % solver.getAvg(); % Get average performance metrics
30 % Copyright (c) 2012-2026, Imperial College London
31 % All rights reserved.
34 properties (Access = protected)
35 handles; % performance metric handles
40 function self = NetworkSolver(model, name, options)
41 % NETWORKSOLVER Create a NetworkSolver instance
43 % @brief Creates a NetworkSolver with
the specified model and options
44 % @param model Network model to be analyzed
45 % @param name String identifier
for the solver instance
46 % @param options Optional SolverOptions structure
for configuration
47 % @
return self NetworkSolver instance ready
for analysis
49 % The constructor initializes
the solver with
the provided model,
50 % validates inputs, sets options, and prepares performance metric handles.
51 self@Solver(model, name);
53 line_error(mfilename,
'The model supplied in input is empty');
55 if nargin>=3 %exist(
'options',
'var'),
56 self.setOptions(options);
60 if isempty(model.obj) % not a Java
object
65 function setLang(self)
66 % NOTE: self.model
is passed by reference so it affects
the
68 switch self.options.lang %
69 case 'matlab' % matlab solver
70 if self.model.isJavaNative() % java model
71 matlab_model = JLINE.jline_to_line(self.model.obj);
72 matlab_model.obj = self.model.obj;
73 self.model = matlab_model;
78 case 'java' % solver lang
79 joptions = self.options;
80 if ~isempty(self.model.obj) % java model
83 self.model.obj = JLINE.line_to_jline(self.model);
87 self.obj = JLINE.SolverCTMC(self.model.obj, joptions);
89 self.obj = JLINE.SolverLDES(self.model.obj, joptions);
90 case {
'SolverFluid',
'SolverFLD'}
91 self.obj = JLINE.SolverFluid(self.model.obj, joptions);
93 self.obj = JLINE.SolverJMT(self.model.obj, joptions);
95 self.obj = JLINE.SolverMAM(self.model.obj, joptions);
97 self.obj = JLINE.SolverMVA(self.model.obj, joptions);
99 self.obj = JLINE.SolverNC(self.model.obj, joptions);
101 self.obj = JLINE.SolverSSA(self.model.obj, joptions);
103 self.obj = JLINE.SolverQNS(self.model.obj, joptions);
108 function initHandles(self)
109 [Q,U,R,T,A,W] = self.model.getAvgHandles();
111 % Get tardiness handles
if available
112 if ismethod(self.model,
'getAvgTardHandles')
113 Tard = self.model.getAvgTardHandles();
117 if ismethod(self.model, 'getAvgSysTardHandles')
118 SysTard = self.model.getAvgSysTardHandles();
123 self.setAvgHandles(Q,U,R,T,A,W,Tard,SysTard);
125 [Qt,Ut,Tt] = self.model.getTranHandles;
126 self.setTranHandles(Qt,Ut,Tt);
128 % refreshStruct
is called lazily by model.getStruct() when first needed
131 function self = runAnalyzerChecks(self, options)
132 % Single, method-aware feature gate shared by every solver.
133 % It resolves
the concrete method that will actually run (for
134 % options.method='default' this may
map to a specific method via
135 % feature-driven selection), then gates
the model against that
136 % method's per-method feature set rather than
the solver-level
137 % union. Solvers whose methods are all equivalent inherit
the base
138 % behavior transparently (getMethodFeatureSet defaults to
the
139 % coarse solver set), so this replaces
the previous bespoke
140 % per-solver overrides without changing their behavior.
142 % Propagate solver verbose level to global so that model-level
143 % messages (e.g., priority info in refreshStruct) respect it
144 GlobalConstants.setVerbose(options.verbose);
145 if ~self.enableChecks
148 if ~any(cellfun(@(s) strcmp(s,options.method),self.listValidMethods))
149 line_error(mfilename,sprintf('The ''%s'' method
is unsupported by this solver.\n',options.method));
151 method = self.resolveMethod(options);
152 [
bool, reason] = self.supportsModelMethod(method);
154 if strcmp(method, options.method)
155 line_error(mfilename, sprintf('This model contains features not supported by
the solver. %s', reason));
157 line_error(mfilename, sprintf('This model contains features not supported by
the solver''s ''%s'' method. %s', method, reason));
162 function [
bool, reason] = supportsModelMethod(self, method)
163 % Fine, method-aware gate: does
the model fit
the concrete METHOD?
164 % Base behavior derives
the answer from getMethodFeatureSet(method):
165 % when that
is empty
the solver does not diverge per method and
the
166 % solver's own supports(model)
is used (preserving structural checks
167 % such as LQNS layers / LDES LayeredNetwork special-casing).
168 % Solvers with non-feature-set structural per-method rules (e.g. NC
169 % 'mem' via solver_nc_mem_supports) override this.
170 featSupported = self.getMethodFeatureSet(method);
171 if isempty(featSupported)
172 bool = self.supports(self.model);
175 featUsed = self.model.getUsedLangFeatures();
176 [
bool, reason] = SolverFeatureSet.supports(featSupported, featUsed);
180 function method = resolveMethod(self, options)
181 % Resolve
the concrete method that will run. Base behavior
is a
182 % no-op (returns options.method unchanged). Solvers that perform
183 % feature-driven selection for options.method='default' override
184 % this (typically via selectMethod).
185 method = options.method;
188 function featSupported = getMethodFeatureSet(self, method) %
#ok<INUSD>
189 % Per-method feature set. Returning empty ([]) signals "this solver
190 % does not diverge per method": the gate then uses the solver's own
191 % supports(model), preserving any structural checks it carries.
192 % Divergent solvers (e.g. MVA, MAM, NC) override this to return the
193 % base envelope with per-method add/remove deltas applied.
197 function method = selectMethod(self, preferenceList)
198 % Feature-driven method selection: returns the first method in
199 % PREFERENCELIST whose per-method feature set covers the model.
200 % Falls back to the last entry when none fully covers (
the base
201 % gate then reports
the precise unsupported features).
202 method = preferenceList{end};
203 for k = 1:numel(preferenceList)
204 cand = preferenceList{k};
205 [ok, ~] = self.supportsModelMethod(cand);
213 function self = setTranHandles(self,Qt,Ut,Tt)
214 self.handles.Qt = Qt;
215 self.handles.Ut = Ut;
216 self.handles.Tt = Tt;
219 function self = setAvgHandles(self,Q,U,R,T,A,W,Tard,SysTard)
227 self.handles.Tard = Tard;
230 self.handles.SysTard = SysTard;
234 function [Qt,Ut,Tt] = getTranHandles(self)
235 Qt = self.handles.Qt;
236 Ut = self.handles.Ut;
237 Tt = self.handles.Tt;
240 function [Q,U,R,T,A,W] = getAvgHandles(self)
241 if isempty(self.handles)
248 self.handles.Tard = [];
249 self.handles.SysTard = [];
260 function Q = getAvgQLenHandles(self)
261 if isempty(self.handles) || ~isstruct(self.handles)
262 self.getAvgHandles();
267 function U = getAvgUtilHandles(self)
268 if isempty(self.handles) || ~isstruct(self.handles)
269 self.getAvgHandles();
274 function R = getAvgRespTHandles(self)
275 if isempty(self.handles) || ~isstruct(self.handles)
276 self.getAvgHandles();
281 function T = getAvgTputHandles(self)
282 if isempty(self.handles) || ~isstruct(self.handles)
283 self.getAvgHandles();
288 function A = getAvgArvRHandles(self)
289 if isempty(self.handles) || ~isstruct(self.handles)
290 self.getAvgHandles();
295 function W = getAvgResidTHandles(self)
296 if isempty(self.handles) || ~isstruct(self.handles)
297 self.getAvgHandles();
304 methods (Access = 'protected')
305 function
bool = hasAvgResults(self)
306 % BOOL = HASAVGRESULTS()
308 % Returns true if
the solver has computed steady-state average metrics.
311 if isfield(self.result,'Avg')
317 function
bool = hasTranResults(self)
318 % BOOL = HASTRANRESULTS()
320 % Return true if
the solver has computed transient average metrics.
323 if isfield(self.result,'Tran')
324 if isfield(self.result.Tran,'Avg')
325 bool = isfield(self.result.Tran.Avg,'Q');
331 function
bool = hasDistribResults(self)
332 % BOOL = HASDISTRIBRESULTS()
334 % Return true if
the solver has computed steady-state distribution metrics.
337 bool = isfield(self.result.Distribution,'C');
344 function self = setModel(self, model)
345 % SELF = SETMODEL(MODEL)
347 % Assign
the model to be solved.
351 function QN = getAvgQLen(self)
354 % Compute average queue-lengths at steady-state
355 Q = getAvgQLenHandles(self);
356 [QN,~,~,~] = self.getAvg(Q,[],[],[],[],[]);
359 function UN = getAvgUtil(self)
362 % Compute average utilizations at steady-state
363 U = getAvgUtilHandles(self);
364 [~,UN,~,~] = self.getAvg([],U,[],[],[],[]);
367 function RN = getAvgRespT(self)
370 % Compute average response times at steady-state
371 R = getAvgRespTHandles(self);
372 [~,~,RN,~] = self.getAvg([],[],R,[],[],[]);
375 function WN = getAvgResidT(self)
376 % WN = GETAVGRESIDT()
378 % Compute average residence times at steady-state
379 R = getAvgRespTHandles(self);
380 W = getAvgResidTHandles(self);
381 [~,~,~,~,~,WN] = self.getAvg([],[],R,[],[],W);
384 function WT = getAvgWaitT(self)
386 % Compute average waiting time in queue excluding service
387 R = getAvgRespTHandles(self);
388 [~,~,RN,~] = self.getAvg([],[],R,[],[],[]);
393 sn = self.model.getStruct;
394 WT = RN - 1./ sn.rates(:);
395 WT(sn.nodetype==NodeType.Source) = 0;
398 function TN = getAvgTput(self)
401 % Compute average throughputs at steady-state
402 T = getAvgTputHandles(self);
403 [~,~,~,TN] = self.getAvg([],[],[],T,[],[]);
406 function AN = getAvgArvR(self)
408 sn = self.model.getStruct();
410 % Compute average arrival rate at steady-state
411 TH = getAvgTputHandles(self);
412 [~,~,~,TN] = self.getAvg([],[],[],TH,[],[]);
413 AN = sn_get_arvr_from_tput(sn, TN, TH);
416 % also accepts a cell array with
the handlers in it
417 [QN,UN,RN,TN,AN,WN] = getAvg(self,Q,U,R,T,A,W);
418 [QN,UN,RN,TN,AN,WN] = getAvgNode(self,Q,U,R,T,A,W);
420 [AvgTable,QT,UT,
RT,WT,AT,TT] = getAvgTable(self,Q,U,R,T,A,keepDisabled);
422 [AvgTable,QT] = getAvgQLenTable(self,Q,keepDisabled);
423 [AvgTable,UT] = getAvgUtilTable(self,U,keepDisabled);
424 [AvgTable,
RT] = getAvgRespTTable(self,R,keepDisabled);
425 [AvgTable,TT] = getAvgTputTable(self,T,keepDisabled);
427 [NodeAvgTable,QTn,UTn,RTn,WTn,ATn,TTn] = getAvgNodeTable(self,Q,U,R,T,A,keepDisabled);
428 [CacheAvgTable] = getAvgCacheTable(self);
429 [ItemAvgTable] = getAvgItemTable(self);
430 [AvgChain,QTc,UTc,RTc,WTc,ATc,TTc] = getAvgChainTable(self,Q,U,R,T);
431 [AvgChain,QTc,UTc,RTc,WTc,ATc,TTc] = getAvgNodeChainTable(self,Q,U,R,T);
433 [QNc,UNc,RNc,WNc,ANc,TNc] = getAvgChain(self,Q,U,R,T);
434 [QNc] = getAvgQLenChain(self,Q);
435 [UNc] = getAvgUtilChain(self,U);
436 [RNc] = getAvgRespTChain(self,R);
437 [WNc] = getAvgResidTChain(self,W);
438 [TNc] = getAvgTputChain(self,T);
439 [ANc] = getAvgArvRChain(self,A);
440 [QNc] = getAvgNodeQLenChain(self,Q);
441 [UNc] = getAvgNodeUtilChain(self,U);
442 [RNc] = getAvgNodeRespTChain(self,R);
443 [WNc] = getAvgNodeResidTChain(self,W);
444 [TNc] = getAvgNodeTputChain(self,T);
445 [ANc] = getAvgNodeArvRChain(sef,A);
447 [CNc,XNc] = getAvgSys(self,R,T);
448 [CT,XT] = getAvgSysTable(self,R,T);
449 [RN] = getAvgSysRespT(self,R);
450 [TN] = getAvgSysTput(self,T);
453 function self = setAvgResults(self,Q,U,R,T,A,W,C,X,runtime,method,iter)
454 % SELF = SETAVGRESULTS(SELF,Q,U,R,T,A,W,C,X,RUNTIME,METHOD,ITER)
455 % Store average metrics at steady-state
456 self.result.('solver') = getName(self);
457 if nargin<11 %~exist('method','var')
458 method = getOptions(self).method;
460 if nargin<12 %~exist('iter','var')
463 self.result.Avg.('method') = method;
464 self.result.Avg.('iter') = iter;
465 if isnan(Q), Q=[]; end
466 if isnan(R), R=[]; end
467 if isnan(T), T=[]; end
468 if isnan(U), U=[]; end
469 if isnan(X), X=[]; end
470 if isnan(C), C=[]; end
471 if isnan(A), A=[]; end
472 if isnan(W), W=[]; end
473 self.result.Avg.Q = real(Q);
474 self.result.Avg.R = real(R);
475 self.result.Avg.X = real(X);
476 self.result.Avg.U = real(U);
477 self.result.Avg.T = real(T);
478 self.result.Avg.C = real(C);
479 self.result.Avg.A = real(A);
480 self.result.Avg.W = real(W);
481 self.result.Avg.runtime = runtime;
482 if ~isfield(self.result.Avg,'timedOut')
483 % Wall-clock time-budget flag;
solvers that stop early on
484 % options.timeout overwrite this with true after calling setAvgResults.
485 self.result.Avg.timedOut = false;
487 if getOptions(self).verbose
489 solvername = erase(self.result.solver,'Solver');
491 solvername = self.result.solver(7:end);
493 if isnan(iter) || iter==1 || strcmp(solvername,'LDES') || strcmp(solvername,'SSA')
494 line_printf('%s analysis [method: %s, lang: %s, env: %s] completed in %fs.',solvername,self.result.Avg.method,self.options.lang,version("-release"),runtime);
496 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);
502 function self = setAvgResultsCI(self, QCI, UCI, RCI, TCI, ACI, WCI, CCI, XCI)
503 % SELF = SETAVGRESULTSCI(SELF, QCI, UCI, RCI, TCI, ACI, WCI, CCI, XCI)
504 % Store confidence interval bounds for average metrics
505 % Each CI parameter
is an [M x K x 2] array with lower/upper bounds
506 % or an [M x K] array with half-widths (mean ± halfwidth)
507 if nargin >= 2 && ~isempty(QCI)
508 self.result.Avg.QCI = QCI;
510 if nargin >= 3 && ~isempty(UCI)
511 self.result.Avg.UCI = UCI;
513 if nargin >= 4 && ~isempty(RCI)
514 self.result.Avg.RCI = RCI;
516 if nargin >= 5 && ~isempty(TCI)
517 self.result.Avg.TCI = TCI;
519 if nargin >= 6 && ~isempty(ACI)
520 self.result.Avg.ACI = ACI;
522 if nargin >= 7 && ~isempty(WCI)
523 self.result.Avg.WCI = WCI;
525 if nargin >= 8 && ~isempty(CCI)
526 self.result.Avg.CCI = CCI;
528 if nargin >= 9 && ~isempty(XCI)
529 self.result.Avg.XCI = XCI;
533 function self = setDistribResults(self,Cd,runtime)
534 % SELF = SETDISTRIBRESULTS(SELF,CD,RUNTIME)
536 % Store distribution metrics at steady-state
537 self.result.('solver') = getName(self);
538 self.result.Distribution.('method') = getOptions(self).method;
539 self.result.Distribution.C = Cd;
540 self.result.Distribution.runtime = runtime;
543 function self = setTranProb(self,t,pi_t,SS,runtimet)
544 % SELF = SETTRANPROB(SELF,T,PI_T,SS,RUNTIMET)
546 % Store transient average metrics
547 self.result.('solver') = getName(self);
548 self.result.Tran.Prob.('method') = getOptions(self).method;
549 self.result.Tran.Prob.t = t;
550 self.result.Tran.Prob.pi_t = pi_t;
551 self.result.Tran.Prob.SS = SS;
552 self.result.Tran.Prob.runtime = runtimet;
555 function self = setTranAvgResults(self,Qt,Ut,Rt,Tt,Ct,Xt,runtimet)
556 % SELF = SETTRANAVGRESULTS(SELF,QT,UT,
RT,TT,CT,XT,RUNTIMET)
558 % Store transient average metrics
559 self.result.('solver') = getName(self);
560 self.result.Tran.Avg.('method') = getOptions(self).method;
561 % Clear individual cells that are scalar NaN (not entire array)
562 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
563 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
564 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
565 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
566 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
567 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
568 self.result.Tran.Avg.Q = Qt;
569 self.result.Tran.Avg.R = Rt;
570 self.result.Tran.Avg.U = Ut;
571 self.result.Tran.Avg.T = Tt;
572 self.result.Tran.Avg.X = Xt;
573 self.result.Tran.Avg.C = Ct;
574 self.result.Tran.Avg.runtime = runtimet;
580 [QNt,UNt,TNt] = getTranAvg(self,Qt,Ut,Tt);
582 function [lNormConst] = getProbNormConstAggr(self)
583 % [LNORMCONST] = GETPROBNORMCONST()
585 % Return normalizing constant of state probabilities
586 line_error(mfilename,sprintf(
'getProbNormConstAggr is not supported by %s',
class(self)));
589 function Pstate = getProb(self, node, state)
590 % PSTATE = GETPROBSTATE(NODE, STATE)
592 % Return marginal state probability
for station ist state
593 line_error(mfilename,sprintf(
'getProb is not supported by %s',
class(self)));
596 function Psysstate = getProbSys(self)
597 % PSYSSTATE = GETPROBSYSSTATE()
599 % Return joint state probability
600 line_error(mfilename,sprintf(
'getProbSys is not supported by %s',
class(self)));
603 function Pnir = getProbAggr(self, node, state_a)
604 % PNIR = GETPROBSTATEAGGR(NODE, STATE_A)
606 % Return marginal state probability
for station ist state
607 line_error(mfilename,sprintf(
'getProbAggr is not supported by %s',
class(self)));
610 function Pnjoint = getProbSysAggr(self)
611 % PNJOINT = GETPROBSYSSTATEAGGR()
613 % Return joint state probability
614 line_error(mfilename,sprintf(
'getProbSysAggr is not supported by %s',
class(self)));
617 function Pmarg = getProbMarg(self, node,
jobclass, state_m)
618 % PMARG = GETPROBMARG(NODE, JOBCLASS, STATE_M)
620 % Return marginalized state probability
for station and
class
621 % This computes probabilities marginalized on a given
class, in contrast to
622 % getProbAggr which aggregates over all classes.
623 line_error(mfilename,sprintf(
'getProbMarg is not supported by %s',
class(self)));
626 function tstate = sample(self, node, numEvents)
627 % TSTATE = SAMPLE(NODE, numEvents)
629 % Return marginal state probability
for station ist state
630 line_error(mfilename,sprintf(
'sample is not supported by %s',
class(self)));
633 function tstate = sampleAggr(self, node, numEvents)
634 % TSTATE = SAMPLEAGGR(NODE, numEvents)
636 % Return marginal state probability
for station ist state
637 line_error(mfilename,sprintf(
'sampleAggr is not supported by %s',
class(self)));
640 function tstate = sampleSys(self, numEvents)
641 % TSTATE = SAMPLESYS(numEvents)
643 % Return joint state probability
644 line_error(mfilename,sprintf(
'sampleSys is not supported by %s',
class(self)));
647 function tstate = sampleSysAggr(self, numEvents)
648 % TSTATE = SAMPLESYSAGGR(numEvents)
650 % Return joint state probability
651 line_error(mfilename,sprintf(
'sampleSysAggr is not supported by %s',
class(self)));
654 function
RD = getCdfRespT(self, R)
655 %
RD = GETCDFRESPT(R)
657 % Return cumulative distribution of response times at steady-state
658 % This uses a trivial approximation that assumes exponential
659 % distributions everywhere with mean as RN(i,r)
662 RD = cell(sn.nstations,sn.nclasses);
663 if GlobalConstants.DummyMode
667 if nargin<2 %~exist(
'R',
'var')
668 R = self.getAvgRespTHandles;
669 % to do: check if some R are disabled
671 if ~self.hasAvgResults
672 self.getAvg; % get steady-state solution
675 if sn.nodetype(sn.stationToNode(i)) ~= NodeType.Source
677 if isfinite(self.result.Avg.R(i,c)) && self.result.Avg.R(i,c)>0
678 lambda = 1/self.result.Avg.R(i,c);
679 n = 100; % number of points
680 quantiles = linspace(0.001, 0.999, n);
681 RD{i,c} = [quantiles;-log(1 - quantiles) / lambda]
';
689 self.setDistribResults(RD, runtime);
692 function RD = getTranCdfRespT(self, R)
693 % RD = GETTRANCDFRESPT(R)
695 % Return cumulative distribution of response times during transient
696 line_error(mfilename,sprintf('getTranCdfRespT
is not supported by %s
',class(self)));
699 function RD = getCdfPassT(self, R)
700 % RD = GETCDFPASST(R)
702 % Return cumulative distribution of passage times at steady-state
703 line_error(mfilename,sprintf('getCdfPassT
is not supported by %s
',class(self)));
706 function RD = getTranCdfPassT(self, R)
707 % RD = GETTRANCDFPASST(R)
709 % Return cumulative distribution of passage times during transient
710 line_error(mfilename,sprintf('getTranCdfPassT
is not supported by %s
',class(self)));
713 % Kotlin-style aliases for getAvg* methods
714 function avg_table = avgTable(self)
715 % AVGTABLE Kotlin-style alias for getAvgTable
716 avg_table = self.getAvgTable();
719 function avg_sys_table = avgSysTable(self)
720 % AVGSYSTABLE Kotlin-style alias for getAvgSysTable
721 avg_sys_table = self.getAvgSysTable();
724 function avg_node_table = avgNodeTable(self)
725 % AVGNODETABLE Kotlin-style alias for getAvgNodeTable
726 avg_node_table = self.getAvgNodeTable();
729 function avg_chain_table = avgChainTable(self)
730 % AVGCHAINTABLE Kotlin-style alias for getAvgChainTable
731 avg_chain_table = self.getAvgChainTable();
734 function avg_node_chain_table = avgNodeChainTable(self)
735 % AVGNODECHAINTABLE Kotlin-style alias for getAvgNodeChainTable
736 avg_node_chain_table = self.getAvgNodeChainTable();
740 function avg_table = avgT(self)
741 % AVGT Short alias for avgTable
742 avg_table = self.avgTable();
745 function avg_sys_table = avgSysT(self)
746 % AVGSYST Short alias for avgSysTable
747 avg_sys_table = self.avgSysTable();
750 function avg_node_table = avgNodeT(self)
751 % AVGNODET Short alias for avgNodeTable
752 avg_node_table = self.avgNodeTable();
755 function avg_chain_table = avgChainT(self)
756 % AVGCHAINT Short alias for avgChainTable
757 avg_chain_table = self.avgChainTable();
760 function avg_node_chain_table = avgNodeChainT(self)
761 % AVGNODECHAINT Short alias for avgNodeChainTable
762 avg_node_chain_table = self.avgNodeChainTable();
765 function varargout = avgChain(self, varargin)
766 % AVGCHAIN Kotlin-style alias for getAvgChain
767 [varargout{1:nargout}] = self.getAvgChain(varargin{:});
770 function varargout = avgSys(self, varargin)
771 % AVGSYS Kotlin-style alias for getAvgSys
772 [varargout{1:nargout}] = self.getAvgSys(varargin{:});
775 function varargout = avgNode(self, varargin)
776 % AVGNODE Kotlin-style alias for getAvgNode
777 [varargout{1:nargout}] = self.getAvgNode(varargin{:});
780 function sys_resp_time = avgSysRespT(self, varargin)
781 % AVGSYSRESPT Kotlin-style alias for getAvgSysRespT
782 sys_resp_time = self.getAvgSysRespT(varargin{:});
785 function sys_tput = avgSysTput(self, varargin)
786 % AVGSYSTPUT Kotlin-style alias for getAvgSysTput
787 sys_tput = self.getAvgSysTput(varargin{:});
790 function arvr_chain = avgArvRChain(self, varargin)
791 % AVGARVCHAIN Kotlin-style alias for getAvgArvRChain
792 arvr_chain = self.getAvgArvRChain(varargin{:});
795 function qlen_chain = avgQLenChain(self, varargin)
796 % AVGQLENCHAIN Kotlin-style alias for getAvgQLenChain
797 qlen_chain = self.getAvgQLenChain(varargin{:});
800 function util_chain = avgUtilChain(self, varargin)
801 % AVGUTILCHAIN Kotlin-style alias for getAvgUtilChain
802 util_chain = self.getAvgUtilChain(varargin{:});
805 function resp_t_chain = avgRespTChain(self, varargin)
806 % AVGRESPTCHAIN Kotlin-style alias for getAvgRespTChain
807 resp_t_chain = self.getAvgRespTChain(varargin{:});
810 function resid_t_chain = avgResidTChain(self, varargin)
811 % AVGRESIDTCHAIN Kotlin-style alias for getAvgResidTChain
812 resid_t_chain = self.getAvgResidTChain(varargin{:});
815 function tput_chain = avgTputChain(self, varargin)
816 % AVGTPUTCHAIN Kotlin-style alias for getAvgTputChain
817 tput_chain = self.getAvgTputChain(varargin{:});
820 function node_arvr_chain = avgNodeArvRChain(self, varargin)
821 % AVGNODERVRCHAIN Kotlin-style alias for getAvgNodeArvRChain
822 node_arvr_chain = self.getAvgNodeArvRChain(varargin{:});
825 function node_qlen_chain = avgNodeQLenChain(self, varargin)
826 % AVGNODEQLENCHAIN Kotlin-style alias for getAvgNodeQLenChain
827 node_qlen_chain = self.getAvgNodeQLenChain(varargin{:});
830 function node_util_chain = avgNodeUtilChain(self, varargin)
831 % AVGNODEUTILCHAIN Kotlin-style alias for getAvgNodeUtilChain
832 node_util_chain = self.getAvgNodeUtilChain(varargin{:});
835 function node_resp_t_chain = avgNodeRespTChain(self, varargin)
836 % AVGNODERESPTCHAIN Kotlin-style alias for getAvgNodeRespTChain
837 node_resp_t_chain = self.getAvgNodeRespTChain(varargin{:});
840 function node_resid_t_chain = avgNodeResidTChain(self, varargin)
841 % AVGNODERESIDTCHAIN Kotlin-style alias for getAvgNodeResidTChain
842 node_resid_t_chain = self.getAvgNodeResidTChain(varargin{:});
845 function node_tput_chain = avgNodeTputChain(self, varargin)
846 % AVGNODETPUTCHAIN Kotlin-style alias for getAvgNodeTputChain
847 node_tput_chain = self.getAvgNodeTputChain(varargin{:});
850 % Kotlin-style aliases for getTran* methods
851 function varargout = tranAvg(self, varargin)
852 % TRANAVG Kotlin-style alias for getTranAvg
853 [varargout{1:nargout}] = self.getTranAvg(varargin{:});
856 function rd = tranCdfRespT(self, varargin)
857 % TRANCDFRESPT Kotlin-style alias for getTranCdfRespT
858 rd = self.getTranCdfRespT(varargin{:});
861 function rd = tranCdfPassT(self, varargin)
862 % TRANCDFPASST Kotlin-style alias for getTranCdfPassT
863 rd = self.getTranCdfPassT(varargin{:});
866 % Kotlin-style aliases for getCdf* methods
867 function rd = cdfRespT(self, varargin)
868 % CDFRESPT Kotlin-style alias for getCdfRespT
869 rd = self.getCdfRespT(varargin{:});
872 function rd = cdfPassT(self, varargin)
873 % CDFPASST Kotlin-style alias for getCdfPassT
874 rd = self.getCdfPassT(varargin{:});
877 % Kotlin-style aliases for getProb* methods
878 function pstate = prob(self, varargin)
879 % PROB Kotlin-style alias for getProb
880 pstate = self.getProb(varargin{:});
883 function psysstate = probSys(self)
884 % PROBSYS Kotlin-style alias for getProbSys
885 psysstate = self.getProbSys();
888 function tf = supportsExactSensitivity(self) %#ok<MANU>
889 % TF = SUPPORTSEXACTSENSITIVITY()
890 % True when the solver evaluates a product-form recursion that
891 % getSensitivityTable can differentiate analytically. False here,
892 % so that a solver reaching this base implementation obtains its
893 % sensitivities by finite differences on its own predictions.
894 % Overridden by SolverMVA and SolverNC.
898 function pnir = probAggr(self, varargin)
899 % PROBAGGR Kotlin-style alias for getProbAggr
900 pnir = self.getProbAggr(varargin{:});
903 function pnjoint = probSysAggr(self)
904 % PROBSYSAGGR Kotlin-style alias for getProbSysAggr
905 pnjoint = self.getProbSysAggr();
908 function pmarg = probMarg(self, varargin)
909 % PROBMARG Kotlin-style alias for getProbMarg
910 pmarg = self.getProbMarg(varargin{:});
913 function lnormconst = probNormConstAggr(self)
914 % PROBNORMCONSTAGGR Kotlin-style alias for getProbNormConstAggr
915 lnormconst = self.getProbNormConstAggr();
918 % Kotlin-style aliases for get*Handles methods
919 function varargout = avgHandles(self)
920 % AVGHANDLES Kotlin-style alias for getAvgHandles
921 [varargout{1:nargout}] = self.getAvgHandles();
924 function varargout = tranHandles(self)
925 % TRANHANDLES Kotlin-style alias for getTranHandles
926 [varargout{1:nargout}] = self.getTranHandles();
929 function q = avgQLenHandles(self)
930 % AVGQLENHANDLES Kotlin-style alias for getAvgQLenHandles
931 q = self.getAvgQLenHandles();
934 function u = avgUtilHandles(self)
935 % AVGUTILHANDLES Kotlin-style alias for getAvgUtilHandles
936 u = self.getAvgUtilHandles();
939 function r = avgRespTHandles(self)
940 % AVGRESPTHANDLES Kotlin-style alias for getAvgRespTHandles
941 r = self.getAvgRespTHandles();
944 function t = avgTputHandles(self)
945 % AVGTPUTHANDLES Kotlin-style alias for getAvgTputHandles
946 t = self.getAvgTputHandles();
949 function a = avgArvRHandles(self)
950 % AVGARVRHANDLES Kotlin-style alias for getAvgArvRHandles
951 a = self.getAvgArvRHandles();
954 function w = avgResidTHandles(self)
955 % AVGRESIDTHANDLES Kotlin-style alias for getAvgResidTHandles
956 w = self.getAvgResidTHandles();
959 % Kotlin-style aliases for basic get* methods
960 function qn = avgQLen(self)
961 % AVGQLEN Kotlin-style alias for getAvgQLen
962 qn = self.getAvgQLen();
965 function un = avgUtil(self)
966 % AVGUTIL Kotlin-style alias for getAvgUtil
967 un = self.getAvgUtil();
970 function rn = avgRespT(self)
971 % AVGRESPT Kotlin-style alias for getAvgRespT
972 rn = self.getAvgRespT();
975 function wn = avgResidT(self)
976 % AVGRESIDT Kotlin-style alias for getAvgResidT
977 wn = self.getAvgResidT();
980 function wt = avgWaitT(self)
981 % AVGWAITT Kotlin-style alias for getAvgWaitT
982 wt = self.getAvgWaitT();
985 function tn = avgTput(self)
986 % AVGTPUT Kotlin-style alias for getAvgTput
987 tn = self.getAvgTput();
990 function an = avgArvR(self)
991 % AVGARVR Kotlin-style alias for getAvgArvR
992 an = self.getAvgArvR();
998 % Integer placement decided by an auxiliary solver steady state
999 placement = warmStartPlacement(initSolver, sn)
1001 function [bool, reason] = checkBindingCapacity(model, solverName)
1002 % [BOOL, REASON] = CHECKBINDINGCAPACITY(MODEL, SOLVERNAME)
1003 % Shared structural gate for finite station capacity
1004 % (setCapacity) and finite per-class buffers (classCap), used by
1005 % the product-form solvers (MVA, NC). A product-form solver has no
1006 % representation of a finite buffer, so without this gate it
1007 % silently returns the UNCONSTRAINED answer (e.g. QLen=4 instead
1008 % of the M/M/1/2 value 0.8525). There is no registry feature name
1009 % for plain capacity, hence the structural test; this mirrors the
1010 % native Python check in solvers/solver_mva/solver_mva.py.
1012 % The test reads the node-level cap/classCap set by the user, NOT
1013 % sn.cap/sn.classcap: refreshCapacity derives a FINITE sn.classcap
1014 % (= the chain population) for every closed model, so an sn-level
1015 % test would reject every closed model.
1017 % Only a capacity that can actually BIND is rejected. A closed
1018 % model whose station capacity is at least the total population
1019 % can never block a job, so the declaration is a no-op and the
1020 % product-form answer stays exact (a common idiom: setCap(N) on an
1021 % order-independent station of an N-job closed model). njobs is Inf
1022 % for an open class, so any finite capacity reachable by an open
1025 % Cache models are exempt: Cache.m sets classCap=1 on the
1026 % retrieval queues it builds, and MVA/NC solve those through their
1027 % dedicated cache/retrieval analyzers rather than as a buffer
1031 if ~isa(model, 'Network
')
1034 nodes = model.getNodes();
1035 for i = 1:numel(nodes)
1036 if isa(nodes{i}, 'Cache
')
1040 njobs = model.getStruct().njobs(:)';
1041 totalJobs = sum(njobs); % Inf as soon as one
class is open
1042 for i = 1:numel(
nodes)
1044 if ~isa(node,
'Station') || isa(node,
'Source') || isa(node,
'Sink')
1047 if ~isempty(node.cap) && ~isinf(node.cap) && node.cap >= 0 && node.cap < totalJobs
1049 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);
1052 ccap = node.classCap;
1053 for r = 1:min(numel(ccap), numel(njobs))
1054 if ~isinf(ccap(r)) && ccap(r) > 0 && ccap(r) < njobs(r)
1056 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);
1063 function
solvers = getAllSolvers(model, options)
1064 % SOLVERS = GETALLSOLVERS(MODEL, OPTIONS)
1066 % Return a cell array with all Network
solvers
1067 if nargin<2 %~exist('options','var')
1068 options = Solver.defaultOptions;
1071 solvers{end+1} = SolverCTMC(model, options);
1072 solvers{end+1} = SolverFluid(model, options);
1073 solvers{end+1} = SolverJMT(model, options);
1074 solvers{end+1} = SolverMAM(model, options);
1075 solvers{end+1} = SolverMVA(model, options);
1076 solvers{end+1} = SolverNC(model, options);
1077 solvers{end+1} = SolverSSA(model, options);