147 : envObj(e), opt(o), stage_fn_(stage_fn) {
155 if (
opt.method !=
"avg" &&
opt.method !=
"dec")
157 "' is not a closed-form environment limit; the two are 'avg' "
158 "(fast environment) and 'dec' (slow environment)");
159 dec_ = (
opt.method ==
"dec");
160 if (!stage_fn_ &&
opt.stage_solver !=
"fluid")
162 "SolverENV limit: stage solver '" +
opt.stage_solver +
163 "' is not available; the limits solve each stage in STEADY STATE and the fluid "
164 "analyzer is the one this port wires into the environment. Hand a stage solver "
165 "to the EnvStageAvgFn constructor to run another one");
166 if (!std::is_same<T, double>::value)
168 "SolverENV limit: a fluid stage integrates its drift with LSODA, which is double "
169 "only; rerun with --arith double");
176 envObj.reject_lqn_stages(
178 "the fast/slow limits read a stage's station rates directly -- 'dec' solves each "
179 "stage network in steady state and 'avg' builds one network at the probEnv-weighted "
180 "rates -- and a layered model has no such rate table, only the layers SolverLN "
183 const std::size_t E = envObj.nstages();
184 M = envObj.stage(0).model.nstations;
185 K = envObj.stage(0).model.nclasses;
186 for (std::size_t e = 1; e < E; ++e)
187 if (envObj.stage(e).model.nstations != M || envObj.stage(e).model.nclasses != K)
189 "SolverENV limit: every stage must have the same stations and classes; the "
190 "metrics are blended entrywise across them");
204 const std::vector<double> arv_all = cache_arrival(
sn, stage.TN);
205 for (std::size_t c = 0; c < stage.cache.caches.size(); ++c) {
207 Entry& en = entry(cm);
209 std::vector<double> arv(std::max(K, en.arv.size()), 0.0);
210 for (std::size_t r = 0; r < arv.size(); ++r) {
211 const std::size_t ind = cm.
node;
212 const std::size_t off = (ind >= 1 ? (ind - 1) *
sn.nclasses + r : arv_all.size());
213 if (off < arv_all.size()) arv[r] = w * arv_all[off];
215 grow(en.arv, arv.size());
216 for (std::size_t r = 0; r < arv.size(); ++r) en.arv[r] += arv[r];
217 accum(en.hit, cm.
hitprob, arv);
225 solvers::CacheMetrics<double> finish()
const {
226 solvers::CacheMetrics<double> out;
227 for (std::size_t c = 0; c < order_.size(); ++c) {
228 const Entry& en = order_[c];
229 solvers::CacheNodeMetrics<double> cm;
232 cm.itemcap = en.itemcap;
233 cm.nitems = en.nitems;
234 cm.hitprob = divide(en.hit, en.arv);
235 cm.missprob = divide(en.miss, en.arv);
236 cm.delayedprob = divide(en.dhit, en.arv);
237 if (en.hitl.rows() > 0) {
238 cm.hitproblist = Matrix<double>(en.hitl.rows(), en.hitl.cols(), nan_());
239 for (std::size_t r = 0; r < en.hitl.rows(); ++r) {
240 const double d = r < en.arv.size() ? en.arv[r] : 0.0;
241 for (std::size_t l = 0; l < en.hitl.cols(); ++l)
242 cm.hitproblist(r, l) = d > 0.0 ? en.hitl(r, l) / d : nan_();
245 out.caches.push_back(cm);
252 std::size_t node = 0;
254 std::vector<double> itemcap;
255 std::size_t nitems = 0;
256 std::vector<double> arv;
258 std::vector<double> hit, miss, dhit;
262 static double nan_() {
return std::numeric_limits<double>::quiet_NaN(); }
264 static void grow(std::vector<double>& v, std::size_t n) {
265 if (v.size() < n) v.resize(n, 0.0);
268 Entry& entry(
const solvers::CacheNodeMetrics<T>& cm) {
269 for (std::size_t i = 0; i < order_.size(); ++i)
270 if (order_[i].name == cm.name)
return order_[i];
274 en.itemcap = cm.itemcap;
275 en.nitems = cm.nitems;
276 order_.push_back(en);
277 return order_.back();
281 static void accum(std::vector<double>& a,
const std::vector<T>& ratio,
282 const std::vector<double>& arv) {
283 if (ratio.empty())
return;
284 grow(a, ratio.size());
285 for (std::size_t r = 0; r < ratio.size() && r < arv.size(); ++r) {
286 const double v = num_traits<T>::to_double(ratio[r]);
287 if (std::isfinite(v)) a[r] += arv[r] * v;
291 static void accum_rows(Matrix<double>& a,
const Matrix<T>& hl,
292 const std::vector<double>& arv) {
293 if (hl.rows() == 0 || hl.cols() == 0)
return;
294 if (a.rows() == 0) a = Matrix<double>(hl.rows(), hl.cols(), 0.0);
295 for (std::size_t r = 0; r < hl.rows() && r < a.rows(); ++r) {
296 const double w = r < arv.size() ? arv[r] : 0.0;
297 for (std::size_t l = 0; l < hl.cols() && l < a.cols(); ++l) {
298 const double v = num_traits<T>::to_double(hl(r, l));
299 if (std::isfinite(v)) a(r, l) += w * v;
308 static std::vector<double> divide(
const std::vector<double>& a,
309 const std::vector<double>& d) {
310 std::vector<double> r;
311 if (a.empty())
return r;
312 r.assign(a.size(), nan_());
313 for (std::size_t i = 0; i < a.size(); ++i)
314 if (i < d.size() && d[i] > 0.0) r[i] = a[i] / d[i];
327 static std::vector<double> cache_arrival(
const qn::NetworkStruct<T>& sn,
328 const Matrix<double>& TN) {
329 const std::size_t I = sn.nodes.size(), R = sn.nclasses, M = sn.nstations;
330 std::vector<double> out(I * R, 0.0);
331 if (TN.rows() == 0)
return out;
332 Matrix<T> TNt(TN.rows(), TN.cols(), num_traits<T>::from_int(0));
333 for (std::size_t i = 0; i < TN.rows(); ++i)
334 for (std::size_t r = 0; r < TN.cols(); ++r)
335 TNt(i, r) = num_traits<T>::from_double(TN(i, r));
336 const Matrix<T> AN(M, R, num_traits<T>::from_int(0));
338 if (ANn.rows() != I)
return out;
339 for (std::size_t ind = 0; ind < I; ++ind)
340 for (std::size_t r = 0; r < R; ++r) {
341 const double v = num_traits<T>::to_double(ANn(ind, r));
342 out[ind * R + r] = std::isfinite(v) ? v : 0.0;
347 std::vector<Entry> order_;
351 EnvLimitSolution solve_dec() {
352 const std::size_t E = envObj.nstages();
353 EnvLimitSolution out;
355 out.prob_env = envObj.prob_env;
356 out.QN = Matrix<double>(M, K, 0.0);
357 out.UN = Matrix<double>(M, K, 0.0);
358 out.TN = Matrix<double>(M, K, 0.0);
359 out.QStage.assign(E, Matrix<double>(M, K, 0.0));
360 out.UStage.assign(E, Matrix<double>(M, K, 0.0));
361 out.TStage.assign(E, Matrix<double>(M, K, 0.0));
363 for (std::size_t e = 0; e < E; ++e) {
364 const EnvStageAvg<T> s = stage_steady_state(envObj.stage(e).model);
365 const double p = envObj.prob_env[e];
366 acc.add(envObj.stage(e).model, s, p);
367 for (std::size_t i = 0; i < M; ++i)
368 for (std::size_t r = 0; r < K; ++r) {
369 out.QStage[e](i, r) = s.QN(i, r);
370 out.UStage[e](i, r) = s.UN(i, r);
371 out.TStage[e](i, r) = s.TN(i, r);
372 out.QN(i, r) += p * s.QN(i, r);
373 out.UN(i, r) += p * s.UN(i, r);
374 out.TN(i, r) += p * s.TN(i, r);
377 out.cache = acc.finish();
382 EnvLimitSolution solve_avg() {
383 EnvLimitSolution out;
385 out.prob_env = envObj.prob_env;
386 const qn::NetworkStruct<T> avg = rate_averaged_model();
387 const EnvStageAvg<T> s = stage_steady_state(avg);
391 acc.add(avg, s, 1.0);
392 out.cache = acc.finish();
393 out.QN = Matrix<double>(M, K, 0.0);
394 out.UN = Matrix<double>(M, K, 0.0);
395 out.TN = Matrix<double>(M, K, 0.0);
396 for (std::size_t i = 0; i < M; ++i)
397 for (std::size_t r = 0; r < K; ++r) {
398 out.QN(i, r) = s.QN(i, r);
399 out.UN(i, r) = s.UN(i, r);
400 out.TN(i, r) = s.TN(i, r);
429 EnvStageAvg<T> stage_steady_state(
const qn::NetworkStruct<T>& sn)
const {
430 if (stage_fn_)
return stage_fn_(sn);
431 fluid::FluidOptions fo = opt.stage;
436 const fluid::FluidSolution s =
437 fluid::detail::fluid_has_cache(sn)
454 qn::NetworkStruct<T> rate_averaged_model()
const {
455 const std::size_t E = envObj.nstages();
456 qn::NetworkStruct<T> sn = envObj.stage(0).model;
457 std::vector<double> r(E, 0.0);
458 for (std::size_t i = 0; i < M; ++i) {
463 for (std::size_t k = 0; k < K; ++k) {
465 for (std::size_t e = 0; e < E && ok; ++e) {
466 const qn::NetworkStruct<T>& se = envObj.stage(e).model;
470 if (se.disabled[i][k]) {
474 r[e] = num_traits<T>::to_double(se.rates(i, k));
475 if (!(r[e] > 0.0) || !std::isfinite(r[e])) ok =
false;
478 double lo = r[0], hi = r[0], avg = 0.0;
479 for (std::size_t e = 0; e < E; ++e) {
480 lo = std::min(lo, r[e]);
481 hi = std::max(hi, r[e]);
482 avg += envObj.prob_env[e] * r[e];
486 if (hi - lo <= 1e-12 * std::max(1.0, hi))
continue;
487 sn.set_service(i + 1, k + 1,
495 Environment<T>& envObj;
498 std::size_t M = 0, K = 0;