LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
refreshStruct.m
1function refreshStruct(self, hardRefresh)
2% REFRESHSTRUCT()
3%
4% Copyright (c) 2012-2026, Imperial College London
5% All rights reserved.
6
7sanitize(self);
8resolveSignals(self); % Resolve Signal placeholders to OpenSignal or ClosedSignal
9if nargin<2
10 hardRefresh = true;
11end
12
13%% store invariant information
14if self.hasStruct && ~hardRefresh
15 rtorig = self.sn.rtorig; % this must be destroyed with resetNetwork
16end
17
18if self.hasStruct && ~hardRefresh
19 nodetypes = sn.nodetypes;
20 classnames = sn.classnames;
21 nodenames = sn.nodenames;
22 refstat = sn.refstat;
23else
24 nodetypes = getNodeTypes(self);
25 classnames = getClassNames(self);
26 nodenames = getNodeNames(self);
27 refstat = getReferenceStations(self);
28
29 % Append FCR names and types to node lists (only when refreshing)
30 for f = 1:length(self.regions)
31 fcr = self.regions{f};
32 nodenames{end+1} = fcr.getName();
33 nodetypes(end+1) = NodeType.Region;
34 end
35end
36conn = self.getConnectionMatrix;
37njobs = getNumberOfJobs(self);
38numservers = getStationServers(self);
39lldscaling = getLimitedLoadDependence(self);
40cdscaling = getLimitedClassDependence(self);
41cdscalingpeak = getLimitedClassDependencePeak(self);
42jdscaling = getLimitedJointDependence(self);
43jdscalingpeak = getLimitedJointDependencePeak(self);
44
45%% init minimal structure
46sn = NetworkStruct(); % create in self to ensure propagation
47sn.isfjaugmented = self.isFJAugmented; % FJ tag-augmented copy marker (see ModelAdapter.fjtag)
48if isempty(self.sn)
49 sn.rtorig = {};
50 sn.reward = {};
51else
52 sn.rtorig = self.sn.rtorig;
53 if isfield(self.sn, 'reward')
54 sn.reward = self.sn.reward; % preserve reward definitions
55 else
56 sn.reward = {};
57 end
58end
59% sn.nnodes counts physical nodes only; FCRs are virtual nodes appended to nodenames/nodetypes
60sn.nnodes = numel(self.nodes);
61sn.nclasses = length(classnames);
62
63%% get routing strategies
64routing = zeros(sn.nnodes, sn.nclasses);
65for ind=1:sn.nnodes
66 for r=1:sn.nclasses
67 if isempty(self.nodes{ind}.output.outputStrategy{r})
68 routing(ind,r) = RoutingStrategy.DISABLED;
69 else
70 routing(ind,r) = RoutingStrategy.fromText(self.nodes{ind}.output.outputStrategy{r}{2});
71 end
72 end
73end
74sn.isslc = false(sn.nclasses,1);
75for r=1:sn.nclasses
76 if isa(self.classes{r},'SelfLoopingClass')
77 sn.isslc(r) = true;
78 end
79end
80sn.issignal = false(sn.nclasses,1);
81sn.signaltype = cell(sn.nclasses,1);
82for r=1:sn.nclasses
83 sn.signaltype{r} = NaN;
84end
85% Detect Signal/OpenSignal/ClosedSignal classes; signaltarget semantics -- see _kb/04-networkstruct.md
86sn.signaltarget = -ones(sn.nclasses, 1);
87for r=1:sn.nclasses
88 if isa(self.classes{r},'Signal') || isa(self.classes{r},'OpenSignal') || isa(self.classes{r},'ClosedSignal')
89 sn.issignal(r) = true;
90 sn.signaltype{r} = self.classes{r}.signalType;
91 if ismethod(self.classes{r}, 'getTargetJobClassIndex')
92 ti = self.classes{r}.getTargetJobClassIndex();
93 if ~isempty(ti) && ti >= 1
94 sn.signaltarget(r) = ti;
95 end
96 end
97 end
98end
99% Initialize syncreply - maps each class to its expected reply signal class index (-1 if none)
100sn.syncreply = -ones(sn.nclasses, 1);
101for r=1:sn.nclasses
102 if ~isempty(self.classes{r}.replySignalClass)
103 sn.syncreply(r) = self.classes{r}.replySignalClass.index - 1; % 0-based for JAR
104 end
105end
106% Initialize classspawn - class injected at the same station on each completion (-1 if none)
107sn.classspawn = -ones(sn.nclasses, 1);
108for r=1:sn.nclasses
109 if isprop(self.classes{r}, 'spawnClass') && ~isempty(self.classes{r}.spawnClass)
110 sn.classspawn(r) = self.classes{r}.spawnClass.index - 1; % 0-based for JAR
111 end
112end
113% Initialize signal removal configuration fields
114sn.signalremdist = cell(sn.nclasses,1);
115sn.signalrempolicy = zeros(sn.nclasses, 1);
116sn.iscatastrophe = false(sn.nclasses, 1);
117% iscatastrophe derived from signaltype for every signal kind, not isCatastrophe() gated to open kinds -- see _kb/04-networkstruct.md
118for r=1:sn.nclasses
119 if isa(self.classes{r}, 'Signal') || isa(self.classes{r}, 'OpenSignal') ...
120 || isa(self.classes{r}, 'ClosedSignal')
121 if self.classes{r}.isCatastrophe()
122 sn.iscatastrophe(r) = true;
123 end
124 sn.signalremdist{r} = self.classes{r}.removalDistribution;
125 sn.signalrempolicy(r) = self.classes{r}.removalPolicy;
126 end
127end
128sn.nclosedjobs = sum(njobs(isfinite(njobs)));
129sn.nservers = numservers;
130sn.isstation = (nodetypes == NodeType.Source | nodetypes == NodeType.Delay | nodetypes == NodeType.Queue | nodetypes == NodeType.Join | nodetypes == NodeType.Place);
131sn.nstations = sum(sn.isstation);
132sn.scv = ones(sn.nstations,sn.nclasses);
133sn.njobs = njobs(:)';
134sn.refstat = refstat;
135sn.space = cell(sn.nstations,1);
136sn.routing = routing;
137sn.chains = [];
138sn.lst = {};
139sn.lldscaling = lldscaling;
140sn.cdscaling = cdscaling;
141sn.cdscalingpeak = cdscalingpeak;
142sn.jdscaling = jdscaling;
143sn.jdscalingpeak = jdscalingpeak;
144sn.nodetype = nodetypes;
145sn.nstations = sum(sn.isstation);
146sn.isstateful = (nodetypes == NodeType.Source | nodetypes == NodeType.Delay | nodetypes == NodeType.Queue | nodetypes == NodeType.Cache | nodetypes == NodeType.Join | nodetypes == NodeType.Router | nodetypes == NodeType.Place | nodetypes == NodeType.Transition | (nodetypes == NodeType.Fork & self.forkStateful));
147sn.isstatedep = false(sn.nnodes,3); % col 1: buffer, col 2: srv, col 3: routing
148sn.isfunction = [];
149for ind = 1:sn.nstations
150 if isa(self.stations{ind},'Queue')
151 sn.isfunction(ind) = ~isempty(self.stations{ind}.setupTime);
152 end
153end
154
155for ind=1:sn.nnodes
156 switch sn.nodetype(ind)
157 case NodeType.Cache
158 sn.isstatedep(ind,2) = true; % state dependent service
159 % case NodeType.Place
160 % self.nodes{ind}.init();
161 % case NodeType.Transition
162 % self.nodes{ind}.init(); % this erases enablingConditions
163 end
164
165 for r=1:sn.nclasses
166 switch sn.routing(ind,r)
167 case {RoutingStrategy.RROBIN, RoutingStrategy.WRROBIN, RoutingStrategy.JSQ, RoutingStrategy.RL, RoutingStrategy.SQ}
168 sn.isstatedep(ind,3) = true; % state dependent routing
169 end
170 end
171end
172
173sn.nstateful = sum(sn.isstateful);
174sn.state = cell(sn.nstations,1);
175for i=1:sn.nstateful
176 sn.state{i} = [];
177end
178sn.nodenames = nodenames;
179sn.classnames = classnames;
180sn.connmatrix = conn;
181
182sn.nodeToStateful =[];
183sn.nodeToStation =[];
184sn.stationToNode =[];
185sn.stationToStateful =[];
186sn.statefulToNode =[];
187sn.statefulToStation =[];
188for ind=1:sn.nnodes
189 sn.nodeToStateful(ind) = nd2sf(sn,ind);
190 sn.nodeToStation(ind) = nd2st(sn,ind);
191end
192for ist=1:sn.nstations
193 sn.stationToNode(ist) = st2nd(sn,ist);
194 sn.stationToStateful(ist) = st2sf(sn,ist);
195end
196for isf=1:sn.nstateful
197 sn.statefulToNode(isf) = sf2nd(sn,isf);
198 sn.statefulToStation(isf) = sf2st(sn,isf);
199end
200
201% Populate immediate feedback matrix (station x class)
202sn.immfeed = false(sn.nstations, sn.nclasses);
203for ist=1:sn.nstations
204 nodeIdx = sn.stationToNode(ist);
205 node = self.nodes{nodeIdx};
206 for r=1:sn.nclasses
207 % Check station-level setting (Queue only)
208 stationHas = false;
209 if isa(node, 'Queue') && ~isempty(node.immediateFeedback)
210 if ischar(node.immediateFeedback) && strcmp(node.immediateFeedback, 'all')
211 stationHas = true;
212 elseif iscell(node.immediateFeedback)
213 stationHas = any(cellfun(@(x) x == r, node.immediateFeedback));
214 end
215 end
216 % Check class-level setting
217 classHas = self.classes{r}.immediateFeedback;
218 sn.immfeed(ist, r) = stationHas || classHas;
219 end
220end
221
222sn.fj = self.getForkJoins();
223self.sn = sn;
224refreshPriorities(self);
225if exist('refreshDeadlines', 'file')
226 refreshDeadlines(self);
227else
228 % Inline implementation if method not loaded
229 K = getNumberOfClasses(self);
230 classdeadline = zeros(1,K);
231 for r=1:K
232 classdeadline(r) = self.getClassByIndex(r).deadline;
233 end
234 if ~isempty(self.sn)
235 self.sn.classdeadline = classdeadline;
236 end
237end
238% sn.markidx populated BEFORE refreshProcesses so refreshProcessRepresentations can see the marked groups
239if ~isempty(self.sn)
240 self.sn.markidx = -ones(self.sn.nstations, self.sn.nclasses);
241 for ist = 1:self.sn.nstations
242 node = self.stations{ist};
243 if isa(node, 'Source') && ~isempty(node.markedClasses)
244 for k = 1:numel(node.markedClasses)
245 self.sn.markidx(ist, node.markedClasses(k)) = k;
246 end
247 end
248 end
249end
250
251refreshProcesses(self);
252
253% Export patience/impatience fields for abandonment-aware solvers (MAPMsG, JMT, etc.)
254sn = self.sn;
255sn.patienceProc = cell(sn.nstations, sn.nclasses);
256sn.impatienceClass = zeros(sn.nstations, sn.nclasses); % ImpatienceType (RENEGING, BALKING)
257sn.impatienceType = zeros(sn.nstations, sn.nclasses); % ProcessType (EXP, ERLANG, etc.)
258sn.impatienceMu = zeros(sn.nstations, sn.nclasses); % Rate parameter (1/mean)
259sn.impatiencePhi = zeros(sn.nstations, sn.nclasses); % SCV parameter
260sn.impatiencePhases = zeros(sn.nstations, sn.nclasses);
261sn.impatienceProc = cell(sn.nstations, sn.nclasses);
262sn.impatiencePie = cell(sn.nstations, sn.nclasses);
263for ist = 1:sn.nstations
264 node = self.stations{ist};
265 if isa(node, 'Queue')
266 for r = 1:sn.nclasses
267 patienceDist = node.getPatience(self.classes{r});
268 if ~isempty(patienceDist) && ~isa(patienceDist, 'Disabled')
269 % Convert patience distribution to MAP representation
270 patienceMAP = patienceDist.getProcess();
271 if iscell(patienceMAP) && length(patienceMAP) >= 2
272 sn.patienceProc{ist, r} = patienceMAP;
273 sn.impatienceProc{ist, r} = patienceMAP;
274
275 % Get the ProcessType of the patience distribution
276 if isprop(patienceDist, 'type')
277 sn.impatienceType(ist, r) = patienceDist.type;
278 elseif isa(patienceDist, 'Exp')
279 sn.impatienceType(ist, r) = ProcessType.EXP;
280 elseif isa(patienceDist, 'Erlang')
281 sn.impatienceType(ist, r) = ProcessType.ERLANG;
282 elseif isa(patienceDist, 'HyperExp')
283 sn.impatienceType(ist, r) = ProcessType.HYPEREXP;
284 elseif isa(patienceDist, 'Det')
285 sn.impatienceType(ist, r) = ProcessType.DET;
286 elseif isa(patienceDist, 'Gamma')
287 sn.impatienceType(ist, r) = ProcessType.GAMMA;
288 elseif isa(patienceDist, 'Pareto')
289 sn.impatienceType(ist, r) = ProcessType.PARETO;
290 elseif isa(patienceDist, 'Weibull')
291 sn.impatienceType(ist, r) = ProcessType.WEIBULL;
292 elseif isa(patienceDist, 'Lognormal')
293 sn.impatienceType(ist, r) = ProcessType.LOGNORMAL;
294 elseif isa(patienceDist, 'Uniform')
295 sn.impatienceType(ist, r) = ProcessType.UNIFORM;
296 elseif isa(patienceDist, 'Coxian')
297 sn.impatienceType(ist, r) = ProcessType.COXIAN;
298 elseif isa(patienceDist, 'APH')
299 sn.impatienceType(ist, r) = ProcessType.APH;
300 elseif isa(patienceDist, 'PH')
301 sn.impatienceType(ist, r) = ProcessType.PH;
302 else
303 % Default to PH for other Markovian distributions
304 sn.impatienceType(ist, r) = ProcessType.PH;
305 end
306
307 % Extract distribution parameters for JMT export
308 % Get rate (mu = 1/mean)
309 if ismethod(patienceDist, 'getMean')
310 meanVal = patienceDist.getMean();
311 if meanVal > 0
312 sn.impatienceMu(ist, r) = 1 / meanVal;
313 else
314 sn.impatienceMu(ist, r) = Inf;
315 end
316 else
317 % Estimate from MAP: mean = -pi * D0^(-1) * e
318 D0 = patienceMAP{1};
319 D1 = patienceMAP{2};
320 n = size(D0, 1);
321 pi = ones(1, n) / n; % Approximate stationary distribution
322 meanVal = -pi * (D0 \ ones(n, 1));
323 sn.impatienceMu(ist, r) = 1 / meanVal;
324 end
325
326 % Get SCV (phi)
327 if ismethod(patienceDist, 'getSCV')
328 sn.impatiencePhi(ist, r) = patienceDist.getSCV();
329 else
330 sn.impatiencePhi(ist, r) = 1.0; % Default to exponential SCV
331 end
332
333 % Get number of phases
334 if ismethod(patienceDist, 'getNumberOfPhases')
335 sn.impatiencePhases(ist, r) = patienceDist.getNumberOfPhases();
336 else
337 sn.impatiencePhases(ist, r) = size(patienceMAP{1}, 1);
338 end
339
340 % Get initial probability vector (pie)
341 n = size(patienceMAP{1}, 1);
342 D0 = patienceMAP{1};
343 D1 = patienceMAP{2};
344 % For PH: pie is from the MAP representation D1 = (-D0*e)*pie
345 exitRates = -D0 * ones(n, 1);
346 idx = find(exitRates > 1e-10, 1);
347 if ~isempty(idx)
348 sn.impatiencePie{ist, r} = D1(idx, :) / exitRates(idx);
349 else
350 sn.impatiencePie{ist, r} = ones(1, n) / n;
351 end
352 end
353 % Get impatience class (RENEGING, BALKING)
354 impType = node.getImpatienceType(self.classes{r});
355 if ~isempty(impType)
356 sn.impatienceClass(ist, r) = impType;
357 end
358 end
359 end
360 end
361end
362
363% Initialize varsparam for cache item state tracking (mirrors JAR Network.java:4745-4747)
364sn.varsparam = -ones(sn.nnodes, 1);
365
366% Export balking and retrial fields for sn-driven solvers
367sn.balkingStrategy = zeros(sn.nstations, sn.nclasses);
368sn.balkingThresholds = cell(sn.nstations, sn.nclasses);
369sn.retrialType = zeros(sn.nstations, sn.nclasses);
370sn.retrialMu = zeros(sn.nstations, sn.nclasses);
371sn.retrialPhi = zeros(sn.nstations, sn.nclasses);
372sn.retrialProc = cell(sn.nstations, sn.nclasses);
373sn.retrialMaxAttempts = -ones(sn.nstations, sn.nclasses);
374sn.retrialPolicy = RetrialPolicy.LINEAR * ones(sn.nstations, sn.nclasses);
375sn.orbitMaxJobs = -ones(sn.nstations, sn.nclasses);
376sn.orbitImpatience = cell(sn.nstations, sn.nclasses);
377sn.batchRejectProb = zeros(sn.nstations, sn.nclasses);
378% Breakdown/repair rates are per STATION (server property); degraded service while down is per (station,class)
379sn.hasbreakdown = zeros(sn.nnodes, 1);
380sn.breakdownMu = zeros(sn.nstations, 1);
381sn.repairMu = zeros(sn.nstations, 1);
382sn.breakdownProc = cell(sn.nstations, 1);
383sn.repairProc = cell(sn.nstations, 1);
384sn.downServiceRates = zeros(sn.nstations, sn.nclasses);
385for ist = 1:sn.nstations
386 node = self.stations{ist};
387 if isa(node, 'Queue') && ~isempty(node.breakdownFailure) && ~isempty(node.breakdownRepair)
388 sn.hasbreakdown(sn.stationToNode(ist)) = 1;
389 sn.breakdownMu(ist) = 1 / node.breakdownFailure.getMean();
390 sn.repairMu(ist) = 1 / node.breakdownRepair.getMean();
391 sn.breakdownProc{ist} = node.breakdownFailure.getProcess();
392 sn.repairProc{ist} = node.breakdownRepair.getProcess();
393 for r = 1:sn.nclasses
394 dsvc = [];
395 if numel(node.breakdownDownService) == 1
396 dsvc = node.breakdownDownService{1};
397 elseif numel(node.breakdownDownService) >= r
398 dsvc = node.breakdownDownService{r};
399 end
400 if ~isempty(dsvc) && isa(dsvc, 'Distribution') && ~isa(dsvc, 'Disabled')
401 if ~isa(dsvc, 'Exp')
402 line_error(mfilename, sprintf(['Station ''%s'': the down-server service distribution must be ' ...
403 'exponential; a phase-type degraded service would need its own phase block in the joint chain.'], ...
404 node.getName()));
405 end
406 dmean = dsvc.getMean();
407 if dmean > 0
408 sn.downServiceRates(ist, r) = 1 / dmean;
409 end
410 end
411 end
412 end
413end
414for ist = 1:sn.nstations
415 node = self.stations{ist};
416 if isa(node, 'Queue')
417 for r = 1:sn.nclasses
418 % Balking
419 [bStrat, bThresh] = node.getBalking(self.classes{r});
420 if ~isempty(bStrat)
421 if isnumeric(bStrat)
422 sn.balkingStrategy(ist, r) = bStrat;
423 elseif isa(bStrat, 'BalkingStrategy') && isprop(bStrat, 'id')
424 sn.balkingStrategy(ist, r) = bStrat.id;
425 else
426 sn.balkingStrategy(ist, r) = double(bStrat);
427 end
428 sn.balkingThresholds{ist, r} = bThresh;
429 end
430 % retrialPolicy/orbitMaxJobs default to the classical unbounded per-customer orbit (plain setRetrial)
431 if length(node.retrialPolicies) >= r && node.retrialPolicies(r) > 0
432 sn.retrialPolicy(ist, r) = node.retrialPolicies(r);
433 end
434 if length(node.orbitMaxJobs) >= r && ~isempty(node.orbitMaxJobs(r))
435 sn.orbitMaxJobs(ist, r) = node.orbitMaxJobs(r);
436 end
437 % Retrial
438 [rDist, rMax] = node.getRetrial(self.classes{r});
439 if ~isempty(rDist) && ~isa(rDist, 'Disabled')
440 if isprop(rDist, 'type')
441 sn.retrialType(ist, r) = rDist.type;
442 elseif isa(rDist, 'Exp')
443 sn.retrialType(ist, r) = ProcessType.EXP;
444 elseif isa(rDist, 'Erlang')
445 sn.retrialType(ist, r) = ProcessType.ERLANG;
446 elseif isa(rDist, 'HyperExp')
447 sn.retrialType(ist, r) = ProcessType.HYPEREXP;
448 elseif isa(rDist, 'Det')
449 sn.retrialType(ist, r) = ProcessType.DET;
450 elseif isa(rDist, 'Gamma')
451 sn.retrialType(ist, r) = ProcessType.GAMMA;
452 elseif isa(rDist, 'Pareto')
453 sn.retrialType(ist, r) = ProcessType.PARETO;
454 elseif isa(rDist, 'Weibull')
455 sn.retrialType(ist, r) = ProcessType.WEIBULL;
456 elseif isa(rDist, 'Lognormal')
457 sn.retrialType(ist, r) = ProcessType.LOGNORMAL;
458 elseif isa(rDist, 'Uniform')
459 sn.retrialType(ist, r) = ProcessType.UNIFORM;
460 elseif isa(rDist, 'Coxian')
461 sn.retrialType(ist, r) = ProcessType.COXIAN;
462 elseif isa(rDist, 'APH')
463 sn.retrialType(ist, r) = ProcessType.APH;
464 elseif isa(rDist, 'PH')
465 sn.retrialType(ist, r) = ProcessType.PH;
466 else
467 sn.retrialType(ist, r) = ProcessType.PH;
468 end
469 if ismethod(rDist, 'getMean')
470 meanVal = rDist.getMean();
471 if meanVal > 0
472 sn.retrialMu(ist, r) = 1 / meanVal;
473 else
474 sn.retrialMu(ist, r) = Inf;
475 end
476 end
477 if ismethod(rDist, 'getSCV')
478 sn.retrialPhi(ist, r) = rDist.getSCV();
479 else
480 sn.retrialPhi(ist, r) = 1.0;
481 end
482 if ismethod(rDist, 'getProcess')
483 sn.retrialProc{ist, r} = rDist.getProcess();
484 end
485 sn.retrialMaxAttempts(ist, r) = rMax;
486 end
487 % Orbit impatience (abandonment from the retrial orbit); store {D0,D1}
488 oDist = node.getOrbitImpatience(self.classes{r});
489 if ~isempty(oDist) && ~isa(oDist, 'Disabled') && ismethod(oDist, 'getProcess')
490 sn.orbitImpatience{ist, r} = oDist.getProcess();
491 end
492 % Batch rejection probability (retrial queues: probability that an
493 % arriving batch is rejected in full when it does not fit)
494 sn.batchRejectProb(ist, r) = node.getBatchRejectProbability(self.classes{r});
495 end
496 end
497end
498self.sn = sn;
499
500% Check if priorities are specified but no priority-aware scheduling policy is used
501sn = self.sn;
502if ~all(sn.classprio == sn.classprio(1))
503 % Priority classes exist, check if any station uses priority-aware scheduling
504 prioScheds = [SchedStrategy.PSPRIO, SchedStrategy.DPSPRIO, SchedStrategy.GPSPRIO, ...
505 SchedStrategy.HOL, SchedStrategy.FCFSPRIO, SchedStrategy.LCFSPRIO, ...
506 SchedStrategy.LCFSPRPRIO, SchedStrategy.LCFSPIPRIO, ...
507 SchedStrategy.FCFSPRPRIO, SchedStrategy.FCFSPIPRIO, ...
508 SchedStrategy.LCFS, SchedStrategy.LCFSPR, ...
509 SchedStrategy.FCFSPR];
510 if ~any(ismember(sn.sched, prioScheds))
511 line_warning(mfilename, 'Priority classes are specified but no priority-aware scheduling policy is used in the model. Priorities will be ignored.');
512 else
513 % Display priority info unless silent
514 global LINEVerbose;
515 if isempty(LINEVerbose) || LINEVerbose ~= VerboseLevel.SILENT
516 [minPrio, minIdx] = min(sn.classprio);
517 [maxPrio, maxIdx] = max(sn.classprio);
518 highestPrioClasses = find(sn.classprio == minPrio);
519 lowestPrioClasses = find(sn.classprio == maxPrio);
520 highNames = strjoin(arrayfun(@(i) sn.classnames{i}, highestPrioClasses, 'UniformOutput', false), ',');
521 lowNames = strjoin(arrayfun(@(i) sn.classnames{i}, lowestPrioClasses, 'UniformOutput', false), ',');
522 line_printf('Priority: highest=%s, lowest=%s\n', highNames, lowNames);
523 end
524 end
525end
526
527if any(nodetypes == NodeType.Cache)
528 % this also refreshes the routing matrix and the visits
529 refreshChains(self, false); % wantVisits
530else
531 % this also refreshes the routing matrix and the visits
532 refreshChains(self, true); % wantVisits
533end
534sn = self.sn;
535refclasses = getReferenceClasses(self);
536refclass = zeros(1,sn.nchains);
537for c=1:sn.nchains
538 isect = intersect(sn.inchain{c},find(refclasses));
539 if any(isect)
540 refclass(c) = isect;
541 end
542end
543sn.refclass = refclass;
544self.sn = sn;
545refreshLocalVars(self); % depends on chains (rtnodes)
546refreshPetriNetNodes(self);
547refreshSync(self); % this assumes that refreshChain is called before
548refreshGlobalSync(self);
549
550sn = self.sn;
551self.hasStruct = true;
552% Bump the version so that anything cached against the previous compilation
553% (see MNetwork.structVersion) is treated as stale.
554self.structVersion = self.structVersion + 1;
555
556if any(sn.fj(:)) && ~self.isFJAugmented % if there are forks
557 % Recompute visits post-mmt: sum auxiliary-class visits into originals (skipped on FJ tag-augmented copies) -- see _kb/04-networkstruct.md
558 %try
559 [nonfjmodel, fjclassmap, forkmap, fanOut] = ModelAdapter.mmt(self);
560 if any(fanOut==1)
561 % in this case, one of the forks is degenerate with a single
562 % outgoing link so no visit correction is needed
563 line_warning(mfilename,'The specified fork-join topology has partial support, only SolverJMT simulation results may be reliable.\n');
564 % return;
565 end
566 fsn = nonfjmodel.getStruct();
567 for new_chain=(sn.nchains+1):fsn.nchains
568 anyAuxClass = fsn.inchain{new_chain}(1);
569 origFork = forkmap(anyAuxClass);
570 origChain = find(sn.chains(:,fjclassmap(anyAuxClass))); % original chain of the class
571 fsn.nodevisits{new_chain}(fsn.nodetype == NodeType.Source | fsn.nodetype == NodeType.Sink | fsn.nodetype == NodeType.Fork,:) = 0;
572 Vaux = fsn.nodevisits{new_chain}(:,fsn.inchain{new_chain});
573 if fsn.nnodes ~= sn.nnodes
574 % Build mapping from fsn nodes to sn nodes by name
575 % This handles cases where ClassSwitch/Source/Sink differ between models
576 VauxMapped = zeros(sn.nnodes, size(Vaux,2));
577 for fsnRow = 1:fsn.nnodes
578 nodeName = fsn.nodenames{fsnRow};
579 snRow = find(strcmp(sn.nodenames, nodeName), 1);
580 if ~isempty(snRow)
581 % This fsn node exists in sn - copy its visit data
582 VauxMapped(snRow, :) = Vaux(fsnRow, :);
583 end
584 % Nodes not in sn (ClassSwitch, Source, Sink) are skipped
585 end
586 Vaux = VauxMapped;
587 end
588 X = sn.nodevisits{origChain};
589 for jaux=1:length(fsn.inchain{new_chain})
590 % Pair via fjclassmap explicitly; positional pairing against sn.inchain{origChain} breaks under mmt confinement -- see _kb/04-networkstruct.md
591 a = fsn.inchain{new_chain}(jaux);
592 if a > length(fjclassmap) || fjclassmap(a) <= 0
593 continue; % not an auxiliary class
594 end
595 j = fjclassmap(a); % original class mirrored by aux class a
596 self.sn.nodevisits{origChain}(:,j) = sn.nodeparam{origFork}.fanOut*(X(:,j) + Vaux(:,jaux));
597 end
598 end
599end
600
601sn = refreshRegions(self);
602self.sn = sn;
603
604% Heterogeneous server fields populated LAST so refreshLocalVars (which rebuilds nodeparam) cannot wipe them
605for ist = 1:self.sn.nstations
606 if isa(self.stations{ist}, 'Queue') && ~isempty(self.stations{ist}.serverTypes)
607 nodeIdx = self.sn.stationToNode(ist);
608 nTypes = length(self.stations{ist}.serverTypes);
609 self.sn.nodeparam{nodeIdx}.nservertypes = nTypes;
610 self.sn.nodeparam{nodeIdx}.servertypenames = cell(1, nTypes);
611 self.sn.nodeparam{nodeIdx}.serverspertype = zeros(1, nTypes);
612 self.sn.nodeparam{nodeIdx}.servercompat = zeros(nTypes, self.sn.nclasses);
613 % heterorates(t,r) = service rate of class r on a type-t server
614 % (1/mean; 0 where incompatible or unset - falls back to base rate).
615 self.sn.nodeparam{nodeIdx}.heterorates = zeros(nTypes, self.sn.nclasses);
616
617 hsd = self.stations{ist}.heteroServiceDistributions;
618 for t = 1:nTypes
619 st = self.stations{ist}.serverTypes{t};
620 self.sn.nodeparam{nodeIdx}.servertypenames{t} = st.getName();
621 self.sn.nodeparam{nodeIdx}.serverspertype(t) = st.numOfServers;
622
623 % Build compatibility matrix and per-(type,class) rates
624 classMap = [];
625 if ~isempty(hsd) && isKey(hsd, st.getName())
626 classMap = hsd(st.getName());
627 end
628 for r = 1:self.sn.nclasses
629 if st.isCompatible(self.classes{r})
630 self.sn.nodeparam{nodeIdx}.servercompat(t, r) = 1;
631 if ~isempty(classMap) && isKey(classMap, self.classes{r}.getName())
632 dist = classMap(self.classes{r}.getName());
633 mval = dist.getMean();
634 if mval > 0
635 self.sn.nodeparam{nodeIdx}.heterorates(t, r) = 1/mval;
636 end
637 end
638 end
639 end
640 end
641
642 % Get heterogeneous scheduling policy
643 if ~isempty(self.stations{ist}.heteroSchedPolicy)
644 self.sn.nodeparam{nodeIdx}.heteroschedpolicy = self.stations{ist}.heteroSchedPolicy;
645 end
646 end
647end
648end
649
650function stat_idx = nd2st(sn, node_idx)
651% STAT_IDX = ND2ST(NODE_IDX)
652
653if sn.isstation(node_idx)
654 stat_idx = at(cumsum(sn.isstation),node_idx);
655else
656 stat_idx = NaN;
657end
658end
659
660function node_idx = st2nd(sn,stat_idx)
661% NODE_IDX = ST2ND(SELF,STAT_IDX)
662
663v = cumsum(sn.isstation) == stat_idx;
664if any(v)
665 node_idx = find(v, 1);
666else
667 node_idx = NaN;
668end
669end
670
671function sful_idx = st2sf(sn,stat_idx)
672% SFUL_IDX = ST2SF(SELF,STAT_IDX)
673
674sful_idx = nd2sf(sn,st2nd(sn,stat_idx));
675end
676
677function sful_idx = nd2sf(sn, node_idx)
678% SFUL_IDX = ND2SF(NODE_IDX)
679
680if sn.isstateful(node_idx)
681 sful_idx = at(cumsum(sn.isstateful),node_idx);
682else
683 sful_idx = NaN;
684end
685end
686
687function node_idx = sf2nd(sn,stat_idx)
688% NODE_IDX = SF2ND(SELF,STAT_IDX)
689
690v = cumsum(sn.isstateful) == stat_idx;
691if any(v)
692 node_idx = find(v, 1);
693else
694 node_idx = NaN;
695end
696end
697
698function stat_idx = sf2st(sn,sful_idx)
699% STAT_IDX = SF2ST(SELF,SFUL_IDX)
700
701stat_idx = nd2st(sn,sf2nd(sn,sful_idx));
702end
Definition fjtag.m:161