LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Toggle main menu visibility
Loading...
Searching...
No Matches
fluid_conservation_guard.h
Go to the documentation of this file.
1
#pragma once
2
/**
3
* @file fluid_conservation_guard.h
4
* @ingroup line_solvers
5
* @brief Detects a moment-closure trajectory that has left the model.
6
*
7
* WHY IT EXISTS. The moment-closure drift can leave the simplex: on a station
8
* where min(n,c) is not the identity the Gaussian correction to the per-class
9
* share can drive a coordinate negative, and since the drift is conservative
10
* another grows to match. In MATLAB `odeset('NonNegative')` projects the
11
* ACCEPTED step, so the excursion is CLAMPED rather than reported -- which
12
* injects mass, collapses the step size, and leaves the window never returning.
13
* One MATLAB suite run sat in `test_CQN_Cox_CS_9` for 3h16m, and the 2026-08-27
14
* run was killed after `test11_interlock_lqnx` had held the suite for 100
15
* minutes, taking every block after it down with it.
16
*
17
* THIS PORT CANNOT HANG THE SAME WAY, and the difference is worth stating
18
* rather than papering over: `solver_fluid.h` clamps the state AFTER each
19
* window, not inside the integration, so the same divergence surfaces as a
20
* finished window holding a state that is not a solution -- silently, with
21
* every later window integrated from it. That is what this makes loud. Python
22
* DOES reproduce the reference's semantics (`_integrate_nonnegative` clips and
23
* restarts), so its guard runs per accepted step and halts the window.
24
*
25
* THE TEST IS AN EXACT INVARIANT, not a heuristic bound on time or magnitude.
26
* The drift conserves the population of every CLOSED CHAIN exactly, so any
27
* deviation is a divergence and nothing else. The tolerance is a generous
28
* fraction of that population rather than a numerical tolerance: the
29
* integrator's own error is ~1e-4 relative, while the documented excursion
30
* reaches 5.2e4 against a true population of 0.05. A closed model whose
31
* population has moved by TOL is no longer solving the model, whatever it is
32
* converging to.
33
*
34
* THE CHAIN IS THE CONSERVED UNIT, NOT THE CLASS, and the difference is the
35
* whole correctness of this check. A class population is what that class
36
* STARTS with; class switching then moves jobs between the classes of one
37
* chain, so only the chain total is invariant. Watching classes instead
38
* condemns every class-switching model out of hand -- measured on
39
* `cqn_twoclass_hyperl` (313 of 447 accepted states), on `init_state_ps` (286
40
* of 310) and on every one of the 162 fluid layers an LQN builds under the
41
* `srvn.cs` encoding, where the chain sum never moved at all. A cache model is
42
* the same story with the hit/miss classes.
43
*
44
* A wall-clock budget would have caught the same thing and was rejected: it
45
* makes the answer depend on how busy the host is, so the same model would fall
46
* back on one machine and not on another. This invariant is deterministic.
47
*/
48
49
#include <cmath>
50
#include <cstddef>
51
#include <limits>
52
#include <vector>
53
54
#include "
line/lang/qn/network_struct.h
"
55
#include "
line/solvers/fluid/fluid_nonhyperbolic.h
"
56
#include "
line/solvers/fluid/fluid_odes.h
"
57
58
namespace
line
{
59
namespace
fluid
{
60
61
/** Relative population drift that counts as having left the model. */
62
inline
constexpr
double
kFluidConservationTol
= 0.1;
63
64
// The "is the closure active" predicate is FluidClosure::gaussian(), which
65
// already exists: the first pass of the closure runs at sigma2 = 0 -- that pass
66
// IS the first-order solve -- and only the later ones can diverge. Gating on it
67
// keeps the guard off `closing` and `matrix`, which are also the fallback
68
// ladder's own rungs and must not be sent down a fallback by their own
69
// watchdog.
70
71
/**
72
* The classes of each chain, as 0-based column indices.
73
*
74
* `sn.inchain` holds them 1-based, one vector per chain. A struct declaring no
75
* chains degrades to one chain per class, which is the safe reading rather
76
* than a guess: with no chain map there is no class switching to merge
77
* classes, so each class IS its own conserved unit.
78
*/
79
template
<
typename
T>
80
inline
std::vector<std::vector<std::size_t>>
fluid_chain_partition
(
81
const
qn::NetworkStruct<T>
&
sn
, std::size_t K) {
82
std::vector<std::vector<std::size_t>> out;
83
if
(
sn
.inchain.empty()) {
84
out.reserve(K);
85
for
(std::size_t r = 0; r < K; ++r) out.push_back({r});
86
return
out;
87
}
88
out.reserve(
sn
.inchain.size());
89
for
(
const
std::vector<std::size_t>& chain :
sn
.inchain) {
90
std::vector<std::size_t> members;
91
members.reserve(chain.size());
92
for
(std::size_t r1 : chain)
93
if
(r1 >= 1 && r1 - 1 < K) members.push_back(r1 - 1);
94
out.push_back(members);
95
}
96
return
out;
97
}
98
99
/**
100
* The closed chain whose conserved population has drifted past `tol`, or -1.
101
*
102
* @param sn the network struct, for the chain membership and populations
103
* @param L the state layout, which fixes each (station,class) block
104
* @param x a state vector of length `L.nstates`
105
* @param tol relative deviation that counts as having left the model
106
*/
107
template
<
typename
T>
108
int
fluid_conservation_violation
(
const
qn::NetworkStruct<T>
&
sn
,
109
const
FluidLayout
& L,
110
const
std::vector<double>& x,
111
double
tol =
kFluidConservationTol
) {
112
const
std::size_t M = L.
qidx
.size();
113
if
(M == 0)
return
-1;
114
const
std::size_t K = L.
qidx
[0].size();
115
const
std::vector<std::vector<std::size_t>> chains =
fluid_chain_partition
(
sn
, K);
116
for
(std::size_t c = 0; c < chains.size(); ++c) {
117
double
target = 0.0;
118
for
(std::size_t r : chains[c])
119
target +=
static_cast<
double
>
(
sn
.classes[r].population);
120
if
(!std::isfinite(target) || target <= 0.0) {
121
continue
;
// open, or absent: no conserved population to check
122
}
123
double
mass = 0.0;
124
bool
any =
false
;
125
for
(std::size_t r : chains[c]) {
126
for
(std::size_t i = 0; i < M; ++i) {
127
const
std::size_t n = L.
kic
[i][r];
128
if
(n == 0)
continue
;
129
const
std::size_t base = L.
qidx
[i][r];
130
if
(base + n > x.size())
continue
;
// a different state vector
131
for
(std::size_t k = 0; k < n; ++k) mass += x[base + k];
132
any =
true
;
133
}
134
}
135
if
(!any)
continue
;
136
if
(std::fabs(mass - target) > tol * std::max(1.0, target)) {
137
return
static_cast<
int
>
(c);
138
}
139
}
140
return
-1;
141
}
142
143
}
// namespace fluid
144
}
// namespace line
line::qn::NetworkStruct
A network plus its refreshed NetworkStruct.
Definition
network_struct.h:838
fluid_nonhyperbolic.h
The one exception the fluid fallback ladder catches.
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_chain_partition
std::vector< std::vector< std::size_t > > fluid_chain_partition(const qn::NetworkStruct< T > &sn, std::size_t K)
The classes of each chain, as 0-based column indices.
Definition
fluid_conservation_guard.h:80
line::fluid::kFluidConservationTol
constexpr double kFluidConservationTol
Relative population drift that counts as having left the model.
Definition
fluid_conservation_guard.h:62
line::fluid::fluid_conservation_violation
int fluid_conservation_violation(const qn::NetworkStruct< T > &sn, const FluidLayout &L, const std::vector< double > &x, double tol=kFluidConservationTol)
The closed chain whose conserved population has drifted past tol, or -1.
Definition
fluid_conservation_guard.h:108
line::sn
Definition
sn_gd_balance.h:42
line
Definition
aoi_dist2ph.h:52
network_struct.h
A queueing network and its refreshed NetworkStruct.
line::fluid::FluidLayout
Where each (station, class) block sits in the state vector.
Definition
fluid_odes.h:86
line::fluid::FluidLayout::qidx
std::vector< std::vector< std::size_t > > qidx
0-based first index of (i,r)
Definition
fluid_odes.h:88
line::fluid::FluidLayout::kic
std::vector< std::vector< std::size_t > > kic
phases held by (i,r); 0 when disabled
Definition
fluid_odes.h:89
include
line
solvers
fluid
fluid_conservation_guard.h
Generated by
1.18.0