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
21 % Monotonic counter bumped every time the struct
is recompiled or
22 % discarded. Consumers that cache something derived from the topology
23 % (e.g. SolverMVA's MMT fork-join transformation) tag the cache with the
24 % value seen at build time and reuse it only while the counter still
25 % matches, so any structural change forces a rebuild. Python and the JAR
26 % test identity of the NetworkStruct
object for this; sn
is a value
27 % struct in MATLAB, so it needs an explicit version instead.
30 forkStateful = false; % Fork
nodes are stateful (FJ-augmented copies only, see ModelAdapter.fjtag)
31 isFJAugmented = false; % true on fork-join tag-augmented copies (skips the MMT visit correction)
36 sourceidx; % cached value
37 sinkidx; % cached value
50 nodes = getNodes(self)
51 sa = getStruct(self, structType, wantState) % get abritrary representation
52 used = getUsedLangFeatures(self) % get used features
53 ft = getForkJoins(self, rt) % get fork-join pairs
54 [chainsObj,chainsMatrix] = getChains(self, rt) % get chain table
55 [rt,rtNodes,connections,chains,rtNodesByClass,rtNodesByStation] = getRoutingMatrix(self, arvRates) % get routing matrix
56 [Q,U,R,T,A,W] = getAvgHandles(self)
57 Q = getAvgQLenHandles(self);
58 U = getAvgUtilHandles(self);
59 R = getAvgRespTHandles(self);
60 T = getAvgTputHandles(self);
61 A = getAvgArvRHandles(self);
62 W = getAvgResidTHandles(self);
63 [Qt,Ut,Tt] = getTranHandles(self)
64 connections = getConnectionMatrix(self);
65 [absorbingStations, absorbingIdxs] = getAbsorbingStations(self) % get absorbing stations
66 info = getReducibilityInfo(self) % get reducibility analysis
69 methods % ergodicity analysis methods
70 [isErg, info] = isRoutingErgodic(self,
P) % check if routing
is ergodic
71 P = makeErgodic(self, targetNode) % generate ergodic routing matrix
74 methods % link, reset, refresh methods
76 self = relink(self,
P)
77 [loggerBefore,loggerAfter] = linkAndLog(self,
nodes, classes,
P, wantLogger, logPath)
80 reset(self, resetState)
82 resetModel(self, resetState)
83 nodes = resetNetwork(self, deleteCSnodes)
86 refreshStruct(self, hard);
87 [rates, scv, hasRateChanged, hasSCVChanged] = refreshRates(self, statSet, classSet);
88 [ph, mu, phi, phases] = refresProcessPhases(self, statSet, classSet);
89 proctypes = refreshProcessTypes(self);
90 [rt, rtfun, rtnodes] = refreshRoutingMatrix(self, rates);
91 [lt] = refreshLST(self, statSet, classSet);
92 sync = refreshSync(self);
93 classprio = refreshPriorities(self);
94 [sched, schedparam] = refreshScheduling(self);
95 [rates, scv, mu, phi, phases] = refreshProcesses(self, statSet, classSet);
96 [chains,
visits, rt] = refreshChains(self, propagate)
97 [cap, classcap] = refreshCapacity(self);
98 nvars = refreshLocalVars(self);
102 methods (Access=public)
105 function self = MNetwork(modelName, varargin)
106 % SELF = NETWORK(MODELNAME)
107 self@Model(modelName);
111 self.connections = [];
112 initUsedFeatures(self);
114 self.hasState =
false;
120 self.setChecks(
true);
121 self.hasStruct =
false;
122 self.allowReplace =
false;
125 setInitialized(self,
bool);
127 function self = setChecks(self,
bool)
128 self.enableChecks = bool;
131 function
bool = getChecks(self)
133 % True
if model validation
is enabled on
this model.
134 bool = self.enableChecks;
137 P = getLinkedRoutingMatrix(self)
139 function logPath = getLogPath(self)
140 % LOGPATH = GETLOGPATH()
142 logPath = self.logPath;
145 function setLogPath(self, logPath)
146 % SETLOGPATH(LOGPATH)
148 self.logPath = logPath;
151 bool = hasInitState(self)
153 function [M,R] = getSize(self)
156 M = self.getNumberOfNodes;
157 R = self.getNumberOfClasses;
160 function
bool = hasOpenClasses(self)
161 % BOOL = HASOPENCLASSES()
163 bool = any(isinf(getNumberOfJobs(self)));
166 function
bool = hasClassSwitching(self)
167 % BOOL = HASCLASSSWITCHING()
169 bool = any(cellfun(@(c) isa(c,'ClassSwitch'), self.
nodes));
172 function
bool = hasFork(self)
175 bool = any(cellfun(@(c) isa(c,'Fork'), self.
nodes));
178 function
bool = hasJoin(self)
181 bool = any(cellfun(@(c) isa(c,'Join'), self.
nodes));
184 function
bool = hasClosedClasses(self)
185 % BOOL = HASCLOSEDCLASSES()
187 bool = any(isfinite(getNumberOfJobs(self)));
190 function
bool = isMatlabNative(self)
191 % BOOL = ISMATLABNATIVE()
193 % Returns true for MATLAB native (MNetwork) implementation
198 function
bool = isJavaNative(self)
199 % BOOL = ISJAVANATIVE()
201 % Returns false for MATLAB native (MNetwork) implementation
206 function index = getIndexOpenClasses(self)
207 % INDEX = GETINDEXOPENCLASSES()
209 index = find(isinf(getNumberOfJobs(self)))';
212 function index = getIndexClosedClasses(self)
213 % INDEX = GETINDEXCLOSEDCLASSES()
215 index = find(isfinite(getNumberOfJobs(self)))';
218 function classes = getClasses(self)
219 classes= self.classes;
222 chain = getClassChain(self, className)
223 c = getClassChainIndex(self, className)
224 classNames = getClassNames(self)
226 nodeNames = getNodeNames(self)
227 nodeTypes = getNodeTypes(self)
229 P = initRoutingMatrix(self)
231 ind = getNodeIndex(self, name)
232 lldScaling = getLimitedLoadDependence(self)
233 lcdScaling = getLimitedClassDependence(self)
234 ljdScaling = getLimitedJointDependence(self)
236 function stationIndex = getStationIndex(self, name)
237 % STATIONINDEX = GETSTATIONINDEX(NAME)
241 name = node.getName();
243 stationIndex = find(cellfun(@(c) strcmp(c,name),self.getStationNames));
246 function statefulIndex = getStatefulNodeIndex(self, name)
247 % STATEFULINDEX = GETSTATEFULNODEINDEX(NAME)
251 name = node.getName();
253 statefulIndex = find(cellfun(@(c) strcmp(c,name),self.getStatefulNodeNames));
256 function classIndex = getClassIndex(self, name)
257 % CLASSINDEX = GETCLASSINDEX(NAME)
258 if isa(name,'JobClass')
262 classIndex = find(cellfun(@(c) strcmp(c,name),self.getClassNames));
265 function stationnames = getStationNames(self)
266 % STATIONNAMES = GETSTATIONNAMES()
269 nodenames = self.sn.nodenames;
270 isstation = self.sn.isstation;
271 stationnames = {nodenames{isstation}}
';
274 for i=self.getStationIndexes
275 stationnames{end+1,1} = self.nodes{i}.name;
280 function nodes = getNodeByName(self, name)
281 % NODES = GETNODEBYNAME(SELF, NAME)
282 idx = findstring(self.getNodeNames,name);
284 nodes = self.nodes{idx};
290 function station = getStationByName(self, name)
291 % STATION = GETSTATIONBYNAME(SELF, NAME)
292 idx = findstring(self.getStationNames,name);
294 station = self.stations{idx};
300 function class = getClassByName(self, name)
301 % CLASS = GETCLASSBYNAME(SELF, NAME)
302 idx = findstring(self.getClassNames,name);
304 class = self.classes{idx};
310 function nodes = getNodeByIndex(self, idx)
311 % NODES = GETNODEBYINDEX(SELF, NAME)
313 nodes = self.nodes{idx};
319 function station = getStationByIndex(self, idx)
320 % STATION = GETSTATIONBYINDEX(SELF, NAME)
322 station = self.stations{idx};
328 function class = getClassByIndex(self, idx)
329 % CLASS = GETCLASSBYINDEX(SELF, NAME)
331 class = self.classes{idx};
337 function [infGen, eventFilt, ev] = getGenerator(self, varargin)
338 line_warning(mfilename,'Results will not be cached. Use SolverCTMC(model,...).getGenerator(...) instead.\n
');
339 [infGen, eventFilt, ev] = SolverCTMC(self).getGenerator(varargin{:});
342 function [stateSpace,nodeStateSpace] = getStateSpace(self, varargin)
343 line_warning(mfilename,'Results will not be cached. Use SolverCTMC(model,...).getStateSpace(...) instead.\n
');
344 [stateSpace,nodeStateSpace] = SolverCTMC(self).getStateSpace(varargin{:});
347 function [stateSpace,nodeStateSpace] = stateSpace(self, varargin)
348 % [STATESPACE,NODESTATESPACE] = STATESPACE(SELF, VARARGIN)
349 % Alias for getStateSpace
350 [stateSpace,nodeStateSpace] = self.getStateSpace(varargin{:});
353 function summary(self)
355 for i=1:self.getNumberOfClasses
356 self.classes{i}.summary();
357 if i<self.getNumberOfClasses
361 for i=1:self.getNumberOfNodes
362 self.nodes{i}.summary();
364 line_printf('\n<strong>Routing matrix</strong>:
');
365 self.printRoutingMatrix
366 line_printf('\n<strong>Product-
form parameters</strong>:
');
367 [arvRates,servDemands,nJobs,thinkTimes,ldScalings,nServers]= getProductFormParameters(self);
368 line_printf('\nArrival rates: %s
',mat2str(arvRates,6));
369 line_printf('\nService demands: %s
',mat2str(servDemands,6));
370 line_printf('\nNumber of jobs: %s
',mat2str(nJobs));
371 line_printf('\nThink times: %s
',mat2str(thinkTimes,6));
372 line_printf('\nLoad-dependent scalings: %s
',mat2str(ldScalings,6));
373 line_printf('\nNumber of servers: %s\n
',mat2str(nServers));
374 line_printf('\n<strong>Chains</strong>:
');
377 function [D,Z] = getDemands(self)
378 % [D,Z]= GETDEMANDS()
384 [~,D,~,Z,~,~] = sn_get_product_form_params(self.getStruct);
387 function [lambda,D,N,Z,mu,S]= getProductFormParameters(self)
388 % [LAMBDA,D,N,Z,MU,S]= GETPRODUCTFORMPARAMETERS()
391 % LAMBDA: arrival rates for open classes
393 % N: population vector for closed classes
395 % mu: load-dependent rates
396 % S: number of servers
398 % mu also returns max(S) elements after population |N| as this is
399 % required by MVALDMX
401 [lambda,D,N,Z,mu,S] = sn_get_product_form_params(self.getStruct);
404 function [lambda,D,N,Z,mu,S]= getProductFormChainParameters(self)
405 % [LAMBDA,D,N,Z,MU,S]= GETPRODUCTFORMCHAINPARAMETERS()
408 % LAMBDA: arrival rates for open classes
410 % N: population vector for closed classes
412 % mu: load-dependent rates
413 % S: number of servers
415 % mu also returns max(S) elements after population |N| as this is
416 % required by MVALDMX
418 [lambda,D,N,Z,mu,S]= sn_get_product_form_chain_params(self.getStruct);
421 function statefulnodes = getStatefulNodes(self)
423 for i=1:self.getNumberOfNodes
424 if self.nodes{i}.isStateful
425 statefulnodes{end+1,1} = self.nodes{i};
430 function statefulnames = getStatefulNodeNames(self)
431 % STATEFULNAMES = GETSTATEFULNODENAMES()
434 for i=1:self.getNumberOfNodes
435 if self.nodes{i}.isStateful
436 statefulnames{end+1,1} = self.nodes{i}.name;
441 function M = getNumberOfNodes(self)
442 % M = GETNUMBEROFNODES()
444 M = length(self.nodes);
447 function S = getNumberOfStatefulNodes(self)
448 % S = GETNUMBEROFSTATEFULNODES()
450 S = sum(cellisa(self.nodes,'StatefulNode
'));
453 function M = getNumberOfStations(self)
454 % M = GETNUMBEROFSTATIONS()
456 M = length(self.stations);
459 function R = getNumberOfClasses(self)
460 % R = GETNUMBEROFCLASSES()
462 R = length(self.classes);
465 function C = getNumberOfChains(self)
466 % C = GETNUMBEROFCHAINS()
472 function Dchain = getDemandsChain(self)
473 % DCHAIN = GETDEMANDSCHAIN()
474 sn_get_demands_chain(self.getStruct);
477 function self = setUsedLangFeature(self,className)
478 % SELF = SETUSEDLANGFEATURE(SELF,CLASSNAME)
480 % An empty name means "no gated capability" (the toFeature
481 % convention for internal markers such as FIRING/DISABLED routing);
482 % it is skipped, as in jar Network.getUsedLangFeatures.
483 if isempty(className)
486 % see _kb/04-networkstruct.md (MNetwork.m feature-name normalization)
487 if strcmp(className,'MarkedMAP
') || strcmp(className,'MarkedMMPP
')
490 self.usedFeatures.setTrue(className);
493 %% Add the components to the model
494 addJobClass(self, customerClass);
495 bool = addNode(self, node);
496 fcr = addRegion(self, nodes);
497 addLink(self, nodeA, nodeB);
498 addLinks(self, nodeList);
499 addItemSet(self, itemSet);
501 node = getSource(self);
502 node = getSink(self);
504 function list = getStationIndexes(self)
505 % LIST = GETSTATIONINDEXES()
508 list = find(self.sn.isstation)';
510 % returns the ids of
nodes that are stations
511 list = find(cellisa(self.nodes,
'Station'))
';
515 function list = getIndexStatefulNodes(self)
516 % LIST = GETINDEXSTATEFULNODES()
518 % returns the ids of nodes that are stations
519 list = find(cellisa(self.nodes, 'StatefulNode
'))';
521 % FJ tag-augmented copies treat Fork
nodes as stateful
522 list =
union(list, find(cellisa(self.nodes,
'Fork'))
');
526 index = getIndexSourceStation(self);
527 index = getIndexSourceNode(self);
528 index = getIndexSinkNode(self);
530 N = getNumberOfJobs(self);
531 refstat = getReferenceStations(self);
532 refclass = getReferenceClasses(self);
533 sched = getStationScheduling(self);
534 S = getStationServers(self);
535 S = getStatefulServers(self);
540 % VIEW() Open the model in JSIMgraph
544 function modelView(self)
545 % MODELVIEW() Open the model in ModelVisualizer
546 jnetwork = JLINE.line_to_jline(self);
550 function islld = isLimitedLoadDependent(self)
551 islld = isempty(self.getStruct().lldscaling);
554 function [isvalid] = isStateValid(self)
555 % [ISVALID] = ISSTATEVALID()
557 isvalid = sn_is_state_valid(self.getStruct);
560 [state, priorStateSpace, stateSpace] = getState(self) % get initial state
562 initFromAvgTableQLen(self, AvgTable)
563 initFromAvgQLen(self, AvgQLen)
564 initDefault(self, nodes)
566 initFromMarginal(self, n, options) % n(i,r) : number of jobs of class r in node or station i (autodetected)
567 initFromMarginalAndRunning(self, n, s, options) % n(i,r) : number of jobs of class r in node or station i (autodetected)
568 initFromMarginalAndStarted(self, n, s, options) % n(i,r) : number of jobs of class r in node or station i (autodetected)
570 [H,G] = getGraph(self)
572 function mask = getClassSwitchingMask(self)
573 % MASK = GETCLASSSWITCHINGMASK()
575 mask = self.getStruct.csmask;
578 function printRoutingMatrix(self, onlyclass)
579 % PRINTROUTINGMATRIX()
581 sn_print_routing_matrix(self.getStruct);
583 sn_print_routing_matrix(self.getStruct, onlyclass);
589 methods (Access = protected)
591 function out = getModelNameExtension(self)
592 % OUT = GETMODELNAMEEXTENSION()
594 out = [getModelName(self), ['.
', self.fileFormat]];
597 function self = initUsedFeatures(self)
598 % SELF = INITUSEDFEATURES()
600 % The list includes all classes but Model and Hidden or
601 % Constant or Abstract or Solvers
602 self.usedFeatures = SolverFeatureSet;
606 methods(Access = protected)
607 % Override copyElement method:
608 function clone = copyElement(self)
609 % CLONE = COPYELEMENT()
611 % Make a shallow copy of all properties
612 clone = copyElement@Copyable(self);
613 % Make a deep copy of each handle
614 for i=1:length(self.classes)
615 clone.classes{i} = self.classes{i}.copy;
617 % Make a deep copy of each handle
618 for i=1:length(self.nodes)
619 clone.nodes{i} = self.nodes{i}.copy;
620 if isa(clone.nodes{i},'Station
')
621 clone.stations{i} = clone.nodes{i};
623 clone.connections = self.connections;
629 function bool = hasFCFS(self)
632 bool = sn_has_fcfs(self.getStruct);
635 function bool = hasHomogeneousScheduling(self, strategy)
636 % BOOL = HASHOMOGENEOUSSCHEDULING(STRATEGY)
638 bool = sn_has_homogeneous_scheduling(self.getStruct, strategy);
641 function bool = hasDPS(self)
644 bool = sn_has_dps(self.getStruct);
647 function bool = hasGPS(self)
650 bool = sn_has_gps(self.getStruct);
653 function bool = hasINF(self)
656 bool = sn_has_inf(self.getStruct);
659 function bool = hasPS(self)
662 bool = sn_has_ps(self.getStruct);
665 function bool = hasSIRO(self)
668 bool = sn_has_siro(self.getStruct);
671 function bool = hasHOL(self)
674 bool = sn_has_hol(self.getStruct);
677 function bool = hasLCFS(self)
680 bool = sn_has_lcfs(self.getStruct);
683 function bool = hasLCFSPR(self)
686 bool = sn_has_lcfs_pr(self.getStruct);
689 function bool = hasSEPT(self)
692 bool = sn_has_sept(self.getStruct);
695 function bool = hasLEPT(self)
698 bool = sn_has_lept(self.getStruct);
701 function bool = hasSJF(self)
704 bool = sn_has_sjf(self.getStruct);
707 function bool = hasLJF(self)
710 bool = sn_has_ljf(self.getStruct);
713 function bool = hasMultiClassFCFS(self)
714 % BOOL = HASMULTICLASSFCFS()
716 bool = sn_has_multi_class_fcfs(self.getStruct);
719 function bool = hasMultiClassHeterFCFS(self)
720 % BOOL = HASMULTICLASSFCFS()
722 bool = sn_has_multi_class_heter_fcfs(self.getStruct);
725 function bool = hasMultiServer(self)
726 % BOOL = HASMULTISERVER()
728 bool = sn_has_multi_server(self.getStruct);
731 function bool = hasSingleChain(self)
732 % BOOL = HASSINGLECHAIN()
734 bool = sn_has_single_chain(self.getStruct);
737 function bool = hasMultiChain(self)
738 % BOOL = HASMULTICHAIN()
740 bool = sn_has_multi_chain(self.getStruct);
743 function bool = hasSingleClass(self)
744 % BOOL = HASSINGLECLASS()
746 bool = sn_has_single_class(self.getStruct);
749 function bool = hasMultiClass(self)
750 % BOOL = HASMULTICLASS()
752 bool = sn_has_multi_class(self.getStruct);
757 function bool = hasProductFormSolution(self)
758 % BOOL = HASPRODUCTFORMSOLUTION()
759 bool = sn_has_product_form(self.getStruct);
764 [~,H] = self.getGraph;
765 H.Nodes.Name=strrep(H.Nodes.Name,'_
','\_
');
766 h=plot(H,'EdgeLabel
',H.Edges.Weight,'Layout
','Layered
');
767 highlight(h,self.getNodeTypes==3,'NodeColor
','r
'); % class-switch nodes
770 function varargout = getMarkedCTMC( varargin )
771 [varargout{1:nargout}] = getCTMC( varargin{:} );
774 function mctmc = getCTMC(self, par1, par2)
776 options = SolverCTMC.defaultOptions;
778 options = SolverCTMC.defaultOptions;
779 options.(par1) = par2;
780 options.cache = false;
781 elseif isstruct(par1)
784 solver = SolverCTMC(self,options);
785 [infGen, eventFilt, ev] = solver.getInfGen();
786 mctmc = MarkedMarkovProcess(infGen, eventFilt, ev, true);
787 mctmc.setStateSpace(solver.getStateSpace);
793 function model = tandemPs(lambda,D)
794 % MODEL = TANDEMPS(LAMBDA,D)
796 model = Network.tandemPsInf(lambda,D,[]);
799 function model = tandemPsInf(lambda,D,Z)
800 % MODEL = TANDEMPSINF(LAMBDA,D,Z)
802 if nargin<3%~exist('Z
','var
')
809 strategy{i} = SchedStrategy.INF;
812 strategy{Mz+i} = SchedStrategy.PS;
814 model = Network.tandem(lambda,[Z;D],strategy);
817 function model = tandemFcfs(lambda,D)
818 % MODEL = TANDEMFCFS(LAMBDA,D)
820 model = Network.tandemFcfsInf(lambda,D,[]);
823 function model = tandemFcfsInf(lambda,D,Z)
824 % MODEL = TANDEMFCFSINF(LAMBDA,D,Z)
826 if nargin<3%~exist('Z
','var
')
833 strategy{i} = SchedStrategy.INF;
836 strategy{Mz+i} = SchedStrategy.FCFS;
838 model = Network.tandem(lambda,[Z;D],strategy);
841 function model = tandem(lambda,D,strategy)
842 % MODEL = TANDEM(LAMBDA,S,STRATEGY)
844 % D(i,r) - mean service demand of class r at station i, equal
845 % to mean service time since the topology is linear
846 % lambda(r) - number of jobs of class r
847 % station(i) - scheduling strategy at station i
848 model = Network('Model
');
850 node{1} = Source(model, 'Source
');
852 switch SchedStrategy.toId(strategy{i})
853 case SchedStrategy.INF
854 node{end+1} = Delay(model, ['Station',num2str(i)]);
856 node{end+1} = Queue(model, ['Station',num2str(i)], strategy{i});
859 node{end+1} = Sink(model, 'Sink
');
860 P = cellzeros(R,R,M+2,M+2);
862 jobclass{r} = OpenClass(model, ['Class
',num2str(r)], 0);
863 P{r,r} = circul(length(node)); P{r}(end,:) = 0;
866 node{1}.setArrival(jobclass{r}, Exp.fitMean(1/lambda(r)));
868 node{1+i}.setService(jobclass{r}, Exp.fitMean(D(i,r)));
874 function model = cyclicPs(N,D)
875 % MODEL = CYCLICPS(N,D)
880 model = Network.cyclicPsInf(N,D,[],S);
883 function model = cyclicPsInf(N,D,Z,S)
884 % MODEL = CYCLICPSINF(N,D,Z,S)
893 strategy = cell(M+Mz,1);
895 strategy{i} = SchedStrategy.INF;
898 strategy{Mz+i} = SchedStrategy.PS;
900 model = Network.cyclic(N,[Z;D],strategy,[Inf*ones(size(Z,1),1);S]);
903 function model = cyclicFcfs(N,D,S)
904 % MODEL = CYCLICFCFS(N,D,S)
909 model = Network.cyclicFcfsInf(N,D,[],S);
912 function model = cyclicFcfsInf(N,D,Z,S)
913 % MODEL = CYCLICFCFSINF(N,D,Z,S)
915 if nargin<3%~exist('Z
','var
')
926 strategy{i} = SchedStrategy.INF;
929 strategy{Mz+i} = SchedStrategy.FCFS;
931 model = Network.cyclic(N,[Z;D],strategy,[Inf*ones(size(Z,1),1);S]);
934 function model = cyclic(N,D,strategy,S)
935 % MODEL = CYCLIC(N,D,STRATEGY,S)
937 % L(i,r) - demand of class r at station i
938 % N(r) - number of jobs of class r
939 % strategy(i) - scheduling strategy at station i
940 % S(i) - number of servers at station i
941 model = Network('Model
');
946 switch SchedStrategy.toId(strategy{ist})
947 case SchedStrategy.INF
949 node{ist} = Delay(model, ['Delay
',num2str(nD)]);
952 node{ist} = Queue(model, ['Queue
',num2str(nQ)], strategy{ist});
953 node{ist}.setNumberOfServers(S(ist));
957 jobclass = cell(R,1);
959 jobclass{r} = ClosedClass(model, ['Class
',num2str(r)], N(r), node{1}, 0);
964 node{ist}.setService(jobclass{r}, Exp.fitMean(D(ist,r)));
970 function model = cluster(lambda, D, strategy, S, dispatching)
971 % MODEL = SERVERFARM(LAMBDA, D, STRATEGY, S, DISPATCHING)
973 % Open cluster: Source -> Dispatcher -> Server[1..M] -> Sink.
975 % lambda(r) - arrival rate of class r
976 % D(i,r) - mean service time of class r at server i
977 % strategy{i} - scheduling strategy at server i
978 % S(i) - number of identical servers at queue i (default: 1)
979 % dispatching - RoutingStrategy applied at the router (default: RAND)
980 if nargin < 4 || isempty(S), S = ones(size(D,1),1); end
981 if nargin < 5, dispatching = RoutingStrategy.RAND; end
983 model = Network('Cluster
');
986 source = Source(model, 'Source
');
987 dispatcher = Router(model, 'Dispatcher
');
990 servers{i} = Queue(model, ['Station', num2str(i)], strategy{i});
992 servers{i}.setNumberOfServers(S(i));
995 sink = Sink(model, 'Sink
');
997 jobclass = cell(R,1);
999 jobclass{r} = OpenClass(model, ['Class
', num2str(r)], 0);
1000 source.setArrival(jobclass{r}, Exp.fitMean(1/lambda(r)));
1002 servers{i}.setService(jobclass{r}, Exp.fitMean(D(i,r)));
1006 model.addLink(source, dispatcher);
1008 model.addLink(dispatcher, servers{i});
1009 model.addLink(servers{i}, sink);
1012 dispatcher.setRouting(jobclass{r}, dispatching);
1016 function model = clusterClosed(N, Z, D, strategy, S, dispatching)
1017 % MODEL = SERVERFARMCLOSED(N, Z, D, STRATEGY, S, DISPATCHING)
1019 % Closed cluster: Think -> Dispatcher -> Server[1..M] -> Think.
1021 % N(r) - population of class r
1022 % Z(r) - per-class think time at the delay
1023 % D(i,r) - mean service time of class r at server i
1024 % strategy{i} - scheduling strategy at server i
1025 % S(i) - number of identical servers at queue i (default: 1)
1026 % dispatching - RoutingStrategy applied at the router (default: RAND)
1027 if nargin < 5 || isempty(S), S = ones(size(D,1),1); end
1028 if nargin < 6, dispatching = RoutingStrategy.RAND; end
1030 model = Network('Cluster
');
1033 think = Delay(model, 'Think
');
1034 dispatcher = Router(model, 'Dispatcher
');
1035 servers = cell(M,1);
1037 servers{i} = Queue(model, ['Station', num2str(i)], strategy{i});
1039 servers{i}.setNumberOfServers(S(i));
1043 jobclass = cell(R,1);
1045 jobclass{r} = ClosedClass(model, ['Class
', num2str(r)], N(r), think, 0);
1046 think.setService(jobclass{r}, Exp.fitMean(Z(r)));
1048 servers{i}.setService(jobclass{r}, Exp.fitMean(D(i,r)));
1052 model.addLink(think, dispatcher);
1054 model.addLink(dispatcher, servers{i});
1055 model.addLink(servers{i}, think);
1058 dispatcher.setRouting(jobclass{r}, dispatching);
1062 function P = serialRouting(varargin)
1063 % P = SERIALROUTING(VARARGIN)
1065 if length(varargin)==1
1066 varargin = varargin{1};
1068 model = varargin{1}.model;
1069 P = zeros(model.getNumberOfNodes);
1070 for i=1:length(varargin)-1
1071 P(varargin{i},varargin{i+1})=1;
1073 % Auto-close the cycle (last -> first) unless the last node is a Sink
1074 % or the caller already closed the loop by repeating the first node as
1075 % the last (e.g. serialRouting(d,q,d)), which would otherwise add a
1076 % spurious self-loop. Mirrors the Python serial_routing guard.
1077 if ~isa(varargin{end},'Sink
') && varargin{end} ~= varargin{1}
1078 P(varargin{end},varargin{1})=1;
1080 P = P ./ repmat(sum(P,2),1,length(P));
1084 function printInfGen(Q,SS)
1086 SolverCTMC.printInfGen(Q,SS);
1089 function printEventFilt(sync,D,SS,myevents)
1090 % PRINTEVENTFILT(SYNC,D,SS,MYEVENTS)
1091 SolverCTMC.printEventFilt(sync,D,SS,myevents);