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