LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
sn_to_qrf_alpha.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_API_SN_SN_TO_QRF_ALPHA_H
6#define LINE_API_SN_SN_TO_QRF_ALPHA_H
7
8/**
9 * @file
10 * @ingroup api_sn
11 * The QRF load-dependent rate scaling alpha(i,n), derived from an sn.
12 *
13 * Port of matlab/src/api/sn/sn_to_qrf_alpha.m.
14 *
15 * The load-dependent QRF arms carry a scaling alpha(i,n) that multiplies EVERY
16 * rate out of station i while it holds n jobs, completions mu and background
17 * phase changes v alike -- see the q construction in `qrf_noblo_mmi_ld`. That
18 * is exactly the rate law of
19 *
20 * an infinite server alpha(i,n) = n
21 * a c-server station alpha(i,n) = min(n, c_i)
22 * limited load dependence alpha(i,n) = sn.lldscaling(i,n)
23 *
24 * so the three COMPOSE BY MULTIPLICATION and not one of them is an
25 * approximation: the relaxed chain is the model's own, and the QRF answer keeps
26 * whatever status it had on a single-server model.
27 *
28 * WHERE IT STOPS BEING THE MODEL'S OWN IS PHASE-TYPE SERVICE AT A STATION THAT
29 * SERVES SEVERAL JOBS AT ONCE. The QRF local state carries ONE phase per
30 * station, a faithful description of one job in service and of nothing else:
31 * min(n,c) jobs served in parallel each advance through a phase of their own,
32 * and no scaling of a single-phase process reproduces that joint motion. A
33 * multiserver or delay station must therefore be exponential -- scaling a PH
34 * server by min(n,c) would answer a DIFFERENT chain, so the relaxation would
35 * stop containing the model's stationary distribution and the number would
36 * bound nothing. Limited load dependence at a SINGLE server is exempt and
37 * admits PH freely: one job is in service whatever the rate.
38 *
39 * THE UTILIZATION NORMALIZER IS THE DECLARED PEAK, NOT max(alpha). LINE reports
40 * U = T*S/peak at every station whose rate scales with the population, one
41 * convention shared by multiserver, lld and class dependence. `peak` is
42 * therefore nservers(i) times the largest lld scaling the model can REACH, and
43 * not max(alpha(i,:)): at c = 3 with N = 2 the reachable alpha peaks at 2 while
44 * the station still has three servers, and normalizing by 2 would report a
45 * utilization the model never attains. Infinite at a delay, where LINE reports
46 * U = QN instead.
47 */
48
49#include <algorithm>
50#include <cmath>
51#include <cstddef>
52#include <limits>
53#include <sstream>
54#include <string>
55#include <vector>
56
59#include "line/num/number.h"
60
61namespace line {
62namespace sn {
63
64/** The scaling, the utilization normalizer, and why they may not exist. */
65struct QrfAlpha {
66 std::vector<std::vector<double> > alpha; ///< (M x N) scaling at population n = 1..N
67 std::string msg; ///< empty on success
68 bool ld = false; ///< alpha is not identically 1
69 std::vector<double> peak; ///< (M) utilization normalizer; inf at a delay
70};
71
72/**
73 * `ld` stays TRUE through a refusal: the model IS load dependent, and the
74 * caller has to tell "no arm serves this" from "the arm you asked for does
75 * not". Clearing it would report the latter for both.
76 */
77template <class T>
79 QrfAlpha out;
80 const std::size_t M = L.nstations;
81 out.peak.assign(M, 1.0);
82
83 double Nd = 0.0;
84 const std::vector<double> njobs = L.njobs();
85 for (std::size_t r = 0; r < njobs.size(); ++r) Nd += njobs[r];
86 if (!(Nd >= 1.0) || std::isinf(Nd)) {
87 out.alpha.assign(M, std::vector<double>(1, 1.0));
88 out.msg = "the QRF bounds need a closed model with a finite population.";
89 return out;
90 }
91 const std::size_t N = static_cast<std::size_t>(Nd + 0.5);
92 out.alpha.assign(M, std::vector<double>(N, 1.0));
93
94 for (std::size_t i = 0; i < M; ++i) {
95 const double c = L.stations[i].nservers;
96 const bool is_delay = std::isinf(c) || L.stations[i].sched == qn::SchedStrategy::INF;
97 const bool serves_many = is_delay || c > 1.0;
98 const std::size_t smax = L.stations[i].lldscaling.size();
99 // The phase count is read where the adapter reads it, from the service
100 // process: that {D0,D1} pair is what sizes the local state, so testing
101 // it keeps the refusal and the formulation on one quantity.
102 std::size_t ki = 1;
103 {
104 const mam::Map<T> m = lang::dist_to_map(L.service[i][0]);
105 if (m.D0.rows() > 0) ki = static_cast<std::size_t>(m.D0.rows());
106 }
107 if (serves_many && ki > 1) {
108 std::ostringstream os;
109 os << "station " << (i + 1) << " serves ";
110 if (is_delay)
111 os << "unboundedly many";
112 else
113 os << "up to " << static_cast<int>(c);
114 os << " jobs at once with " << ki
115 << "-phase service, and the QRF local state carries one phase per station, which "
116 "describes one job in service and no more. Give that station exponential "
117 "service, or use a single-server model.";
118 out.msg = os.str();
119 out.ld = true;
120 return out;
121 }
122 double lldpeak = 1.0;
123 for (std::size_t n = 1; n <= N; ++n) {
124 double a = 1.0;
125 if (is_delay)
126 a = static_cast<double>(n);
127 else if (c > 1.0)
128 a = std::min(static_cast<double>(n), c);
129 if (smax > 0) {
130 const double s =
131 num_traits<T>::to_double(L.stations[i].lldscaling[std::min(n, smax) - 1]);
132 lldpeak = std::max(lldpeak, s);
133 a *= s;
134 }
135 out.alpha[i][n - 1] = a;
136 if (a != 1.0) out.ld = true;
137 }
138 out.peak[i] = is_delay ? std::numeric_limits<double>::infinity() : c * lldpeak;
139 }
140 return out;
141}
142
143} // namespace sn
144} // namespace line
145
146#endif // LINE_API_SN_SN_TO_QRF_ALPHA_H
A network plus its refreshed NetworkStruct.
std::vector< std::vector< Distrib< T > > > service
service[i][r], 0-based station and class; a disabled entry marks a pair never visited.
std::vector< Station< T > > stations
stations[k-1] is the k-th station
std::vector< double > njobs() const
sn.njobs: the population of each class, infinite for an open one.
What refreshProcessRepresentations and refreshLST compute FROM a distribution: the (D0,...
mam::Map< T > dist_to_map(const Distrib< T > &d)
QrfAlpha sn_to_qrf_alpha(const qn::NetworkStruct< T > &L)
ld stays TRUE through a refusal: the model IS load dependent, and the caller has to tell "no arm serv...
A queueing network and its refreshed NetworkStruct.
Number-type abstraction for the templated API port.
A MAP as the pair of matrices (D0, D1).
Definition map_moment.h:53
Matrix< T > D0
Definition map_moment.h:54
The scaling, the utilization normalizer, and why they may not exist.
bool ld
alpha is not identically 1
std::string msg
empty on success
std::vector< double > peak
(M) utilization normalizer; inf at a delay
std::vector< std::vector< double > > alpha
(M x N) scaling at population n = 1..N