LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
Layered.m
1classdef Layered
2 % Layered LayeredNetwork (LQN) support for line-opt. Static-method twin of
3 % native-Python line_solver.opt.layered: model-type detection, element
4 % resolution by name in a per-evaluation LQN model copy, the activity ->
5 % processor mapping used to tag host-layer variables and key the per-layer
6 % sensitivity table, and the SolverLN avg/sensitivity readers.
7 %
8 % IMPORTANT (see SolverLN.getSensitivityTable): the per-layer table holds
9 % WITHIN-LAYER PARTIAL service-rate derivatives (fixed-point layer
10 % parameters held constant); it omits cross-layer coupling and is thus a
11 % biased estimate of the total derivative. lqnGradient='fd' finite-
12 % differences the whole LayeredNetwork instead (correct total derivative);
13 % 'partial_sens' uses this table directly; 'partial_plus_fd' corrects it
14 % with a periodic full-model finite difference.
15
16 methods (Static)
17 function tf = isLayered(model)
18 % True if MODEL is a LayeredNetwork (LQN), false for a flat Network.
19 tf = ~isempty(model) && isa(model, 'LayeredNetwork');
20 end
21
22 function n = elemName(element)
23 % Name of an LQN element (Processor/Task/Entry/Activity).
24 if ischar(element)
25 n = element;
26 else
27 n = element.getName();
28 end
29 end
30
31 function m = distMean(value)
32 % Mean of a think-time/demand that may be a distribution or scalar
33 % ([] if unavailable). Used by LQN variables' currentValue.
34 m = [];
35 if isempty(value)
36 return;
37 end
38 if isnumeric(value)
39 m = double(value);
40 return;
41 end
42 if isa(value, 'Distribution')
43 try
44 m = value.getMean();
45 catch
46 m = [];
47 end
48 end
49 end
50
51 function el = byName(elements, name)
52 % First element of the cell/array ELEMENTS whose name is NAME ([]).
53 el = [];
54 for i = 1:numel(elements)
55 if iscell(elements)
56 cand = elements{i};
57 else
58 cand = elements(i);
59 end
60 if strcmp(opt.Layered.elemName(cand), name)
61 el = cand;
62 return;
63 end
64 end
65 end
66
67 function proc = resolveProcessor(model, name)
68 % Resolve a Processor/Host by name inside a (copied) LQN model.
69 proc = opt.Layered.byName(model.getHosts(), name);
70 end
71
72 function task = resolveTask(model, name)
73 % Resolve a Task by name inside a (copied) LQN model.
74 task = opt.Layered.byName(model.getTasks(), name);
75 end
76
77 function act = resolveActivity(model, name)
78 % Resolve an Activity by name inside a (copied) LQN model.
79 act = opt.Layered.byName(model.getActivities(), name);
80 end
81
82 function task = taskOfActivity(model, activityName)
83 % The Task an activity belongs to, resolved in MODEL ([] if none).
84 % Prefers the activity's own parent handle; falls back to the task
85 % whose activity list or name matches.
86 act = opt.Layered.resolveActivity(model, activityName);
87 task = [];
88 if isempty(act)
89 return;
90 end
91 p = act.getParent();
92 if ~isempty(p) && isa(p, 'Task')
93 task = p;
94 return;
95 end
96 % Fall back: activity.parentName is the owning task's name.
97 pname = '';
98 if isprop(act, 'parentName'), pname = act.parentName; end
99 tasks = model.getTasks();
100 for i = 1:numel(tasks)
101 t = tasks{i};
102 if ~isempty(pname) && strcmp(t.getName(), pname)
103 task = t; return;
104 end
105 acts = t.activities;
106 for j = 1:numel(acts)
107 if strcmp(acts(j).getName(), activityName)
108 task = t; return;
109 end
110 end
111 end
112 end
113
114 function name = activityProcessorName(model, activityName)
115 % Name of the processor an activity ultimately runs on ([] if the
116 % Activity -> Task -> Processor chain is incomplete). Used both to
117 % tag a HostDemand variable's host layer and to key its host-layer
118 % sensitivity row (Layer=processor, Station=processor,
119 % JobClass=activity).
120 name = [];
121 task = opt.Layered.taskOfActivity(model, activityName);
122 if isempty(task)
123 return;
124 end
125 proc = task.getParent();
126 if isempty(proc)
127 return;
128 end
129 name = proc.getName();
130 end
131
132 function name = taskProcessorName(model, taskName)
133 % Name of the processor a task is deployed on ([] if undeployed).
134 name = [];
135 task = opt.Layered.resolveTask(model, taskName);
136 if isempty(task)
137 return;
138 end
139 proc = task.getParent();
140 if ~isempty(proc)
141 name = proc.getName();
142 end
143 end
144
145 function tf = isRefTask(task)
146 % True if TASK is a reference (workload-generating) task.
147 s = task.getScheduling();
148 if ~ischar(s)
149 try
150 s = SchedStrategy.toText(s);
151 catch
152 s = char(string(s));
153 end
154 end
155 tf = strcmpi(s, 'ref') || strcmpi(s, 'reference');
156 end
157
158 function solver = makeSolver(model)
159 % Construct a quiet SolverLN for a per-evaluation LQN model copy.
160 solver = SolverLN(model, 'verbose', false);
161 end
162
163 function [solver, avgTable] = solveAvg(model)
164 % Solve an LQN and return (solver, avgTable). The table has one row
165 % per LQN node with columns Node, NodeType, QLen, Util, RespT,
166 % ResidT, ArvR, Tput.
167 solver = opt.Layered.makeSolver(model);
168 avgTable = solver.getAvgTable();
169 end
170
171 function sens = computeSensitivities(solver)
172 % Per-(Station,JobClass) within-layer service-rate partial
173 % derivatives from SolverLN.getSensitivityTable, reshaped into a
174 % containers.Map keyed 'Station||JobClass' -> struct with fields
175 % Tput/RespT/QLen/Util (d(metric)/d(service rate)). [] on failure.
176 sens = [];
177 try
178 SensTable = solver.getSensitivityTable();
179 catch
180 return;
181 end
182 if isempty(SensTable) || height(SensTable) == 0
183 return;
184 end
185 sens = containers.Map('KeyType', 'char', 'ValueType', 'any');
186 for r = 1:height(SensTable)
187 st = SensTable.Station{r};
188 cl = SensTable.JobClass{r};
189 key = [st '||' cl];
190 entry = struct('Tput', SensTable.dTput_dRate(r), ...
191 'RespT', SensTable.dRespT_dRate(r), ...
192 'QLen', SensTable.dQLen_dRate(r), ...
193 'Util', SensTable.dUtil_dRate(r));
194 sens(key) = entry; %#ok<NASGU>
195 end
196 end
197 end
198end