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 P = getLinkedRoutingMatrix(self)
133 function logPath = getLogPath(self)
134 % LOGPATH = GETLOGPATH()
136 logPath = self.logPath;
139 function setLogPath(self, logPath)
140 % SETLOGPATH(LOGPATH)
142 self.logPath = logPath;
145 bool = hasInitState(self)
147 function [M,R] = getSize(self)
150 M = self.getNumberOfNodes;
151 R = self.getNumberOfClasses;
154 function
bool = hasOpenClasses(self)
155 % BOOL = HASOPENCLASSES()
157 bool = any(isinf(getNumberOfJobs(self)));
160 function
bool = hasClassSwitching(self)
161 % BOOL = HASCLASSSWITCHING()
163 bool = any(cellfun(@(c) isa(c,'ClassSwitch'), self.
nodes));
166 function
bool = hasFork(self)
169 bool = any(cellfun(@(c) isa(c,'Fork'), self.
nodes));
172 function
bool = hasJoin(self)
175 bool = any(cellfun(@(c) isa(c,'Join'), self.
nodes));
178 function
bool = hasClosedClasses(self)
179 % BOOL = HASCLOSEDCLASSES()
181 bool = any(isfinite(getNumberOfJobs(self)));
184 function
bool = isMatlabNative(self)
185 % BOOL = ISMATLABNATIVE()
187 % Returns true for MATLAB native (MNetwork) implementation
192 function
bool = isJavaNative(self)
193 % BOOL = ISJAVANATIVE()
195 % Returns false for MATLAB native (MNetwork) implementation
200 function index = getIndexOpenClasses(self)
201 % INDEX = GETINDEXOPENCLASSES()
203 index = find(isinf(getNumberOfJobs(self)))';
206 function index = getIndexClosedClasses(self)
207 % INDEX = GETINDEXCLOSEDCLASSES()
209 index = find(isfinite(getNumberOfJobs(self)))';
212 function classes = getClasses(self)
213 classes= self.classes;
216 chain = getClassChain(self, className)
217 c = getClassChainIndex(self, className)
218 classNames = getClassNames(self)
220 nodeNames = getNodeNames(self)
221 nodeTypes = getNodeTypes(self)
223 P = initRoutingMatrix(self)
225 ind = getNodeIndex(self, name)
226 lldScaling = getLimitedLoadDependence(self)
227 lcdScaling = getLimitedClassDependence(self)
229 function stationIndex = getStationIndex(self, name)
230 % STATIONINDEX = GETSTATIONINDEX(NAME)
234 name = node.getName();
236 stationIndex = find(cellfun(@(c) strcmp(c,name),self.getStationNames));
239 function statefulIndex = getStatefulNodeIndex(self, name)
240 % STATEFULINDEX = GETSTATEFULNODEINDEX(NAME)
244 name = node.getName();
246 statefulIndex = find(cellfun(@(c) strcmp(c,name),self.getStatefulNodeNames));
249 function classIndex = getClassIndex(self, name)
250 % CLASSINDEX = GETCLASSINDEX(NAME)
251 if isa(name,'JobClass')
255 classIndex = find(cellfun(@(c) strcmp(c,name),self.getClassNames));
258 function stationnames = getStationNames(self)
259 % STATIONNAMES = GETSTATIONNAMES()
262 nodenames = self.sn.nodenames;
263 isstation = self.sn.isstation;
264 stationnames = {nodenames{isstation}}
';
267 for i=self.getStationIndexes
268 stationnames{end+1,1} = self.nodes{i}.name;
273 function nodes = getNodeByName(self, name)
274 % NODES = GETNODEBYNAME(SELF, NAME)
275 idx = findstring(self.getNodeNames,name);
277 nodes = self.nodes{idx};
283 function station = getStationByName(self, name)
284 % STATION = GETSTATIONBYNAME(SELF, NAME)
285 idx = findstring(self.getStationNames,name);
287 station = self.stations{idx};
293 function class = getClassByName(self, name)
294 % CLASS = GETCLASSBYNAME(SELF, NAME)
295 idx = findstring(self.getClassNames,name);
297 class = self.classes{idx};
303 function nodes = getNodeByIndex(self, idx)
304 % NODES = GETNODEBYINDEX(SELF, NAME)
306 nodes = self.nodes{idx};
312 function station = getStationByIndex(self, idx)
313 % STATION = GETSTATIONBYINDEX(SELF, NAME)
315 station = self.stations{idx};
321 function class = getClassByIndex(self, idx)
322 % CLASS = GETCLASSBYINDEX(SELF, NAME)
324 class = self.classes{idx};
330 function [infGen, eventFilt, ev] = getGenerator(self, varargin)
331 line_warning(mfilename,'Results will not be cached. Use SolverCTMC(model,...).getGenerator(...) instead.\n
');
332 [infGen, eventFilt, ev] = SolverCTMC(self).getGenerator(varargin{:});
335 function [stateSpace,nodeStateSpace] = getStateSpace(self, varargin)
336 line_warning(mfilename,'Results will not be cached. Use SolverCTMC(model,...).getStateSpace(...) instead.\n
');
337 [stateSpace,nodeStateSpace] = SolverCTMC(self).getStateSpace(varargin{:});
340 function [stateSpace,nodeStateSpace] = stateSpace(self, varargin)
341 % [STATESPACE,NODESTATESPACE] = STATESPACE(SELF, VARARGIN)
342 % Alias for getStateSpace
343 [stateSpace,nodeStateSpace] = self.getStateSpace(varargin{:});
346 function summary(self)
348 for i=1:self.getNumberOfClasses
349 self.classes{i}.summary();
350 if i<self.getNumberOfClasses
354 for i=1:self.getNumberOfNodes
355 self.nodes{i}.summary();
357 line_printf('\n<strong>Routing matrix</strong>:
');
358 self.printRoutingMatrix
359 line_printf('\n<strong>Product-form parameters</strong>:
');
360 [arvRates,servDemands,nJobs,thinkTimes,ldScalings,nServers]= getProductFormParameters(self);
361 line_printf('\nArrival rates: %s
',mat2str(arvRates,6));
362 line_printf('\nService demands: %s
',mat2str(servDemands,6));
363 line_printf('\nNumber of jobs: %s
',mat2str(nJobs));
364 line_printf('\nThink times: %s
',mat2str(thinkTimes,6));
365 line_printf('\nLoad-dependent scalings: %s
',mat2str(ldScalings,6));
366 line_printf('\nNumber of servers: %s\n
',mat2str(nServers));
367 line_printf('\n<strong>Chains</strong>:
');
370 function [D,Z] = getDemands(self)
371 % [D,Z]= GETDEMANDS()
377 [~,D,~,Z,~,~] = sn_get_product_form_params(self.getStruct);
380 function [lambda,D,N,Z,mu,S]= getProductFormParameters(self)
381 % [LAMBDA,D,N,Z,MU,S]= GETPRODUCTFORMPARAMETERS()
384 % LAMBDA: arrival rates for open classes
386 % N: population vector for closed classes
388 % mu: load-dependent rates
389 % S: number of servers
391 % mu also returns max(S) elements after population |N| as this is
392 % required by MVALDMX
394 [lambda,D,N,Z,mu,S] = sn_get_product_form_params(self.getStruct);
397 function [lambda,D,N,Z,mu,S]= getProductFormChainParameters(self)
398 % [LAMBDA,D,N,Z,MU,S]= GETPRODUCTFORMCHAINPARAMETERS()
401 % LAMBDA: arrival rates for open classes
403 % N: population vector for closed classes
405 % mu: load-dependent rates
406 % S: number of servers
408 % mu also returns max(S) elements after population |N| as this is
409 % required by MVALDMX
411 [lambda,D,N,Z,mu,S]= sn_get_product_form_chain_params(self.getStruct);
414 function statefulnodes = getStatefulNodes(self)
416 for i=1:self.getNumberOfNodes
417 if self.nodes{i}.isStateful
418 statefulnodes{end+1,1} = self.nodes{i};
423 function statefulnames = getStatefulNodeNames(self)
424 % STATEFULNAMES = GETSTATEFULNODENAMES()
427 for i=1:self.getNumberOfNodes
428 if self.nodes{i}.isStateful
429 statefulnames{end+1,1} = self.nodes{i}.name;
434 function M = getNumberOfNodes(self)
435 % M = GETNUMBEROFNODES()
437 M = length(self.nodes);
440 function S = getNumberOfStatefulNodes(self)
441 % S = GETNUMBEROFSTATEFULNODES()
443 S = sum(cellisa(self.nodes,'StatefulNode
'));
446 function M = getNumberOfStations(self)
447 % M = GETNUMBEROFSTATIONS()
449 M = length(self.stations);
452 function R = getNumberOfClasses(self)
453 % R = GETNUMBEROFCLASSES()
455 R = length(self.classes);
458 function C = getNumberOfChains(self)
459 % C = GETNUMBEROFCHAINS()
465 function Dchain = getDemandsChain(self)
466 % DCHAIN = GETDEMANDSCHAIN()
467 sn_get_demands_chain(self.getStruct);
470 function self = setUsedLangFeature(self,className)
471 % SELF = SETUSEDLANGFEATURE(SELF,CLASSNAME)
473 % Normalize marked-process class names to the registered MMAP
474 % feature; dynamic names absent from SolverFeatureSet.fields are
475 % silently ignored by SolverFeatureSet.supports().
476 if strcmp(className,'MarkedMAP
') || strcmp(className,'MarkedMMPP
')
479 self.usedFeatures.setTrue(className);
482 %% Add the components to the model
483 addJobClass(self, customerClass);
484 bool = addNode(self, node);
485 fcr = addRegion(self, nodes);
486 addLink(self, nodeA, nodeB);
487 addLinks(self, nodeList);
488 addItemSet(self, itemSet);
490 node = getSource(self);
491 node = getSink(self);
493 function list = getStationIndexes(self)
494 % LIST = GETSTATIONINDEXES()
497 list = find(self.sn.isstation)';
499 % returns
the ids of
nodes that are stations
500 list = find(cellisa(self.nodes,
'Station'))
';
504 function list = getIndexStatefulNodes(self)
505 % LIST = GETINDEXSTATEFULNODES()
507 % returns the ids of nodes that are stations
508 list = find(cellisa(self.nodes, 'StatefulNode
'))';
510 % FJ tag-augmented copies treat Fork
nodes as stateful
511 list =
union(list, find(cellisa(self.nodes,
'Fork'))
');
515 index = getIndexSourceStation(self);
516 index = getIndexSourceNode(self);
517 index = getIndexSinkNode(self);
519 N = getNumberOfJobs(self);
520 refstat = getReferenceStations(self);
521 refclass = getReferenceClasses(self);
522 sched = getStationScheduling(self);
523 S = getStationServers(self);
524 S = getStatefulServers(self);
529 % VIEW() Open the model in JSIMgraph
533 function modelView(self)
534 % MODELVIEW() Open the model in ModelVisualizer
535 jnetwork = JLINE.line_to_jline(self);
539 function islld = isLimitedLoadDependent(self)
540 islld = isempty(self.getStruct().lldscaling);
543 function [isvalid] = isStateValid(self)
544 % [ISVALID] = ISSTATEVALID()
546 isvalid = sn_is_state_valid(self.getStruct);
549 [state, priorStateSpace, stateSpace] = getState(self) % get initial state
551 initFromAvgTableQLen(self, AvgTable)
552 initFromAvgQLen(self, AvgQLen)
553 initDefault(self, nodes)
555 initFromMarginal(self, n, options) % n(i,r) : number of jobs of class r in node or station i (autodetected)
556 initFromMarginalAndRunning(self, n, s, options) % n(i,r) : number of jobs of class r in node or station i (autodetected)
557 initFromMarginalAndStarted(self, n, s, options) % n(i,r) : number of jobs of class r in node or station i (autodetected)
559 [H,G] = getGraph(self)
561 function mask = getClassSwitchingMask(self)
562 % MASK = GETCLASSSWITCHINGMASK()
564 mask = self.getStruct.csmask;
567 function printRoutingMatrix(self, onlyclass)
568 % PRINTROUTINGMATRIX()
570 sn_print_routing_matrix(self.getStruct);
572 sn_print_routing_matrix(self.getStruct, onlyclass);
578 methods (Access = protected)
580 function out = getModelNameExtension(self)
581 % OUT = GETMODELNAMEEXTENSION()
583 out = [getModelName(self), ['.
', self.fileFormat]];
586 function self = initUsedFeatures(self)
587 % SELF = INITUSEDFEATURES()
589 % The list includes all classes but Model and Hidden or
590 % Constant or Abstract or Solvers
591 self.usedFeatures = SolverFeatureSet;
595 methods(Access = protected)
596 % Override copyElement method:
597 function clone = copyElement(self)
598 % CLONE = COPYELEMENT()
600 % Make a shallow copy of all properties
601 clone = copyElement@Copyable(self);
602 % Make a deep copy of each handle
603 for i=1:length(self.classes)
604 clone.classes{i} = self.classes{i}.copy;
606 % Make a deep copy of each handle
607 for i=1:length(self.nodes)
608 clone.nodes{i} = self.nodes{i}.copy;
609 if isa(clone.nodes{i},'Station
')
610 clone.stations{i} = clone.nodes{i};
612 clone.connections = self.connections;
618 function bool = hasFCFS(self)
621 bool = sn_has_fcfs(self.getStruct);
624 function bool = hasHomogeneousScheduling(self, strategy)
625 % BOOL = HASHOMOGENEOUSSCHEDULING(STRATEGY)
627 bool = sn_has_homogeneous_scheduling(self.getStruct, strategy);
630 function bool = hasDPS(self)
633 bool = sn_has_dps(self.getStruct);
636 function bool = hasGPS(self)
639 bool = sn_has_gps(self.getStruct);
642 function bool = hasINF(self)
645 bool = sn_has_inf(self.getStruct);
648 function bool = hasPS(self)
651 bool = sn_has_ps(self.getStruct);
654 function bool = hasSIRO(self)
657 bool = sn_has_siro(self.getStruct);
660 function bool = hasHOL(self)
663 bool = sn_has_hol(self.getStruct);
666 function bool = hasLCFS(self)
669 bool = sn_has_lcfs(self.getStruct);
672 function bool = hasLCFSPR(self)
675 bool = sn_has_lcfs_pr(self.getStruct);
678 function bool = hasSEPT(self)
681 bool = sn_has_sept(self.getStruct);
684 function bool = hasLEPT(self)
687 bool = sn_has_lept(self.getStruct);
690 function bool = hasSJF(self)
693 bool = sn_has_sjf(self.getStruct);
696 function bool = hasLJF(self)
699 bool = sn_has_ljf(self.getStruct);
702 function bool = hasMultiClassFCFS(self)
703 % BOOL = HASMULTICLASSFCFS()
705 bool = sn_has_multi_class_fcfs(self.getStruct);
708 function bool = hasMultiClassHeterFCFS(self)
709 % BOOL = HASMULTICLASSFCFS()
711 bool = sn_has_multi_class_heter_fcfs(self.getStruct);
714 function bool = hasMultiServer(self)
715 % BOOL = HASMULTISERVER()
717 bool = sn_has_multi_server(self.getStruct);
720 function bool = hasSingleChain(self)
721 % BOOL = HASSINGLECHAIN()
723 bool = sn_has_single_chain(self.getStruct);
726 function bool = hasMultiChain(self)
727 % BOOL = HASMULTICHAIN()
729 bool = sn_has_multi_chain(self.getStruct);
732 function bool = hasSingleClass(self)
733 % BOOL = HASSINGLECLASS()
735 bool = sn_has_single_class(self.getStruct);
738 function bool = hasMultiClass(self)
739 % BOOL = HASMULTICLASS()
741 bool = sn_has_multi_class(self.getStruct);
746 function bool = hasProductFormSolution(self)
747 % BOOL = HASPRODUCTFORMSOLUTION()
748 bool = sn_has_product_form(self.getStruct);
753 [~,H] = self.getGraph;
754 H.Nodes.Name=strrep(H.Nodes.Name,'_
','\_
');
755 h=plot(H,'EdgeLabel
',H.Edges.Weight,'Layout
','Layered
');
756 highlight(h,self.getNodeTypes==3,'NodeColor
','r
'); % class-switch nodes
759 function varargout = getMarkedCTMC( varargin )
760 [varargout{1:nargout}] = getCTMC( varargin{:} );
763 function mctmc = getCTMC(self, par1, par2)
765 options = SolverCTMC.defaultOptions;
767 options = SolverCTMC.defaultOptions;
768 options.(par1) = par2;
769 options.cache = false;
770 elseif isstruct(par1)
773 solver = SolverCTMC(self,options);
774 [infGen, eventFilt, ev] = solver.getInfGen();
775 mctmc = MarkedMarkovProcess(infGen, eventFilt, ev, true);
776 mctmc.setStateSpace(solver.getStateSpace);
782 function model = tandemPs(lambda,D)
783 % MODEL = TANDEMPS(LAMBDA,D)
785 model = Network.tandemPsInf(lambda,D,[]);
788 function model = tandemPsInf(lambda,D,Z)
789 % MODEL = TANDEMPSINF(LAMBDA,D,Z)
791 if nargin<3%~exist('Z
','var
')
798 strategy{i} = SchedStrategy.INF;
801 strategy{Mz+i} = SchedStrategy.PS;
803 model = Network.tandem(lambda,[Z;D],strategy);
806 function model = tandemFcfs(lambda,D)
807 % MODEL = TANDEMFCFS(LAMBDA,D)
809 model = Network.tandemFcfsInf(lambda,D,[]);
812 function model = tandemFcfsInf(lambda,D,Z)
813 % MODEL = TANDEMFCFSINF(LAMBDA,D,Z)
815 if nargin<3%~exist('Z
','var
')
822 strategy{i} = SchedStrategy.INF;
825 strategy{Mz+i} = SchedStrategy.FCFS;
827 model = Network.tandem(lambda,[Z;D],strategy);
830 function model = tandem(lambda,D,strategy)
831 % MODEL = TANDEM(LAMBDA,S,STRATEGY)
833 % D(i,r) - mean service demand of class r at station i, equal
834 % to mean service time since the topology is linear
835 % lambda(r) - number of jobs of class r
836 % station(i) - scheduling strategy at station i
837 model = Network('Model
');
839 node{1} = Source(model, 'Source
');
841 switch SchedStrategy.toId(strategy{i})
842 case SchedStrategy.INF
843 node{end+1} = Delay(model, ['Station',num2str(i)]);
845 node{end+1} = Queue(model, ['Station',num2str(i)], strategy{i});
848 node{end+1} = Sink(model, 'Sink
');
849 P = cellzeros(R,R,M+2,M+2);
851 jobclass{r} = OpenClass(model, ['Class
',num2str(r)], 0);
852 P{r,r} = circul(length(node)); P{r}(end,:) = 0;
855 node{1}.setArrival(jobclass{r}, Exp.fitMean(1/lambda(r)));
857 node{1+i}.setService(jobclass{r}, Exp.fitMean(D(i,r)));
863 function model = cyclicPs(N,D)
864 % MODEL = CYCLICPS(N,D)
869 model = Network.cyclicPsInf(N,D,[],S);
872 function model = cyclicPsInf(N,D,Z,S)
873 % MODEL = CYCLICPSINF(N,D,Z,S)
882 strategy = cell(M+Mz,1);
884 strategy{i} = SchedStrategy.INF;
887 strategy{Mz+i} = SchedStrategy.PS;
889 model = Network.cyclic(N,[Z;D],strategy,[Inf*ones(size(Z,1),1);S]);
892 function model = cyclicFcfs(N,D,S)
893 % MODEL = CYCLICFCFS(N,D,S)
898 model = Network.cyclicFcfsInf(N,D,[],S);
901 function model = cyclicFcfsInf(N,D,Z,S)
902 % MODEL = CYCLICFCFSINF(N,D,Z,S)
904 if nargin<3%~exist('Z
','var
')
915 strategy{i} = SchedStrategy.INF;
918 strategy{Mz+i} = SchedStrategy.FCFS;
920 model = Network.cyclic(N,[Z;D],strategy,[Inf*ones(size(Z,1),1);S]);
923 function model = cyclic(N,D,strategy,S)
924 % MODEL = CYCLIC(N,D,STRATEGY,S)
926 % L(i,r) - demand of class r at station i
927 % N(r) - number of jobs of class r
928 % strategy(i) - scheduling strategy at station i
929 % S(i) - number of servers at station i
930 model = Network('Model
');
935 switch SchedStrategy.toId(strategy{ist})
936 case SchedStrategy.INF
938 node{ist} = Delay(model, ['Delay
',num2str(nD)]);
941 node{ist} = Queue(model, ['Queue
',num2str(nQ)], strategy{ist});
942 node{ist}.setNumberOfServers(S(ist));
946 jobclass = cell(R,1);
948 jobclass{r} = ClosedClass(model, ['Class
',num2str(r)], N(r), node{1}, 0);
953 node{ist}.setService(jobclass{r}, Exp.fitMean(D(ist,r)));
959 function model = cluster(lambda, D, strategy, S, dispatching)
960 % MODEL = SERVERFARM(LAMBDA, D, STRATEGY, S, DISPATCHING)
962 % Open cluster: Source -> Dispatcher -> Server[1..M] -> Sink.
964 % lambda(r) - arrival rate of class r
965 % D(i,r) - mean service time of class r at server i
966 % strategy{i} - scheduling strategy at server i
967 % S(i) - number of identical servers at queue i (default: 1)
968 % dispatching - RoutingStrategy applied at the router (default: RAND)
969 if nargin < 4 || isempty(S), S = ones(size(D,1),1); end
970 if nargin < 5, dispatching = RoutingStrategy.RAND; end
972 model = Network('Cluster
');
975 source = Source(model, 'Source
');
976 dispatcher = Router(model, 'Dispatcher
');
979 servers{i} = Queue(model, ['Station', num2str(i)], strategy{i});
981 servers{i}.setNumberOfServers(S(i));
984 sink = Sink(model, 'Sink
');
986 jobclass = cell(R,1);
988 jobclass{r} = OpenClass(model, ['Class
', num2str(r)], 0);
989 source.setArrival(jobclass{r}, Exp.fitMean(1/lambda(r)));
991 servers{i}.setService(jobclass{r}, Exp.fitMean(D(i,r)));
995 model.addLink(source, dispatcher);
997 model.addLink(dispatcher, servers{i});
998 model.addLink(servers{i}, sink);
1001 dispatcher.setRouting(jobclass{r}, dispatching);
1005 function model = clusterClosed(N, Z, D, strategy, S, dispatching)
1006 % MODEL = SERVERFARMCLOSED(N, Z, D, STRATEGY, S, DISPATCHING)
1008 % Closed cluster: Think -> Dispatcher -> Server[1..M] -> Think.
1010 % N(r) - population of class r
1011 % Z(r) - per-class think time at the delay
1012 % D(i,r) - mean service time of class r at server i
1013 % strategy{i} - scheduling strategy at server i
1014 % S(i) - number of identical servers at queue i (default: 1)
1015 % dispatching - RoutingStrategy applied at the router (default: RAND)
1016 if nargin < 5 || isempty(S), S = ones(size(D,1),1); end
1017 if nargin < 6, dispatching = RoutingStrategy.RAND; end
1019 model = Network('Cluster
');
1022 think = Delay(model, 'Think
');
1023 dispatcher = Router(model, 'Dispatcher
');
1024 servers = cell(M,1);
1026 servers{i} = Queue(model, ['Station', num2str(i)], strategy{i});
1028 servers{i}.setNumberOfServers(S(i));
1032 jobclass = cell(R,1);
1034 jobclass{r} = ClosedClass(model, ['Class
', num2str(r)], N(r), think, 0);
1035 think.setService(jobclass{r}, Exp.fitMean(Z(r)));
1037 servers{i}.setService(jobclass{r}, Exp.fitMean(D(i,r)));
1041 model.addLink(think, dispatcher);
1043 model.addLink(dispatcher, servers{i});
1044 model.addLink(servers{i}, think);
1047 dispatcher.setRouting(jobclass{r}, dispatching);
1051 function P = serialRouting(varargin)
1052 % P = SERIALROUTING(VARARGIN)
1054 if length(varargin)==1
1055 varargin = varargin{1};
1057 model = varargin{1}.model;
1058 P = zeros(model.getNumberOfNodes);
1059 for i=1:length(varargin)-1
1060 P(varargin{i},varargin{i+1})=1;
1062 % Auto-close the cycle (last -> first) unless the last node is a Sink
1063 % or the caller already closed the loop by repeating the first node as
1064 % the last (e.g. serialRouting(d,q,d)), which would otherwise add a
1065 % spurious self-loop. Mirrors the Python serial_routing guard.
1066 if ~isa(varargin{end},'Sink
') && varargin{end} ~= varargin{1}
1067 P(varargin{end},varargin{1})=1;
1069 P = P ./ repmat(sum(P,2),1,length(P));
1073 function printInfGen(Q,SS)
1075 SolverCTMC.printInfGen(Q,SS);
1078 function printEventFilt(sync,D,SS,myevents)
1079 % PRINTEVENTFILT(SYNC,D,SS,MYEVENTS)
1080 SolverCTMC.printEventFilt(sync,D,SS,myevents);