1classdef MNetwork < Model
2 % An extended queueing network model.
4 % Copyright (c) 2012-2026, Imperial College London
7 properties (Access=
private)
11 usedFeatures; % structure of booleans listing the used
classes
12 % it must be accessed via getUsedLangFeatures that updates
13 % the Distribution
classes dynamically
26 sourceidx; % cached value
27 sinkidx; % cached value
40 nodes = getNodes(self)
41 sa = getStruct(self, structType, wantState) % get abritrary representation
42 used = getUsedLangFeatures(self) % get used features
43 ft = getForkJoins(self, rt) % get fork-join pairs
44 [chainsObj,chainsMatrix] = getChains(self, rt) % get chain table
45 [rt,rtNodes,connections,chains,rtNodesByClass,rtNodesByStation] = getRoutingMatrix(self, arvRates) % get routing matrix
46 [Q,U,R,T,A,W] = getAvgHandles(self)
47 Q = getAvgQLenHandles(self);
48 U = getAvgUtilHandles(self);
49 R = getAvgRespTHandles(self);
50 T = getAvgTputHandles(self);
51 A = getAvgArvRHandles(self);
52 W = getAvgResidTHandles(self);
53 [Qt,Ut,Tt] = getTranHandles(self)
54 connections = getConnectionMatrix(self);
55 [absorbingStations, absorbingIdxs] = getAbsorbingStations(self) % get absorbing stations
56 info = getReducibilityInfo(self) % get reducibility analysis
59 methods % ergodicity analysis methods
60 [isErg, info] = isRoutingErgodic(self,
P) % check if routing
is ergodic
61 P = makeErgodic(self, targetNode) % generate ergodic routing matrix
64 methods % link, reset, refresh methods
66 self = relink(self,
P)
67 [loggerBefore,loggerAfter] = linkAndLog(self,
nodes,
classes,
P, wantLogger, logPath)
70 reset(self, resetState)
72 resetModel(self, resetState)
73 nodes = resetNetwork(self, deleteCSnodes)
76 refreshStruct(self, hard);
77 [rates, scv, hasRateChanged, hasSCVChanged] = refreshRates(self, statSet, classSet);
78 [ph, mu, phi, phases] = refresProcessPhases(self, statSet, classSet);
79 proctypes = refreshProcessTypes(self);
80 [rt, rtfun, rtnodes] = refreshRoutingMatrix(self, rates);
81 [lt] = refreshLST(self, statSet, classSet);
82 sync = refreshSync(self);
83 classprio = refreshPriorities(self);
84 [sched, schedparam] = refreshScheduling(self);
85 [rates, scv, mu, phi, phases] = refreshProcesses(self, statSet, classSet);
86 [chains,
visits, rt] = refreshChains(self, propagate)
87 [cap, classcap] = refreshCapacity(self);
88 nvars = refreshLocalVars(self);
92 methods (Access=public)
95 function self = MNetwork(modelName, varargin)
96 % SELF = NETWORK(MODELNAME)
97 self@Model(modelName);
101 self.connections = [];
102 initUsedFeatures(self);
104 self.hasState =
false;
110 self.setChecks(
true);
111 self.hasStruct =
false;
112 self.allowReplace =
false;
115 setInitialized(self,
bool);
117 function self = setChecks(self,
bool)
118 self.enableChecks = bool;
121 P = getLinkedRoutingMatrix(self)
123 function logPath = getLogPath(self)
124 % LOGPATH = GETLOGPATH()
126 logPath = self.logPath;
129 function setLogPath(self, logPath)
130 % SETLOGPATH(LOGPATH)
132 self.logPath = logPath;
135 bool = hasInitState(self)
137 function [M,R] = getSize(self)
140 M = self.getNumberOfNodes;
141 R = self.getNumberOfClasses;
144 function
bool = hasOpenClasses(self)
145 % BOOL = HASOPENCLASSES()
147 bool = any(isinf(getNumberOfJobs(self)));
150 function
bool = hasClassSwitching(self)
151 % BOOL = HASCLASSSWITCHING()
153 bool = any(cellfun(@(c) isa(c,'ClassSwitch'), self.
nodes));
156 function
bool = hasFork(self)
159 bool = any(cellfun(@(c) isa(c,'Fork'), self.
nodes));
162 function
bool = hasJoin(self)
165 bool = any(cellfun(@(c) isa(c,'Join'), self.
nodes));
168 function
bool = hasClosedClasses(self)
169 % BOOL = HASCLOSEDCLASSES()
171 bool = any(isfinite(getNumberOfJobs(self)));
174 function
bool = isMatlabNative(self)
175 % BOOL = ISMATLABNATIVE()
177 % Returns true for MATLAB native (MNetwork) implementation
182 function
bool = isJavaNative(self)
183 % BOOL = ISJAVANATIVE()
185 % Returns false for MATLAB native (MNetwork) implementation
190 function index = getIndexOpenClasses(self)
191 % INDEX = GETINDEXOPENCLASSES()
193 index = find(isinf(getNumberOfJobs(self)))';
196 function index = getIndexClosedClasses(self)
197 % INDEX = GETINDEXCLOSEDCLASSES()
199 index = find(isfinite(getNumberOfJobs(self)))';
202 function
classes = getClasses(self)
206 chain = getClassChain(self, className)
207 c = getClassChainIndex(self, className)
208 classNames = getClassNames(self)
210 nodeNames = getNodeNames(self)
211 nodeTypes = getNodeTypes(self)
213 P = initRoutingMatrix(self)
215 ind = getNodeIndex(self, name)
216 lldScaling = getLimitedLoadDependence(self)
217 lcdScaling = getLimitedClassDependence(self)
219 function stationIndex = getStationIndex(self, name)
220 % STATIONINDEX = GETSTATIONINDEX(NAME)
224 name = node.getName();
226 stationIndex = find(cellfun(@(c) strcmp(c,name),self.getStationNames));
229 function statefulIndex = getStatefulNodeIndex(self, name)
230 % STATEFULINDEX = GETSTATEFULNODEINDEX(NAME)
234 name = node.getName();
236 statefulIndex = find(cellfun(@(c) strcmp(c,name),self.getStatefulNodeNames));
239 function classIndex = getClassIndex(self, name)
240 % CLASSINDEX = GETCLASSINDEX(NAME)
241 if isa(name,'JobClass')
245 classIndex = find(cellfun(@(c) strcmp(c,name),self.getClassNames));
248 function stationnames = getStationNames(self)
249 % STATIONNAMES = GETSTATIONNAMES()
252 nodenames = self.sn.nodenames;
253 isstation = self.sn.isstation;
254 stationnames = {nodenames{isstation}}
';
257 for i=self.getStationIndexes
258 stationnames{end+1,1} = self.nodes{i}.name;
263 function nodes = getNodeByName(self, name)
264 % NODES = GETNODEBYNAME(SELF, NAME)
265 idx = findstring(self.getNodeNames,name);
267 nodes = self.nodes{idx};
273 function station = getStationByName(self, name)
274 % STATION = GETSTATIONBYNAME(SELF, NAME)
275 idx = findstring(self.getStationNames,name);
277 station = self.stations{idx};
283 function class = getClassByName(self, name)
284 % CLASS = GETCLASSBYNAME(SELF, NAME)
285 idx = findstring(self.getClassNames,name);
287 class = self.classes{idx};
293 function nodes = getNodeByIndex(self, idx)
294 % NODES = GETNODEBYINDEX(SELF, NAME)
296 nodes = self.nodes{idx};
302 function station = getStationByIndex(self, idx)
303 % STATION = GETSTATIONBYINDEX(SELF, NAME)
305 station = self.stations{idx};
311 function class = getClassByIndex(self, idx)
312 % CLASS = GETCLASSBYINDEX(SELF, NAME)
314 class = self.classes{idx};
320 function [infGen, eventFilt, ev] = getGenerator(self, varargin)
321 line_warning(mfilename,'Results will not be cached. Use SolverCTMC(model,...).getGenerator(...) instead.\n
');
322 [infGen, eventFilt, ev] = SolverCTMC(self).getGenerator(varargin{:});
325 function [stateSpace,nodeStateSpace] = getStateSpace(self, varargin)
326 line_warning(mfilename,'Results will not be cached. Use SolverCTMC(model,...).getStateSpace(...) instead.\n
');
327 [stateSpace,nodeStateSpace] = SolverCTMC(self).getStateSpace(varargin{:});
330 function summary(self)
332 for i=1:self.getNumberOfClasses
333 self.classes{i}.summary();
334 if i<self.getNumberOfClasses
338 for i=1:self.getNumberOfNodes
339 self.nodes{i}.summary();
341 line_printf('\n<strong>Routing matrix</strong>:
');
342 self.printRoutingMatrix
343 line_printf('\n<strong>Product-form parameters</strong>:
');
344 [arvRates,servDemands,nJobs,thinkTimes,ldScalings,nServers]= getProductFormParameters(self);
345 line_printf('\nArrival rates: %s
',mat2str(arvRates,6));
346 line_printf('\nService demands: %s
',mat2str(servDemands,6));
347 line_printf('\nNumber of jobs: %s
',mat2str(nJobs));
348 line_printf('\nThink times: %s
',mat2str(thinkTimes,6));
349 line_printf('\nLoad-dependent scalings: %s
',mat2str(ldScalings,6));
350 line_printf('\nNumber of servers: %s\n
',mat2str(nServers));
351 line_printf('\n<strong>Chains</strong>:
');
354 function [D,Z] = getDemands(self)
355 % [D,Z]= GETDEMANDS()
361 [~,D,~,Z,~,~] = sn_get_product_form_params(self.getStruct);
364 function [lambda,D,N,Z,mu,S]= getProductFormParameters(self)
365 % [LAMBDA,D,N,Z,MU,S]= GETPRODUCTFORMPARAMETERS()
368 % LAMBDA: arrival rates for open classes
370 % N: population vector for closed classes
372 % mu: load-dependent rates
373 % S: number of servers
375 % mu also returns max(S) elements after population |N| as this is
376 % required by MVALDMX
378 [lambda,D,N,Z,mu,S] = sn_get_product_form_params(self.getStruct);
381 function [lambda,D,N,Z,mu,S]= getProductFormChainParameters(self)
382 % [LAMBDA,D,N,Z,MU,S]= GETPRODUCTFORMCHAINPARAMETERS()
385 % LAMBDA: arrival rates for open classes
387 % N: population vector for closed classes
389 % mu: load-dependent rates
390 % S: number of servers
392 % mu also returns max(S) elements after population |N| as this is
393 % required by MVALDMX
395 [lambda,D,N,Z,mu,S]= sn_get_product_form_chain_params(self.getStruct);
398 function statefulnodes = getStatefulNodes(self)
400 for i=1:self.getNumberOfNodes
401 if self.nodes{i}.isStateful
402 statefulnodes{end+1,1} = self.nodes{i};
407 function statefulnames = getStatefulNodeNames(self)
408 % STATEFULNAMES = GETSTATEFULNODENAMES()
411 for i=1:self.getNumberOfNodes
412 if self.nodes{i}.isStateful
413 statefulnames{end+1,1} = self.nodes{i}.name;
418 function M = getNumberOfNodes(self)
419 % M = GETNUMBEROFNODES()
421 M = length(self.nodes);
424 function S = getNumberOfStatefulNodes(self)
425 % S = GETNUMBEROFSTATEFULNODES()
427 S = sum(cellisa(self.nodes,'StatefulNode
'));
430 function M = getNumberOfStations(self)
431 % M = GETNUMBEROFSTATIONS()
433 M = length(self.stations);
436 function R = getNumberOfClasses(self)
437 % R = GETNUMBEROFCLASSES()
439 R = length(self.classes);
442 function C = getNumberOfChains(self)
443 % C = GETNUMBEROFCHAINS()
449 function Dchain = getDemandsChain(self)
450 % DCHAIN = GETDEMANDSCHAIN()
451 sn_get_demands_chain(self.getStruct);
454 function self = setUsedLangFeature(self,className)
455 % SELF = SETUSEDLANGFEATURE(SELF,CLASSNAME)
457 self.usedFeatures.setTrue(className);
460 %% Add the components to the model
461 addJobClass(self, customerClass);
462 bool = addNode(self, node);
463 fcr = addRegion(self, nodes);
464 addLink(self, nodeA, nodeB);
465 addLinks(self, nodeList);
466 addItemSet(self, itemSet);
468 node = getSource(self);
469 node = getSink(self);
471 function list = getStationIndexes(self)
472 % LIST = GETSTATIONINDEXES()
475 list = find(self.sn.isstation)';
477 % returns the ids of
nodes that are stations
478 list = find(cellisa(self.nodes,
'Station'))
';
482 function list = getIndexStatefulNodes(self)
483 % LIST = GETINDEXSTATEFULNODES()
485 % returns the ids of nodes that are stations
486 list = find(cellisa(self.nodes, 'StatefulNode
'))';
489 index = getIndexSourceStation(self);
490 index = getIndexSourceNode(self);
491 index = getIndexSinkNode(self);
493 N = getNumberOfJobs(self);
494 refstat = getReferenceStations(self);
495 refclass = getReferenceClasses(self);
496 sched = getStationScheduling(self);
497 S = getStationServers(self);
498 S = getStatefulServers(self);
506 function islld = isLimitedLoadDependent(self)
507 islld = isempty(self.getStruct().lldscaling);
510 function [isvalid] = isStateValid(self)
511 % [ISVALID] = ISSTATEVALID()
513 isvalid = sn_is_state_valid(self.getStruct);
516 [state, priorStateSpace, stateSpace] = getState(self) % get initial state
518 initFromAvgTableQLen(self, AvgTable)
519 initFromAvgQLen(self, AvgQLen)
520 initDefault(self,
nodes)
522 initFromMarginal(self, n, options) % n(i,r) : number of jobs of class r in node or station i (autodetected)
523 initFromMarginalAndRunning(self, n, s, options) % n(i,r) : number of jobs of class r in node or station i (autodetected)
524 initFromMarginalAndStarted(self, n, s, options) % n(i,r) : number of jobs of class r in node or station i (autodetected)
526 [H,G] = getGraph(self)
528 function mask = getClassSwitchingMask(self)
529 % MASK = GETCLASSSWITCHINGMASK()
531 mask = self.getStruct.csmask;
534 function printRoutingMatrix(self, onlyclass)
535 % PRINTROUTINGMATRIX()
537 sn_print_routing_matrix(self.getStruct);
539 sn_print_routing_matrix(self.getStruct, onlyclass);
545 methods (Access = protected)
547 function out = getModelNameExtension(self)
548 % OUT = GETMODELNAMEEXTENSION()
550 out = [getModelName(self), ['.', self.fileFormat]];
553 function self = initUsedFeatures(self)
554 % SELF = INITUSEDFEATURES()
556 % The list includes all
classes but Model and Hidden or
557 % Constant or Abstract or Solvers
558 self.usedFeatures = SolverFeatureSet;
562 methods(Access = protected)
563 % Override copyElement method:
564 function clone = copyElement(self)
565 % CLONE = COPYELEMENT()
567 % Make a shallow copy of all properties
568 clone = copyElement@Copyable(self);
569 % Make a deep copy of each handle
571 clone.
classes{i} = self.classes{i}.copy;
573 % Make a deep copy of each handle
574 for i=1:length(self.nodes)
575 clone.
nodes{i} = self.nodes{i}.copy;
576 if isa(clone.nodes{i},
'Station')
577 clone.stations{i} = clone.nodes{i};
579 clone.connections = self.connections;
585 function
bool = hasFCFS(self)
588 bool = sn_has_fcfs(self.getStruct);
591 function
bool = hasHomogeneousScheduling(self, strategy)
592 % BOOL = HASHOMOGENEOUSSCHEDULING(STRATEGY)
594 bool = sn_has_homogeneous_scheduling(self.getStruct, strategy);
597 function
bool = hasDPS(self)
600 bool = sn_has_dps(self.getStruct);
603 function
bool = hasGPS(self)
606 bool = sn_has_gps(self.getStruct);
609 function
bool = hasINF(self)
612 bool = sn_has_inf(self.getStruct);
615 function
bool = hasPS(self)
618 bool = sn_has_ps(self.getStruct);
621 function
bool = hasSIRO(self)
624 bool = sn_has_siro(self.getStruct);
627 function
bool = hasHOL(self)
630 bool = sn_has_hol(self.getStruct);
633 function
bool = hasLCFS(self)
636 bool = sn_has_lcfs(self.getStruct);
639 function
bool = hasLCFSPR(self)
642 bool = sn_has_lcfs_pr(self.getStruct);
645 function
bool = hasSEPT(self)
648 bool = sn_has_sept(self.getStruct);
651 function
bool = hasLEPT(self)
654 bool = sn_has_lept(self.getStruct);
657 function
bool = hasSJF(self)
660 bool = sn_has_sjf(self.getStruct);
663 function
bool = hasLJF(self)
666 bool = sn_has_ljf(self.getStruct);
669 function
bool = hasMultiClassFCFS(self)
670 % BOOL = HASMULTICLASSFCFS()
672 bool = sn_has_multi_class_fcfs(self.getStruct);
675 function
bool = hasMultiClassHeterFCFS(self)
676 % BOOL = HASMULTICLASSFCFS()
678 bool = sn_has_multi_class_heter_fcfs(self.getStruct);
681 function
bool = hasMultiServer(self)
682 % BOOL = HASMULTISERVER()
684 bool = sn_has_multi_server(self.getStruct);
687 function
bool = hasSingleChain(self)
688 % BOOL = HASSINGLECHAIN()
690 bool = sn_has_single_chain(self.getStruct);
693 function
bool = hasMultiChain(self)
694 % BOOL = HASMULTICHAIN()
696 bool = sn_has_multi_chain(self.getStruct);
699 function
bool = hasSingleClass(self)
700 % BOOL = HASSINGLECLASS()
702 bool = sn_has_single_class(self.getStruct);
705 function
bool = hasMultiClass(self)
706 % BOOL = HASMULTICLASS()
708 bool = sn_has_multi_class(self.getStruct);
713 function
bool = hasProductFormSolution(self)
714 % BOOL = HASPRODUCTFORMSOLUTION()
715 bool = sn_has_product_form(self.getStruct);
720 [~,H] = self.getGraph;
721 H.Nodes.Name=strrep(H.Nodes.Name,'_','\_');
722 h=plot(H,'EdgeLabel',H.Edges.Weight,'Layout','Layered');
723 highlight(h,self.getNodeTypes==3,'NodeColor','r'); % class-switch
nodes
726 function varargout = getMarkedCTMC( varargin )
727 [varargout{1:nargout}] = getCTMC( varargin{:} );
730 function mctmc = getCTMC(self, par1, par2)
732 options = SolverCTMC.defaultOptions;
734 options = SolverCTMC.defaultOptions;
735 options.(par1) = par2;
736 options.cache =
false;
737 elseif isstruct(par1)
740 solver = SolverCTMC(self,options);
741 [infGen, eventFilt, ev] = solver.getInfGen();
742 mctmc = MarkedMarkovProcess(infGen, eventFilt, ev,
true);
743 mctmc.setStateSpace(solver.getStateSpace);
749 function model = tandemPs(lambda,D)
750 % MODEL = TANDEMPS(LAMBDA,D)
752 model = Network.tandemPsInf(lambda,D,[]);
755 function model = tandemPsInf(lambda,D,Z)
756 % MODEL = TANDEMPSINF(LAMBDA,D,Z)
758 if nargin<3%~exist(
'Z',
'var')
765 strategy{i} = SchedStrategy.INF;
768 strategy{Mz+i} = SchedStrategy.PS;
770 model = Network.tandem(lambda,[Z;D],strategy);
773 function model = tandemFcfs(lambda,D)
774 % MODEL = TANDEMFCFS(LAMBDA,D)
776 model = Network.tandemFcfsInf(lambda,D,[]);
779 function model = tandemFcfsInf(lambda,D,Z)
780 % MODEL = TANDEMFCFSINF(LAMBDA,D,Z)
782 if nargin<3%~exist(
'Z',
'var')
789 strategy{i} = SchedStrategy.INF;
792 strategy{Mz+i} = SchedStrategy.FCFS;
794 model = Network.tandem(lambda,[Z;D],strategy);
797 function model = tandem(lambda,D,strategy)
798 % MODEL = TANDEM(LAMBDA,S,STRATEGY)
800 % D(i,r) - mean service demand of
class r at station i, equal
801 % to mean service time since the topology
is linear
802 % lambda(r) - number of jobs of
class r
803 % station(i) - scheduling strategy at station i
804 model = Network(
'Model');
806 node{1} = Source(model,
'Source');
808 switch SchedStrategy.toId(strategy{i})
809 case SchedStrategy.INF
810 node{end+1} = Delay(model, [
'Station',num2str(i)]);
812 node{end+1} = Queue(model, [
'Station',num2str(i)], strategy{i});
815 node{end+1} = Sink(model,
'Sink');
816 P = cellzeros(R,R,M+2,M+2);
818 jobclass{r} = OpenClass(model, [
'Class',num2str(r)], 0);
819 P{r,r} = circul(length(node));
P{r}(end,:) = 0;
822 node{1}.setArrival(
jobclass{r}, Exp.fitMean(1/lambda(r)));
824 node{1+i}.setService(
jobclass{r}, Exp.fitMean(D(i,r)));
830 function model = cyclicPs(N,D)
831 % MODEL = CYCLICPS(N,D)
836 model = Network.cyclicPsInf(N,D,[],S);
839 function model = cyclicPsInf(N,D,Z,S)
840 % MODEL = CYCLICPSINF(N,D,Z,S)
849 strategy = cell(M+Mz,1);
851 strategy{i} = SchedStrategy.INF;
854 strategy{Mz+i} = SchedStrategy.PS;
856 model = Network.cyclic(N,[Z;D],strategy,[Inf*ones(size(Z,1),1);S]);
859 function model = cyclicFcfs(N,D,S)
860 % MODEL = CYCLICFCFS(N,D,S)
865 model = Network.cyclicFcfsInf(N,D,[],S);
868 function model = cyclicFcfsInf(N,D,Z,S)
869 % MODEL = CYCLICFCFSINF(N,D,Z,S)
871 if nargin<3%~exist(
'Z',
'var')
882 strategy{i} = SchedStrategy.INF;
885 strategy{Mz+i} = SchedStrategy.FCFS;
887 model = Network.cyclic(N,[Z;D],strategy,[Inf*ones(size(Z,1),1);S]);
890 function model = cyclic(N,D,strategy,S)
891 % MODEL = CYCLIC(N,D,STRATEGY,S)
893 % L(i,r) - demand of
class r at station i
894 % N(r) - number of jobs of
class r
895 % strategy(i) - scheduling strategy at station i
896 % S(i) - number of servers at station i
897 model = Network(
'Model');
902 switch SchedStrategy.toId(strategy{ist})
903 case SchedStrategy.INF
905 node{ist} = Delay(model, [
'Delay',num2str(nD)]);
908 node{ist} = Queue(model, [
'Queue',num2str(nQ)], strategy{ist});
909 node{ist}.setNumberOfServers(S(ist));
915 jobclass{r} = ClosedClass(model, [
'Class',num2str(r)], N(r), node{1}, 0);
920 node{ist}.setService(
jobclass{r}, Exp.fitMean(D(ist,r)));
926 function
P = serialRouting(varargin)
927 %
P = SERIALROUTING(VARARGIN)
929 if length(varargin)==1
930 varargin = varargin{1};
932 model = varargin{1}.model;
933 P = zeros(model.getNumberOfNodes);
934 for i=1:length(varargin)-1
935 P(varargin{i},varargin{i+1})=1;
937 if ~isa(varargin{end},
'Sink')
938 P(varargin{end},varargin{1})=1;
940 P =
P ./ repmat(sum(
P,2),1,length(
P));
944 function printInfGen(Q,SS)
946 SolverCTMC.printInfGen(Q,SS);
949 function printEventFilt(sync,D,SS,myevents)
950 % PRINTEVENTFILT(SYNC,D,SS,MYEVENTS)
951 SolverCTMC.printEventFilt(sync,D,SS,myevents);