LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
layered_variables.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_OPT_LAYERED_VARIABLES_H
6#define LINE_OPT_LAYERED_VARIABLES_H
7
8/**
9 * @file
10 * @ingroup line_opt
11 * The decision variables of a layered queueing network.
12 *
13 * The LQN counterparts of `variables.h`: `ProcessorMultiplicity` and
14 * `TaskMultiplicity` are the multiplicities of a processor and a task,
15 * `TaskReplication` the replication factor, `HostDemand` an activity's demand on
16 * its processor, and `TaskThinkTime` and `ActivityThinkTime` the two think times.
17 * Each applies itself to an `lqn::LqnModel<double>` by name, so a variable
18 * survives a rebuild of the model it was declared against.
19 */
20
21#include <algorithm>
22#include <cmath>
23#include <optional>
24#include <string>
25#include <vector>
26
27#include "line/opt/variables.h"
28
29namespace line {
30namespace opt {
31
32namespace layered_detail {
33
34inline std::size_t processor(const lqn::LqnModel<double>& model, const std::string& name) {
35 for (std::size_t i = 0; i < model.procs.size(); ++i)
36 if (model.procs[i].name == name) return i;
37 return model.procs.size();
38}
39inline std::size_t task(const lqn::LqnModel<double>& model, const std::string& name) {
40 for (std::size_t i = 0; i < model.tasks.size(); ++i)
41 if (model.tasks[i].name == name) return i;
42 return model.tasks.size();
43}
44inline std::size_t activity(const lqn::LqnModel<double>& model, const std::string& name) {
45 for (std::size_t i = 0; i < model.acts.size(); ++i)
46 if (model.acts[i].name == name) return i;
47 return model.acts.size();
48}
49inline std::string task_processor(const lqn::LqnModel<double>& model,
50 const std::string& task_name) {
51 const std::size_t index = task(model, task_name);
52 if (index >= model.tasks.size() || model.tasks[index].proc_slot >= model.procs.size())
53 return {};
54 return model.procs[model.tasks[index].proc_slot].name;
55}
56inline std::string activity_task(const lqn::LqnModel<double>& model,
57 const std::string& activity_name) {
58 const std::size_t index = activity(model, activity_name);
59 if (index >= model.acts.size() || model.acts[index].task_slot >= model.tasks.size())
60 return {};
61 return model.tasks[model.acts[index].task_slot].name;
62}
63inline std::string activity_processor(const lqn::LqnModel<double>& model,
64 const std::string& activity_name) {
65 return task_processor(model, activity_task(model, activity_name));
66}
67inline Value integer_decode(const std::vector<double>& x, int low, int high) {
68 return {static_cast<double>(std::clamp<int>(
69 static_cast<int>(std::llround(low + x.at(0) * (high - low))), low, high))};
70}
71inline Value continuous_decode(const std::vector<double>& x, double low, double high) {
72 return {low + x.at(0) * (high - low)};
73}
74
75} // namespace layered_detail
76
78public:
79 ProcessorMultiplicity(std::string processor, int low, int high, std::string name = "")
80 : DecisionVariable(name.empty() ? processor + "_multiplicity" : std::move(name)),
81 processor_(std::move(processor)), low_(low), high_(high) {}
82 Value decode(const std::vector<double>& x) const override {
83 return layered_detail::integer_decode(x, low_, high_);
84 }
85 void apply(lqn::LqnModel<double>& model, const Value& value) const override {
86 const std::size_t i = layered_detail::processor(model, processor_);
87 if (i < model.procs.size()) model.procs[i].mult = std::round(scalar_value(value));
88 }
89 std::string type() const override { return "processor_multiplicity"; }
90 std::vector<std::string> layers(const lqn::LqnModel<double>&) const override {
91 return {processor_};
92 }
93 std::optional<Value> current_value(const lqn::LqnModel<double>& model) const override {
94 const std::size_t i = layered_detail::processor(model, processor_);
95 if (i >= model.procs.size() || !std::isfinite(model.procs[i].mult)) return std::nullopt;
96 return Value{std::round(model.procs[i].mult)};
97 }
98private:
99 std::string processor_;
100 int low_, high_;
101};
102
103class TaskMultiplicity final : public DecisionVariable {
104public:
105 TaskMultiplicity(std::string task, int low, int high, std::string name = "")
106 : DecisionVariable(name.empty() ? task + "_multiplicity" : std::move(name)),
107 task_(std::move(task)), low_(low), high_(high) {}
108 Value decode(const std::vector<double>& x) const override {
109 return layered_detail::integer_decode(x, low_, high_);
110 }
111 void apply(lqn::LqnModel<double>& model, const Value& value) const override {
112 const std::size_t i = layered_detail::task(model, task_);
113 if (i < model.tasks.size()) model.tasks[i].mult = std::round(scalar_value(value));
114 }
115 std::string type() const override { return "task_multiplicity"; }
116 std::vector<std::string> layers(const lqn::LqnModel<double>& model) const override {
117 std::vector<std::string> out{task_};
118 const std::string processor = layered_detail::task_processor(model, task_);
119 if (!processor.empty()) out.push_back(processor);
120 return out;
121 }
122 std::optional<Value> current_value(const lqn::LqnModel<double>& model) const override {
123 const std::size_t i = layered_detail::task(model, task_);
124 if (i >= model.tasks.size() || !std::isfinite(model.tasks[i].mult)) return std::nullopt;
125 return Value{std::round(model.tasks[i].mult)};
126 }
127private:
128 std::string task_;
129 int low_, high_;
130};
131
132class TaskReplication final : public DecisionVariable {
133public:
134 TaskReplication(std::string task, int low, int high, std::string name = "")
135 : DecisionVariable(name.empty() ? task + "_replication" : std::move(name)),
136 task_(std::move(task)), low_(low), high_(high) {}
137 Value decode(const std::vector<double>& x) const override {
138 return layered_detail::integer_decode(x, low_, high_);
139 }
140 void apply(lqn::LqnModel<double>& model, const Value& value) const override {
141 const std::size_t i = layered_detail::task(model, task_);
142 if (i < model.tasks.size()) model.tasks[i].repl = std::round(scalar_value(value));
143 }
144 std::string type() const override { return "task_replication"; }
145 std::vector<std::string> layers(const lqn::LqnModel<double>& model) const override {
146 std::vector<std::string> out{task_};
147 const std::string processor = layered_detail::task_processor(model, task_);
148 if (!processor.empty()) out.push_back(processor);
149 return out;
150 }
151 std::optional<Value> current_value(const lqn::LqnModel<double>& model) const override {
152 const std::size_t i = layered_detail::task(model, task_);
153 if (i >= model.tasks.size() || !std::isfinite(model.tasks[i].repl)) return std::nullopt;
154 return Value{std::round(model.tasks[i].repl)};
155 }
156private:
157 std::string task_;
158 int low_, high_;
159};
160
161class HostDemand final : public DecisionVariable {
162public:
163 HostDemand(std::string activity, double low, double high, std::string name = "")
164 : DecisionVariable(name.empty() ? activity + "_hostdemand" : std::move(name)),
165 activity_(std::move(activity)), low_(low), high_(high) {}
166 Value decode(const std::vector<double>& x) const override {
167 return layered_detail::continuous_decode(x, low_, high_);
168 }
169 void apply(lqn::LqnModel<double>& model, const Value& value) const override {
170 const std::size_t i = layered_detail::activity(model, activity_);
171 if (i < model.acts.size())
172 model.acts[i].hostdem = lang::Distrib<double>::exp_mean(scalar_value(value));
173 }
174 std::string type() const override { return "host_demand"; }
175 std::vector<std::string> layers(const lqn::LqnModel<double>& model) const override {
176 const std::string processor = layered_detail::activity_processor(model, activity_);
177 return processor.empty() ? std::vector<std::string>()
178 : std::vector<std::string>{processor};
179 }
180 std::optional<Value> current_value(const lqn::LqnModel<double>& model) const override {
181 const std::size_t i = layered_detail::activity(model, activity_);
182 if (i >= model.acts.size() || model.acts[i].hostdem.disabled) return std::nullopt;
183 return Value{model.acts[i].hostdem.mean};
184 }
185 bool supports_sensitivity() const override { return true; }
186 // the host-layer row of the sensitivity table: Station = processor, JobClass = this activity
187 std::string sensitivity_key(const lqn::LqnModel<double>& model) const override {
188 const std::string processor = layered_detail::activity_processor(model, activity_);
189 return processor.empty() ? std::string() : processor + "||" + activity_;
190 }
191 // srvn.ph collapses each caller into one class named after the TASK, a coarser row serving all its activities
192 std::vector<std::string> sensitivity_keys(const lqn::LqnModel<double>& model) const override {
193 std::vector<std::string> keys;
194 const std::string key = sensitivity_key(model);
195 if (key.empty()) return keys;
196 keys.push_back(key);
197 const std::string task = layered_detail::activity_task(model, activity_);
198 if (!task.empty() && task != activity_)
199 keys.push_back(layered_detail::activity_processor(model, activity_) + "||" + task);
200 return keys;
201 }
202 std::map<std::string,std::string> sensitivity_metric_targets(
203 const lqn::LqnModel<double>& model) const override {
204 const std::string processor = layered_detail::activity_processor(model, activity_);
205 return {{"Util", processor}, {"Tput", metric_key(activity_, activity_)},
206 {"QLen", metric_key(activity_, activity_)},
207 {"RespT", metric_key(activity_, activity_)}};
208 }
209 double rate_jacobian(const Value& value) const override {
210 const double demand = scalar_value(value);
211 return demand <= 0.0 ? 0.0 : -1.0 / (demand * demand);
212 }
213 double decode_jacobian(double) const override { return high_ - low_; }
214private:
215 std::string activity_;
216 double low_, high_;
217};
218
219class TaskThinkTime final : public DecisionVariable {
220public:
221 TaskThinkTime(std::string task, double low, double high, std::string name = "")
222 : DecisionVariable(name.empty() ? task + "_thinktime" : std::move(name)),
223 task_(std::move(task)), low_(low), high_(high) {}
224 Value decode(const std::vector<double>& x) const override {
225 return layered_detail::continuous_decode(x, low_, high_);
226 }
227 void apply(lqn::LqnModel<double>& model, const Value& value) const override {
228 const std::size_t i = layered_detail::task(model, task_);
229 if (i < model.tasks.size())
230 model.tasks[i].thinktime = lang::Distrib<double>::exp_mean(scalar_value(value));
231 }
232 std::string type() const override { return "think_time"; }
233 std::vector<std::string> layers(const lqn::LqnModel<double>& model) const override {
234 std::vector<std::string> out{task_};
235 const std::string processor = layered_detail::task_processor(model, task_);
236 if (!processor.empty()) out.push_back(processor);
237 return out;
238 }
239 std::optional<Value> current_value(const lqn::LqnModel<double>& model) const override {
240 const std::size_t i = layered_detail::task(model, task_);
241 if (i >= model.tasks.size() || model.tasks[i].thinktime.disabled) return std::nullopt;
242 return Value{model.tasks[i].thinktime.mean};
243 }
244private:
245 std::string task_;
246 double low_, high_;
247};
248
250public:
251 ActivityThinkTime(std::string activity, double low, double high, std::string name = "")
252 : DecisionVariable(name.empty() ? activity + "_thinktime" : std::move(name)),
253 activity_(std::move(activity)), low_(low), high_(high) {}
254 Value decode(const std::vector<double>& x) const override {
255 return layered_detail::continuous_decode(x, low_, high_);
256 }
257 void apply(lqn::LqnModel<double>& model, const Value& value) const override {
258 const std::size_t i = layered_detail::activity(model, activity_);
259 if (i < model.acts.size())
260 model.acts[i].thinktime = lang::Distrib<double>::exp_mean(scalar_value(value));
261 }
262 std::string type() const override { return "think_time"; }
263 std::vector<std::string> layers(const lqn::LqnModel<double>& model) const override {
264 std::vector<std::string> out;
265 const std::string task = layered_detail::activity_task(model, activity_);
266 const std::string processor = layered_detail::activity_processor(model, activity_);
267 if (!task.empty()) out.push_back(task);
268 if (!processor.empty()) out.push_back(processor);
269 return out;
270 }
271 std::optional<Value> current_value(const lqn::LqnModel<double>& model) const override {
272 const std::size_t i = layered_detail::activity(model, activity_);
273 if (i >= model.acts.size() || model.acts[i].thinktime.disabled) return std::nullopt;
274 return Value{model.acts[i].thinktime.mean};
275 }
276private:
277 std::string activity_;
278 double low_, high_;
279};
280
281} // namespace opt
282} // namespace line
283
284#endif
std::string type() const override
std::optional< Value > current_value(const lqn::LqnModel< double > &model) const override
std::vector< std::string > layers(const lqn::LqnModel< double > &model) const override
ActivityThinkTime(std::string activity, double low, double high, std::string name="")
Value decode(const std::vector< double > &x) const override
void apply(lqn::LqnModel< double > &model, const Value &value) const override
DecisionVariable(std::string n, std::size_t d=1)
Definition variables.h:36
const std::string & name() const
Definition variables.h:38
void apply(lqn::LqnModel< double > &model, const Value &value) const override
double rate_jacobian(const Value &value) const override
std::optional< Value > current_value(const lqn::LqnModel< double > &model) const override
std::vector< std::string > layers(const lqn::LqnModel< double > &model) const override
double decode_jacobian(double) const override
std::map< std::string, std::string > sensitivity_metric_targets(const lqn::LqnModel< double > &model) const override
HostDemand(std::string activity, double low, double high, std::string name="")
Value decode(const std::vector< double > &x) const override
std::string type() const override
std::vector< std::string > sensitivity_keys(const lqn::LqnModel< double > &model) const override
Candidate sensitivity-table rows, most specific first; the LN method decides how a layer names its cl...
bool supports_sensitivity() const override
std::string sensitivity_key(const lqn::LqnModel< double > &model) const override
std::string type() const override
ProcessorMultiplicity(std::string processor, int low, int high, std::string name="")
Value decode(const std::vector< double > &x) const override
void apply(lqn::LqnModel< double > &model, const Value &value) const override
std::vector< std::string > layers(const lqn::LqnModel< double > &) const override
std::optional< Value > current_value(const lqn::LqnModel< double > &model) const override
TaskMultiplicity(std::string task, int low, int high, std::string name="")
std::string type() const override
Value decode(const std::vector< double > &x) const override
std::vector< std::string > layers(const lqn::LqnModel< double > &model) const override
std::optional< Value > current_value(const lqn::LqnModel< double > &model) const override
void apply(lqn::LqnModel< double > &model, const Value &value) const override
std::string type() const override
TaskReplication(std::string task, int low, int high, std::string name="")
void apply(lqn::LqnModel< double > &model, const Value &value) const override
std::optional< Value > current_value(const lqn::LqnModel< double > &model) const override
Value decode(const std::vector< double > &x) const override
std::vector< std::string > layers(const lqn::LqnModel< double > &model) const override
Value decode(const std::vector< double > &x) const override
std::string type() const override
TaskThinkTime(std::string task, double low, double high, std::string name="")
std::optional< Value > current_value(const lqn::LqnModel< double > &model) const override
std::vector< std::string > layers(const lqn::LqnModel< double > &model) const override
void apply(lqn::LqnModel< double > &model, const Value &value) const override
double scalar_value(const Value &v)
Definition results.h:35
std::string metric_key(const std::string &station, const std::string &jobclass)
Definition results.h:36
std::vector< double > Value
Definition results.h:32
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
static Distrib exp_mean(const T &m)
Definition lang_types.h:940
The intermediate model, and the second stage that flattens it.
Definition lqn_reader.h:411
std::vector< detail::RawTask< T > > tasks
Definition lqn_reader.h:415
std::vector< detail::RawActivity< T > > acts
Definition lqn_reader.h:417
std::vector< detail::RawProc > procs
Definition lqn_reader.h:414
The decision variables of a flat queueing network.