LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
MNetwork.m
1classdef MNetwork < Model
2 % An extended queueing network model.
3 %
4 % Copyright (c) 2012-2026, Imperial College London
5 % All rights reserved.
6
7 properties (Access=private)
8 enableChecks;
9 hasState;
10 logPath;
11 usedFeatures; % structure of booleans listing the used classes
12 % it must be accessed via getUsedLangFeatures that updates
13 % the Distribution classes dynamically
14 end
15
16 properties (Hidden)
17 obj; % empty
18 sn;
19 csMatrix;
20 hasStruct;
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.
28 structVersion = 0;
29 allowReplace;
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)
32 end
33
34 properties (Hidden)
35 handles;
36 sourceidx; % cached value
37 sinkidx; % cached value
38 end
39
40 properties
41 classes;
42 items;
43 stations;
44 nodes;
45 connections;
46 regions;
47 end
48
49 methods % get methods
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
67 end
68
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
72 end
73
74 methods % link, reset, refresh methods
75 self = link(self, P)
76 self = relink(self, P)
77 [loggerBefore,loggerAfter] = linkAndLog(self, nodes, classes, P, wantLogger, logPath)
78 sanitize(self);
79
80 reset(self, resetState)
81 resetHandles(self)
82 resetModel(self, resetState)
83 nodes = resetNetwork(self, deleteCSnodes)
84 resetStruct(self)
85
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);
99 end
100
101 % PUBLIC METHODS
102 methods (Access=public)
103
104 %Constructor
105 function self = MNetwork(modelName, varargin)
106 % SELF = NETWORK(MODELNAME)
107 self@Model(modelName);
108 self.nodes = {};
109 self.stations = {};
110 self.classes = {};
111 self.connections = [];
112 initUsedFeatures(self);
113 self.sn = [];
114 self.hasState = false;
115 self.logPath = '';
116 self.items = {};
117 self.regions = {};
118 self.sourceidx = [];
119 self.sinkidx = [];
120 self.setChecks(true);
121 self.hasStruct = false;
122 self.allowReplace = false;
123 end
124
125 setInitialized(self, bool);
126
127 function self = setChecks(self, bool)
128 self.enableChecks = bool;
129 end
130
131 function bool = getChecks(self)
132 % BOOL = GETCHECKS()
133 % True if model validation is enabled on this model.
134 bool = self.enableChecks;
135 end
136
137 P = getLinkedRoutingMatrix(self)
138
139 function logPath = getLogPath(self)
140 % LOGPATH = GETLOGPATH()
141
142 logPath = self.logPath;
143 end
144
145 function setLogPath(self, logPath)
146 % SETLOGPATH(LOGPATH)
147
148 self.logPath = logPath;
149 end
150
151 bool = hasInitState(self)
152
153 function [M,R] = getSize(self)
154 % [M,R] = GETSIZE()
155
156 M = self.getNumberOfNodes;
157 R = self.getNumberOfClasses;
158 end
159
160 function bool = hasOpenClasses(self)
161 % BOOL = HASOPENCLASSES()
162
163 bool = any(isinf(getNumberOfJobs(self)));
164 end
165
166 function bool = hasClassSwitching(self)
167 % BOOL = HASCLASSSWITCHING()
168
169 bool = any(cellfun(@(c) isa(c,'ClassSwitch'), self.nodes));
170 end
171
172 function bool = hasFork(self)
173 % BOOL = HASFORK()
174
175 bool = any(cellfun(@(c) isa(c,'Fork'), self.nodes));
176 end
177
178 function bool = hasJoin(self)
179 % BOOL = HASJOIN()
180
181 bool = any(cellfun(@(c) isa(c,'Join'), self.nodes));
182 end
183
184 function bool = hasClosedClasses(self)
185 % BOOL = HASCLOSEDCLASSES()
186
187 bool = any(isfinite(getNumberOfJobs(self)));
188 end
189
190 function bool = isMatlabNative(self)
191 % BOOL = ISMATLABNATIVE()
192 %
193 % Returns true for MATLAB native (MNetwork) implementation
194
195 bool = true;
196 end
197
198 function bool = isJavaNative(self)
199 % BOOL = ISJAVANATIVE()
200 %
201 % Returns false for MATLAB native (MNetwork) implementation
202
203 bool = false;
204 end
205
206 function index = getIndexOpenClasses(self)
207 % INDEX = GETINDEXOPENCLASSES()
208
209 index = find(isinf(getNumberOfJobs(self)))';
210 end
211
212 function index = getIndexClosedClasses(self)
213 % INDEX = GETINDEXCLOSEDCLASSES()
214
215 index = find(isfinite(getNumberOfJobs(self)))';
216 end
217
218 function classes = getClasses(self)
219 classes= self.classes;
220 end
221
222 chain = getClassChain(self, className)
223 c = getClassChainIndex(self, className)
224 classNames = getClassNames(self)
225
226 nodeNames = getNodeNames(self)
227 nodeTypes = getNodeTypes(self)
228
229 P = initRoutingMatrix(self)
230
231 ind = getNodeIndex(self, name)
232 lldScaling = getLimitedLoadDependence(self)
233 lcdScaling = getLimitedClassDependence(self)
234 ljdScaling = getLimitedJointDependence(self)
235
236 function stationIndex = getStationIndex(self, name)
237 % STATIONINDEX = GETSTATIONINDEX(NAME)
238
239 if isa(name,'Node')
240 node = name;
241 name = node.getName();
242 end
243 stationIndex = find(cellfun(@(c) strcmp(c,name),self.getStationNames));
244 end
245
246 function statefulIndex = getStatefulNodeIndex(self, name)
247 % STATEFULINDEX = GETSTATEFULNODEINDEX(NAME)
248
249 if isa(name,'Node')
250 node = name;
251 name = node.getName();
252 end
253 statefulIndex = find(cellfun(@(c) strcmp(c,name),self.getStatefulNodeNames));
254 end
255
256 function classIndex = getClassIndex(self, name)
257 % CLASSINDEX = GETCLASSINDEX(NAME)
258 if isa(name,'JobClass')
259 jobclass = name;
260 name = jobclass.getName();
261 end
262 classIndex = find(cellfun(@(c) strcmp(c,name),self.getClassNames));
263 end
264
265 function stationnames = getStationNames(self)
266 % STATIONNAMES = GETSTATIONNAMES()
267
268 if self.hasStruct
269 nodenames = self.sn.nodenames;
270 isstation = self.sn.isstation;
271 stationnames = {nodenames{isstation}}';
272 else
273 stationnames = {};
274 for i=self.getStationIndexes
275 stationnames{end+1,1} = self.nodes{i}.name;
276 end
277 end
278 end
279
280 function nodes = getNodeByName(self, name)
281 % NODES = GETNODEBYNAME(SELF, NAME)
282 idx = findstring(self.getNodeNames,name);
283 if idx > 0
284 nodes = self.nodes{idx};
285 else
286 nodes = NaN;
287 end
288 end
289
290 function station = getStationByName(self, name)
291 % STATION = GETSTATIONBYNAME(SELF, NAME)
292 idx = findstring(self.getStationNames,name);
293 if idx > 0
294 station = self.stations{idx};
295 else
296 station = NaN;
297 end
298 end
299
300 function class = getClassByName(self, name)
301 % CLASS = GETCLASSBYNAME(SELF, NAME)
302 idx = findstring(self.getClassNames,name);
303 if idx > 0
304 class = self.classes{idx};
305 else
306 class = NaN;
307 end
308 end
309
310 function nodes = getNodeByIndex(self, idx)
311 % NODES = GETNODEBYINDEX(SELF, NAME)
312 if idx > 0
313 nodes = self.nodes{idx};
314 else
315 nodes = NaN;
316 end
317 end
318
319 function station = getStationByIndex(self, idx)
320 % STATION = GETSTATIONBYINDEX(SELF, NAME)
321 if idx > 0
322 station = self.stations{idx};
323 else
324 station = NaN;
325 end
326 end
327
328 function class = getClassByIndex(self, idx)
329 % CLASS = GETCLASSBYINDEX(SELF, NAME)
330 if idx > 0
331 class = self.classes{idx};
332 else
333 class = NaN;
334 end
335 end
336
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{:});
340 end
341
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{:});
345 end
346
347 function [stateSpace,nodeStateSpace] = stateSpace(self, varargin)
348 % [STATESPACE,NODESTATESPACE] = STATESPACE(SELF, VARARGIN)
349 % Alias for getStateSpace
350 [stateSpace,nodeStateSpace] = self.getStateSpace(varargin{:});
351 end
352
353 function summary(self)
354 % SUMMARY()
355 for i=1:self.getNumberOfClasses
356 self.classes{i}.summary();
357 if i<self.getNumberOfClasses
358 line_printf('\n');
359 end
360 end
361 for i=1:self.getNumberOfNodes
362 self.nodes{i}.summary();
363 end
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>:');
375 end
376
377 function [D,Z] = getDemands(self)
378 % [D,Z]= GETDEMANDS()
379 %
380 % Outputs:
381 % D: service demands
382 % Z: think times
383
384 [~,D,~,Z,~,~] = sn_get_product_form_params(self.getStruct);
385 end
386
387 function [lambda,D,N,Z,mu,S]= getProductFormParameters(self)
388 % [LAMBDA,D,N,Z,MU,S]= GETPRODUCTFORMPARAMETERS()
389 %
390 % Outputs:
391 % LAMBDA: arrival rates for open classes
392 % D: service demands
393 % N: population vector for closed classes
394 % Z: think times
395 % mu: load-dependent rates
396 % S: number of servers
397
398 % mu also returns max(S) elements after population |N| as this is
399 % required by MVALDMX
400
401 [lambda,D,N,Z,mu,S] = sn_get_product_form_params(self.getStruct);
402 end
403
404 function [lambda,D,N,Z,mu,S]= getProductFormChainParameters(self)
405 % [LAMBDA,D,N,Z,MU,S]= GETPRODUCTFORMCHAINPARAMETERS()
406 %
407 % Outputs:
408 % LAMBDA: arrival rates for open classes
409 % D: service demands
410 % N: population vector for closed classes
411 % Z: think times
412 % mu: load-dependent rates
413 % S: number of servers
414
415 % mu also returns max(S) elements after population |N| as this is
416 % required by MVALDMX
417
418 [lambda,D,N,Z,mu,S]= sn_get_product_form_chain_params(self.getStruct);
419 end
420
421 function statefulnodes = getStatefulNodes(self)
422 statefulnodes = {};
423 for i=1:self.getNumberOfNodes
424 if self.nodes{i}.isStateful
425 statefulnodes{end+1,1} = self.nodes{i};
426 end
427 end
428 end
429
430 function statefulnames = getStatefulNodeNames(self)
431 % STATEFULNAMES = GETSTATEFULNODENAMES()
432
433 statefulnames = {};
434 for i=1:self.getNumberOfNodes
435 if self.nodes{i}.isStateful
436 statefulnames{end+1,1} = self.nodes{i}.name;
437 end
438 end
439 end
440
441 function M = getNumberOfNodes(self)
442 % M = GETNUMBEROFNODES()
443
444 M = length(self.nodes);
445 end
446
447 function S = getNumberOfStatefulNodes(self)
448 % S = GETNUMBEROFSTATEFULNODES()
449
450 S = sum(cellisa(self.nodes,'StatefulNode'));
451 end
452
453 function M = getNumberOfStations(self)
454 % M = GETNUMBEROFSTATIONS()
455
456 M = length(self.stations);
457 end
458
459 function R = getNumberOfClasses(self)
460 % R = GETNUMBEROFCLASSES()
461
462 R = length(self.classes);
463 end
464
465 function C = getNumberOfChains(self)
466 % C = GETNUMBEROFCHAINS()
467
468 sn = self.getStruct;
469 C = sn.nchains;
470 end
471
472 function Dchain = getDemandsChain(self)
473 % DCHAIN = GETDEMANDSCHAIN()
474 sn_get_demands_chain(self.getStruct);
475 end
476
477 function self = setUsedLangFeature(self,className)
478 % SELF = SETUSEDLANGFEATURE(SELF,CLASSNAME)
479
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)
484 return
485 end
486 % see _kb/04-networkstruct.md (MNetwork.m feature-name normalization)
487 if strcmp(className,'MarkedMAP') || strcmp(className,'MarkedMMPP')
488 className = 'MMAP';
489 end
490 self.usedFeatures.setTrue(className);
491 end
492
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);
500
501 node = getSource(self);
502 node = getSink(self);
503
504 function list = getStationIndexes(self)
505 % LIST = GETSTATIONINDEXES()
506
507 if self.hasStruct
508 list = find(self.sn.isstation)';
509 else
510 % returns the ids of nodes that are stations
511 list = find(cellisa(self.nodes, 'Station'))';
512 end
513 end
514
515 function list = getIndexStatefulNodes(self)
516 % LIST = GETINDEXSTATEFULNODES()
517
518 % returns the ids of nodes that are stations
519 list = find(cellisa(self.nodes, 'StatefulNode'))';
520 if self.forkStateful
521 % FJ tag-augmented copies treat Fork nodes as stateful
522 list = union(list, find(cellisa(self.nodes, 'Fork'))');
523 end
524 end
525
526 index = getIndexSourceStation(self);
527 index = getIndexSourceNode(self);
528 index = getIndexSinkNode(self);
529
530 N = getNumberOfJobs(self);
531 refstat = getReferenceStations(self);
532 refclass = getReferenceClasses(self);
533 sched = getStationScheduling(self);
534 S = getStationServers(self);
535 S = getStatefulServers(self);
536
537 jsimwView(self)
538 jsimgView(self)
539 function view(self)
540 % VIEW() Open the model in JSIMgraph
541 jsimgView(self);
542 end
543
544 function modelView(self)
545 % MODELVIEW() Open the model in ModelVisualizer
546 jnetwork = JLINE.line_to_jline(self);
547 jnetwork.plot();
548 end
549
550 function islld = isLimitedLoadDependent(self)
551 islld = isempty(self.getStruct().lldscaling);
552 end
553
554 function [isvalid] = isStateValid(self)
555 % [ISVALID] = ISSTATEVALID()
556
557 isvalid = sn_is_state_valid(self.getStruct);
558 end
559
560 [state, priorStateSpace, stateSpace] = getState(self) % get initial state
561
562 initFromAvgTableQLen(self, AvgTable)
563 initFromAvgQLen(self, AvgQLen)
564 initDefault(self, nodes)
565
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)
569
570 [H,G] = getGraph(self)
571
572 function mask = getClassSwitchingMask(self)
573 % MASK = GETCLASSSWITCHINGMASK()
574
575 mask = self.getStruct.csmask;
576 end
577
578 function printRoutingMatrix(self, onlyclass)
579 % PRINTROUTINGMATRIX()
580 if nargin==1
581 sn_print_routing_matrix(self.getStruct);
582 else
583 sn_print_routing_matrix(self.getStruct, onlyclass);
584 end
585 end
586 end
587
588 % Private methods
589 methods (Access = protected)
590
591 function out = getModelNameExtension(self)
592 % OUT = GETMODELNAMEEXTENSION()
593
594 out = [getModelName(self), ['.', self.fileFormat]];
595 end
596
597 function self = initUsedFeatures(self)
598 % SELF = INITUSEDFEATURES()
599
600 % The list includes all classes but Model and Hidden or
601 % Constant or Abstract or Solvers
602 self.usedFeatures = SolverFeatureSet;
603 end
604 end
605
606 methods(Access = protected)
607 % Override copyElement method:
608 function clone = copyElement(self)
609 % CLONE = COPYELEMENT()
610
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;
616 end
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};
622 end
623 clone.connections = self.connections;
624 end
625 end
626 end
627
628 methods % wrappers
629 function bool = hasFCFS(self)
630 % BOOL = HASFCFS()
631
632 bool = sn_has_fcfs(self.getStruct);
633 end
634
635 function bool = hasHomogeneousScheduling(self, strategy)
636 % BOOL = HASHOMOGENEOUSSCHEDULING(STRATEGY)
637
638 bool = sn_has_homogeneous_scheduling(self.getStruct, strategy);
639 end
640
641 function bool = hasDPS(self)
642 % BOOL = HASDPS()
643
644 bool = sn_has_dps(self.getStruct);
645 end
646
647 function bool = hasGPS(self)
648 % BOOL = HASGPS()
649
650 bool = sn_has_gps(self.getStruct);
651 end
652
653 function bool = hasINF(self)
654 % BOOL = HASINF()
655
656 bool = sn_has_inf(self.getStruct);
657 end
658
659 function bool = hasPS(self)
660 % BOOL = HASPS()
661
662 bool = sn_has_ps(self.getStruct);
663 end
664
665 function bool = hasSIRO(self)
666 % BOOL = HASSIRO()
667
668 bool = sn_has_siro(self.getStruct);
669 end
670
671 function bool = hasHOL(self)
672 % BOOL = HASHOL()
673
674 bool = sn_has_hol(self.getStruct);
675 end
676
677 function bool = hasLCFS(self)
678 % BOOL = HASLCFS()
679
680 bool = sn_has_lcfs(self.getStruct);
681 end
682
683 function bool = hasLCFSPR(self)
684 % BOOL = HASLCFSPR()
685
686 bool = sn_has_lcfs_pr(self.getStruct);
687 end
688
689 function bool = hasSEPT(self)
690 % BOOL = HASSEPT()
691
692 bool = sn_has_sept(self.getStruct);
693 end
694
695 function bool = hasLEPT(self)
696 % BOOL = HASLEPT()
697
698 bool = sn_has_lept(self.getStruct);
699 end
700
701 function bool = hasSJF(self)
702 % BOOL = HASSJF()
703
704 bool = sn_has_sjf(self.getStruct);
705 end
706
707 function bool = hasLJF(self)
708 % BOOL = HASLJF()
709
710 bool = sn_has_ljf(self.getStruct);
711 end
712
713 function bool = hasMultiClassFCFS(self)
714 % BOOL = HASMULTICLASSFCFS()
715
716 bool = sn_has_multi_class_fcfs(self.getStruct);
717 end
718
719 function bool = hasMultiClassHeterFCFS(self)
720 % BOOL = HASMULTICLASSFCFS()
721
722 bool = sn_has_multi_class_heter_fcfs(self.getStruct);
723 end
724
725 function bool = hasMultiServer(self)
726 % BOOL = HASMULTISERVER()
727
728 bool = sn_has_multi_server(self.getStruct);
729 end
730
731 function bool = hasSingleChain(self)
732 % BOOL = HASSINGLECHAIN()
733
734 bool = sn_has_single_chain(self.getStruct);
735 end
736
737 function bool = hasMultiChain(self)
738 % BOOL = HASMULTICHAIN()
739
740 bool = sn_has_multi_chain(self.getStruct);
741 end
742
743 function bool = hasSingleClass(self)
744 % BOOL = HASSINGLECLASS()
745
746 bool = sn_has_single_class(self.getStruct);
747 end
748
749 function bool = hasMultiClass(self)
750 % BOOL = HASMULTICLASS()
751
752 bool = sn_has_multi_class(self.getStruct);
753 end
754 end
755
756 methods
757 function bool = hasProductFormSolution(self)
758 % BOOL = HASPRODUCTFORMSOLUTION()
759 bool = sn_has_product_form(self.getStruct);
760 end
761
762 function plot(self)
763 % PLOT()
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
768 end
769
770 function varargout = getMarkedCTMC( varargin )
771 [varargout{1:nargout}] = getCTMC( varargin{:} );
772 end
773
774 function mctmc = getCTMC(self, par1, par2)
775 if nargin<2
776 options = SolverCTMC.defaultOptions;
777 elseif ischar(par1)
778 options = SolverCTMC.defaultOptions;
779 options.(par1) = par2;
780 options.cache = false;
781 elseif isstruct(par1)
782 options = par1;
783 end
784 solver = SolverCTMC(self,options);
785 [infGen, eventFilt, ev] = solver.getInfGen();
786 mctmc = MarkedMarkovProcess(infGen, eventFilt, ev, true);
787 mctmc.setStateSpace(solver.getStateSpace);
788 end
789 end
790
791 methods (Static)
792
793 function model = tandemPs(lambda,D)
794 % MODEL = TANDEMPS(LAMBDA,D)
795
796 model = Network.tandemPsInf(lambda,D,[]);
797 end
798
799 function model = tandemPsInf(lambda,D,Z)
800 % MODEL = TANDEMPSINF(LAMBDA,D,Z)
801
802 if nargin<3%~exist('Z','var')
803 Z = [];
804 end
805 M = size(D,1);
806 Mz = size(Z,1);
807 strategy = {};
808 for i=1:Mz
809 strategy{i} = SchedStrategy.INF;
810 end
811 for i=1:M
812 strategy{Mz+i} = SchedStrategy.PS;
813 end
814 model = Network.tandem(lambda,[Z;D],strategy);
815 end
816
817 function model = tandemFcfs(lambda,D)
818 % MODEL = TANDEMFCFS(LAMBDA,D)
819
820 model = Network.tandemFcfsInf(lambda,D,[]);
821 end
822
823 function model = tandemFcfsInf(lambda,D,Z)
824 % MODEL = TANDEMFCFSINF(LAMBDA,D,Z)
825
826 if nargin<3%~exist('Z','var')
827 Z = [];
828 end
829 M = size(D,1);
830 Mz = size(Z,1);
831 strategy = {};
832 for i=1:Mz
833 strategy{i} = SchedStrategy.INF;
834 end
835 for i=1:M
836 strategy{Mz+i} = SchedStrategy.FCFS;
837 end
838 model = Network.tandem(lambda,[Z;D],strategy);
839 end
840
841 function model = tandem(lambda,D,strategy)
842 % MODEL = TANDEM(LAMBDA,S,STRATEGY)
843
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');
849 [M,R] = size(D);
850 node{1} = Source(model, 'Source');
851 for i=1:M
852 switch SchedStrategy.toId(strategy{i})
853 case SchedStrategy.INF
854 node{end+1} = Delay(model, ['Station',num2str(i)]);
855 otherwise
856 node{end+1} = Queue(model, ['Station',num2str(i)], strategy{i});
857 end
858 end
859 node{end+1} = Sink(model, 'Sink');
860 P = cellzeros(R,R,M+2,M+2);
861 for r=1:R
862 jobclass{r} = OpenClass(model, ['Class',num2str(r)], 0);
863 P{r,r} = circul(length(node)); P{r}(end,:) = 0;
864 end
865 for r=1:R
866 node{1}.setArrival(jobclass{r}, Exp.fitMean(1/lambda(r)));
867 for i=1:M
868 node{1+i}.setService(jobclass{r}, Exp.fitMean(D(i,r)));
869 end
870 end
871 model.link(P);
872 end
873
874 function model = cyclicPs(N,D)
875 % MODEL = CYCLICPS(N,D)
876 M = size(D,1);
877 if nargin<4
878 S = ones(M,1);
879 end
880 model = Network.cyclicPsInf(N,D,[],S);
881 end
882
883 function model = cyclicPsInf(N,D,Z,S)
884 % MODEL = CYCLICPSINF(N,D,Z,S)
885 if nargin<3
886 Z = [];
887 end
888 M = size(D,1);
889 if nargin<4
890 S = ones(M,1);
891 end
892 Mz = size(Z,1);
893 strategy = cell(M+Mz,1);
894 for i=1:Mz
895 strategy{i} = SchedStrategy.INF;
896 end
897 for i=1:M
898 strategy{Mz+i} = SchedStrategy.PS;
899 end
900 model = Network.cyclic(N,[Z;D],strategy,[Inf*ones(size(Z,1),1);S]);
901 end
902
903 function model = cyclicFcfs(N,D,S)
904 % MODEL = CYCLICFCFS(N,D,S)
905 M = size(D,1);
906 if nargin<4
907 S = ones(M,1);
908 end
909 model = Network.cyclicFcfsInf(N,D,[],S);
910 end
911
912 function model = cyclicFcfsInf(N,D,Z,S)
913 % MODEL = CYCLICFCFSINF(N,D,Z,S)
914
915 if nargin<3%~exist('Z','var')
916 Z = [];
917 end
918 M = size(D,1);
919 if nargin<4
920 S = ones(M,1);
921 end
922
923 Mz = size(Z,1);
924 strategy = {};
925 for i=1:Mz
926 strategy{i} = SchedStrategy.INF;
927 end
928 for i=1:M
929 strategy{Mz+i} = SchedStrategy.FCFS;
930 end
931 model = Network.cyclic(N,[Z;D],strategy,[Inf*ones(size(Z,1),1);S]);
932 end
933
934 function model = cyclic(N,D,strategy,S)
935 % MODEL = CYCLIC(N,D,STRATEGY,S)
936
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');
942 [M,R] = size(D);
943 node = cell(M,1);
944 nQ = 0; nD = 0;
945 for ist=1:M
946 switch SchedStrategy.toId(strategy{ist})
947 case SchedStrategy.INF
948 nD = nD + 1;
949 node{ist} = Delay(model, ['Delay',num2str(nD)]);
950 otherwise
951 nQ = nQ + 1;
952 node{ist} = Queue(model, ['Queue',num2str(nQ)], strategy{ist});
953 node{ist}.setNumberOfServers(S(ist));
954 end
955 end
956 P = cellzeros(R,M);
957 jobclass = cell(R,1);
958 for r=1:R
959 jobclass{r} = ClosedClass(model, ['Class',num2str(r)], N(r), node{1}, 0);
960 P{r,r} = circul(M);
961 end
962 for ist=1:M
963 for r=1:R
964 node{ist}.setService(jobclass{r}, Exp.fitMean(D(ist,r)));
965 end
966 end
967 model.link(P);
968 end
969
970 function model = cluster(lambda, D, strategy, S, dispatching)
971 % MODEL = SERVERFARM(LAMBDA, D, STRATEGY, S, DISPATCHING)
972 %
973 % Open cluster: Source -> Dispatcher -> Server[1..M] -> Sink.
974 %
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
982
983 model = Network('Cluster');
984 [M,R] = size(D);
985
986 source = Source(model, 'Source');
987 dispatcher = Router(model, 'Dispatcher');
988 servers = cell(M,1);
989 for i = 1:M
990 servers{i} = Queue(model, ['Station', num2str(i)], strategy{i});
991 if S(i) > 1
992 servers{i}.setNumberOfServers(S(i));
993 end
994 end
995 sink = Sink(model, 'Sink');
996
997 jobclass = cell(R,1);
998 for r = 1:R
999 jobclass{r} = OpenClass(model, ['Class', num2str(r)], 0);
1000 source.setArrival(jobclass{r}, Exp.fitMean(1/lambda(r)));
1001 for i = 1:M
1002 servers{i}.setService(jobclass{r}, Exp.fitMean(D(i,r)));
1003 end
1004 end
1005
1006 model.addLink(source, dispatcher);
1007 for i = 1:M
1008 model.addLink(dispatcher, servers{i});
1009 model.addLink(servers{i}, sink);
1010 end
1011 for r = 1:R
1012 dispatcher.setRouting(jobclass{r}, dispatching);
1013 end
1014 end
1015
1016 function model = clusterClosed(N, Z, D, strategy, S, dispatching)
1017 % MODEL = SERVERFARMCLOSED(N, Z, D, STRATEGY, S, DISPATCHING)
1018 %
1019 % Closed cluster: Think -> Dispatcher -> Server[1..M] -> Think.
1020 %
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
1029
1030 model = Network('Cluster');
1031 [M,R] = size(D);
1032
1033 think = Delay(model, 'Think');
1034 dispatcher = Router(model, 'Dispatcher');
1035 servers = cell(M,1);
1036 for i = 1:M
1037 servers{i} = Queue(model, ['Station', num2str(i)], strategy{i});
1038 if S(i) > 1
1039 servers{i}.setNumberOfServers(S(i));
1040 end
1041 end
1042
1043 jobclass = cell(R,1);
1044 for r = 1:R
1045 jobclass{r} = ClosedClass(model, ['Class', num2str(r)], N(r), think, 0);
1046 think.setService(jobclass{r}, Exp.fitMean(Z(r)));
1047 for i = 1:M
1048 servers{i}.setService(jobclass{r}, Exp.fitMean(D(i,r)));
1049 end
1050 end
1051
1052 model.addLink(think, dispatcher);
1053 for i = 1:M
1054 model.addLink(dispatcher, servers{i});
1055 model.addLink(servers{i}, think);
1056 end
1057 for r = 1:R
1058 dispatcher.setRouting(jobclass{r}, dispatching);
1059 end
1060 end
1061
1062 function P = serialRouting(varargin)
1063 % P = SERIALROUTING(VARARGIN)
1064
1065 if length(varargin)==1
1066 varargin = varargin{1};
1067 end
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;
1072 end
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;
1079 end
1080 P = P ./ repmat(sum(P,2),1,length(P));
1081 P(isnan(P)) = 0;
1082 end
1083
1084 function printInfGen(Q,SS)
1085 % PRINTINFGEN(Q,SS)
1086 SolverCTMC.printInfGen(Q,SS);
1087 end
1088
1089 function printEventFilt(sync,D,SS,myevents)
1090 % PRINTEVENTFILT(SYNC,D,SS,MYEVENTS)
1091 SolverCTMC.printEventFilt(sync,D,SS,myevents);
1092 end
1093
1094 end
1095end
Definition fjtag.m:161