LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
evaluator.h
Go to the documentation of this file.
1#ifndef LINE_OPT_EVALUATOR_H
2#define LINE_OPT_EVALUATOR_H
3
4/**
5 * @file
6 * @ingroup line_opt
7 * Solves the model at one point of the variable space.
8 *
9 * `LineEvaluator` takes the model, the variables and the fixed values, applies a
10 * `VariableValues` to a copy of the model, solves it, and returns an
11 * `EvaluationResult`. This is the only place the optimizer touches a solver, so
12 * a caller can substitute its own by passing a `SolveFunction` -- which is what
13 * the tests do, and what lets a search run against a surrogate.
14 *
15 * `evaluation_count()` reports how many solves have been paid for, which is the
16 * cost measure the results carry. `LayerSensitivity` carries the per-layer
17 * derivatives the gradient optimizer of `line_opt_solver.h` consumes.
18 */
19
20#include <chrono>
21#include <cmath>
22#include <functional>
23#include <map>
24#include <string>
25#include <utility>
26#include <variant>
27#include <vector>
28
29#include "line/opt/results.h"
31#include "line/opt/variables.h"
35
36namespace line {
37namespace opt {
38
40 double throughput = 0.0;
41 double response_time = 0.0;
42 double queue_length = 0.0;
43 double utilization = 0.0;
44};
45
47public:
48 using Model = std::variant<qn::Network<double>, lqn::LqnModel<double>>;
49 using SolveFunction = std::function<mva::AvgResult<double>(
51 using LayerSensitivityMap = std::map<std::string, LayerSensitivity>;
52
53 LineEvaluator(qn::Network<double> model, std::vector<VariablePtr> variables,
54 std::vector<std::pair<VariablePtr, Value>> fixed = {},
56 : LineEvaluator(Model(std::move(model)), std::move(variables), std::move(fixed),
57 std::move(solve)) {}
58
59 LineEvaluator(lqn::LqnModel<double> model, std::vector<VariablePtr> variables,
60 std::vector<std::pair<VariablePtr, Value>> fixed = {})
61 : LineEvaluator(Model(std::move(model)), std::move(variables), std::move(fixed), {}) {}
62
63 LineEvaluator(Model model, std::vector<VariablePtr> variables,
64 std::vector<std::pair<VariablePtr, Value>> fixed = {},
66 : base_(std::move(model)), variables_(std::move(variables)),
67 fixed_(std::move(fixed)), solve_(std::move(solve)) {
68 if (!solve_)
69 solve_ = [](const qn::NetworkStruct<double>& structure) {
71 };
72 std::size_t dimension = 0;
73 for (const VariablePtr& variable : variables_) {
74 offsets_.push_back(dimension);
75 dimension += variable->dimension();
76 }
77 dimension_ = dimension;
78 }
79
80 bool is_layered() const {
81 return std::holds_alternative<lqn::LqnModel<double>>(base_);
82 }
83 std::size_t evaluation_count() const { return count_; }
84 std::vector<std::pair<double, double>> bounds() const {
85 return std::vector<std::pair<double, double>>(dimension_, {0.0, 1.0});
86 }
87 VariableValues decode(const std::vector<double>& x) const {
89 for (std::size_t i = 0; i < variables_.size(); ++i) {
90 const auto begin = x.begin() + offsets_[i];
91 out[variables_[i]->name()] = variables_[i]->decode(
92 std::vector<double>(begin, begin + variables_[i]->dimension()));
93 }
94 return out;
95 }
96
98 ++count_;
99 const auto start = std::chrono::steady_clock::now();
101 try {
102 if (is_layered())
103 evaluate_layered(values, out);
104 else
105 evaluate_flat(values, out);
106 out.feasible = true;
107 } catch (const std::exception&) {
108 out.feasible = false;
109 }
110 out.solve_time = std::chrono::duration<double>(
111 std::chrono::steady_clock::now() - start).count();
112 return out;
113 }
114
115 std::optional<LayerSensitivityMap> evaluate_layered_sensitivities(
116 const VariableValues& values) const {
117 if (!is_layered()) return std::nullopt;
118 try {
119 lqn::LqnModel<double> model = std::get<lqn::LqnModel<double>>(base_);
120 apply_layered(model, values);
121 lqn::LqnStruct<double> structure = lqn::lqn_finalize(model);
122 ln::SolverLN<double> solver(structure, ln::LnOptions());
126 for (const auto& row : table.rows)
127 out[metric_key(layered_name(row.station), layered_name(row.jobclass))] = {
128 row.dTput, row.dRespT, row.dQLen, row.dUtil};
129 return out;
130 } catch (const std::exception&) {
131 return std::nullopt;
132 }
133 }
134
135private:
136 static std::string layered_name(const std::string& value) {
137 if (value.size() > 2 && value[1] == ':' &&
138 std::string("PTERA").find(value[0]) != std::string::npos)
139 return value.substr(2);
140 return value;
141 }
142
143 void evaluate_flat(const VariableValues& values, EvaluationResult& out) const {
144 qn::Network<double> model = std::get<qn::Network<double>>(base_);
145 for (const auto& fixed : fixed_) fixed.first->apply(model, fixed.second);
146 for (const VariablePtr& variable : variables_) {
147 const auto found = values.find(variable->name());
148 if (found != values.end()) variable->apply(model, found->second);
149 }
150 const qn::NetworkStruct<double>& structure = model.get_struct();
151 const mva::AvgResult<double> average = solve_(structure);
152 out.solver_used = average.actualmethod;
153 for (std::size_t i = 0; i < structure.nstations; ++i) {
154 double utilization = 0.0;
155 for (std::size_t r = 0; r < structure.nclasses; ++r) {
156 const std::string& station = structure.stations[i].name;
157 const std::string& jobclass = structure.classes[r].name;
158 if (!average.RN.empty()) out.set_response_time(station, jobclass, average.RN(i, r));
159 if (!average.TN.empty()) out.set_throughput(station, jobclass, average.TN(i, r));
160 if (!average.QN.empty()) out.set_queue_length(station, jobclass, average.QN(i, r));
161 if (!average.UN.empty()) utilization += average.UN(i, r);
162 }
163 out.utilizations[structure.stations[i].name] = utilization;
164 }
165 const auto system = solvers::solver_get_avg_sys(structure, average);
166 for (std::size_t c = 0; c < structure.nchains; ++c) {
167 double response = c < system.CN.size()
168 ? system.CN[c]
169 : std::numeric_limits<double>::quiet_NaN();
170 const double throughput = c < system.XN.size() ? system.XN[c] : 0.0;
171 bool open = false;
172 double jobs = 0.0;
173 for (const std::size_t member : structure.inchain[c]) {
174 open = open || std::isinf(structure.classes[member - 1].population);
175 for (std::size_t i = 0; i < structure.nstations; ++i)
176 jobs += out.queue_length(structure.stations[i].name,
177 structure.classes[member - 1].name);
178 }
179 if (open && throughput > 0.0) response = jobs / throughput;
180 for (const std::size_t member : structure.inchain[c]) {
181 out.system_response_times[structure.classes[member - 1].name] = response;
182 out.system_throughputs[structure.classes[member - 1].name] = throughput;
183 }
184 }
185 const std::optional<SensitivityData> sensitivity =
187 if (sensitivity)
188 out.sensitivities = std::make_shared<SensitivityData>(*sensitivity);
189 }
190
191 void apply_layered(lqn::LqnModel<double>& model, const VariableValues& values) const {
192 for (const auto& fixed : fixed_) fixed.first->apply(model, fixed.second);
193 for (const VariablePtr& variable : variables_) {
194 const auto found = values.find(variable->name());
195 if (found != values.end()) variable->apply(model, found->second);
196 }
197 }
198
199 void evaluate_layered(const VariableValues& values, EvaluationResult& out) const {
200 lqn::LqnModel<double> model = std::get<lqn::LqnModel<double>>(base_);
201 apply_layered(model, values);
202 const lqn::LqnStruct<double> structure = lqn::lqn_finalize(model);
203 ln::SolverLN<double> solver(structure, ln::LnOptions());
204 const ln::LnSolution<double> average = solver.get_ensemble_avg();
205 out.solver_used = "SolverLN";
206 for (std::size_t i = 1; i <= structure.nidx; ++i) {
207 const std::string& name = structure.names[i];
208 if (i < average.RN.size() && i < average.defined_R.size() &&
209 average.defined_R[i] && std::isfinite(average.RN[i]))
210 out.set_response_time(name, name, average.RN[i]);
211 if (i < average.TN.size() && i < average.defined_T.size() &&
212 average.defined_T[i] && std::isfinite(average.TN[i]))
213 out.set_throughput(name, name, average.TN[i]);
214 if (i < average.QN.size() && i < average.defined_Q.size() &&
215 average.defined_Q[i] && std::isfinite(average.QN[i]))
216 out.set_queue_length(name, name, average.QN[i]);
217 if (i < average.UN.size() && i < average.defined_U.size() &&
218 average.defined_U[i] && std::isfinite(average.UN[i]))
219 out.utilizations[name] = average.UN[i];
220 }
221 for (std::size_t task = 1; task <= structure.ntasks; ++task) {
222 const std::size_t index = structure.tshift + task;
223 if (index >= structure.isref.size() || !structure.isref[index]) continue;
224 const std::string& name = structure.names[index];
225 const double throughput = out.throughput(name, name);
226 if (throughput > 0.0) out.system_throughputs[name] = throughput;
227 double response = 0.0;
228 bool have_response = false;
229 for (const std::size_t entry : structure.entriesof[index]) {
230 const double value = out.response_time(structure.names[entry],
231 structure.names[entry]);
232 if (std::isfinite(value)) {
233 response += value;
234 have_response = true;
235 }
236 }
237 if (have_response) out.system_response_times[name] = response;
238 }
239 }
240
241 Model base_;
242 std::vector<VariablePtr> variables_;
243 std::vector<std::pair<VariablePtr, Value>> fixed_;
244 SolveFunction solve_;
245 std::vector<std::size_t> offsets_;
246 std::size_t dimension_ = 0;
247 std::size_t count_ = 0;
248};
249
250} // namespace opt
251} // namespace line
252
253#endif
LnSensTable< T > get_sensitivity_table(const sens::SensOptions &sopt)
Port of @SolverLN/getSensitivityTable: solve the ensemble, then concatenate each layer solver's own t...
Definition solver_ln.h:582
LineEvaluator(Model model, std::vector< VariablePtr > variables, std::vector< std::pair< VariablePtr, Value > > fixed={}, SolveFunction solve={})
Definition evaluator.h:63
std::optional< LayerSensitivityMap > evaluate_layered_sensitivities(const VariableValues &values) const
Definition evaluator.h:115
VariableValues decode(const std::vector< double > &x) const
Definition evaluator.h:87
LineEvaluator(qn::Network< double > model, std::vector< VariablePtr > variables, std::vector< std::pair< VariablePtr, Value > > fixed={}, SolveFunction solve={})
Definition evaluator.h:53
std::function< mva::AvgResult< double >( const qn::NetworkStruct< double > &)> SolveFunction
Definition evaluator.h:49
bool is_layered() const
Definition evaluator.h:80
std::vector< std::pair< double, double > > bounds() const
Definition evaluator.h:84
EvaluationResult evaluate(const VariableValues &values)
Definition evaluator.h:97
std::variant< qn::Network< double >, lqn::LqnModel< double > > Model
Definition evaluator.h:48
LineEvaluator(lqn::LqnModel< double > model, std::vector< VariablePtr > variables, std::vector< std::pair< VariablePtr, Value > > fixed={})
Definition evaluator.h:59
std::map< std::string, LayerSensitivity > LayerSensitivityMap
Definition evaluator.h:51
std::size_t evaluation_count() const
Definition evaluator.h:83
A network plus its refreshed NetworkStruct.
A queueing network under construction.
const NetworkStruct< T > & get_struct()
The refreshed struct, MATLAB's model.getStruct().
LqnStruct< T > lqn_finalize(const LqnModel< T > &m)
Port of @LayeredNetwork/getStruct.m: flatten the model into its struct.
Definition lqn_reader.h:432
AvgResult< T > solver_mva_run_analyzer(const qn::NetworkStruct< T > &L, const MvaOptions &opt_in, const Matrix< T > &init_sol)
Port of @@SolverMVA/runAnalyzer.m for the lang='matlab' path: gate, solve, convert,...
std::string metric_key(const std::string &station, const std::string &jobclass)
Definition results.h:36
std::shared_ptr< DecisionVariable > VariablePtr
Definition variables.h:160
std::optional< SensitivityData > compute_model_sensitivities(qn::Network< double > model, bool use_ctmc=false)
std::map< std::string, Value > VariableValues
Definition results.h:33
SysResult< T > solver_get_avg_sys(const qn::NetworkStruct< T > &sn, const mva::AvgResult< T > &r)
Port of @@NetworkSolver/getAvgSys.m.
std::vector< T > solve(const Matrix< T > &A, const std::vector< T > &b)
Convenience: solve Ax = b, leaving A and b untouched.
Definition lu.h:158
What an evaluation and a solve return.
Parametric sensitivities of a solved network, as optimizer input.
The CHAIN-level and SYSTEM-level views of a solved model.
SolverLN: layered decomposition of a layered queueing network.
The SolverMVA class surface: @@SolverMVA/runAnalyzer.m and the gates around it.
Options of SolverLN.
Definition solver_ln.h:264
getSensitivityTable of the ensemble: the layer tables under a Layer column.
Definition solver_ln.h:414
std::vector< Row > rows
Definition solver_ln.h:419
The intermediate model, and the second stage that flattens it.
Definition lqn_reader.h:399
The options SolverMVA reads.
Definition mva_types.h:31
The name-value contract of getSensitivityTable.
The decision variables of a flat queueing network.