LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
sn_patience_handles.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_PATIENCE_HANDLES_H
6#define LINE_API_SN_SN_PATIENCE_HANDLES_H
7
8/**
9 * @file
10 * @ingroup api_sn
11 * Port of matlab/src/api/sn/sn_patience_handles.m.
12 *
13 * The abandonment solvers need the patience law as FUNCTIONS -- a complementary
14 * cdf and a hazard rate -- not as moments, because that is what the underlying
15 * theory consumes: Whitt's engineering solution reads the hazard near the
16 * origin, and the fluid models integrate the ccdf. This port reads the law from
17 * `Station::patience`, which holds the distribution itself rather than the
18 * (D0,D1) pair the MATLAB and Java structs carry.
19 *
20 * ARITHMETIC: transcendental. The ccdf of a phase-type law is a matrix
21 * exponential.
22 */
23
24#include <cmath>
25#include <cstddef>
26#include <functional>
27#include <limits>
28#include <string>
29
37#include "line/num/number.h"
38
39namespace line {
40namespace api {
41
42/** The patience law of one station-class pair, in the forms the solvers consume. */
43template <class T>
45 /** Whether the pair declares reneging at all; everything below is unset when false. */
46 bool present = false;
47 /** F^c(t) = P(patience > t). */
48 std::function<T(const T&)> ccdf;
49 /** The patience density. */
50 std::function<T(const T&)> pdf;
51 /** The hazard rate h = f/(1-F). */
52 std::function<T(const T&)> hazard;
53 /** Mean patience. */
55 /** Whether the law is exponential, in which case the analysis is exact. */
56 bool isExponential = false;
57 /** The abandonment rate 1/mean. */
59
60 /** This law as the `Patience` argument of the abandonment solvers. */
65};
66
67/**
68 * Is `d` a Markovian law, i.e. one with a (D0,D1) pair the abandonment solvers
69 * can read exactly? The same family `basic_detail::is_markovian_type` names
70 * for SolverMAM's patience.
71 */
72template <class T>
93
94/**
95 * Port of `sn_patience_refusal.m`: the by-name refusal of a reneging patience
96 * that is not a Markovian law at station `ist` (0-based), class `r`, or "" when
97 * there is none. A Det, Gamma, Lognormal, Pareto, Uniform or Weibull patience is
98 * REFUSED, never replaced by an exponential of the same mean (or a fitted PH);
99 * SolverMAM, SolverMVA and SolverFLD raise this sentence in the gate and the run.
100 */
101template <class T>
102std::string sn_patience_refusal(const qn::NetworkStruct<T>& sn, std::size_t ist, std::size_t r) {
103 if (ist >= sn.nstations || r >= sn.nclasses) return std::string();
104 const qn::Station<T>& st = sn.stations[ist];
105 if (r >= st.impatience.size() || st.impatience[r] != lang::ImpatienceType::RENEGING) return std::string();
106 if (r >= st.patience.size() || st.patience[r].disabled ||
108 return std::string(); // reneging declared with no law at all: nothing to name
109 if (patience_is_markovian(st.patience[r])) return std::string();
110 return std::string("The patience of this class is ") + lang::process_to_text(st.patience[r].type) +
111 ", not a Markovian law (the patience must be a Markovian law: Exp, Erlang, HyperExp, "
112 "Coxian, APH or PH).";
113}
114
115/** `sn_patience_refusal` over every station-class pair: the first refusal, or "". */
116template <class T>
118 for (std::size_t i = 0; i < sn.nstations; ++i)
119 for (std::size_t r = 0; r < sn.nclasses; ++r) {
120 std::string why = sn_patience_refusal(sn, i, r);
121 if (!why.empty()) return why;
122 }
123 return std::string();
124}
125
126/**
127 * Port of `sn_patience_exp_refusal.m`: the by-name refusal of a reneging
128 * patience that is not EXPONENTIAL at station `ist` (0-based), class `r`, or ""
129 * when there is none. The stricter twin of `sn_patience_refusal`, worded in the
130 * same shape, for the analyses that read the patience as one memoryless rate:
131 * SolverCTMC and SolverSSA, whose RENEGE event keeps no patience phase per
132 * waiting job, and SolverMVA method 'erlanga', the M/M/s/r+M chain. Callers
133 * append their own redirect.
134 */
135template <class T>
136std::string sn_patience_exp_refusal(const qn::NetworkStruct<T>& sn, std::size_t ist, std::size_t r) {
137 if (ist >= sn.stations.size()) return std::string();
138 const qn::Station<T>& st = sn.stations[ist];
139 if (r >= st.impatience.size() || st.impatience[r] != lang::ImpatienceType::RENEGING) return std::string();
140 if (r >= st.patience.size() || st.patience[r].disabled || st.patience[r].type == lang::ProcessType::EXP)
141 return std::string();
142 return std::string("The patience of this class is ") + lang::process_to_text(st.patience[r].type) +
143 ", not an exponential law (the patience must be an exponential law: Exp).";
144}
145
146/** `sn_patience_exp_refusal` over every station-class pair: the first refusal, or "". */
147template <class T>
149 for (std::size_t i = 0; i < sn.stations.size(); ++i)
150 for (std::size_t r = 0; r < sn.stations[i].impatience.size(); ++r) {
151 std::string why = sn_patience_exp_refusal(sn, i, r);
152 if (!why.empty()) return why;
153 }
154 return std::string();
155}
156
157/**
158 * Build the patience handles of station `ist` (0-based), class `r`.
159 *
160 * Returns a handle set with `present == false` when the pair declares no
161 * Markovian reneging patience; a non-Markovian one is named by
162 * `sn_patience_refusal` (before 2026-10-22 it was read through dist_to_map).
163 */
164template <class T>
166 std::size_t r) {
168 if (ist >= sn.nstations || r >= sn.nclasses) return h;
169 const qn::Station<T>& st = sn.stations[ist];
170 if (r >= st.impatience.size() || st.impatience[r] != lang::ImpatienceType::RENEGING) return h;
171 if (r >= st.patience.size() || st.patience[r].disabled) return h;
172 if (!patience_is_markovian(st.patience[r])) return h;
173
174 const lang::Distrib<T>& d = st.patience[r];
175 const T zero = num_traits<T>::from_int(0);
176 const T one = num_traits<T>::from_int(1);
177 (void)one;
178 h.present = true;
179 h.mean = d.mean;
180 h.rate = d.mean > zero ? T(one / d.mean) : zero;
182
183 // WHAT IS AVAILABLE UNDER EXACT ARITHMETIC, and what is not. Whether the
184 // pair reneges, at what rate and under which family are all read off the
185 // struct, so `present`, `mean`, `rate` and `isExponential` are answered for
186 // every T -- which is what lets the method list and the method resolution
187 // run under Rational. The ccdf and the hazard are a matrix exponential, so
188 // they exist only where the arithmetic has one; the abandonment methods
189 // themselves are refused by name before they are ever called.
190 if constexpr (num_traits<T>::has_transcendental) {
191 if (h.isExponential) {
192 const T theta = h.rate;
193 h.ccdf = [theta](const T& t) { return qsys::detail::num_exp(T(-theta * t)); };
194 h.pdf = [theta](const T& t) { return T(theta * qsys::detail::num_exp(T(-theta * t))); };
195 h.hazard = [theta](const T&) { return theta; };
196 return h;
197 }
198
199 const mam::Map<T> m = lang::dist_to_map(d);
200 h.ccdf = [m, one](const T& t) {
201 std::vector<T> pts(1, t);
202 return T(one - mam::map_cdf(m, pts)[0]);
203 };
204 h.pdf = [m](const T& t) {
205 std::vector<T> pts(1, t);
206 return mam::map_pdf(m, pts)[0];
207 };
208 const T asymptotic = h.rate;
209 std::function<T(const T&)> ccdf = h.ccdf;
210 std::function<T(const T&)> pdf = h.pdf;
211 h.hazard = [ccdf, pdf, asymptotic](const T& t) {
212 // h = f/(1-F). Past the point where the ccdf underflows the hazard
213 // is the asymptotic decay rate, and returning that is better
214 // conditioned than dividing two zeros.
215 const T c = ccdf(t);
216 if (num_traits<T>::to_double(c) <= 1e-300) return asymptotic;
217 return T(pdf(t) / c);
218 };
219 }
220 return h;
221}
222
223} // namespace api
224} // namespace line
225
226#endif // LINE_API_SN_SN_PATIENCE_HANDLES_H
A network plus its refreshed NetworkStruct.
What refreshProcessRepresentations and refreshLST compute FROM a distribution: the (D0,...
Enumerations and the minimal distribution descriptor shared by the model layer of the C++ port.
Cumulative distribution of the inter-arrival time of a MAP.
Probability density of the inter-arrival time of a MAP.
PatienceHandles< T > sn_patience_handles(const qn::NetworkStruct< T > &sn, std::size_t ist, std::size_t r)
Build the patience handles of station ist (0-based), class r.
std::string sn_patience_exp_refusal(const qn::NetworkStruct< T > &sn, std::size_t ist, std::size_t r)
Port of sn_patience_exp_refusal.m: the by-name refusal of a reneging patience that is not EXPONENTIAL...
std::string sn_patience_refusal(const qn::NetworkStruct< T > &sn, std::size_t ist, std::size_t r)
Port of sn_patience_refusal.m: the by-name refusal of a reneging patience that is not a Markovian law...
bool patience_is_markovian(const lang::Distrib< T > &d)
Is d a Markovian law, i.e.
mam::Map< T > dist_to_map(const Distrib< T > &d)
The (D0,D1) pair that reaches sn.proc.
@ MPH
The MARKED families, MATLAB's ProcessType.m:36-38.
Definition lang_types.h:564
const char * process_to_text(ProcessType p)
The MATLAB ProcessType name, as sn.procid prints it.
Definition lang_types.h:601
std::vector< T > map_cdf(const Map< T > &m, const std::vector< T > &points)
Cumulative distribution of the inter-arrival time at the given points.
Definition map_cdf.h:63
std::vector< T > map_pdf(const Map< T > &m, const std::vector< T > &tset)
Probability density of the inter-arrival time at the given points.
Definition map_pdf.h:43
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
A queueing network and its refreshed NetworkStruct.
Number-type abstraction for the templated API port.
Engineering solution of the call-center model M/GI/s/r+GI.
Shared return type and arithmetic helpers for the templated qsys port.
The patience law of one station-class pair, in the forms the solvers consume.
std::function< T(const T &)> pdf
The patience density.
qsys::Patience< T > as_patience() const
This law as the Patience argument of the abandonment solvers.
std::function< T(const T &)> ccdf
F^c(t) = P(patience > t).
bool present
Whether the pair declares reneging at all; everything below is unset when false.
std::function< T(const T &)> hazard
The hazard rate h = f/(1-F).
bool isExponential
Whether the law is exponential, in which case the analysis is exact.
T rate
The abandonment rate 1/mean.
A MAP as the pair of matrices (D0, D1).
Definition map_moment.h:53
One station of the network.
std::vector< Distrib< T > > patience
Queue.setPatience(class, dist): the abandonment timer of a WAITING job, with impatience[r] naming whi...
std::vector< lang::ImpatienceType > impatience
The patience (time-to-abandon) law, in the three forms the algorithm accepts.
static Patience hazard(std::function< T(const T &)> h)
Patience given by its hazard rate h = f/(1-F).
static Patience exponential(const T &theta)
Exponential patience of rate theta, h(t) = theta, the Erlang A case.