![]() |
LINE Solver (C++)
Templated C++ port of the LINE queueing solver
|
Detects a moment-closure trajectory that has left the model. More...
#include <cmath>#include <cstddef>#include <limits>#include <vector>#include "line/lang/qn/network_struct.h"#include "line/solvers/fluid/fluid_nonhyperbolic.h"#include "line/solvers/fluid/fluid_odes.h"Go to the source code of this file.
Namespaces | |
| namespace | line |
| namespace | line::fluid |
Functions | |
| template<typename T> | |
| std::vector< std::vector< std::size_t > > | line::fluid::fluid_chain_partition (const qn::NetworkStruct< T > &sn, std::size_t K) |
| The classes of each chain, as 0-based column indices. | |
| template<typename T> | |
| int | line::fluid::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. | |
Variables | |
| constexpr double | line::fluid::kFluidConservationTol = 0.1 |
| Relative population drift that counts as having left the model. | |
Detects a moment-closure trajectory that has left the model.
WHY IT EXISTS. The moment-closure drift can leave the simplex: on a station where min(n,c) is not the identity the Gaussian correction to the per-class share can drive a coordinate negative, and since the drift is conservative another grows to match. In MATLAB odeset('NonNegative') projects the ACCEPTED step, so the excursion is CLAMPED rather than reported – which injects mass, collapses the step size, and leaves the window never returning. One MATLAB suite run sat in test_CQN_Cox_CS_9 for 3h16m, and the 2026-08-27 run was killed after test11_interlock_lqnx had held the suite for 100 minutes, taking every block after it down with it.
THIS PORT CANNOT HANG THE SAME WAY, and the difference is worth stating rather than papering over: solver_fluid.h clamps the state AFTER each window, not inside the integration, so the same divergence surfaces as a finished window holding a state that is not a solution – silently, with every later window integrated from it. That is what this makes loud. Python DOES reproduce the reference's semantics (_integrate_nonnegative clips and restarts), so its guard runs per accepted step and halts the window.
THE TEST IS AN EXACT INVARIANT, not a heuristic bound on time or magnitude. The drift conserves the population of every CLOSED CHAIN exactly, so any deviation is a divergence and nothing else. The tolerance is a generous fraction of that population rather than a numerical tolerance: the integrator's own error is ~1e-4 relative, while the documented excursion reaches 5.2e4 against a true population of 0.05. A closed model whose population has moved by TOL is no longer solving the model, whatever it is converging to.
THE CHAIN IS THE CONSERVED UNIT, NOT THE CLASS, and the difference is the whole correctness of this check. A class population is what that class STARTS with; class switching then moves jobs between the classes of one chain, so only the chain total is invariant. Watching classes instead condemns every class-switching model out of hand – measured on cqn_twoclass_hyperl (313 of 447 accepted states), on init_state_ps (286 of 310) and on every one of the 162 fluid layers an LQN builds under the srvn.cs encoding, where the chain sum never moved at all. A cache model is the same story with the hit/miss classes.
A wall-clock budget would have caught the same thing and was rejected: it makes the answer depend on how busy the host is, so the same model would fall back on one machine and not on another. This invariant is deterministic.
Definition in file fluid_conservation_guard.h.