![]() |
LINE Solver (C++)
Templated C++ port of the LINE queueing solver
|
The fluid drift: a port of solver_fluid_odes.m and the ode_jumps_new / ode_rate_base / ode_rates_closing triple it drives on the DEFAULT (closing) method. More...
#include <cmath>#include <cstddef>#include <functional>#include <string>#include <vector>#include "line/api/mam/map_moment.h"#include "line/lang/qn/network_struct.h"#include "line/solvers/fluid/fluid_closures.h"#include "line/util/error.h"#include "line/api/mc/dtmc_stochcomp.h"#include "line/util/matrix.h"Go to the source code of this file.
Classes | |
| struct | line::fluid::FluidLayout |
| Where each (station, class) block sits in the state vector. More... | |
| struct | line::fluid::FluidEvent |
| One event of the drift. More... | |
| struct | line::fluid::FluidClosure |
| The second moment the drift closes its non-linear terms with, i.e. More... | |
| struct | line::fluid::FluidRateMult |
| The assembled drift: the layout, the events, and the per-station schedule. More... | |
| struct | line::fluid::FluidOdeSystem |
Namespaces | |
| namespace | line |
| namespace | line::fluid |
Functions | |
| void | line::fluid::fluid_interpcols (const std::vector< double > &tg, const Matrix< double > &B, double tt, std::vector< double > &out) |
| Port of fluid_interpcols.m: clamped piecewise-linear interpolation of the columns of B at a scalar time, into out. | |
| template<class T> | |
| FluidLayout | line::fluid::fluid_layout (const qn::NetworkStruct< T > &sn) |
| Port of the layout half of solver_fluid_odes.m. | |
| template<class T> | |
| FluidOdeSystem | line::fluid::fluid_ode_system (const qn::NetworkStruct< T > &sn) |
| Build the drift of sn: the port of ode_jumps_new and ode_rate_base fused into one pass. | |
| Matrix< double > | line::fluid::fluid_jump_matrix (const FluidOdeSystem &sys) |
| The reference's dense jump matrix D, (nstates x nevents), rebuilt from the two-index event form this port stores instead. | |
| void | line::fluid::fluid_rates_closing_factors (const FluidOdeSystem &sys, const double *x, std::vector< double > &g) |
| Port of ode_rates_closing_factors: the state-dependent factor g(x), in place. | |
| void | line::fluid::fluid_rates_closing (const FluidOdeSystem &sys, const double *x, std::vector< double > &g) |
| The reference's ode_rates_closing name, kept for the first-order callers. | |
| std::function< void(double, const double *, double *)> | line::fluid::fluid_drift (const FluidOdeSystem &sys) |
| The drift dx/dt, ready to hand to the integrator. | |
The fluid drift: a port of solver_fluid_odes.m and the ode_jumps_new / ode_rate_base / ode_rates_closing triple it drives on the DEFAULT (closing) method.
WHAT THE STATE VECTOR IS. One entry per (station, class, service phase), laid out station-major then class-major then phase, and holding the mean number of class-r jobs at station i currently in phase k. q_indices(i,r) is where that block starts and Kic(i,r) is how long it is; a (station, class) pair with no service contributes NO entries at all, which is why the layout has to be computed rather than assumed to be M*K*phases.
WHY THE DRIFT FACTORS THE WAY IT DOES. Every event is a single job moving: it either completes at (i,c,ki) and starts at (j,l,kj), or it changes phase within (i,c). So each event's effect on the state is a vector with one -1 and one +1, and the drift is
dx/dt = sum over events of jump_e * rate_e(x)
The reference stores those jumps as a dense (nstates x nevents) matrix and multiplies. THIS PORT STORES THE TWO INDICES INSTEAD, which is the same arithmetic – the matrix has exactly two nonzeros per column – while turning an O(nstates * nevents) product into O(nevents) per evaluation. On a model with a few hundred states that is the difference between a fluid solve dominated by the drift and one dominated by the integrator, and LSODA evaluates the drift thousands of times.
The rate of an event factors into a part fixed by the model and a part that depends on the state:
rate_e(x) = rateBase_e * g(x)_{eventIdx_e}
rateBase folds the phase completion rate, the routing probability and the destination's entry-phase probability into one number computed once; g(x) is where the scheduling lives, and is the only thing re-evaluated. That split is the reference's and is what makes the drift cheap.
WHAT g(x) IS, PER DISCIPLINE (ode_rates_closing_factors). g starts as x itself, which is already right for a delay and for any station whose population is below its server count, and is then corrected: INF nothing, unless a load-dependent alpha(n_i) scales the station EXT the source keeps unit mass per class, so the first phase absorbs whatever the other phases do not hold PS/FCFS the servers are shared: the block is scaled by psi(n_i)/n_i, with psi = min(n_i,c)*alpha(n_i) the work the station clears DPS the same capacity psi, split by the WEIGHTED share w_j x_j / sum GPS the server is split by weight among the BACKLOGGED classes, then equally among that class's own jobs Any other discipline is left as x, exactly as the reference leaves it – which means it is integrated as an infinite server, and is why the featset gate refuses the disciplines that have no branch here.
ALL THREE NON-LINEAR TERMS ABOVE TAKE A SECOND MOMENT WHEN ONE IS SUPPLIED (FluidClosure): min() through fluid_capacity_closure, the PS/DPS ratio through fluid_share_closure, the GPS indicator through fluid_gps_share. With no closure every one of them collapses to its value at the mean, so the first-order methods take exactly the same code path.
Definition in file fluid_odes.h.