LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
line::ldes Namespace Reference

Namespaces

namespace  engine
namespace  ssj
namespace  warm_detail

Classes

struct  LdesCacheMetrics
 Per-cache hit/miss/latency, as the cacheMetrics block carries them. More...
struct  LdesOptions
 The knobs of one LDES run. More...
struct  LdesResult
 One ldes-result document, parsed. More...
struct  LdesRunner
 One runner: the argv prefix that runs the engine, and the name of its image. More...
struct  LdesStateQuery
 One constraint of a joint-state query: a station and the per-class job counts it is required to hold. More...

Functions

template<class T>
LdesResult ldes_engine_solve_one (const qn::NetworkStruct< T > &sn, const LdesOptions &o)
 Simulate sn in process and return the same record the subprocess client parses out of an ldes-result document.
template<class T>
LdesResult ldes_engine_solve (const qn::NetworkStruct< T > &sn, const LdesOptions &o)
 Simulate sn, over independent REPLICATIONS when options.replications > 1.
template<class T>
engine::LnResult ldes_ln_engine_solve (const lqn::LqnStruct< T > &lsn, const LdesOptions &o)
 Simulate a layered model in process.
std::vector< std::string > list_valid_methods ()
 Port of SolverLDES.listValidMethods.
const std::string & ldes_engine_dir ()
 The directory holding the engine, or empty when there is none.
bool ldes_is_available ()
 True when this machine can run the engine at all, by either image.
template<class T>
Matrix< double > warm_start_placement_from_qlen (const qn::NetworkStruct< T > &sn, const Matrix< T > &QN)
 Port of placementFromMeanQLen: an integer placement from mean queue lengths.
template<class T>
Matrix< double > warm_start_placement_from_ctmc (const qn::NetworkStruct< T > &sn, const ctmc::CtmcOptions &opt)
 Port of placementFromCtmcSteadyState: the mode of the aggregate stationary law.
void init_from_placement (LdesOptions &o, const Matrix< double > &P)
 Port of initFromSolver's last step: the placement becomes init_sol, STATION-MAJOR (reshape(placement', 1, M*K)), and the transient filter is disabled with tranfilter = fixed, warmupfrac = 0.
std::vector< std::string > ldes_flags (const LdesOptions &o, const std::vector< std::string > &extra)
 The engine flags of one run, after solve <model> -o <result>.
std::vector< LdesRunner > ldes_runners (const std::string &doc, const std::vector< std::string > &flags)
 The runners to try, in order (see the file header for why there are two and when the order flips).
detail::Json ldes_solve_rest (const std::string &base_url, const std::string &doc, const std::vector< std::string > &flags, double timeout)
 Solves through an LDES REST server and returns its result document.
LdesResult parse_ldes_result (const detail::Json &d)
 Parses one ldes-result document.
LdesResult solver_ldes_text (const std::string &doc, const LdesOptions &o, const std::vector< std::string > &extra_flags=std::vector< std::string >())
 Runs one LDES simulation on a model.json DOCUMENT and parses its result.
LdesResult solver_ldes_file (const std::string &path, const LdesOptions &o, const std::vector< std::string > &extra_flags=std::vector< std::string >())
 The same, reading the document from a file.
double ldes_prob_from_histogram (const LdesResult &r, std::size_t nclasses, const std::vector< LdesStateQuery > &query)
 Residence-time probability of an aggregate joint state, from a parsed result.
Matrix< double > ldes_cdf_respt (const LdesResult &r, std::size_t station, std::size_t job_class)
 Port of getCdfRespT: the EMPIRICAL response time CDF of one (station, class) pair, built from the per-job samples the engine exports under --respt-samples.
double ldes_prob_aggr (const std::string &doc, const LdesOptions &o, std::size_t station, const std::vector< double > &counts, std::size_t nclasses)
 Port of getProbAggr: the marginal probability of a per-class job count at one station.
double ldes_prob_sys_aggr (const std::string &doc, const LdesOptions &o, const Matrix< double > &target)
 Port of getProbSysAggr: the joint probability of a whole aggregate state.
template<class T>
LdesResult solver_ldes (const qn::NetworkStruct< T > &sn, const LdesOptions &o, const std::vector< std::string > &extra_flags=std::vector< std::string >())
 The same, for a model built through the C++ API.

Function Documentation

◆ init_from_placement()

void line::ldes::init_from_placement ( LdesOptions & o,
const Matrix< double > & P )
inline

Port of initFromSolver's last step: the placement becomes init_sol, STATION-MAJOR (reshape(placement', 1, M*K)), and the transient filter is disabled with tranfilter = fixed, warmupfrac = 0.

Definition at line 166 of file ldes_warm_start.h.

References line::Matrix< T >::cols(), init_from_placement(), line::ldes::LdesOptions::init_sol, line::Matrix< T >::rows(), line::ldes::LdesOptions::tranfilter, and line::ldes::LdesOptions::warmupfrac.

Referenced by init_from_placement().

◆ ldes_cdf_respt()

Matrix< double > line::ldes::ldes_cdf_respt ( const LdesResult & r,
std::size_t station,
std::size_t job_class )
inline

Port of getCdfRespT: the EMPIRICAL response time CDF of one (station, class) pair, built from the per-job samples the engine exports under --respt-samples.

The returned matrix is (n x 2) with columns [F(t), t], NOT [t, F(t)]. That order is the convention every getCdfRespT follows across the codebases (MATLAB @SolverLDES/getCdfRespT.m, Python SolverJMT.getCdfRespT), and it is the REVERSE of the transient getTranCdfRespT, which is a different method with a different contract. Swapping them yields a CDF that reads as a time axis and vice versa, with no error anywhere.

A pair the run observed nothing at returns an EMPTY matrix rather than a fabricated law: an analytical fallback carrying the right mean says nothing about the tail, which is the whole reason to ask a simulator for a CDF. Repeated observations are collapsed, keeping the largest CDF value at each distinct time, or the ecdf is multivalued and an interpolating consumer reads a quantile off whichever duplicate it happens to hit.

Definition at line 811 of file solver_ldes.h.

References ldes_cdf_respt(), and line::ldes::LdesResult::respTimeSamples.

Referenced by ldes_cdf_respt().

◆ ldes_engine_dir()

const std::string & line::ldes::ldes_engine_dir ( )
inline

The directory holding the engine, or empty when there is none.

Definition at line 239 of file ldes_probe.h.

References ldes_engine_dir().

Referenced by ldes_engine_dir(), ldes_is_available(), and ldes_runners().

◆ ldes_engine_solve()

template<class T>
LdesResult line::ldes::ldes_engine_solve ( const qn::NetworkStruct< T > & sn,
const LdesOptions & o )

Simulate sn, over independent REPLICATIONS when options.replications > 1.

Each replication is one path with its OWN seed, seed + r, exactly as Solver_ldes_analyzer_parallel derives them. That is what makes the paths independent while keeping the whole run reproducible; sharing a seed would make every replication identical and collapse the interval to zero, and drawing them randomly would make the run irreproducible.

THE INTERVAL COMES FROM THE SPREAD BETWEEN REPLICATIONS, not from within a path, and it is the honest one: independent paths need no assumption about the correlation structure that batch means has to model. The cost is that replications - 1 degrees of freedom is few, so the t critical value is large and the interval wide – which is the estimate, not a defect of it.

A single replication yields NO interval rather than a zero-width one: with one path there is no spread to measure, and reporting zero would read as perfect precision.

Definition at line 6259 of file ldes_engine.h.

References line::ldes::LdesOptions::cimethod, line::ldes::LdesResult::CN, line::ldes::LdesOptions::confint, ldes_engine_solve(), ldes_engine_solve_one(), line::ldes::LdesResult::nclasses, line::ldes::LdesResult::nstations, line::ldes::LdesResult::QN, line::ldes::LdesResult::QNCI, line::ldes::LdesOptions::replications, line::ldes::LdesResult::RN, line::ldes::LdesResult::RNCI, line::ldes::LdesOptions::seed, line::ldes::LdesResult::TN, line::ldes::LdesResult::TNCI, line::ldes::LdesResult::total_simulated_events, line::ldes::LdesResult::UN, line::ldes::LdesResult::UNCI, and line::ldes::LdesResult::XN.

Referenced by ldes_engine_solve(), and main().

◆ ldes_engine_solve_one()

template<class T>
LdesResult line::ldes::ldes_engine_solve_one ( const qn::NetworkStruct< T > & sn,
const LdesOptions & o )

Simulate sn in process and return the same record the subprocess client parses out of an ldes-result document.

A metric the run did not measure stays EMPTY rather than becoming a matrix of zeros: zero is a measurement and absence is not.

Snap a sampled duration onto the slot lattice, or REFUSE it.

A non-lattice sample is an ERROR, not something to round: rounding silently changes the distribution, and a model whose service law is not lattice-valued is not a discrete-time model. The reference refuses for the same reason. Only Geometric, Det with an integral slot count, and DMAP produce lattice values.

ONE STREAM PER (node, class), with the reference's own offsets.

Solver_ssj never shares a stream between two generators: it builds one MRG32k3a and one java.util.Random per (node, class) and seeds them from seed + offset, with

arrival (source s, class k)   stream (s*K + k)*10,  random the same + 2000
service (svc j, class k)      both ((numSources + j)*K + k)*10 + 1000
routing                       ROUTING_SEED_BASE = 900000

where numSources counts SOURCE nodes and j indexes the SERVICE nodes in order, neither of which is the station index. Sharing three run-wide streams instead, as this engine did, changes the interleaving of every draw: two stations that each consume their own stream in the reference would take turns from one here, so the paths diverge from the first event even with identical generators and identical quantiles.

The station-level draws that are not per-class – a polling switchover, a setup or delay-off, an SPN transition – sit on g_aux[station] in a band of their own, mirroring the reference's own (numSources + numServiceNodes + j) family without claiming to match it draw for draw.

< [class], at the (single) source

< [station][class]

< [station], the draws that carry no class

True when a fork's parameters actually vary: some branch is uncertain, some link carries a count other than the scalar, or some link draws its count. A fork that declares the matrices but leaves them uniform stays on the fixed path, so it keeps drawing nothing.

One draw from a jobs-per-link distribution, by inverse CDF on its pmf.

sn.gdscaling: ONE handle phi(n), read at the FULL (nstations x nclasses) population, that multiplies every station's service rate at once.

It is not a station's own state dependence under another name. A job moving ANYWHERE changes the rate EVERYWHERE, so the four advance and reschedule calls fan out over the whole network instead of touching the station that moved. That fan-out is cheap here precisely because this engine rescales RESIDUAL WORK rather than resampling: repeating a reschedule burns no variate and lands on the same completion instant, so it is idempotent in a way the reference engine's cancel-and-redraw is not.

ROUTING IS RESOLVED OVER NODES, not over stations.

A Router, a ClassSwitch and a Logger appear here as destinations: the stochastic complement that removes them lives in rt, not in the NODE-level Peff this table is read off. They hold nothing, so deliver walks straight through them. A FORK and a JOIN are different again: they are stateful, they change the NUMBER of jobs in flight, and no stochastic complement can express that, so deliver replicates at a Fork and synchronizes at a Join.

The dispatcher each (node, class) declares, and its round-robin pointer.

The pointer is per (node, class) and NOT per destination: a round-robin dispatcher hands out its outgoing links in turn, and one counter per link would let two classes at the same node walk the cycle independently and both start at the head.

One forked parent awaiting its siblings.

required is fixed at FORK time, not at join time: the quorum is a fraction of the siblings THIS fork produced, and a Join shared by two forks of different fan-out would otherwise apply one fork's count to the other's siblings.

(class, instant) of every sibling this parent has parked at the Join.

Held PER PARENT, not per Join: the Join's queue length must fall by the siblings THIS synchronization releases, and a node-wide tally would clear the siblings of every other parent still waiting. It also dates each sibling's own wait, which is forkedJobJoinArrivalTimes.

The initial marking: what the model DECLARED, widened by the closed populations whose reference station is this place.

The widening is not a convenience. A closed class in a Petri net has its jobs in the marking and nowhere else, and Network.initDefault is what puts them there – which the JSON document only records once someone has called setState, so a net that never did would start EMPTY and never fire. default_init_state takes the same maximum for the CTMC, and initClosedClassPopulations skips its own injection on a Place for the same reason, so all three agree on the state the model starts in.

A QUEUEING PLACE holds its tokens in three compartments, and only the last of them is what a Petri net normally calls the marking.

waiting tokens queued for one of the place's own servers in service tokens a server is working on depository tokens whose service is finished

THE OUTPUT ARCS SEE THE DEPOSITORY ALONE. That is the entire construct: a token deposited by an input transition has to be SERVED before it can enable an output one, which is how a queueing Petri net puts a scheduling station inside a place. marking stays the sum of the three, because that is what the place's token count means and what QN reports.

An ORDINARY place keeps avail == marking and never touches the rest, so every existing net is unmoved.

One stream per (place, class), on the reference's own band.

Solver_ssj seeds a queueing place's generators at ((numSources + numServiceNodes + placeIdx) * K + k) * 10 + 5000, and this is that expression: svc_index already numbers the non-Source stations the way numServiceNodes counts them, and a place IS one of them here. The transition clocks sit at the bare +5000 of g_spn, which the expression never reaches because an SPN has at least one place and so at least one full stride of K*10 above it.

Every per-visit response time, when either flag asked for them.

–trajectory counts as asking: the reference NESTS the same samples inside its transient block, and MATLAB's sample() and sampleSys() read them from there. Recording only under –respt-samples left those two returning nothing on the transient path.

G-NETWORK REMOVAL SIGNALS, resolved once per class.

A removal signal is a class like any other while it is in transit and stops being one the instant it reaches a station: it never joins, it removes jobs already there and it is annihilated. is_removal_signal is the whole gate, and everything the removal costs – the registry of jobs held at a Delay station, the extra draws – is paid only where it is true, so a model without signals runs exactly as before.

The jobs a Delay station currently holds, in arrival order.

A Delay keeps no buffer and no server: its occupants live only in the departure events already scheduled for them, and the event list has no handle to cancel one. The registry is that handle – a job removed by a signal is erased from it, and the departure that arrives later finds no entry and is discarded, which is the same tag trick the buffered stations use against a preempted departure.

The Delay job registry is kept for a GLOBAL DEPENDENCE as well as for a removal signal: phi re-times the think times with everything else, and the registry is the only handle on them (an infinite server has no slot to hang a generation off).

SYNCHRONOUS CALLS, sn.syncreply: the class a caller expects its reply in.

A caller that expects one KEEPS ITS SERVER when it leaves for the callee and gets it back only when the matching REPLY class returns, which is the whole difference between a synchronous call and an ordinary hop. The pending calls are keyed by an identity that rides on the job, because the reply comes back from a station the caller never names.

One outstanding call: the slot it holds and the class that holds it.

Whether any (station, class) declares immediate feedback at all.

SPAWN ON COMPLETION, sn.classspawn: completing a job of class r injects a FRESH job of the mapped class at the same station.

It is an LQN phase-2 continuation: the work the server owes after the reply has gone back. The continuation is a new passage, so it starts its own response-time clock and is not the completing job under another name.

Take the next waiting token into service, as often as a server is free.

THE DISCIPLINE CHOOSES WHICH TOKEN, and nothing else about the place: FCFS takes the token that has waited longest, LCFS the one that has waited least, SIRO one at random, and INF takes all of them because it never runs out of servers. The reference's engine accepts the same four names but polls the head of one deque for all of them, so LCFS and SIRO are served FIFO there; the mean token count is the same under any work-conserving order at exponential service, and the two engines part company only in the sojourn-time DISTRIBUTION they report.

Tokens an input arc puts at place p: into the queue, or straight to the arcs.

Tokens an output arc takes from place p, which are its COMPLETIONS.

TN at a place is the rate at which tokens leave it, which is placeCompletions in the reference and what Little's law turns back into the mean token sojourn reported as RespT. A queueing place takes them from the depository, the only compartment an arc can see.

Push onto, and pop from, a station's waiting room.

FSP takes the linear branch: its key is recomputed against the residual work present at the station RIGHT NOW, over the waiting jobs and the ones in service alike, so no stamped ordering can stand in for it. Every other discipline has a per-job constant key and rides the heap.

Free, unblocked, unheld, and its pool accepts cls.

Idle, unblocked, unheld slots: the pool a job seizing n of them draws from.

The free slot a job of class cls may enter, or s.nservers for none.

The POOL is chosen first, by the station's heterogeneous scheduling policy, and the slot is then the first free one inside it. That is the reference's order and it matters: picking the globally-first free slot would make every policy behave as ORDER.

Is any waiting job servable by slot sl?

Pop the job slot sl should serve next.

Off a pooled station this IS buffer_pop. On one it is the job the station's own order would have taken FIRST AMONG THOSE THE SLOT CAN SERVE, which is what a pool means: a server does not skip the queue, it skips the jobs it is not compatible with. The heap is drained and rebuilt because the discipline's order lives in the comparator, not in the sequence, so scanning the vector would answer in heap order rather than in service order.

The RATE multiplier a station applies to a class at its current population: load dependence times class dependence.

A sampled requirement is DIVIDED by this, so a multiplier above one is a faster station. lldscaling is indexed by the total population minus one, saturating at the table's end, which is the reference's convention and not a guard: the table is declared up to the station's capacity and a closed model can hold exactly that many.

A SHARING station does NOT take the load-dependent factor here. Its lldscaling is read instead as the effective SERVER COUNT in ps_shares, because c servers and a c-fold rate are the same thing only when the capacity is shared – applying both would scale it twice.

Read phi at the LIVE population and install it in the cache.

It is called from the RESCHEDULE fan-out and from nowhere else, because that is the instant the new speed takes effect: an advance has to credit the interval that just ended at the speed which held THROUGH it, and the cache still holds that one. Same flush-then-install order as ps_cd and Accum::set_busy_scale.

The effective server count of a SHARING station, which is where its load-dependence table is read. Transcribes getEffectivePSServerCount: with no jobs present the count is 1, not the table's first entry, so the very first arrival gets a whole server.

Integrate the work delivered at a state-dependent station since its last change, then re-time every completion it holds.

sd_advance must run BEFORE the population is written and sd_reschedule after, because the rate in force over the elapsed interval is the one the OLD population implied. The stale departures are neutralised by the tag, exactly as a preemption's are.

A GLOBAL DEPENDENCE REACHES THE INFINITE SERVERS TOO.

phi multiplies every station's rate, an INF station's included, so its think times are integrated forward and re-timed along with everything else. They are reached through the job registry because a Delay has no slot to hang a residual or a generation off, which is why track_delay_jobs keeps the registry for a global dependence as well.

The four calls above, fanned out over the whole network when a global dependence couples the stations.

A job moving anywhere changes every station's rate at once, so an advance has to book the work EVERY station delivered over the interval that just ended, and a reschedule has to re-time every completion in flight against the refreshed phi. Repeating either is free: an advance over a zero interval books nothing, and a reschedule rescales residual work rather than resampling, so it consumes no variate and lands on the instant one call would have produced. Without a global dependence each call touches exactly the station it names, as before.

Whether station j can admit one more job of class r RIGHT NOW, counting the jobs already blocked in front of it.

THE CAPACITY TEST EXCLUDES THE BLOCKED JOBS, and reporting includes them. acc.qlen carries both, because the reference's effectiveQueueLength adds basBlockedAtDest/bbsBlockedAtDest for the statistics, while destinationHasCapacity reads getTotalCustomersAtStation – the jobs actually AT the station. Testing against the inclusive count DEADLOCKS a station of capacity one: the job blocked in front of it occupies the very slot it is waiting for, so the station can never make room and the upstream server is never released.

The blocking policy the DESTINATION declares for an arriving class.

Jobs parked by a WAITQ region: they have NOT entered the region, so they are outside every station's buffer, but they are still in the system and are charged to the station they were trying to enter.

Whether the service requirement of (station, class) is drawn when the job ARRIVES rather than when it reaches a server.

Every time-invariant law is: the two instants give the same distribution, and drawing once on arrival is what the reference engine does. A TIME-INHOMOGENEOUS law is not, because its rate is a function of absolute time and a job that queues is served under a later part of the schedule than the one in force when it joined – so it waits for start_service. Three station kinds cannot wait: a Delay, where the two instants coincide anyway; a processor-sharing station, which never calls start_service and holds the requirement itself; and one whose waiting room is RANKED by the sampled size, which must have a size to rank by. All three draw on arrival.

Arm a BULK SERVER's firing clock and record how many jobs the firing will complete.

The interval comes from the station's own process, drawn at now because a BMMAPt clock is a wall-clock SCHEDULE and a BMAP one simply ignores the instant. The size is whatever block fired, read off the sampler beside the interval and at no extra draw; a MARK the walk may also have returned is DISCARDED, because a completion drives no class switch.

A non-cyclic schedule past its horizon yields no interval: the clock is left UNARMED rather than re-armed at zero delay, which would spin at one instant. The jobs present stay present, which is what "the process has stopped" means for a bulk server.

Free the slots a job held BESIDE its primary one, and answer how many servers it occupied in all.

One at a station that declares no parallelism, which is every caller's previous behaviour; calling it twice on the same slot is harmless, which matters because a blocked slot is released once at the completion that blocked it and again when the handoff finally goes through.

Start the next waiting job on the slot just freed, or leave the slot idle.

SERVER PARALLELISM IS HEAD-OF-LINE: the job the station's own order takes next needs n slots free at once, and when the station cannot offer them it WAITS rather than serving whoever behind it would fit. Skipping ahead to a job that fits is a different discipline, not the same one under load.

Interrupt the job in slot and return it to the waiting room.

Its residual work and attained service are carried on the job, so a resume needs no side table; the tag is left stale, which is how the departure event already in the list is neutralised.

Advance a polling server one leg, or park it.

poll_serve starts the next job at the buffer the server stands at; poll_advance walks it to the next buffer, paying that leg's switchover. The walk stops after a full lap of zero-time legs that found no work, or the server would spin forever at the same simulated instant.

Reschedule the aggregate completion of a pass-and-swap station.

ONE CLOCK FOR THE WHOLE STATION, at the total rate mu(c) of its ordered list, and the position that completes is drawn from the per-position INCREMENTS. Scheduling one clock per job instead would need each job's marginal rate to be constant, which is exactly what an order-independent station does not have: every arrival and departure changes every other job's rate.

The position that completes, drawn from the per-position rate increments, and the job that actually departs after the swap chain.

Admit a job at station i, or drop it when the station is full.

from is the station the job comes from, or M for an arrival that enters from outside the network. IT IS NOT BOOKKEEPING: a hop BETWEEN TWO MEMBERS of the same finite capacity region does not cross the region boundary, so the job must neither be re-tested against the region's caps nor counted into it again. Without that test an intra-region hop adds one to the region on arrival and removes nothing on departure, so occupancy grows by one per hop until every arrival is refused and the run never completes another job.

< defined below, called by admit on a class-switching hop

Region wait of the waiter release_region is re-admitting; admit takes it onto the job and clears it.

< set by admit when the hop stayed inside a region, read by the departure site

Every index of tab, so JSQ can be told to consider all of them.

PROB: one draw against the static row.

The mass is NOT renormalized before the draw. The reference scales the uniform by the row total instead, so a row summing to one only up to rounding still lands, and the two engines consume the routing stream identically.

Eq. (10) of Krzesinski (1987) at the CURRENT marking: the split out of the entry centre into the branch entries, plus the residual that returns the customer to the departure centre.

A Source is reported as zero rather than as the infinite reservoir its encoding carries, which is the station_populations rule and the one the enumerating solvers use, so the sample path here has an exact counterpart in SolverNC rather than merely a similar one.

The candidate holding the FEWEST jobs, ties broken uniformly.

A destination that is not a service station holds nothing to compare, so it is skipped rather than counted as empty – a Sink would otherwise win every comparison and absorb the whole stream. When no candidate is a station the first one is taken, which is the reference's fallback.

One hop out of node inode in class r: the DISPATCHER picks the node, the class-switch row picks the class.

NEITHER STAGE DRAWS WHEN ITS OUTCOME IS DETERMINED. A single destination and a single arriving class each cost nothing, which is the reference's rule and not an optimization: a routing stream that advanced on a forced hop would put every subsequent draw out of step with the Java engine and make a seeded run of a tandem irreproducible across the two.

The class a job routed out of node under cls will ARRIVE the true destination station under, resolving through any ClassSwitch/Router/ Logger chain draw_node_route itself stops one hop short of.

draw_node_route(i, r).cls names the class on the FIRST edge out of a station, and when link() synthesized a ClassSwitch node for that edge (any route whose class differs from station to station), that first edge's class is the PRE-switch one – the switch happens INSIDE the synthesized node, one hop later. The synchronous-call park check below needs the class the job actually lands under, or a callee's own reply (its edge into the synthesized node still reads as the CALL class) is misread as a brand-new call: the reply's job.call is then overwritten with a fresh id, the caller's real call is never answered, and its server stays held while the buffer behind it grows without bound.

Consumes no extra draw when every hop is forced, which a link()- synthesized pass-through node always is (draw_node_route never spends a draw on a single-destination table), so the routing stream stays in step with a caller that already resolved through this same node.

The station and class a job routed onto re ACTUALLY lands at, walking the ClassSwitch/Router/Logger chain draw_node_route stops one hop short of, and only while every hop is FORCED.

WHY IT EXISTS: link() synthesizes CS_<i>_to_<j> for any route whose class changes, so a CLASS-SWITCHING SELF-LOOP leaves the station as an edge into that synthesized node. re.station is then not the station at all and re.cls is still the PRE-switch class, so an immediate-feedback test written on the raw entry reads the self-loop as a departure to elsewhere and the feature goes silently inert on exactly the models that need it – measured against the Java engine and SolverSSA, which both move the held job between classes.

WHY FORCED HOPS ONLY: this is a LOOK-AHEAD. A job that turns out not to be fed back is handed to deliver, which makes the same hops again, so the walk must not spend a routing draw or every ordinary departure would consume the stream twice and put the run out of step with the Java engine. A hop with one destination and one arriving class costs nothing (draw_node_route draws only where the outcome is undetermined), and a link()-synthesized pass-through is always of that shape. Anything else stops the walk and leaves the entry as it was, which reads as "not a self-loop". A Logger is walked with the other two because this engine gives it no side effect of its own – deliver routes straight through it – so skipping the delivery records nothing less.

Hand on the jobs blocked in front of j, oldest first, now that it has room.

OLDEST FIRST is the reference's rule and it matters: releasing the newest would starve a server that has been blocked longest, which no blocking discipline does and which shows up as an unbounded blocked time at one station while another cycles freely.

Let the jobs parked by region rg in, oldest first, now that it has room.

THE WAIT QUEUE IS STRICT FIFO: a head that still does not fit stops the release, and no younger waiter overtakes it, as in JMT and the Java engine (tryReleaseBlockedCustomers). Letting a waiter of another class jump the head starves the head whenever a per-class or linear cap keeps binding for it.

A waiter re-enters through admit, which re-tests every constraint: the region may have freed one slot while the waiter's own class cap or the linear constraints still bind, and admitting on the global count alone would breach them.

The jobs station i actually HOLDS, waiting and in service alike.

Remove ONE job from station i under policy, returning its class or K when the station holds nothing.

THE POLICY IS TWO-TIER, as in the reference: FCFS and LCFS rank the WAITING line by age and only reach into the servers once nobody waits, while RANDOM draws uniformly over waiting and in-service jobs together. A station whose occupants are all in service – Delay, PS, PAS – has no waiting line for the age tiers to rank, so there the three policies differ only in which of the equally-served jobs is taken.

The victim is drawn WITHOUT REGARD TO CLASS. That is Gelenbe's negative customer and what the Java engine and SolverMAM both do; sn.signaltarget narrows the eligible class only in the state-space solvers, whose state carries the per-class composition this one draws over directly.

A G-NETWORK REMOVAL SIGNAL arriving at station i, and annihilated there.

The signal NEVER JOINS the station and never continues along its routing chain: it removes a batch of the jobs it finds, and one that finds the station empty is simply lost. That is Gelenbe's semantics and the one State.afterEventStationSignal and the Java engine both implement; routing the signal onward instead would make a single signal fire once per downstream station, which is what the tandem regression measures.

A CATASTROPHE removes EVERY job held, in-service ones included, and ignores the batch-size law entirely – removing all of them is what the word means. A NEGATIVE signal draws its batch size from signalremdist (absent: exactly one) and CLIPS it at what the station holds, so an oversized batch drains the station instead of driving it negative.

The completion budget, spent by anything the model actually DOES.

Declared ahead of deliver because a service completion is not the only thing that spends it: a Petri net has no services of its own and counts a FIRING, and a cache-only model (Source -> Cache -> Sink) has none either and counts the cache's delivery to the Sink. Without the latter the loop at the bottom of this function never terminates on such a model, since the Source keeps the event queue non-empty forever. Solver_ssj spends the budget at the same point (deliverCacheCompletion calls checkEventCountStop on the sink branch), so this is the reference's rule and not a local convention.

Free every request parked while item was being fetched.

Declared ahead of deliver and assigned after it because the two are mutually recursive: a release routes each freed request onward, and a delivery to a cache is what triggers a release. Each freed request reads the now-cached item and completes at once, counted as a DELAYED HIT and leaving under its own hit class; its original t_sys is preserved so the response time still spans the wait it actually served.

Hand a job to a NODE, replicating at a Fork and synchronizing at a Join.

A FORK sends a sibling down EVERY outgoing edge, not one drawn from the routing probabilities: the edges of a fork are branches taken together, and drawing among them would turn a fork into a probabilistic split with the same picture and a completely different response time. tasksPerLink multiplies each branch.

A JOIN releases ONE job once its strategy is satisfied and DISCARDS the siblings that arrive afterwards – under a quorum they are the losers of the race, and counting them as completions would inflate the throughput of everything downstream.

A REPLY ANSWERS ITS CALL AND TRAVELS ON. It releases the server the caller has been holding, lets that station take its next waiting job, and then CONTINUES along its own routing – which is what separates it from a removal signal, annihilated where it lands.

A reply with no matching call is not an error: the call may have been answered already, or the class may be routed as a reply without any caller having parked. It routes on, having released nothing.

The marking as one flat vector over (place slot, class), p * K + r, which is what a mode's arcs are indexed by.

SUMMED OVER CLASSES UNTIL 2026-08-12, which made every arc colourless and let a token of one class satisfy another's pre-arc.

The tokens the ARCS can see, in the same layout.

Identical to the marking at an ordinary place and the DEPOSITORY at a queueing one, which is the whole difference between the two: enabling, inhibiting and the immediate pick are decided on this vector, while a MARKING-DEPENDENT FIRING RATE is still a function of the marking, because Transition.setFiringRateDependence is declared over token counts and a place's token count includes the ones it is still serving.

Move the tokens of one firing of m.

CONSUME FIRST AND DEPOSIT SECOND, which is what a self-loop arc needs: a mode that takes a token from a queueing place and puts one back takes the SERVED one out of the depository and queues the new one for service again. Netting the two against each other would leave the token where it was and the place would never serve anything.

Fire every enabled IMMEDIATE mode, then arm the timed ones.

The immediate modes are exhausted FIRST and to a fixed point: a marking that enables one is not a marking the net rests in, so letting a timed mode fire from it would visit a state the model does not have. The guard bounds a net whose immediate modes form a cycle, which is a modelling error rather than something to simulate forever.

WARM START, --initsol: the placement a companion solver's steady state implies, as a STATION-MAJOR vector [st0_cl0, ..., stM-1_clK-1].

It REPLACES the reference-station placement of the closed classes; the open arrival streams are armed either way. A Petri net is exempt: its tokens live in Places and not in the service queues this vector describes, so a station-based placement would put nothing anywhere and then fail the conservation check.

CLASSES DESCEND WITHIN A STATION. Every initial job arrives at time 0, so under an order-preserving discipline the LAST one injected holds the server; ascending order puts the highest-index class in service, which is the mirror of what State.initDefault encodes and selects the other closed communicating class on a chain made reducible by non-overtaking routing – a different stationary distribution, not a different tie-break.

The width-weighted time average of one segment list, i.e. the nominal block.

sched_dist builds the nominal D0/D1 this way and mmapt the nominal per-mark blocks; this is the same average applied to a (mark, batch) list, which nothing stores because Dmark carries the MARK reading for a BMMAPt.

JOBS PER EPOCH of an arrival process that carries its own batch sizes, and 1.0 for one that does not.

A batch epoch is ONE EVENT and b JOBS, so the Source row of TN – which is analytic here, not counted – has to report the JOB rate or it will not balance against the counted throughput of the stations downstream. Getting this wrong reads an M[3]/M/2 as an M/M/2 at a third of the task rate, which is exactly the failure the old arrival_batch refusal existed to prevent. An unbatched process returns 1.0, so every existing model's Source row is untouched.

A TRANSIENT run is bounded by SIMULATED TIME, not by completions.

options.timespan = [t0, t1] sets the horizon and the completion budget is then ignored, exactly as the reference does: a transient trajectory is a function of time, so stopping on an event count would end it at a different instant on every path and make the series incomparable across seeds.

The EXACT joint-state residence time, keyed by the aggregate state row.

histogram_space plus histogram_time let a caller evaluate its own reward on the empirical distribution, INCLUDING a nonlinear one: E[r] = sum_s (t_s / sum t) r(state_s) is exact only because the residence time of each distinct state is kept, which a trajectory of means cannot reconstruct.

A token finished its embedded service: it leaves the place's servers for its depository, where the output arcs can take it.

THE MARKING DOES NOT MOVE. The token is still at the place – it has only changed compartment – so QN is untouched and only UN and the arcs' view of the place change. The next waiting token is taken into service before the net settles, which is placeDepartureActions.

A BULK SERVER'S FIRING. One event, min(b, present) completions.

TWO PHASES, and the order is load-bearing: every served job is removed and recorded BEFORE any of them is routed, because a job routed back to this same station would otherwise re-arm the clock while the remaining count is still unsettled and the station would fire twice for one batch. The Java engine's batchServiceDepartureActions has the same split for the same reason.

IMMEDIATE FEEDBACK: the completing job goes straight back into THE SAME SERVER under the destination class, holding the slot instead of re-queueing behind whoever is waiting.

That is the whole content of the feature and it is not the same model as a self-loop: a self-loop puts the job at the tail and lets the head of the queue in, while feedback lets one job hold the server for as many services as its routing keeps returning it. It also never leaves the station, so it exits no region, releases no blocked upstream server and is not a system completion.

A SYNCHRONOUS CALL PARKS THE SERVER. The completing job leaves for the callee and its slot stays held until the matching reply comes back: no waiter is promoted into it, and the time it spends held counts as BUSY, because the server is unavailable and reporting it idle would credit the station with capacity it does not have. That is the same rule the BAS branch below applies for the same reason.

A hop that ALREADY switched into the reply class is not a call: it is the answer, and parking on it would wait for a reply to a reply. That switch may not show on dcls yet – see resolve_final_cls – when the routing matrix moves this edge through a synthesized ClassSwitch node, so the check resolves through it rather than reading dcls directly.

Definition at line 834 of file ldes_engine.h.

References line::qn::Station< T >::ServerType::compatible, line::qn::Station< T >::ServerType::count, line::lang::Distrib< T >::disabled, line::lang::DROP, line::ldes::LdesOptions::events, line::lang::EXHAUSTIVE, line::qn::ForkParam< T >::fan_out_dist, line::qn::ForkParam< T >::fan_out_link, line::qn::ForkParam< T >::fan_out_prob, line::lang::FCFS, line::InputError::InputError(), line::lang::KLIMITED, ldes_engine_solve_one(), line::lang::LPS, line::lang::Distrib< T >::mean, line::qn::Station< T >::ServerType::name, line::lang::NONE, line::lang::process_is_batch(), line::lang::process_to_text(), line::lang::QUEUE_LENGTH, line::lang::RENEGING, line::ldes::LdesOptions::samples, line::ldes::LdesOptions::seed, line::qn::Station< T >::ServerType::service, line::ldes::LdesOptions::slot_length, line::ldes::LdesOptions::slotted, and line::UnsupportedError::UnsupportedError().

Referenced by ldes_engine_solve(), and ldes_engine_solve_one().

◆ ldes_flags()

std::vector< std::string > line::ldes::ldes_flags ( const LdesOptions & o,
const std::vector< std::string > & extra )
inline

The engine flags of one run, after solve <model> -o <result>.

ONE MAPPING for the subprocess and the REST paths, as in both other clients: the server takes the same long-form flags verbatim, and deriving them twice is how the two transports start disagreeing. A knob is emitted only when it differs from the engine default, so a default run yields the minimal command line an older AOT image still parses – except -s and --seed, which are always emitted: the CLI's seed default is -1 (random), so a silent seed makes the run irreproducible and unlike the MATLAB and Python clients.

Definition at line 241 of file solver_ldes.h.

References line::ldes::LdesOptions::busy_period_orders, line::ldes::LdesOptions::busy_period_subnets, line::ldes::LdesOptions::cimethod, line::ldes::LdesOptions::ciminbatch, line::ldes::LdesOptions::ciminobs, line::ldes::LdesOptions::cnvgbatch, line::ldes::LdesOptions::cnvgchk, line::ldes::LdesOptions::cnvgon, line::ldes::LdesOptions::cnvgtol, line::ldes::LdesOptions::events, line::ldes::LdesOptions::has_timespan, line::ldes::LdesOptions::init_sol, ldes_flags(), line::ldes::LdesOptions::method, line::ldes::LdesOptions::mserbatch, line::ldes::LdesOptions::numthreads, line::ldes::LdesOptions::obmoverlap, line::ldes::LdesOptions::replications, line::ldes::LdesOptions::samples, line::ldes::LdesOptions::seed, line::ldes::LdesOptions::slot_length, line::ldes::LdesOptions::slotted, line::ldes::LdesOptions::spectral_low_freq_frac, line::ldes::LdesOptions::t0, line::ldes::LdesOptions::t1, line::ldes::LdesOptions::timeout, line::ldes::LdesOptions::tranfilter, line::ldes::LdesOptions::tranobs, and line::ldes::LdesOptions::warmupfrac.

Referenced by ldes_flags(), and solver_ldes_text().

◆ ldes_is_available()

bool line::ldes::ldes_is_available ( )
inline

True when this machine can run the engine at all, by either image.

Definition at line 245 of file ldes_probe.h.

References ldes_engine_dir(), and ldes_is_available().

Referenced by line::autosolver::auto_solver_is_available(), and ldes_is_available().

◆ ldes_ln_engine_solve()

template<class T>
engine::LnResult line::ldes::ldes_ln_engine_solve ( const lqn::LqnStruct< T > & lsn,
const LdesOptions & o )

Simulate a layered model in process.

The budget is ENTRY COMPLETIONS, mirroring the flat engine's service completions: a layered model has no single notion of "a job leaving", so the count of entry invocations that returned is what a horizon can be set on.

ONE STREAM PER ACTIVITY, as the flat engine now does per (node, class).

The layered reference indexes its generators by the LQN entity rather than by a station, so the offsets here are built the same way from the activity index: host demands in the service band (+1000) and think times in the arrival band, which is what those two are. Sharing one stream across every activity interleaves draws that the reference keeps apart, and the divergence is immediate rather than statistical.

REPLICATION. A processor or task declared with replication r is r identical copies of itself, and a copy is a server of its own: pooling them into one server of r times the capacity would let one queue absorb what r separate queues cannot. So the host state below is indexed by a SLOT – the processor plus its replica – while what the copies share by definition (servers per copy, scheduling) stays indexed by the element. The reported measures are summed over the copies, which keeps throughput conserved across a call and utilization a fraction of the total capacity.

Slot of the processor replica running replica trep of one of its tasks.

Replica of the callee reached by one call of replica crep of the caller. An unset fan-out is the smallest value consistent with repl(caller)*fanout = repl(callee)*fanin, and the caller reaches the block {(i*f+k) mod r}. A call is one indivisible unit of work, so it goes to one member of that block drawn uniformly: over many calls each member carries the 1/f share that LQN2QN splits the call mean into. This is deterministic pairing at f=1 and a uniform spread over every replica at f=r.

A multi-successor activity CHOOSES one branch when it is an OR fork or a loop decision (its own post type, the first test of buildActivityGraph) or when any branch weight is a probability (w != 1, the lazy test of completeActivity); otherwise it FORKS every branch. The weights are renormalised when they do not sum to one, and the draw is u <= cum.

The activity an entry starts at: the one the entry's own graph edge names, else the first predecessor-free sequential activity of its task, else the first activity, which is findBoundActivityForEntry. 0 means none.

< hit/miss activity -> its ItemEntry

The rows are declared once per element and hold separately inside each replica, so A and b are per element and the occupancy per slot. A host's columns are its tasks (tasksof), a task's its entries (entriesof).

True when one more job in column col still satisfies every row.

The pools ARE the servers of the host, so their counts must add up to its multiplicity. An INF host ignores them, as the Java engine's service path does. The operand column of a job is its task's position in tasksof.

< task index of each slot

The new features draw on streams of their own, in blocks above every existing offset, so that a model without them keeps its seeded sample path: branch choices per activity (940000), item draws per cache replica (950000) and random replacement per cache replica (960000). 910000 and 920000 are the SIRO keys of hosts and tasks.

Lines woken by a release, continued after the current walk returns. Waking them inside the walk would re-enter it on another line.

PRIORITY ORDER, as Solver_ssj_ln orders it: a LARGER lsn.prio first (the lqns convention), FIFO within one level under HOL/FCFSPRIO and the FCFS preemptive variants, LIFO under the LCFS ones. A host request carries the priority of the task whose activity it is; a task request that of its CALLER, since every request at one task belongs to that task, and one with no caller (a reference task's own cycle, an open arrival, an asynchronous call) takes 0, the lowest.

Queue id behind every request of a higher priority and, within its own level, behind the ones FIFO (or, lifo, LIFO) puts first. key_of is the arrival instant a level is ordered by; a task queue passes none and relies on requests joining it in arrival order.

Queue id under sc, the order Solver_ssj_ln's comparators impose: FCFS by arrival, LCFS newest first, SIRO by a uniform key drawn now, the priority disciplines by prio then arrival (key_of, the host residence start, when given).

Take the request sc serves next: the head, or under SIRO the smallest key.

PROCESSOR SHARING on a host replica, Solver_ssj_ln's startPSJob / advancePSService: every line present is served at min(1, c/n) of a server, and the next completion is re-read whenever the set changes. The event queue has no removal, so a stale completion is recognised by its generation (event kind 5).

The share of each line of slot hs: min(1, c/n) under PS; under PSPRIO, Solver_ssj_ln's psprioJobRates, the levels served from the largest task priority down, each taking min(servers left, its lines) servers split evenly. Either way the shares sum to min(n, c).

Serve every line of slot hs up to now at the shares in force since the last change.

Busy servers of a PS slot holding n lines, the min(n, c) Solver_ssj_ln.updateHostStats counts.

How many times one execution of the caller makes call cidx.

floor(mean) calls plus one more with probability equal to the fraction, which is Solver_ssj_ln.sampleCallCount exactly. It is DETERMINISTIC at an integer mean, which is what calls-mean="3" asks for: three calls every time, not three on average.

Move a line to activity a, not yet begun.

Enter the task: the invocation now counts at the task. It counts at its ENTRY only once it holds a thread (see advance), since the entry's service starts there.

Arrive at the task, through its admission constraint when it has one. A blocked request holds neither a thread nor a place in the task's queue, so it is counted nowhere while it waits, as arriveAtTask has it.

Continue every line a release woke, and every line those wake.

Put line lid into service at its host replica.

Serve every job of a pooled replica up to now at the rates in force.

Re-read the delivered rate after the job set changed, and arm the next completion. A pooled host's Util integrates the DELIVERED rate, which is hostBusyTime += delivered*dt of advancePSService.

Arm the next completion of PS slot hs under the shares that hold from now on.

Lines in service on a host slot under a preemptive discipline, the candidates to preempt.

Put line lid on a server of queueing host slot hs for the work it has left, drawing the demand when it has none yet (a fresh request, or a PI victim resuming).

Preempt a line in service on full slot hs for lid, Solver_ssj_ln.preemptFor: FCFSPR/PIPRIO take only a STRICTLY lower priority, the lowest in service and the latest started among ties; LCFSPR/PIPRIO take the lowest strictly lower one (earliest arrival among ties), else the equal one that started earliest. The victim rejoins the queue at its original arrival, keeping its residual work under PR and drawing afresh under PI. Returns whether lid took a server.

Ask the host for service. The residence starts HERE, when the line asks, whether it then queues at the processor or waits outside it for a host admission token; a blocked line keeps its thread, being still inside its activity, but holds no server.

Give back a host admission token and admit the blocked head while it fits.

Arm THREAD's cold start; the request that woke it waits in thr_queue.

Arm THREAD's idle countdown; when it expires the thread is OFF.

Give invocation vi a thread of its task.

Returns false when it could not proceed: either every thread is taken, or the only thread available was powered off and is now warming up. Either way it is parked in thr_queue and continued by whoever frees or finishes warming a thread. An infinite-thread task never blocks and holds no identified thread.

Free the thread invocation vi holds and hand it to the next one waiting.

One execution of the line's activity has finished.

ITS RESPONSE TIME CARRIES THE CALLS IT MADE, which is what separates it from the residence act_host_resid accumulates: on an activity that calls a server three times, the response is its own demand plus the three replies and the residence is its own demand alone. The occupancy is closed off through cur_q at the same instant, so QLen, RespT and Tput satisfy Little's law by construction rather than by three separate estimators happening to agree.

The entry replies: its throughput and response time are recorded, and the synchronous caller is released. direct is a reply at completion, whose caller the walk continues itself; an early reply (phase 2 follows, or a cache branch replied) wakes the caller through runnable.

AN ENTRY'S RESPONSE TIME IS THE LQN ENTRY SERVICE TIME, from the instant it took a thread of its task to the reply, and so carries every nested call its activities made. The wait for the thread belongs to the caller's call, as lqns, lqsim, SolverLN and Solver_ssj_ln.sendEntryReply all count it.

cacheMiss: insert into the entry list, evicting by the policy's rule.

cacheHit: refresh or promote the item one list up, demoting the displaced one.

One read of an ItemEntry, accessCache: draw the item, look it up in the live content of the replica serving the request, update the content, and return the branch to continue on – or HELD, when retrieval parks a read of an item that is already being fetched.

The fetch completed: insert the item and let every parked read hit it.

The invocation is complete, completeRequest: reply if it has not, leave the entry and the task, free the thread and the task admission token, and return the line to continue – the synchronous caller, when the reply was made just now – or LN_NONE. A top-level invocation sends its customer back to think.

A line reached an activity with no successor, finishLine.

The line's activity is complete, completeActivity: a cache read picks its branch; an AND-join input is recorded and the join fires on the ROOT once all have arrived; a phase-1 activity entering phase 2, or a cache branch of the entry, replies; then one successor continues, an OR or loop decision draws one, and an AND fork starts every branch. Returns the line to continue, or LN_NONE.

A request at entry on task replica rep that nobody waits on: an asynchronous callee or an open arrival. It runs as an invocation with no caller and no customer, so it just ends.

Continue line lid from wherever it is: take a thread, begin the current activity, ask its host, think, issue its next call, or complete it.

Definition at line 374 of file ldes_ln_engine.h.

References line::Matrix< T >::cols(), line::lqn::ServerPools< T >::compat, line::lqn::ServerPools< T >::counts, line::ldes::LdesOptions::events, line::lang::HLRU, line::lang::INF, line::InputError::InputError(), ldes_ln_engine_solve(), line::lang::LRU, line::lang::NONE, line::lqn::ServerPools< T >::npools(), line::lang::POST_CACHE, line::lang::POST_LOOP, line::lang::POST_OR, line::lang::PRE_AND, line::lang::PRE_SEQ, line::lang::QLRU, line::lqn::ServerPools< T >::rates, line::Matrix< T >::rows(), line::lang::RR, line::ldes::LdesOptions::samples, line::lang::sched_to_text(), line::ldes::LdesOptions::seed, line::lang::SFIFO, line::lang::SYNC, and line::UnsupportedError::UnsupportedError().

Referenced by ldes_ln_engine_solve().

◆ ldes_prob_aggr()

double line::ldes::ldes_prob_aggr ( const std::string & doc,
const LdesOptions & o,
std::size_t station,
const std::vector< double > & counts,
std::size_t nclasses )
inline

Port of getProbAggr: the marginal probability of a per-class job count at one station.

The run is a plain steady-state solve with --export-histogram added, which is what getAvgReward already does, so the estimate is over the same post-warmup path the mean metrics come from.

Parameters
docthe model.json document, forwarded byte for byte
othe run's knobs
station1-based station index
countsper-class job counts to test at that station
nclassesthe model's class count

Definition at line 849 of file solver_ldes.h.

References ldes_prob_aggr(), ldes_prob_from_histogram(), and solver_ldes_text().

Referenced by ldes_prob_aggr().

◆ ldes_prob_from_histogram()

double line::ldes::ldes_prob_from_histogram ( const LdesResult & r,
std::size_t nclasses,
const std::vector< LdesStateQuery > & query )
inline

Residence-time probability of an aggregate joint state, from a parsed result.

WHY THE HISTOGRAM AND NOT THE TRAJECTORY. stateHistogram is the exact residence time of every integer joint state the run visited, keyed on the state itself (Solver_ssj.updateRewardStats accumulates it per marking), so P(state) = t(state) / sum(t) is an unbiased estimate that is exact on the sampled path. The transient QNt series is a sequence of INTERVAL MEANS of the queue length; comparing one against an integer state matches only where a bucket mean happens to land on an integer, which is why the three clients that estimated a probability that way reported a near-zero number for a state the chain occupies most of the time (BUG-96).

The layout is the aggregated station-major, class-minor one ctmc_state_space_aggr builds: column (i-1)*nclasses + k is the number of class-k jobs at station i. THE RESOLUTION IS PER CLASS, not per phase: the engine records the integer queue lengths, so a phase-resolved query cannot be answered from this document and the caller must aggregate first.

Parameters
ra result produced with --export-histogram
nclassesthe model's class count, which fixes the column stride
querythe stations to constrain, and to what
Returns
the residence-time fraction, 0 when the state was never visited
Exceptions
NumericErrorwhen the run carried no histogram (the flag was omitted)

Definition at line 750 of file solver_ldes.h.

References line::Matrix< T >::cols(), line::ldes::LdesResult::histogram_space, line::ldes::LdesResult::histogram_time, line::InputError::InputError(), ldes_prob_from_histogram(), line::NumericError::NumericError(), and line::Matrix< T >::rows().

Referenced by ldes_prob_aggr(), ldes_prob_from_histogram(), and ldes_prob_sys_aggr().

◆ ldes_prob_sys_aggr()

double line::ldes::ldes_prob_sys_aggr ( const std::string & doc,
const LdesOptions & o,
const Matrix< double > & target )
inline

Port of getProbSysAggr: the joint probability of a whole aggregate state.

Parameters
target(nstations x nclasses) job counts, EVERY station constrained

Definition at line 865 of file solver_ldes.h.

References line::Matrix< T >::cols(), line::InputError::InputError(), ldes_prob_from_histogram(), ldes_prob_sys_aggr(), line::Matrix< T >::rows(), and solver_ldes_text().

Referenced by ldes_prob_sys_aggr().

◆ ldes_runners()

std::vector< LdesRunner > line::ldes::ldes_runners ( const std::string & doc,
const std::vector< std::string > & flags )
inline

The runners to try, in order (see the file header for why there are two and when the order flips).

Parameters
docthe model document, scanned for features the AOT image predates
flagsthe resolved flags, scanned for the same reason
Exceptions
UnsupportedErrorwhen no engine can run on this host

Definition at line 370 of file solver_ldes.h.

References line::ldes::LdesRunner::argv, line::ldes::LdesRunner::engine, ldes_engine_dir(), ldes_runners(), and line::UnsupportedError::UnsupportedError().

Referenced by ldes_runners(), and solver_ldes_text().

◆ ldes_solve_rest()

detail::Json line::ldes::ldes_solve_rest ( const std::string & base_url,
const std::string & doc,
const std::vector< std::string > & flags,
double timeout )
inline

Solves through an LDES REST server and returns its result document.

The payload is the model text plus the same flag vector the subprocess would have been given, so a fixed seed gives the same numbers on both transports.

Definition at line 412 of file solver_ldes.h.

References line::http::Response::body, ldes_solve_rest(), line::NumericError::NumericError(), line::http::post_json(), and line::http::Response::status.

Referenced by ldes_solve_rest(), and solver_ldes_text().

◆ list_valid_methods()

std::vector< std::string > line::ldes::list_valid_methods ( )
inline

Port of SolverLDES.listValidMethods.

Two names, and only one engine behind them: 'parallel' asks the engine for INDEPENDENT REPLICATIONS and the mean over them, which the client turns into --replications (the caller's count when set, 8 otherwise). It is not a second simulator, which is why the list is this short in all four codebases. 'para' is its short spelling, as in SolverSSA.

Definition at line 53 of file ldes_options.h.

References list_valid_methods().

Referenced by line::autosolver::auto_family_methods(), list_valid_methods(), and line::NetworkSolver::list_valid_methods().

◆ parse_ldes_result()

LdesResult line::ldes::parse_ldes_result ( const detail::Json & d)
inline

Parses one ldes-result document.

format is CHECKED, not assumed: the runner falls through on failure, and a runner that wrote some other JSON to the output path would otherwise be read as a result full of absent metrics rather than as the failure it is.

Definition at line 456 of file solver_ldes.h.

References line::ldes::LdesResult::AN, line::ldes::LdesResult::ANCI, line::ldes::LdesResult::ANfcr, line::ldes::LdesResult::avgOrbitSize, line::ldes::LdesResult::avgRenegingWaitTime, line::ldes::LdesResult::balkedCustomers, line::ldes::LdesResult::balkingProbability, line::ldes::LdesResult::busy_periods, line::ldes::LdesResult::cache_metrics, line::ldes::LdesResult::class_names, line::ldes::LdesResult::CN, line::ldes::LdesResult::converged, line::ldes::LdesResult::convergence_batches, line::ldes::LdesResult::BusyPeriodTarget::count, line::ldes::LdesCacheMetrics::delayed, line::ldes::LdesResult::DropRateJoin, line::ldes::LdesResult::DropRateNfcr, line::ldes::LdesResult::histogram_space, line::ldes::LdesResult::histogram_time, line::ldes::LdesCacheMetrics::hit, line::ldes::LdesCacheMetrics::hitList, line::ldes::LdesCacheMetrics::itemProb, line::ldes::LdesResult::BusyPeriodTarget::job_class, line::ldes::LdesCacheMetrics::latency, line::ldes::LdesCacheMetrics::listCost, line::ldes::LdesResult::BusyPeriodTarget::mean, line::ldes::LdesResult::MemOccNfcr, line::ldes::LdesResult::method, line::ldes::LdesCacheMetrics::miss, line::ldes::LdesResult::BusyPeriodTarget::name, line::ldes::LdesResult::nchains, line::ldes::LdesResult::nclasses, line::ldes::LdesResult::nregions, line::ldes::LdesResult::nstations, line::NumericError::NumericError(), parse_ldes_result(), line::ldes::LdesResult::QN, line::ldes::LdesResult::QNCI, line::ldes::LdesResult::QNfcr, line::ldes::LdesResult::QNRelPrec, line::ldes::LdesResult::QNSamples, line::ldes::LdesResult::QNt, line::ldes::LdesResult::renegedCustomers, line::ldes::LdesResult::renegingRate, line::ldes::LdesResult::respTimeSamples, line::ldes::LdesResult::retrialDropped, line::ldes::LdesResult::retriedCustomers, line::ldes::LdesResult::RN, line::ldes::LdesResult::RNCI, line::ldes::LdesResult::RNfcr, line::ldes::LdesResult::RNRelPrec, line::ldes::LdesResult::RNSamples, line::ldes::LdesResult::runtime, line::Matrix< T >::size(), line::ldes::LdesResult::station_names, line::ldes::LdesResult::BusyPeriodTarget::stations, line::ldes::LdesResult::stopping_reason, line::ldes::LdesResult::t, line::ldes::LdesResult::TN, line::ldes::LdesResult::TNCI, line::ldes::LdesResult::TNfcr, line::ldes::LdesResult::TNRelPrec, line::ldes::LdesResult::TNSamples, line::ldes::LdesResult::TNt, line::ldes::LdesResult::total_simulated_events, line::ldes::LdesResult::traj_space, line::ldes::LdesResult::traj_time, line::ldes::LdesResult::UN, line::ldes::LdesResult::UNCI, line::ldes::LdesResult::UNfcr, line::ldes::LdesResult::UNRelPrec, line::ldes::LdesResult::UNSamples, line::ldes::LdesResult::UNt, line::ldes::LdesResult::warnings, line::ldes::LdesResult::WeightNfcr, line::ldes::LdesResult::WN, line::ldes::LdesResult::WNCI, line::ldes::LdesResult::WNfcr, and line::ldes::LdesResult::XN.

Referenced by parse_ldes_result(), and solver_ldes_text().

◆ solver_ldes()

template<class T>
LdesResult line::ldes::solver_ldes ( const qn::NetworkStruct< T > & sn,
const LdesOptions & o,
const std::vector< std::string > & extra_flags = std::vector<std::string>() )

The same, for a model built through the C++ API.

The struct is serialized with io::network_json_envelope, the writer whose output the reference readers consume, and the document then takes the same path as any other. WHAT THE STRUCT CANNOT CARRY DOES NOT CROSS: a reward built from a lambda and any construct outside the writer's schema are dropped by the writer, with its own warning, which is why a caller holding a model.json should pass the DOCUMENT rather than a struct parsed from it – the round trip can only lose.

Definition at line 894 of file solver_ldes.h.

References line::io::network_json_envelope(), solver_ldes(), and solver_ldes_text().

Referenced by line::NetworkSolver::avg_table(), and solver_ldes().

◆ solver_ldes_file()

LdesResult line::ldes::solver_ldes_file ( const std::string & path,
const LdesOptions & o,
const std::vector< std::string > & extra_flags = std::vector<std::string>() )
inline

The same, reading the document from a file.

Definition at line 705 of file solver_ldes.h.

References solver_ldes_file(), and solver_ldes_text().

Referenced by solver_ldes_file().

◆ solver_ldes_text()

LdesResult line::ldes::solver_ldes_text ( const std::string & doc,
const LdesOptions & o,
const std::vector< std::string > & extra_flags = std::vector<std::string>() )
inline

Runs one LDES simulation on a model.json DOCUMENT and parses its result.

The document is written into a private scratch directory, the engine is run there, and the directory is removed on both the success and the failure path (TempDir's destructor), because a wrapper that leaks one directory per failed solve leaks silently.

THE RUNNERS ARE TRIED IN ORDER and the LAST failure is reported in full, merged stdout and stderr, because the engine states why it refused there and an exit code alone is not a diagnosis.

Parameters
docthe model.json text, forwarded byte for byte
othe run's knobs
extra_flagsflags an analysis adds (–trajectory, –export-histogram, –respt-samples)
Returns
the parsed result, carrying which image produced it

Definition at line 642 of file solver_ldes.h.

References line::util::capture(), line::ldes::LdesResult::engine, line::util::ProcResult::exitCode, line::util::TempDir::file(), ldes_flags(), ldes_runners(), ldes_solve_rest(), line::NumericError::NumericError(), line::util::ProcResult::out, parse_ldes_result(), line::ldes::LdesOptions::rest_url, solver_ldes_text(), line::util::LineConsole::step(), line::ldes::LdesResult::stopping_reason, line::ldes::LdesResult::timed_out, line::util::ProcResult::timedOut, line::ldes::LdesOptions::timeout, line::util::trim(), and line::ldes::LdesOptions::verbose.

Referenced by ldes_prob_aggr(), ldes_prob_sys_aggr(), solver_ldes(), solver_ldes_file(), and solver_ldes_text().

◆ warm_start_placement_from_ctmc()

template<class T>
Matrix< double > line::ldes::warm_start_placement_from_ctmc ( const qn::NetworkStruct< T > & sn,
const ctmc::CtmcOptions & opt )

◆ warm_start_placement_from_qlen()

template<class T>
Matrix< double > line::ldes::warm_start_placement_from_qlen ( const qn::NetworkStruct< T > & sn,
const Matrix< T > & QN )

Port of placementFromMeanQLen: an integer placement from mean queue lengths.

Parameters
snthe refreshed network struct the means were computed on
QN(nstations x nclasses) steady-state mean queue lengths

Definition at line 65 of file ldes_warm_start.h.

References line::InputError::InputError(), line::ldes::warm_detail::is_service_station(), and warm_start_placement_from_qlen().

Referenced by warm_start_placement_from_qlen().