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 std::string sensitivity_key(const lqn::LqnModel<double>& model) const override {
187 const std::string processor = layered_detail::activity_processor(model, activity_);
188 const std::string task = layered_detail::activity_task(model, activity_);
189 return processor.empty() || task.empty() ? std::string()
190 : processor + "||" + task;
191 }
192 std::map<std::string,std::string> sensitivity_metric_targets(
193 const lqn::LqnModel<double>& model) const override {
194 const std::string processor = layered_detail::activity_processor(model, activity_);
195 return {{"Util", processor}, {"Tput", metric_key(activity_, activity_)},
196 {"QLen", metric_key(activity_, activity_)},
197 {"RespT", metric_key(activity_, activity_)}};
198 }
199 double rate_jacobian(const Value& value) const override {
200 const double demand = scalar_value(value);
201 return demand <= 0.0 ? 0.0 : -1.0 / (demand * demand);
202 }
203 double decode_jacobian(double) const override { return high_ - low_; }
204private:
205 std::string activity_;
206 double low_, high_;
207};
208
209class TaskThinkTime final : public DecisionVariable {
210public:
211 TaskThinkTime(std::string task, double low, double high, std::string name = "")
212 : DecisionVariable(name.empty() ? task + "_thinktime" : std::move(name)),
213 task_(std::move(task)), low_(low), high_(high) {}
214 Value decode(const std::vector<double>& x) const override {
215 return layered_detail::continuous_decode(x, low_, high_);
216 }
217 void apply(lqn::LqnModel<double>& model, const Value& value) const override {
218 const std::size_t i = layered_detail::task(model, task_);
219 if (i < model.tasks.size())
220 model.tasks[i].thinktime = lang::Distrib<double>::exp_mean(scalar_value(value));
221 }
222 std::string type() const override { return "think_time"; }
223 std::vector<std::string> layers(const lqn::LqnModel<double>& model) const override {
224 std::vector<std::string> out{task_};
225 const std::string processor = layered_detail::task_processor(model, task_);
226 if (!processor.empty()) out.push_back(processor);
227 return out;
228 }
229 std::optional<Value> current_value(const lqn::LqnModel<double>& model) const override {
230 const std::size_t i = layered_detail::task(model, task_);
231 if (i >= model.tasks.size() || model.tasks[i].thinktime.disabled) return std::nullopt;
232 return Value{model.tasks[i].thinktime.mean};
233 }
234private:
235 std::string task_;
236 double low_, high_;
237};
238
240public:
241 ActivityThinkTime(std::string activity, double low, double high, std::string name = "")
242 : DecisionVariable(name.empty() ? activity + "_thinktime" : std::move(name)),
243 activity_(std::move(activity)), low_(low), high_(high) {}
244 Value decode(const std::vector<double>& x) const override {
245 return layered_detail::continuous_decode(x, low_, high_);
246 }
247 void apply(lqn::LqnModel<double>& model, const Value& value) const override {
248 const std::size_t i = layered_detail::activity(model, activity_);
249 if (i < model.acts.size())
250 model.acts[i].thinktime = lang::Distrib<double>::exp_mean(scalar_value(value));
251 }
252 std::string type() const override { return "think_time"; }
253 std::vector<std::string> layers(const lqn::LqnModel<double>& model) const override {
254 std::vector<std::string> out;
255 const std::string task = layered_detail::activity_task(model, activity_);
256 const std::string processor = layered_detail::activity_processor(model, activity_);
257 if (!task.empty()) out.push_back(task);
258 if (!processor.empty()) out.push_back(processor);
259 return out;
260 }
261 std::optional<Value> current_value(const lqn::LqnModel<double>& model) const override {
262 const std::size_t i = layered_detail::activity(model, activity_);
263 if (i >= model.acts.size() || model.acts[i].thinktime.disabled) return std::nullopt;
264 return Value{model.acts[i].thinktime.mean};
265 }
266private:
267 std::string activity_;
268 double low_, high_;
269};
270
271} // namespace opt
272} // namespace line
273
274#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
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
static Distrib exp_mean(const T &m)
Definition lang_types.h:799
The intermediate model, and the second stage that flattens it.
Definition lqn_reader.h:399
std::vector< detail::RawTask< T > > tasks
Definition lqn_reader.h:401
std::vector< detail::RawActivity< T > > acts
Definition lqn_reader.h:403
std::vector< detail::RawProc > procs
Definition lqn_reader.h:400
The decision variables of a flat queueing network.