LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Toggle main menu visibility
Loading...
Searching...
No Matches
fluid_closing.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_SOLVERS_FLUID_FLUID_CLOSING_H
6
#define LINE_SOLVERS_FLUID_FLUID_CLOSING_H
7
8
/**
9
* @file
10
* @ingroup line_solvers
11
* Port of `solver_fluid_initsol.m`, and of the entry point of
12
* `solver_fluid_closing.m` that consumes it.
13
*
14
* WHY THE INITIAL CONDITION IS NOT AN IMPLEMENTATION DETAIL. The fluid answer
15
* is a fixed point of the drift, and a drift that is not convex has more than
16
* one: a closed model whose queue can either drain or saturate settles wherever
17
* it was pushed from. The integration therefore SELECTS a fixed point, and what
18
* selects it is y0. The reference does not guess: it decodes the model's own
19
* initial state, `sn.state`, into the fluid coordinates, so the ODE starts
20
* where the model says the system starts. Reproducing that exactly is the whole
21
* point of this file; a start that merely conserves the population is a
22
* different question with a possibly different answer.
23
*
24
* WHERE `sn.state` WENT. This port's NetworkStruct carries no state field, so
25
* there is nothing to read. What the reference reads, however, is the state
26
* `Network.initDefault` wrote: every closed class at its reference station,
27
* one job per class in service at each Source, everything else empty, and every
28
* started job in PHASE ONE -- `State.fromMarginalAndStarted` writes
29
* `init(1) = si(r)` and never enumerates the phase assignment.
30
*
31
* THE ONE RULE THAT IS EASY TO MISS. Outside a Source, phase one of a block
32
* does NOT get `kir(r,1)`: it gets `nir(r) - sum_{k>=2} kir(r,k)`, the jobs in
33
* service in phase one PLUS everyone waiting in the buffer. The fluid state has
34
* no waiting room -- a queue is one mass per phase -- so the buffer has to land
35
* somewhere, and the reference restarts it in phase one. On the state
36
* `initDefault` writes every `kir(r,k>=2)` is zero, so that term returns the
37
* station's whole population to phase one and the decode collapses to the
38
* closed form `detail::fluid_default_initsol` computes directly. The rule is
39
* recorded because it is what makes the two the same vector, not because two
40
* of them are kept.
41
*
42
* WHAT THIS FILE DOES NOT REDO. The drift, the restarting integration and the
43
* Q/U/R/T extraction of `solver_fluid_closing.m` are already ported in
44
* `fluid_odes.h` and `solver_fluid.h` (`fluid_closing_metrics`,
45
* `detail::fluid_dispatch`). `solver_fluid_closing` below is the reference
46
* function's SHAPE -- seed the initial condition, gate the method, integrate --
47
* over that machinery, so there is one copy of the metric rules and not two.
48
* The reference's second output, the expanded `state` cell, is not ported: no
49
* caller in `solver_fluid_analyzer.m` reads it.
50
*/
51
52
#include <cmath>
53
#include <cstddef>
54
#include <string>
55
#include <type_traits>
56
#include <vector>
57
58
#include "
line/lang/qn/network_struct.h
"
59
#include "
line/solvers/fluid/fluid_odes.h
"
60
#include "
line/solvers/fluid/solver_fluid.h
"
61
#include "
line/util/error.h
"
62
63
namespace
line
{
64
namespace
fluid
{
65
66
namespace
detail {
67
68
/**
69
* The disciplines `solver_fluid_initsol.m` knows how to decode.
70
*
71
* The reference's switch has two arms and an error arm, and the error arm is
72
* not a gap to be filled by falling through to the first arm: a discipline
73
* outside this list either holds its buffer in an encoding whose waiting jobs
74
* cannot be attributed to a phase (POLLING carries a controller, SRPT a
75
* remaining-work order) or is preemptive-resume, where a waiting job's phase is
76
* recorded per job and restarting it in phase one throws that away. Both would
77
* start the drift somewhere the model never is.
78
*
79
* GPS IS DECODED HERE AND STILL REFUSED BY MOST METHODS, which is not a
80
* contradiction: its state encoding folds into one mass per phase exactly like
81
* PS's, so `solver_fluid_initsol.m` lists it, while its capacity SHARE needs the
82
* backlog probability that only `minnormal` supplies. The refusal therefore
83
* belongs to the featset gate (`fluid_feature_set`), per method, and not here.
84
*
85
* Checked BEFORE the state row is decoded, which the reference does after; for
86
* an accepted discipline the two are the same, and for a refused one this
87
* reports the discipline rather than whatever the encoder happens to say first.
88
*/
89
inline
void
fluid_initsol_check_sched(
lang::SchedStrategy
s, std::size_t ist) {
90
switch
(s) {
91
case
lang::SchedStrategy::EXT
:
92
case
lang::SchedStrategy::FCFS
:
93
case
lang::SchedStrategy::SIRO
:
94
case
lang::SchedStrategy::PS
:
95
case
lang::SchedStrategy::INF
:
96
case
lang::SchedStrategy::DPS
:
97
case
lang::SchedStrategy::GPS
:
98
case
lang::SchedStrategy::HOL
:
99
case
lang::SchedStrategy::LCFS
:
100
case
lang::SchedStrategy::LCFSPR
:
101
return
;
102
default
:
103
break
;
104
}
105
throw
UnsupportedError
(
106
std::string(
"solver_fluid_initsol: station "
) + std::to_string(ist) +
" is scheduled '"
+
107
lang::sched_to_text
(s) +
108
"', whose state encoding this port cannot fold into one fluid mass per service phase; "
109
"only ext, fcfs, siro, ps, inf, dps, gps, hol, lcfs and lcfspr are decoded"
);
110
}
111
112
}
// namespace detail
113
114
/**
115
* Port of `solver_fluid_initsol.m`: the ODE's initial condition, in the layout
116
* the drift indexes.
117
*
118
* THIS IS THE DISCIPLINE GATE OVER `detail::fluid_default_initsol`, AND NOT A
119
* SECOND CONSTRUCTION OF THE SAME VECTOR. There used to be two: this one
120
* synthesized the `initDefault` marginal, pushed it through `from_marginal`,
121
* took the FIRST row and decoded it back through `to_marginal`, while the
122
* analyzer's own default wrote the population straight into the reference
123
* station's phase-one entry. They agreed on a one-phase model and disagreed on
124
* every other, so a multi-phase TRANSIENT was integrated from one of two
125
* different initial conditions depending on which entry point was called. The
126
* closed form is the correct one, for two reasons:
127
*
128
* THE ROW WAS THE WRONG ROW. `from_marginal` enumerates every phase
129
* assignment of the jobs in service in `multichoose` order, whose first row
130
* is `[0,...,0,n]` -- all of the mass in the LAST phase. The reference never
131
* enumerates: `State.fromMarginalAndStarted` writes `init(1) = si(r)` and so
132
* always starts its jobs in phase ONE, which is also what its
133
* `space(end:-1:1,:)` reordering exists to guarantee. Seeding an Erlang-2
134
* station's whole population in phase two starts the drift half a service
135
* ahead of where the model says the system is.
136
*
137
* AND IT WAS PAID FOR IN FACTORIALS. The FCFS branch of `from_marginal`
138
* enumerates the buffer PERMUTATIONS, so decoding the initial state of a
139
* closed station holding N jobs cost O(N!) before the first integration step.
140
*
141
* What the decode contributes that the closed form does not is the by-name
142
* refusal above, which is checked here per station and is the reference's own
143
* error arm; it is kept, and is the reason this wrapper exists at all.
144
*/
145
template
<
class
T>
146
std::vector<double>
fluid_initsol
(
const
qn::NetworkStruct<T>
&
sn
,
const
FluidLayout
& L) {
147
const
std::size_t M =
sn
.nstations, K =
sn
.nclasses;
148
for
(std::size_t i = 0; i < M; ++i) {
149
// A station with no enabled class occupies no fluid coordinates, which
150
// is how the reference's `isnan(ist)` skip comes out here.
151
bool
any =
false
;
152
for
(std::size_t r = 0; r < K; ++r) any = any || L.
enabled
[i][r];
153
if
(any) detail::fluid_initsol_check_sched(
sn
.stations[i].sched, i + 1);
154
}
155
return
detail::fluid_default_initsol(
sn
, L);
156
}
157
158
/** The same, for a caller that has not built the layout itself. */
159
template
<
class
T>
160
std::vector<double>
fluid_initsol
(
const
qn::NetworkStruct<T>
&
sn
) {
161
return
fluid_initsol
(
sn
,
fluid_layout
(
sn
));
162
}
163
164
/**
165
* Port of `solver_fluid_closing.m`: the closing family's entry point.
166
*
167
* IT RETURNS THE UNCORRECTED TABLE, as the reference function does. The
168
* utilization and response time that reach a user go through
169
* `fluid_analyzer_correct`, which `solver_fluid_analyzer.m` applies AFTER the
170
* method switch and to every branch alike; applying it here as well would
171
* either double it or fork it. Callers who want the analyzer's answer call
172
* `solver_fluid`, which is that function.
173
*
174
* The method is gated rather than forwarded because the analyzer's switch sends
175
* only these names here; `matrix` and `pnorm` are a different drift and
176
* `mfq`, `diffusion` and `rmf` are different solvers entirely, and answering
177
* for them under this name would report one method's number as another's.
178
*/
179
template
<
class
T>
180
FluidSolution
solver_fluid_closing
(
const
qn::NetworkStruct<T>
&
sn
,
const
FluidOptions
&
opt
) {
181
if
(!std::is_same<T, double>::value)
182
throw
UnsupportedError
(
183
"solver_fluid_closing: the fluid solver integrates its drift with LSODA, whose "
184
"coefficients assume double precision; rerun with --arith double"
);
185
186
std::string m =
opt
.method;
187
if
(m.size() > 6 && m.compare(0, 6,
"fluid."
) == 0) m = m.substr(6);
188
// `default` at THIS entry point means the closing drift: the analyzer's own
189
// default resolves to the matrix method, and a caller who wants that calls
190
// `solver_fluid`.
191
if
(m ==
"default"
) m =
"closing"
;
192
if
(!(m ==
"closing"
|| m ==
"statedep"
|| m ==
"softmin"
|| m ==
"tbi"
))
193
throw
UnsupportedError
(
"solver_fluid_closing: the '"
+
opt
.method +
194
"' method is not part of the closing family; 'matrix' and 'pnorm' "
195
"are solved by solver_fluid_matrix and 'mfq', 'diffusion' and 'rmf' "
196
"by their own solvers, all reachable through solver_fluid"
);
197
198
FluidOptions
o =
opt
;
199
o.
method
= m;
200
// The reference fills `options.init_sol` in the analyzer, before the switch,
201
// and re-fills it after every phase refitting; an empty one here means the
202
// caller has not overridden it, not that the drift may start anywhere.
203
if
(o.
init_sol
.empty()) o.
init_sol
=
fluid_initsol
(
sn
,
fluid_layout
(
sn
));
204
return
detail::fluid_dispatch(
sn
, o);
205
}
206
207
}
// namespace fluid
208
}
// namespace line
209
210
#endif
// LINE_SOLVERS_FLUID_FLUID_CLOSING_H
line::UnsupportedError::UnsupportedError
UnsupportedError(const std::string &what)
Definition
error.h:51
line::qn::NetworkStruct
A network plus its refreshed NetworkStruct.
Definition
network_struct.h:838
error.h
The exception types the port throws.
fluid_odes.h
The fluid drift: a port of solver_fluid_odes.m and the ode_jumps_new / ode_rate_base / ode_rates_clos...
line::fluid
Definition
fluid_aoi.h:81
line::fluid::fluid_layout
FluidLayout fluid_layout(const qn::NetworkStruct< T > &sn)
Port of the layout half of solver_fluid_odes.m.
Definition
fluid_odes.h:282
line::fluid::solver_fluid_closing
FluidSolution solver_fluid_closing(const qn::NetworkStruct< T > &sn, const FluidOptions &opt)
Port of solver_fluid_closing.m: the closing family's entry point.
Definition
fluid_closing.h:180
line::fluid::fluid_initsol
std::vector< double > fluid_initsol(const qn::NetworkStruct< T > &sn, const FluidLayout &L)
Port of solver_fluid_initsol.m: the ODE's initial condition, in the layout the drift indexes.
Definition
fluid_closing.h:146
line::lang::SchedStrategy
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
Definition
lang_types.h:181
line::lang::SchedStrategy::FCFS
@ FCFS
Definition
lang_types.h:183
line::lang::SchedStrategy::LCFS
@ LCFS
Definition
lang_types.h:184
line::lang::SchedStrategy::HOL
@ HOL
Definition
lang_types.h:193
line::lang::SchedStrategy::DPS
@ DPS
Definition
lang_types.h:189
line::lang::SchedStrategy::SIRO
@ SIRO
Definition
lang_types.h:185
line::lang::SchedStrategy::GPS
@ GPS
Definition
lang_types.h:190
line::lang::SchedStrategy::INF
@ INF
Definition
lang_types.h:182
line::lang::SchedStrategy::LCFSPR
@ LCFSPR
Definition
lang_types.h:197
line::lang::SchedStrategy::PS
@ PS
Definition
lang_types.h:188
line::lang::SchedStrategy::EXT
@ EXT
Definition
lang_types.h:195
line::lang::sched_to_text
const char * sched_to_text(SchedStrategy s)
Definition
lang_types.h:230
line::opt
Definition
bisection_solver.h:31
line::sn
Definition
sn_gd_balance.h:42
line
Definition
aoi_dist2ph.h:52
network_struct.h
A queueing network and its refreshed NetworkStruct.
solver_fluid.h
SolverFluid: the closing method, a port of solver_fluid.m, solver_fluid_iteration....
line::fluid::FluidLayout
Where each (station, class) block sits in the state vector.
Definition
fluid_odes.h:86
line::fluid::FluidLayout::enabled
std::vector< std::vector< bool > > enabled
whether (i,r) is served at all
Definition
fluid_odes.h:90
line::fluid::FluidOptions
Controls, defaulting to SolverOptions('Fluid') in the reference.
Definition
solver_fluid.h:88
line::fluid::FluidOptions::method
std::string method
Definition
solver_fluid.h:89
line::fluid::FluidOptions::init_sol
std::vector< double > init_sol
initial state; empty selects the default below
Definition
solver_fluid.h:101
line::fluid::FluidSolution
What the analyzer returns, in the same shape as the MVA solver's result.
Definition
solver_fluid.h:291
include
line
solvers
fluid
fluid_closing.h
Generated by
1.18.0