LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
sn_is_discrete_time.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_IS_DISCRETE_TIME_H
6#define LINE_API_SN_SN_IS_DISCRETE_TIME_H
7
8/**
9 * @file
10 * @ingroup api_sn
11 * Port of matlab/src/api/sn/sn_is_discrete_time.m.
12 *
13 * Decides whether every interarrival and service law of a model lives on the
14 * slot lattice {slotLength, 2*slotLength, ...}, which is what lets SolverMAM
15 * take the discrete-time (Q-MAM) path instead of the continuous one.
16 *
17 * The test reads `procid`, `rates` and `scv` and NOT the fitted `proc`
18 * matrices: by the time a solver sees the struct a Geometric has already been
19 * converted to a CONTINUOUS MAP, so the lattice is no longer visible there. A
20 * DMAP is the exception, its (D0,D1) having MAP shape it survives verbatim.
21 * For the same reason this test must run BEFORE any phase-type conversion.
22 *
23 * ARITHMETIC: field, with one square root in the DiscreteUniform width. Every
24 * other test is a comparison, so the family instantiates under Rational apart
25 * from that branch, which needs a real square root.
26 */
27
28#include <cmath>
29#include <cstddef>
30#include <string>
31
34#include "line/num/number.h"
35#include "line/util/error.h"
36
37namespace line {
38namespace api {
39
40/** Which laws the model carries, and why it was refused when it was. */
42 bool has_lattice = false; ///< some law lives on the slot lattice
43 bool has_continuous = false; ///< some law does not
44 bool mixed = false; ///< both, so no single time scale fits
45 bool has_dmap = false; ///< a DMAP is present, which has no continuous reading
46 std::string reason; ///< empty when the model is discrete-time
47};
48
49/** How the caller may override the automatic decision. */
51 /** "auto", "discrete" or "continuous". */
52 std::string timescale = "auto";
53 /** Slot length in model time units. */
54 double slotlength = 1.0;
55};
56
57namespace detail {
58
59/**
60 * Integral bounds of a DiscreteUniform recovered from its mean and SCV, using
61 * var = ((hi-lo+1)^2-1)/12. Returns false when the SCV is not usable.
62 */
63inline bool duniform_bounds(double mean_slots, double scv, double* lo, double* hi) {
64 if (!std::isfinite(scv) || scv < 0) return false;
65 const double var_slots = scv * mean_slots * mean_slots;
66 const double width = std::sqrt(std::max(0.0, 12 * var_slots + 1)) - 1;
67 *lo = std::round(mean_slots - width / 2);
68 *hi = std::round(mean_slots + width / 2);
69 return true;
70}
71
72} // namespace detail
73
74/**
75 * True when every law of `sn` is lattice-valued on `slot_length`.
76 *
77 * `slot_length` receives the slot in model time units and `info` the diagnosis.
78 * A DMAP mixed with continuous laws is an ERROR rather than a `false`: its
79 * (D0,D1) are probability matrices, so the continuous machinery would form
80 * inv(-D0) where the law needs inv(I-D0) and return a wrong number in silence.
81 */
82template <class T>
84 double* slot_length, DiscreteTimeInfo* info) {
86
87 const std::string& timescale = options.timescale;
88 if (timescale != "auto" && timescale != "discrete" && timescale != "continuous")
89 throw InputError(
90 "sn_is_discrete_time: timescale must be 'auto', 'discrete' or 'continuous'");
91 if (!std::isfinite(options.slotlength) || options.slotlength <= 0)
92 throw InputError("sn_is_discrete_time: slotlength must be a positive finite scalar");
93
94 *slot_length = options.slotlength;
95 *info = DiscreteTimeInfo();
96 if (timescale == "continuous") return false;
97
98 const double tol = 1e-12;
99 for (std::size_t ist = 0; ist < sn.nstations; ++ist)
100 for (std::size_t r = 0; r < sn.nclasses; ++r) {
101 const ProcessType pt = sn.procid(ist + 1, r + 1);
102 if (pt == ProcessType::DISABLED || pt == ProcessType::NONE) continue;
103 if (sn.disabled[ist][r]) continue;
104 const double rate = num_traits<T>::to_double(sn.rates(ist, r));
105 if (!std::isfinite(rate) || rate <= 0) continue; // no interval law here
106 const double mean_slots = 1.0 / (rate * options.slotlength);
107
108 if (pt == ProcessType::GEOMETRIC) {
109 // mean 1/p slots; p in (0,1] is recovered exactly from the mean
110 info->has_lattice = true;
111 if (mean_slots < 1 - tol) {
112 info->has_continuous = true;
113 info->reason = "a Geometric has a mean below the one-slot minimum of its "
114 "support {1,2,...}";
115 }
116 } else if (pt == ProcessType::DMAP) {
117 info->has_lattice = true;
118 info->has_dmap = true;
119 } else if (pt == ProcessType::DUNIFORM) {
120 info->has_lattice = true;
121 double lo = 0, hi = 0;
122 if (!detail::duniform_bounds(mean_slots, num_traits<T>::to_double(sn.scv(ist, r)),
123 &lo, &hi) ||
124 lo < 1 - tol) {
125 info->has_continuous = true;
126 info->reason = "a DiscreteUniform spans a range not contained in {1,2,...}";
127 }
128 } else if (pt == ProcessType::DET) {
129 if (std::abs(mean_slots - std::round(mean_slots)) <= tol * std::max(1.0, mean_slots) &&
130 std::round(mean_slots) >= 1) {
131 info->has_lattice = true;
132 } else {
133 // a Det off the lattice is what makes the model continuous
134 info->has_continuous = true;
135 }
136 } else {
137 info->has_continuous = true;
138 }
139 }
140
141 info->mixed = info->has_lattice && info->has_continuous;
142
143 if (timescale == "discrete") {
144 if (info->mixed)
145 throw InputError("sn_is_discrete_time: timescale='discrete' was requested but the "
146 "model mixes lattice and non-lattice laws: " +
147 info->reason);
148 if (!info->has_lattice)
149 throw InputError("sn_is_discrete_time: timescale='discrete' was requested but no "
150 "interarrival or service law is lattice-valued on the given slot");
151 return true;
152 }
153
154 const bool bool_out = info->has_lattice && !info->has_continuous;
155 if (!bool_out && info->has_dmap)
156 throw InputError("sn_is_discrete_time: the model mixes a DMAP with continuous-time laws. "
157 "A DMAP is only defined on a slotted time scale, so no solver can "
158 "interpret this model: " +
159 info->reason);
160 if (!bool_out && info->reason.empty() && info->mixed)
161 info->reason = "the model mixes lattice-valued and continuous laws";
162 return bool_out;
163}
164
165} // namespace api
166} // namespace line
167
168#endif // LINE_API_SN_SN_IS_DISCRETE_TIME_H
InputError(const std::string &what)
Definition error.h:39
A network plus its refreshed NetworkStruct.
The exception types the port throws.
Enumerations and the minimal distribution descriptor shared by the model layer of the C++ port.
bool sn_is_discrete_time(const qn::NetworkStruct< T > &sn, const DiscreteTimeOptions &options, double *slot_length, DiscreteTimeInfo *info)
True when every law of sn is lattice-valued on slot_length.
ProcessType
Distribution kinds, with the values of MATLAB ProcessType.
Definition lang_types.h:483
A queueing network and its refreshed NetworkStruct.
Number-type abstraction for the templated API port.
Which laws the model carries, and why it was refused when it was.
bool mixed
both, so no single time scale fits
bool has_dmap
a DMAP is present, which has no continuous reading
bool has_lattice
some law lives on the slot lattice
bool has_continuous
some law does not
std::string reason
empty when the model is discrete-time
How the caller may override the automatic decision.
std::string timescale
"auto", "discrete" or "continuous".
double slotlength
Slot length in model time units.