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.
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.
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');
22 function n = elemName(element)
23 % Name of an LQN element (Processor/Task/Entry/Activity).
27 n = element.getName();
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.
42 if isa(value, 'Distribution
')
51 function el = byName(elements, name)
52 % First element of the cell/array ELEMENTS whose name is NAME ([]).
54 for i = 1:numel(elements)
60 if strcmp(opt.Layered.elemName(cand), name)
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);
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);
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);
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);
92 if ~isempty(p) && isa(p,
'Task')
96 % Fall back: activity.parentName
is the owning task's name.
98 if isprop(act, 'parentName'), pname = act.parentName; end
99 tasks = model.getTasks();
100 for i = 1:numel(tasks)
102 if ~isempty(pname) && strcmp(t.getName(), pname)
106 for j = 1:numel(acts)
107 if strcmp(acts(j).getName(), activityName)
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).
121 task = opt.Layered.taskOfActivity(model, activityName);
125 proc = task.getParent();
129 name = proc.getName();
132 function name = taskProcessorName(model, taskName)
133 % Name of the processor a task is deployed on ([] if undeployed).
135 task = opt.Layered.resolveTask(model, taskName);
139 proc = task.getParent();
141 name = proc.getName();
145 function tf = isRefTask(task)
146 % True if TASK is a reference (workload-generating) task.
147 s = task.getScheduling();
150 s = SchedStrategy.toText(s);
155 tf = strcmpi(s, 'ref
') || strcmpi(s, 'reference
');
158 function solver = makeSolver(model)
159 % Construct a quiet SolverLN for a per-evaluation LQN model copy.
160 solver = SolverLN(model, 'verbose
', false);
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();
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.
178 SensTable = solver.getSensitivityTable();
182 if isempty(SensTable) || height(SensTable) == 0
185 sens = containers.Map('KeyType
', 'char', 'ValueType
', 'any
');
186 for r = 1:height(SensTable)
187 st = SensTable.Station{r};
188 cl = SensTable.JobClass{r};
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>