LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
Environment.m
1classdef Environment < Ensemble
2 % An environment model defined by a collection of network sub-models
3 % coupled with an environment transition rule that selects the active
4 % sub-model.
5 %
6 % Copyright (c) 2012-2026, Imperial College London
7 % All rights reserved.
8
9 properties
10 env;
11 envGraph;
12 num_stages; % Expected number of stages (optional, for API consistency)
13 proc; % Markovian representation of each stage transition
14 holdTime; % holding times
15 probEnv; % steady-stage probability of the environment
16 probOrig; % probability that a request originated from phase
17 resetFun; % function implementing the reset policy for queue lengths
18 resetEnvRatesFun; % function implementing the reset policy for environment rates
19 resetStateFun; % function implementing the reset policy for the full state-probability vector (SolverENV statevec analyzer)
20 nodeFailures; % cell array of node breakdown/repair descriptors recorded by addNodeBreakdown/addNodeRepair
21 end
22
23 methods
24 function self = Environment(name, num_stages)
25 % SELF = ENVIRONMENT(NAME, NUM_STAGES)
26 % NAME - Name of the environment
27 % NUM_STAGES - (Optional) Expected number of stages. If provided, used for validation only.
28 % Stages can still be added/removed dynamically.
29 if nargin < 2
30 num_stages = 0;
31 end
32 self@Ensemble({});
33 self.name = name;
34 self.num_stages = num_stages;
35 self.nodeFailures = {};
36 self.envGraph = digraph();
37 self.envGraph.Nodes.Model = cell(0);
38 self.envGraph.Nodes.Type = cell(0);
39 end
40
41 function name = addStage(self, name, type, model)
42 wcfg = warning; % store warning configuration
43 warning('off','MATLAB:table:RowsAddedExistingVars');
44 self.envGraph = self.envGraph.addnode(name);
45 warning(wcfg); % restore warning configuration
46 self.envGraph.Nodes.Model{end} = model;
47 self.envGraph.Nodes.Type{end} = type;
48 E = height(self.envGraph.Nodes);
49 if E>1
50 if self.envGraph.Nodes.Model{1}.getNumberOfStatefulNodes ~= model.getNumberOfStatefulNodes
51 line_error(mfilename,'Unsupported feature. Random environment stages must map to networks with identical number of stateful nodes.');
52 end
53 end
54 for e=E
55 for h=1:E
56 self.env{e,h} = Disabled.getInstance();
57 self.env{h,e} = Disabled.getInstance();
58 end
59 end
60 self.ensemble{E} = model;
61 end
62
63 function self = addTransition(self, fromName, toName, distrib, resetFun, resetEnvRatesFun, resetStateFun)
64 self.envGraph = self.envGraph.addedge(fromName, toName);
65 e = self.envGraph.findnode(fromName);
66 h = self.envGraph.findnode(toName);
67 self.env{e,h} = distrib;
68 if nargin<5 %~exist('resetFun','var')
69 self.resetFun{e,h} = @(q) q;
70 else
71 self.resetFun{e,h} = resetFun;
72 end
73 if nargin<6 %~exist('resetEnvRatesFun','var')
74 self.resetEnvRatesFun{e,h} = @(originalDist, QExit, UExit, TExit) originalDist;
75 else
76 self.resetEnvRatesFun{e,h} = resetEnvRatesFun;
77 end
78 if nargin<7 %~exist('resetStateFun','var')
79 self.resetStateFun{e,h} = @(pi) pi; % identity: carry the exit distribution unchanged
80 else
81 self.resetStateFun{e,h} = resetStateFun;
82 end
83 end
84
85 function init(self)
86 E = height(self.envGraph.Nodes);
87 T = height(self.envGraph.Edges);
88 Pemb = zeros(E);
89
90 for t=1:T
91 e = self.envGraph.findnode(self.envGraph.Edges.EndNodes{t,1});
92 h = self.envGraph.findnode(self.envGraph.Edges.EndNodes{t,2});
93 self.envGraph.Edges.Distribution{t} = self.env{e,h};
94 end
95
96 % analyse holding times
97 emmap = cell(E,1);
98 for e=1:E
99 emmap{e} = cell(1,E);
100 end
101 self.holdTime = {};
102 for e=1:E
103 for h=1:E
104 if isa(self.env{e,h},'Disabled')
105 emmap{e}{h} = {0,0}; % multiclass MMAP representation
106 else
107 emmap{e}{h} = self.env{e,h}.getProcess; % multiclass MMAP representation
108 end
109 for j = 1:E
110 if j == h
111 emmap{e}{h}{2+j} = emmap{e}{h}{2};
112 else
113 emmap{e}{h}{2+j} = 0 * emmap{e}{h}{2};
114 end
115 end
116 end
117 self.holdTime{e} = emmap{e}{e};
118 for h=setdiff(1:E,e)
119 self.holdTime{e}{1} = krons(self.holdTime{e}{1},emmap{e}{h}{1});
120 for j = 2:(E+2)
121 self.holdTime{e}{j} = krons(self.holdTime{e}{j},emmap{e}{h}{j});
122 completion_rates = self.holdTime{e}{j}*ones(length(self.holdTime{e}{j}),1);
123 self.holdTime{e}{j} = 0*self.holdTime{e}{j};
124 self.holdTime{e}{j}(:,1) = completion_rates;
125 end
126 self.holdTime{e} = mmap_normalize(self.holdTime{e});
127 end
128 count_lambda = mmap_count_lambda(self.holdTime{e}); % completiom rates for the different transitions
129 Pemb(e,:) = count_lambda/sum(count_lambda);
130 end
131 self.proc = emmap;
132
133 %
134 lambda = zeros(1,E);
135 A = zeros(E); I=eye(E);
136 for e=1:E
137 lambda(e) = 1/map_mean(self.holdTime{e});
138 for h=1:E
139 A(e,h) = -lambda(e)*(I(e,h)-Pemb(e,h));
140 end
141 end
142
143 if all(lambda>0)
144 penv = ctmc_solve_reducible(A);
145 self.probEnv = penv;
146 self.probOrig = zeros(E);
147 for e = 1:E
148 for h = 1:E
149 self.probOrig(h,e) = penv(h) * lambda(h) * Pemb(h,e);
150 end
151 if penv(e) > 0
152 self.probOrig(:,e) = self.probOrig(:,e) / sum(self.probOrig(:,e));
153 end
154 end
155 else
156 %T = A(lambda>0, lambda>0), % subgenerator
157 %t = A(lambda>0, lambda==0), % exit vector
158 end
159 end
160
161 function env = getEnv(self)
162 env = self.env;
163 end
164
165 function self = setEnv(self, env)
166 self.envGraph = env;
167 end
168
169 function self = setStageName(self, stageId, name)
170 self.stageNames{stageId} = name;
171 end
172
173 function self = setStageType(self, stageId, stageCategory)
174 if ischar(stageCategory)
175 self.stageTypes(stageId) = categorical(stageCategory);
176 elseif iscategorical(stageCategory)
177 self.stageTypes(stageId) = stageCategory;
178 else
179 line_error(mfilename,'Stage type must be of type categorical, e.g., categorical("My Semantics").');
180 end
181 end
182
183 function ET = getStageTable(self)
184 E = height(self.envGraph.Nodes);
185 Stage = [];
186 HoldT = {};
187 type = categorical([]);
188 if isempty(self.probEnv)
189 self.init;
190 end
191 for e=1:E
192 Stage(e,1) = e;
193 type(e,1) = self.envGraph.Nodes.Type{e};
194 end
195 Prob = self.probEnv(:);
196 Name = categorical(self.envGraph.Nodes.Name(:));
197 Model = self.ensemble(:);
198 for e=1:E
199 HoldT{e,1} = self.holdTime{e};
200 end
201 Type = type;
202 ET = Table(Stage, Name, Type, Prob, HoldT, Model);
203 end
204
205 function ET = getStageT(self)
206 % GETSTAGET Short alias for getStageTable
207 ET = self.getStageTable();
208 end
209
210 function RT = getReliabilityTable(self)
211 % RT = GETRELIABILITYTABLE()
212 % Compute system-wide reliability metrics (MTTF, MTTR, MTBF, Availability)
213 %
214 % Returns:
215 % RT - Table with columns: Metric, Value, Unit, Description
216 %
217 % Example:
218 % env = Environment('ServerEnv');
219 % env.addNodeFailureRepair(model, 'Server', Exp(0.1), Exp(1.0), Exp(0.5));
220 % env.init();
221 % reliabilityTable = env.getReliabilityTable();
222
223 % Step 1: Initialize and validate
224 if isempty(self.probEnv)
225 self.init();
226 end
227
228 E = height(self.envGraph.Nodes);
229 if E == 0
230 line_error(mfilename, 'Environment has no stages. Add stages before computing reliability metrics.');
231 end
232
233 % Step 2: Identify stage types
234 stageNames = self.envGraph.Nodes.Name;
235 upIdx = find(strcmp(stageNames, 'UP'));
236
237 if isempty(upIdx)
238 line_error(mfilename, 'No UP stage found. Use addNodeBreakdown/addNodeRepair to configure breakdown/repair transitions.');
239 end
240
241 % Find all DOWN stages
242 downIdx = find(startsWith(stageNames, 'DOWN_'));
243
244 if isempty(downIdx)
245 line_error(mfilename, 'No DOWN stages found. Use addNodeBreakdown/addNodeRepair to configure breakdown/repair transitions.');
246 end
247
248 % Step 3: Extract breakdown rates (UP -> DOWN_*)
249 breakdownRates = [];
250 for h = downIdx'
251 if ~isa(self.env{upIdx, h}, 'Disabled')
252 lambda_h = 1 / self.env{upIdx, h}.getMean();
253 breakdownRates(end+1) = lambda_h;
254 end
255 end
256
257 if isempty(breakdownRates)
258 line_error(mfilename, 'No breakdown transitions found (UP -> DOWN_*).');
259 end
260
261 % Total failure rate (competing risks)
262 lambda_total = sum(breakdownRates);
263 MTTF = 1 / lambda_total;
264
265 % Step 4: Extract repair rates (DOWN_* -> UP)
266 repairRates = [];
267 downProbs = [];
268
269 for e = downIdx'
270 if ~isa(self.env{e, upIdx}, 'Disabled')
271 mu_e = 1 / self.env{e, upIdx}.getMean();
272 repairRates(end+1) = mu_e;
273 downProbs(end+1) = self.probEnv(e);
274 end
275 end
276
277 if isempty(repairRates)
278 line_error(mfilename, 'No repair transitions found (DOWN_* -> UP).');
279 end
280
281 % Normalize probabilities over DOWN states only
282 totalDownProb = sum(downProbs);
283 if totalDownProb > 0
284 downProbsNorm = downProbs / totalDownProb;
285 % Weighted average repair time
286 MTTR = sum(downProbsNorm ./ repairRates);
287 else
288 % Fallback: simple average if no steady-state probability
289 MTTR = mean(1 ./ repairRates);
290 end
291
292 % Step 5: Compute derived metrics
293 MTBF = MTTF + MTTR;
294
295 % Availability from steady-state probabilities
296 availUp = self.probEnv(upIdx);
297 availDown = sum(self.probEnv(downIdx));
298 Availability = availUp / (availUp + availDown);
299
300 % Step 6: Create output table
301 Metric = categorical({'MTTF'; 'MTTR'; 'MTBF'; 'Availability'});
302 Value = [MTTF; MTTR; MTBF; Availability];
303 Unit = categorical({'time units'; 'time units'; 'time units'; 'probability'});
304 Description = categorical({
305 'Mean time to failure (UP -> DOWN)';
306 'Mean time to repair (DOWN -> UP)';
307 'Mean time between failures (MTTF + MTTR)';
308 'Steady-state probability of UP state'
309 });
310
311 RT = Table(Metric, Value, Unit, Description);
312 end
313
314 function RT = relT(self)
315 % RELT Short alias for getReliabilityTable
316 RT = self.getReliabilityTable();
317 end
318
319 function RT = getRelT(self)
320 % GETRELT Short alias for getReliabilityTable
321 RT = self.getReliabilityTable();
322 end
323
324 function RT = relTable(self)
325 % RELTABLE Short alias for getReliabilityTable
326 RT = self.getReliabilityTable();
327 end
328
329 function RT = getRelTable(self)
330 % GETRELTABLE Short alias for getReliabilityTable
331 RT = self.getReliabilityTable();
332 end
333
334 function printStageTable(self)
335 % PRINTSTAGETABLE Print a formatted table showing all stages, their properties, and transitions
336 %
337 % Displays stage names, types, associated networks, and transition rates.
338 %
339 % Example:
340 % env = Environment('MyEnv');
341 % env.addStage('UP', 'operational', model1);
342 % env.addStage('DOWN', 'failed', model2);
343 % env.addTransition('UP', 'DOWN', Exp(0.1));
344 % env.printStageTable();
345
346 E = height(self.envGraph.Nodes);
347 fprintf('Stage Table:\n');
348 fprintf('============\n');
349
350 for e = 1:E
351 stageName = self.envGraph.Nodes.Name{e};
352 stageType = self.envGraph.Nodes.Type{e};
353 model = self.envGraph.Nodes.Model{e};
354
355 fprintf('Stage %d: %s (Type: %s)\n', e, stageName, stageType);
356 if ~isempty(model)
357 fprintf(' - Network: %s\n', model.getName());
358 fprintf(' - Nodes: %d\n', model.getNumberOfNodes());
359 fprintf(' - Classes: %d\n', model.getNumberOfClasses());
360 end
361 end
362
363 % Print transitions
364 T = height(self.envGraph.Edges);
365 if T > 0
366 fprintf('\nTransitions:\n');
367 for t = 1:T
368 fromName = self.envGraph.Edges.EndNodes{t, 1};
369 toName = self.envGraph.Edges.EndNodes{t, 2};
370 e = self.envGraph.findnode(fromName);
371 h = self.envGraph.findnode(toName);
372 if ~isa(self.env{e, h}, 'Disabled')
373 rate = 1 / self.env{e, h}.getMean();
374 fprintf(' %s -> %s: rate = %.4f\n', fromName, toName, rate);
375 end
376 end
377 end
378 end
379
380 function self = addNodeBreakdown(self, baseModel, nodeOrName, breakdownDist, downServiceDist, varargin)
381 % SELF = ADDNODEBREAKDOWN(BASEMODEL, NODEORNAME, BREAKDOWNDIST, DOWNSERVICEDIST, RESETFUN)
382 % Adds UP and DOWN stages for a node that can break down and repair
383 %
384 % Parameters:
385 % baseModel - The base network model with normal (UP) service rates
386 % nodeOrName - Node object or name of the node that can break down
387 % breakdownDist - Distribution for time until breakdown (UP->DOWN transition)
388 % downServiceDist - Service distribution when the node is down
389 % resetFun - (Optional) Reset policy for queue lengths on breakdown.
390 % Either a function handle @(q) -> q, or one of the named
391 % policies 'keep' (identity) and 'clear' (empty the queues).
392 % Default: 'keep'. Only named policies are serializable.
393 %
394 % Example:
395 % model = Network('MyNetwork');
396 % queue = Queue(model, 'Server1', SchedStrategy.FCFS);
397 % class = ClosedClass(model, 'Jobs', 10, queue, 0);
398 % queue.setService(class, Exp(2)); % UP service rate
399 %
400 % env = Environment('ServerEnv');
401 % env.addNodeBreakdown(model, 'Server1', Exp(0.1), Exp(0.5));
402 % % Or using node object:
403 % env.addNodeBreakdown(model, queue, Exp(0.1), Exp(0.5));
404
405 % Extract node name if a Node object is passed
406 if isa(nodeOrName, 'Node')
407 nodeName = nodeOrName.name;
408 else
409 nodeName = nodeOrName;
410 end
411
412 if nargin < 6
413 resetSpec = 'keep';
414 else
415 resetSpec = varargin{1};
416 end
417 [resetFun, resetName] = Environment.resolveResetPolicy(resetSpec);
418
419 % Create UP stage (if this is the first call)
420 if height(self.envGraph.Nodes) == 0
421 upModel = baseModel.copy();
422 self.addStage('UP', 'operational', upModel);
423 end
424
425 % Create DOWN stage with modified service rate for the specified node
426 downModel = baseModel.copy();
427 nodes = downModel.getNodes();
428 nodeIdx = [];
429 for i = 1:length(nodes)
430 if strcmp(nodes{i}.name, nodeName)
431 nodeIdx = i;
432 break;
433 end
434 end
435
436 if isempty(nodeIdx)
437 line_error(mfilename, sprintf('Node "%s" not found in the base model.', nodeName));
438 end
439
440 % Update service distribution for the down node
441 classes = downModel.getClasses();
442 for c = 1:length(classes)
443 nodes{nodeIdx}.setService(classes{c}, downServiceDist);
444 end
445
446 % Add DOWN stage
447 downStageName = sprintf('DOWN_%s', nodeName);
448 self.addStage(downStageName, 'failed', downModel);
449
450 % Add breakdown transition (UP -> DOWN)
451 self.addTransition('UP', downStageName, breakdownDist, resetFun);
452
453 % Record the descriptor so that the breakdown can be serialized
454 % declaratively (linemodel_save "nodeFailures"). The expanded
455 % stages/transitions carry the structure, but the reset policy is a
456 % function and is only recoverable from this record.
457 nf = struct();
458 nf.node = nodeName;
459 nf.breakdown = breakdownDist;
460 nf.downService = downServiceDist;
461 nf.repair = [];
462 nf.breakdownResetPolicy = resetName;
463 nf.repairResetPolicy = '';
464 self.nodeFailures{end+1} = nf;
465 end
466
467 function self = addNodeRepair(self, nodeOrName, repairDist, varargin)
468 % SELF = ADDNODEREPAIR(NODEORNAME, REPAIRDIST, RESETFUN)
469 % Adds repair transition from DOWN to UP stage for a previously added breakdown
470 %
471 % Parameters:
472 % nodeOrName - Node object or name of the node that can be repaired
473 % repairDist - Distribution for repair time (DOWN->UP transition)
474 % resetFun - (Optional) Reset policy for queue lengths on repair.
475 % Either a function handle @(q) -> q, or one of the named
476 % policies 'keep' (identity) and 'clear' (empty the queues).
477 % Default: 'keep'. Only named policies are serializable.
478 %
479 % Example:
480 % env.addNodeRepair('Server1', Exp(1.0));
481 % % Or using node object:
482 % env.addNodeRepair(queue, Exp(1.0));
483
484 % Extract node name if a Node object is passed
485 if isa(nodeOrName, 'Node')
486 nodeName = nodeOrName.name;
487 else
488 nodeName = nodeOrName;
489 end
490
491 if nargin < 4
492 resetSpec = 'keep';
493 else
494 resetSpec = varargin{1};
495 end
496 [resetFun, resetName] = Environment.resolveResetPolicy(resetSpec);
497
498 downStageName = sprintf('DOWN_%s', nodeName);
499
500 % Verify DOWN stage exists
501 if isempty(self.envGraph.findnode(downStageName))
502 line_error(mfilename, sprintf('DOWN stage for node "%s" not found. Call addNodeBreakdown first.', nodeName));
503 end
504
505 % Add repair transition (DOWN -> UP)
506 self.addTransition(downStageName, 'UP', repairDist, resetFun);
507
508 % Complete the descriptor recorded by addNodeBreakdown.
509 idx = self.findNodeFailure(nodeName);
510 if idx > 0
511 self.nodeFailures{idx}.repair = repairDist;
512 self.nodeFailures{idx}.repairResetPolicy = resetName;
513 end
514 end
515
516 function self = registerNodeFailure(self, nodeName, breakdownDist, repairDist, downServiceDist, breakdownPolicy, repairPolicy)
517 % SELF = REGISTERNODEFAILURE(NODENAME, BREAKDOWNDIST, REPAIRDIST, DOWNSERVICEDIST, BREAKDOWNPOLICY, REPAIRPOLICY)
518 % Attach a node breakdown/repair descriptor to stages that already exist.
519 %
520 % This is the counterpart of addNodeBreakdown/addNodeRepair for the case
521 % where the UP and DOWN_<node> stages and their transitions have already
522 % been built (for instance by linemodel_load reading the expanded
523 % stages/transitions form). It records the descriptor and applies the
524 % queue-length reset policies, which the expanded form cannot carry.
525 downStageName = sprintf('DOWN_%s', nodeName);
526 upIdx = self.envGraph.findnode('UP');
527 downIdx = self.envGraph.findnode(downStageName);
528 if isempty(upIdx) || upIdx == 0
529 line_error(mfilename, sprintf(['Cannot register a node failure on "%s": no UP stage is ' ...
530 'defined in this environment.'], nodeName));
531 end
532 if isempty(downIdx) || downIdx == 0
533 line_error(mfilename, sprintf(['Cannot register a node failure on "%s": no "%s" stage is ' ...
534 'defined in this environment.'], nodeName, downStageName));
535 end
536
537 [breakdownFun, breakdownName] = Environment.resolveResetPolicy(breakdownPolicy);
538 self.resetFun{upIdx, downIdx} = breakdownFun;
539 repairName = '';
540 if ~isempty(repairDist)
541 [repairFun, repairName] = Environment.resolveResetPolicy(repairPolicy);
542 self.resetFun{downIdx, upIdx} = repairFun;
543 end
544
545 nf = struct();
546 nf.node = nodeName;
547 nf.breakdown = breakdownDist;
548 nf.downService = downServiceDist;
549 nf.repair = repairDist;
550 nf.breakdownResetPolicy = breakdownName;
551 nf.repairResetPolicy = repairName;
552 idx = self.findNodeFailure(nodeName);
553 if idx > 0
554 self.nodeFailures{idx} = nf;
555 else
556 self.nodeFailures{end+1} = nf;
557 end
558 end
559
560 function idx = findNodeFailure(self, nodeName)
561 % IDX = FINDNODEFAILURE(NODENAME)
562 % Index of the node-failure descriptor for NODENAME, or 0 if absent.
563 idx = 0;
564 for i = 1:length(self.nodeFailures)
565 if strcmp(self.nodeFailures{i}.node, nodeName)
566 idx = i;
567 return;
568 end
569 end
570 end
571
572 function self = addNodeFailureRepair(self, baseModel, nodeOrName, breakdownDist, repairDist, downServiceDist, varargin)
573 % SELF = ADDNODEFAILUREREPAIR(BASEMODEL, NODEORNAME, BREAKDOWNDIST, REPAIRDIST, DOWNSERVICEDIST, RESETBREAKDOWN, RESETREPAIR)
574 % Convenience method to add both breakdown and repair for a node
575 %
576 % Parameters:
577 % baseModel - The base network model with normal (UP) service rates
578 % nodeOrName - Node object or name of the node that can break down and repair
579 % breakdownDist - Distribution for time until breakdown
580 % repairDist - Distribution for repair time
581 % downServiceDist - Service distribution when the node is down
582 % resetBreakdown - (Optional) Reset function for breakdown transition
583 % resetRepair - (Optional) Reset function for repair transition
584 %
585 % Example:
586 % env = Environment('ServerEnv');
587 % env.addNodeFailureRepair(model, 'Server1', Exp(0.1), Exp(1.0), Exp(0.5));
588 % % Or using node object:
589 % env.addNodeFailureRepair(model, queue, Exp(0.1), Exp(1.0), Exp(0.5));
590
591 % Extract node name if a Node object is passed
592 if isa(nodeOrName, 'Node')
593 nodeName = nodeOrName.name;
594 else
595 nodeName = nodeOrName;
596 end
597
598 resetBreakdown = 'keep';
599 resetRepair = 'keep';
600
601 if nargin >= 7
602 resetBreakdown = varargin{1};
603 end
604 if nargin >= 8
605 resetRepair = varargin{2};
606 end
607
608 self.addNodeBreakdown(baseModel, nodeName, breakdownDist, downServiceDist, resetBreakdown);
609 self.addNodeRepair(nodeName, repairDist, resetRepair);
610 end
611
612 function self = setBreakdownResetPolicy(self, nodeOrName, resetFun)
613 % SELF = SETBREAKDOWNRESETPOLICY(NODEORNAME, RESETFUN)
614 % Update the reset policy for breakdown transitions (UP -> DOWN) of a node
615 %
616 % Parameters:
617 % nodeOrName - Node object or name of the node
618 % resetFun - Reset policy for queue lengths on breakdown. Either a
619 % function handle, or one of the named policies 'keep'
620 % (identity) and 'clear' (empty the queues). Only named
621 % policies are serializable.
622 % Example: @(q) 0*q to clear queues, @(q) q to keep jobs
623 %
624 % Example:
625 % env.setBreakdownResetPolicy('Server1', @(q) 0*q);
626 % % Or using node object:
627 % env.setBreakdownResetPolicy(queue, 'clear');
628
629 % Extract node name if a Node object is passed
630 if isa(nodeOrName, 'Node')
631 nodeName = nodeOrName.name;
632 else
633 nodeName = nodeOrName;
634 end
635
636 [resetFun, resetName] = Environment.resolveResetPolicy(resetFun);
637 downStageName = sprintf('DOWN_%s', nodeName);
638
639 % Find UP and DOWN stage indices
640 upIdx = self.envGraph.findnode('UP');
641 downIdx = self.envGraph.findnode(downStageName);
642
643 if isempty(upIdx) || upIdx == 0
644 line_error(mfilename, 'UP stage not found. Call addNodeBreakdown first.');
645 end
646 if isempty(downIdx) || downIdx == 0
647 line_error(mfilename, sprintf('DOWN stage for node "%s" not found. Call addNodeBreakdown first.', nodeName));
648 end
649
650 % Update the reset function for the breakdown transition (UP -> DOWN)
651 self.resetFun{upIdx, downIdx} = resetFun;
652 idx = self.findNodeFailure(nodeName);
653 if idx > 0
654 self.nodeFailures{idx}.breakdownResetPolicy = resetName;
655 end
656 end
657
658 function self = setRepairResetPolicy(self, nodeOrName, resetFun)
659 % SELF = SETREPAIRRESETPOLICY(NODEORNAME, RESETFUN)
660 % Update the reset policy for repair transitions (DOWN -> UP) of a node
661 %
662 % Parameters:
663 % nodeOrName - Node object or name of the node
664 % resetFun - Reset policy for queue lengths on repair. Either a
665 % function handle, or one of the named policies 'keep'
666 % (identity) and 'clear' (empty the queues). Only named
667 % policies are serializable.
668 % Example: @(q) q to keep jobs, @(q) 0*q to clear queues
669 %
670 % Example:
671 % env.setRepairResetPolicy('Server1', @(q) q);
672 % % Or using node object:
673 % env.setRepairResetPolicy(queue, 'keep');
674
675 % Extract node name if a Node object is passed
676 if isa(nodeOrName, 'Node')
677 nodeName = nodeOrName.name;
678 else
679 nodeName = nodeOrName;
680 end
681
682 [resetFun, resetName] = Environment.resolveResetPolicy(resetFun);
683 downStageName = sprintf('DOWN_%s', nodeName);
684
685 % Find UP and DOWN stage indices
686 upIdx = self.envGraph.findnode('UP');
687 downIdx = self.envGraph.findnode(downStageName);
688
689 if isempty(upIdx) || upIdx == 0
690 line_error(mfilename, 'UP stage not found. Call addNodeBreakdown first.');
691 end
692 if isempty(downIdx) || downIdx == 0
693 line_error(mfilename, sprintf('DOWN stage for node "%s" not found. Call addNodeBreakdown first.', nodeName));
694 end
695
696 % Update the reset function for the repair transition (DOWN -> UP)
697 self.resetFun{downIdx, upIdx} = resetFun;
698 idx = self.findNodeFailure(nodeName);
699 if idx > 0
700 self.nodeFailures{idx}.repairResetPolicy = resetName;
701 end
702 end
703 end
704
705 methods (Static)
706 function [resetFun, resetName] = resolveResetPolicy(spec)
707 % [RESETFUN, RESETNAME] = RESOLVERESETPOLICY(SPEC)
708 % Resolve a queue-length reset policy given either a named policy or a
709 % function handle.
710 %
711 % Named policies (the only serializable ones):
712 % 'keep' - carry the queue lengths across the transition, @(q) q
713 % 'clear' - empty the queues on the transition, @(q) 0*q
714 %
715 % A function handle is returned unchanged and reported as 'custom':
716 % an arbitrary reset function cannot be reproduced from JSON.
717 if ischar(spec) || isstring(spec)
718 switch lower(char(spec))
719 case 'keep'
720 resetFun = @(q) q;
721 resetName = 'keep';
722 case 'clear'
723 resetFun = @(q) 0*q;
724 resetName = 'clear';
725 otherwise
726 line_error(mfilename, sprintf(['Unknown reset policy "%s". Use ''keep'', ''clear'', ' ...
727 'or a function handle @(q) -> q.'], char(spec)));
728 end
729 elseif isa(spec, 'function_handle')
730 resetFun = spec;
731 resetName = 'custom';
732 else
733 line_error(mfilename, ['Reset policy must be a function handle @(q) -> q, or one of the ' ...
734 'named policies ''keep'' and ''clear''.']);
735 end
736 end
737 end
738end
Definition Station.m:245