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);
503 % VIEW() Open the model in JSIMgraph
507 function modelView(self)
508 % MODELVIEW() Open the model in ModelVisualizer
509 jnetwork = JLINE.line_to_jline(self);
513 function islld = isLimitedLoadDependent(self)
514 islld = isempty(self.getStruct().lldscaling);
517 function [isvalid] = isStateValid(self)
518 % [ISVALID] = ISSTATEVALID()
520 isvalid = sn_is_state_valid(self.getStruct);
523 [state, priorStateSpace, stateSpace] = getState(self) % get initial state
525 initFromAvgTableQLen(self, AvgTable)
526 initFromAvgQLen(self, AvgQLen)
527 initDefault(self,
nodes)
529 initFromMarginal(self, n, options) % n(i,r) : number of jobs of class r in node or station i (autodetected)
530 initFromMarginalAndRunning(self, n, s, options) % n(i,r) : number of jobs of class r in node or station i (autodetected)
531 initFromMarginalAndStarted(self, n, s, options) % n(i,r) : number of jobs of class r in node or station i (autodetected)
533 [H,G] = getGraph(self)
535 function mask = getClassSwitchingMask(self)
536 % MASK = GETCLASSSWITCHINGMASK()
538 mask = self.getStruct.csmask;
541 function printRoutingMatrix(self, onlyclass)
542 % PRINTROUTINGMATRIX()
544 sn_print_routing_matrix(self.getStruct);
546 sn_print_routing_matrix(self.getStruct, onlyclass);
552 methods (Access = protected)
554 function out = getModelNameExtension(self)
555 % OUT = GETMODELNAMEEXTENSION()
557 out = [getModelName(self), ['.', self.fileFormat]];
560 function self = initUsedFeatures(self)
561 % SELF = INITUSEDFEATURES()
563 % The list includes all
classes but Model and Hidden or
564 % Constant or Abstract or Solvers
565 self.usedFeatures = SolverFeatureSet;
569 methods(Access = protected)
570 % Override copyElement method:
571 function clone = copyElement(self)
572 % CLONE = COPYELEMENT()
574 % Make a shallow copy of all properties
575 clone = copyElement@Copyable(self);
576 % Make a deep copy of each handle
578 clone.
classes{i} = self.classes{i}.copy;
580 % Make a deep copy of each handle
581 for i=1:length(self.nodes)
582 clone.
nodes{i} = self.nodes{i}.copy;
583 if isa(clone.nodes{i},
'Station')
584 clone.stations{i} = clone.nodes{i};
586 clone.connections = self.connections;
592 function
bool = hasFCFS(self)
595 bool = sn_has_fcfs(self.getStruct);
598 function
bool = hasHomogeneousScheduling(self, strategy)
599 % BOOL = HASHOMOGENEOUSSCHEDULING(STRATEGY)
601 bool = sn_has_homogeneous_scheduling(self.getStruct, strategy);
604 function
bool = hasDPS(self)
607 bool = sn_has_dps(self.getStruct);
610 function
bool = hasGPS(self)
613 bool = sn_has_gps(self.getStruct);
616 function
bool = hasINF(self)
619 bool = sn_has_inf(self.getStruct);
622 function
bool = hasPS(self)
625 bool = sn_has_ps(self.getStruct);
628 function
bool = hasSIRO(self)
631 bool = sn_has_siro(self.getStruct);
634 function
bool = hasHOL(self)
637 bool = sn_has_hol(self.getStruct);
640 function
bool = hasLCFS(self)
643 bool = sn_has_lcfs(self.getStruct);
646 function
bool = hasLCFSPR(self)
649 bool = sn_has_lcfs_pr(self.getStruct);
652 function
bool = hasSEPT(self)
655 bool = sn_has_sept(self.getStruct);
658 function
bool = hasLEPT(self)
661 bool = sn_has_lept(self.getStruct);
664 function
bool = hasSJF(self)
667 bool = sn_has_sjf(self.getStruct);
670 function
bool = hasLJF(self)
673 bool = sn_has_ljf(self.getStruct);
676 function
bool = hasMultiClassFCFS(self)
677 % BOOL = HASMULTICLASSFCFS()
679 bool = sn_has_multi_class_fcfs(self.getStruct);
682 function
bool = hasMultiClassHeterFCFS(self)
683 % BOOL = HASMULTICLASSFCFS()
685 bool = sn_has_multi_class_heter_fcfs(self.getStruct);
688 function
bool = hasMultiServer(self)
689 % BOOL = HASMULTISERVER()
691 bool = sn_has_multi_server(self.getStruct);
694 function
bool = hasSingleChain(self)
695 % BOOL = HASSINGLECHAIN()
697 bool = sn_has_single_chain(self.getStruct);
700 function
bool = hasMultiChain(self)
701 % BOOL = HASMULTICHAIN()
703 bool = sn_has_multi_chain(self.getStruct);
706 function
bool = hasSingleClass(self)
707 % BOOL = HASSINGLECLASS()
709 bool = sn_has_single_class(self.getStruct);
712 function
bool = hasMultiClass(self)
713 % BOOL = HASMULTICLASS()
715 bool = sn_has_multi_class(self.getStruct);
720 function
bool = hasProductFormSolution(self)
721 % BOOL = HASPRODUCTFORMSOLUTION()
722 bool = sn_has_product_form(self.getStruct);
727 [~,H] = self.getGraph;
728 H.Nodes.Name=strrep(H.Nodes.Name,'_','\_');
729 h=plot(H,'EdgeLabel',H.Edges.Weight,'Layout','Layered');
730 highlight(h,self.getNodeTypes==3,'NodeColor','r'); % class-switch
nodes
733 function varargout = getMarkedCTMC( varargin )
734 [varargout{1:nargout}] = getCTMC( varargin{:} );
737 function mctmc = getCTMC(self, par1, par2)
739 options = SolverCTMC.defaultOptions;
741 options = SolverCTMC.defaultOptions;
742 options.(par1) = par2;
743 options.cache =
false;
744 elseif isstruct(par1)
747 solver = SolverCTMC(self,options);
748 [infGen, eventFilt, ev] = solver.getInfGen();
749 mctmc = MarkedMarkovProcess(infGen, eventFilt, ev,
true);
750 mctmc.setStateSpace(solver.getStateSpace);
756 function model = tandemPs(lambda,D)
757 % MODEL = TANDEMPS(LAMBDA,D)
759 model = Network.tandemPsInf(lambda,D,[]);
762 function model = tandemPsInf(lambda,D,Z)
763 % MODEL = TANDEMPSINF(LAMBDA,D,Z)
765 if nargin<3%~exist(
'Z',
'var')
772 strategy{i} = SchedStrategy.INF;
775 strategy{Mz+i} = SchedStrategy.PS;
777 model = Network.tandem(lambda,[Z;D],strategy);
780 function model = tandemFcfs(lambda,D)
781 % MODEL = TANDEMFCFS(LAMBDA,D)
783 model = Network.tandemFcfsInf(lambda,D,[]);
786 function model = tandemFcfsInf(lambda,D,Z)
787 % MODEL = TANDEMFCFSINF(LAMBDA,D,Z)
789 if nargin<3%~exist(
'Z',
'var')
796 strategy{i} = SchedStrategy.INF;
799 strategy{Mz+i} = SchedStrategy.FCFS;
801 model = Network.tandem(lambda,[Z;D],strategy);
804 function model = tandem(lambda,D,strategy)
805 % MODEL = TANDEM(LAMBDA,S,STRATEGY)
807 % D(i,r) - mean service demand of
class r at station i, equal
808 % to mean service time since the topology
is linear
809 % lambda(r) - number of jobs of
class r
810 % station(i) - scheduling strategy at station i
811 model = Network(
'Model');
813 node{1} = Source(model,
'Source');
815 switch SchedStrategy.toId(strategy{i})
816 case SchedStrategy.INF
817 node{end+1} = Delay(model, [
'Station',num2str(i)]);
819 node{end+1} = Queue(model, [
'Station',num2str(i)], strategy{i});
822 node{end+1} = Sink(model,
'Sink');
823 P = cellzeros(R,R,M+2,M+2);
825 jobclass{r} = OpenClass(model, [
'Class',num2str(r)], 0);
826 P{r,r} = circul(length(node));
P{r}(end,:) = 0;
829 node{1}.setArrival(
jobclass{r}, Exp.fitMean(1/lambda(r)));
831 node{1+i}.setService(
jobclass{r}, Exp.fitMean(D(i,r)));
837 function model = cyclicPs(N,D)
838 % MODEL = CYCLICPS(N,D)
843 model = Network.cyclicPsInf(N,D,[],S);
846 function model = cyclicPsInf(N,D,Z,S)
847 % MODEL = CYCLICPSINF(N,D,Z,S)
856 strategy = cell(M+Mz,1);
858 strategy{i} = SchedStrategy.INF;
861 strategy{Mz+i} = SchedStrategy.PS;
863 model = Network.cyclic(N,[Z;D],strategy,[Inf*ones(size(Z,1),1);S]);
866 function model = cyclicFcfs(N,D,S)
867 % MODEL = CYCLICFCFS(N,D,S)
872 model = Network.cyclicFcfsInf(N,D,[],S);
875 function model = cyclicFcfsInf(N,D,Z,S)
876 % MODEL = CYCLICFCFSINF(N,D,Z,S)
878 if nargin<3%~exist(
'Z',
'var')
889 strategy{i} = SchedStrategy.INF;
892 strategy{Mz+i} = SchedStrategy.FCFS;
894 model = Network.cyclic(N,[Z;D],strategy,[Inf*ones(size(Z,1),1);S]);
897 function model = cyclic(N,D,strategy,S)
898 % MODEL = CYCLIC(N,D,STRATEGY,S)
900 % L(i,r) - demand of
class r at station i
901 % N(r) - number of jobs of
class r
902 % strategy(i) - scheduling strategy at station i
903 % S(i) - number of servers at station i
904 model = Network(
'Model');
909 switch SchedStrategy.toId(strategy{ist})
910 case SchedStrategy.INF
912 node{ist} = Delay(model, [
'Delay',num2str(nD)]);
915 node{ist} = Queue(model, [
'Queue',num2str(nQ)], strategy{ist});
916 node{ist}.setNumberOfServers(S(ist));
922 jobclass{r} = ClosedClass(model, [
'Class',num2str(r)], N(r), node{1}, 0);
927 node{ist}.setService(
jobclass{r}, Exp.fitMean(D(ist,r)));
933 function
P = serialRouting(varargin)
934 %
P = SERIALROUTING(VARARGIN)
936 if length(varargin)==1
937 varargin = varargin{1};
939 model = varargin{1}.model;
940 P = zeros(model.getNumberOfNodes);
941 for i=1:length(varargin)-1
942 P(varargin{i},varargin{i+1})=1;
944 if ~isa(varargin{end},
'Sink')
945 P(varargin{end},varargin{1})=1;
947 P =
P ./ repmat(sum(
P,2),1,length(
P));
951 function printInfGen(Q,SS)
953 SolverCTMC.printInfGen(Q,SS);
956 function printEventFilt(sync,D,SS,myevents)
957 % PRINTEVENTFILT(SYNC,D,SS,MYEVENTS)
958 SolverCTMC.printEventFilt(sync,D,SS,myevents);