io

class LineResultRecorder

Bases: handle

LINERESULTRECORDER Capture result tables with the solver that produced them.

Cross-codebase parity is asserted against one shared golden per example (goldens/baselines/*.json), keyed by SOLVER NAME. Until 2026-08-19 the only way to recover that key was to scrape the banner an example printed above each table – one regex dialect per codebase, and a value truncated to whatever the printer showed. The recorder supplies the same attribution BY CONSTRUCTION: a getter knows which solver it belongs to, which method that solver resolved, and the full-precision values it is returning. This is the MATLAB twin of python/line_solver/result_recorder.py.

It is OFF unless asked for, and an ordinary run pays one appdata lookup per getter and nothing else – no object is even created:

LineResultRecorder.enable(); % in-process setenv(‘LINE_RECORD_RESULTS’,’1’); LineResultRecorder.autoEnable();

WHAT IS RECORDED. One entry per OUTERMOST getter call. SolverAUTO and the ensemble solvers call a member solver’s getter of the same name, and recording both would report the member where the example named the ensemble; the depth guard in ENTER/CAPTURE keeps the outermost only.

WHY THE STATE LIVES IN ROOT APPDATA. Fifteen of the examples in the corpus run clear all, which wipes globals and persistents alike. A buffer stored in either would come back empty half way through those runs and read downstream as a solver that produced nothing – a parity failure with no defect behind it. Root (handle 0) appdata survives clear all and close all force.

See also LINERESULTRECORDER.ENTER, LINERESULTRECORDER.CAPTURE.

Copyright (c) 2012-2026, Imperial College London All rights reserved.

Property Summary
depth

getter nesting depth; only depth 1 records

enabled

recording on/off

notes

non-table facts a run reported (refusals)

records

cell array of record structs

seq

call order, 0-based, as python’s recorder numbers

Method Summary
append(label, method, view, labels, rows, derived)

APPEND(SELF, …) Add one record to the buffer.

static asChar(value)

TEXT = ASCHAR(VALUE) A label cell as plain char.

static asDoubles(value)

OUT = ASDOUBLES(VALUE) Every number in a returned scalar, in order.

static asTable(tbl)

DATA = ASTABLE(TBL) The MATLAB table behind a returned result.

Handles the IndexedTable wrapper (which holds it on .data), a bare table, and anything else by declining.

static autoEnable()

TF = AUTOENABLE() Enable if LINE_RECORD_RESULTS is set to 1.

static capture(depth, solver, view, tbl)

CAPTURE(DEPTH, SOLVER, VIEW, TBL) Record one returned table.

DEPTH is ENTER’s first output; 0 (recording off) and any nested depth record nothing. VIEW names which table this is.

static captureScalar(depth, solver, quantity, value)

CAPTURESCALAR(DEPTH, SOLVER, QUANTITY, VALUE) Record a derived scalar.

Twenty-nine of the goldens hold a quantity no result table carries – a state probability, a workflow’s phase-type moments, a cache hit ratio. Where a library call returns the value, the same construction that records a table records it: the value, the call that produced it, and the solver it belongs to. ParityDerived is what maps a recorded scalar onto the key a particular golden uses, because THAT part is example-specific.

The value is flattened to a list of doubles because these getters return a scalar, a pair or an array depending on the solver; the consumer names the element its golden means. DEPTH is ENTER’s first output, so a delegating solver records once and not twice.

static disable()

DISABLE() Stop recording. The buffer is left intact.

static dump()

OUT = DUMP() Everything recorded, as a struct ready for JSONENCODE.

static enable()

ENABLE() Start recording, and drop anything recorded before.

static enter()

[DEPTH, GUARD] = ENTER() Open a getter’s recording scope.

DEPTH is 0 when recording is off, which makes CAPTURE a no-op and keeps an ordinary run free of everything below. GUARD is an ONCLEANUP whose destruction pops the depth, so it is popped on EVERY exit from the getter – including one that raises, which is how the guard survives an example that catches a solver error and carries on.

THE GUARD IS RETURNED SEPARATELY, not folded into a struct with the depth, because MATLAB does not promise to destroy an ONCLEANUP held inside a struct or a cell when the enclosing value goes out of scope. A depth left elevated would make every later getter in the same run look like a nested call and record nothing.

static instance()

R = INSTANCE() The session recorder, created on first use.

static isEnabled()

TF = ISENABLED() True when a getter should record.

Deliberately does NOT create the recorder: a run that never asked to record must not pay for an object it will never read.

static memberSolver(solver)

MEMBER = MEMBERSOLVER(SOLVER) The layer/stage solver an ensemble ran.

Getting this wrong is not a spelling difference – MVA layers and NC layers are different fixed points (lcq_threehosts: cache hit 0.5 against 0.48331). SolverLN, SolverENV and UQ all populate self.solvers while solving, so the member is read from the object that actually ran rather than guessed from the factory handle.

static note(text)

NOTE(TEXT) Record something that is not a table.

Used for a solver’s own refusal (‘this engine does not implement X’), which a consumer must be able to tell apart from a table that simply never arrived: the first is a fact about the port and is a named skip, the second is a failure.

static pop()

POP() Close one recording scope. Called by ENTER’s onCleanup.

static qualifiedLabel(solver)

LABEL = QUALIFIEDLABEL(SOLVER) ‘MVA:schmidt’ for a pinned AUTO, ‘’ else.

SolverAUTO SPLITS the method name it was given: RESOLVEMETHODTOKEN puts the family in SELECTIONMODE and the submethod in OPTIONS.METHOD, so the pair has to be re-joined here. Reading OPTIONS.METHOD alone would label the solve by a bare submethod (‘schmidt’), which names no family and matches no golden key.

reset()

RESET(SELF) Drop the buffer and the nesting depth.

static resetBuffer()

RESETBUFFER() Drop the buffer without changing the on/off flag.

static solverLabel(solver)

LABEL = SOLVERLABEL(SOLVER) The golden’s key for this solver.

A LAYERED or ENVIRONMENT solve is qualified by the member solver it drove – ‘LN(NC)’, ‘ENV(FLD)’ – because that is how several goldens spell it and because the two are genuinely different computations. The comparator still reconciles the qualified and bare spellings against the golden’s own key; recording the member is what gives it the evidence to do so safely.

static solverMethod(solver)

METHOD = SOLVERMETHOD(SOLVER) The method this solver resolved.

MAM(dec.source) and MAM(inap) differ by 236% on the same model, so the method is not decoration: it is what makes a recorded table attributable to the run the golden was generated from.

static tableRows(tbl, view)

[ROWS, LABELS] = TABLEROWS(TBL, VIEW) Rows of a returned table.

Values come out at FULL precision: quantizing to the golden’s printed precision is the comparator’s job, and doing it here would throw away the digits a future full-precision golden needs. A cell that will not convert stays a string, which is right for a label column and harmless for anything else – the comparator only reads the metrics its golden names.

static viewLabels(view)

NAMES = VIEWLABELS(VIEW) The two label columns a view declares.

Only consulted for a table whose columns carry none of the names in LABEL_COLUMNS, so an unlisted view still records correctly.

line_printf(MSG, varargin)

LINE_PRINTF(MSG, VARARGIN)

class LineStatus

LINESTATUS One line of output that is REWRITTEN rather than repeated.

An iterative solver reports its progress once per iteration. Printed as one line each, a run of a few hundred iterations scrolls everything else out of the terminal to say the same six numbers over and over. LINESTATUS keeps that report on a SINGLE row and updates it in place:

LineStatus.set(‘Iter %2d. Analyze time: %.3fs.’, it, t); LineStatus.append(’ MaxIterErr=%.3e’, err); % same row, more fields LineStatus.close(); % end the row, once

The row is rewound with BACKSPACES, not with a carriage return: ‘b’ is the idiom that works both in the MATLAB desktop Command Window and under matlab -batch, whereas ‘r’ is handled inconsistently between them. A shorter row is padded with blanks so the tail of a longer predecessor cannot survive underneath it.

AN INTERLEAVED PRINT ENDS THE ROW, it does not corrupt it. Backspaces walk back over whatever is actually on the terminal and cannot cross a newline, so a warning raised mid-iteration used to land inside the row and leave the NEXT rewind short, splicing two iterations into one line:

Iter 51. … Runtime: 6.177s. MaxIterErr=1.08e-14 (tol=5.0000Iter 52. …

LINE_PRINTF therefore calls CLOSE before it writes anything, which is why this class emits through its own raw path rather than through line_printf – otherwise every update would close the row it was drawing. A warning now gets its own line and the next iteration opens a fresh row. Inside SolverLN the interleaving is rare to begin with, because the layer solvers are silenced (see SolverLN.setSolver).

CLOSE is idempotent and emits the newline the row never had.

A row containing a newline cannot be rewound, so SET and APPEND strip them. At VerboseLevel.SILENT every entry point is a no-op and no state is kept, so the bookkeeping cannot drift against what was actually written.

See also: line_printf(), LineConsole, VerboseLevel

Copyright (c) 2012-2026, Imperial College London All rights reserved.

Method Summary
static append(fmt, varargin)

APPEND(FMT, …) extends the row and rewrites it

The row is assembled by more than one caller – EnsembleSolver ITERATE lays down the timings and SolverLN CONVERGED adds the iteration error – so appending has to re-render the whole row rather than print a fragment after it.

static close()

CLOSE() ends the row with a newline and forgets it

The blanks that padded a SHRINKING row stay on the finished line. They cannot be taken back: a backspace moves the cursor, it does not erase, so rewinding over the pad and then emitting the newline changes only where the cursor was, not what the line holds. They are invisible on a terminal and only surface if the line is copied. The way to avoid them is to keep one-off notices OFF the row – see how SolverLN.converged prints its averaging notice on a line of its own – so that the row never shrinks.

static isOpen()

TF = ISOPEN() true while a status row is being rewritten

static reset()

RESET() forgets the open row WITHOUT emitting anything

For an interrupted solve, where the row is already lost: closing it would backspace over whatever the error printed instead.

static set(fmt, varargin)

SET(FMT, …) replaces the row with this text

static state(newst)

ST = STATE(NEWST) reads, and optionally replaces, the row state

TWO FIELDS, AND THEY ARE NOT THE SAME STRING. text is the row’s LOGICAL content, which APPEND extends; width is how many characters are actually on the terminal, which is what the next rewind has to walk back over. Keeping only the on-screen string and appending to that made every append start after the blanks that padded the previous row, so the row grew by its own padding on every iteration and ran to hundreds of thousands of columns.

pnml_load(filename, netName)

PNML_LOAD Read a PNML (ISO/IEC 15909-2) place/transition net into LINE.

MODEL = PNML_LOAD(FILENAME) reads the first net of a PNML document in the place/transition grammar and returns the equivalent LINE Network: one Place per PNML place, one Transition per PNML transition, and a single ClosedClass holding the tokens of the initial marking.

MODEL = PNML_LOAD(FILENAME, NETNAME) reads the net with the given id, for a document holding more than one.

THE GRAMMAR IS UNTIMED. A PNML place/transition net says nothing about how long a transition takes to fire, so a file written by another tool is read with every transition TIMED and EXPONENTIAL AT RATE 1, which is the convention of the stochastic Petri net literature and of GreatSPN’s own default. A file written by PNML_SAVE carries LINE’s timing in its toolspecific block and reads back with the distributions, servers, priorities and weights it was written with, and with the modes regrouped into the transitions they were split from.

Inhibitor arcs are read from the <type value=”inhibitor”/> extension that GreatSPN, TINA and PIPE all write, since the grammar itself has no inhibitor arc.

See also PNML_SAVE, LINEMODEL_LOAD.

Copyright (c) 2012-2026, Imperial College London All rights reserved.

linemodel_load(filename)

LINEMODEL_LOAD Load a LINE model from JSON.

MODEL = LINEMODEL_LOAD(FILENAME) loads a model from the specified JSON file (conforming to line-model.schema.json) and returns a Network, LayeredNetwork, Workflow, or Environment object.

Parameters:

filename - path to a .json file

Returns:

model - Network, LayeredNetwork, Workflow, or Environment object

Example

model = linemodel_load(‘mm1.json’); solver = SolverMVA(model); AvgTable = solver.getAvgTable();

Copyright (c) 2012-2026, Imperial College London All rights reserved.

line_java_exe()

JAVAEXE = LINE_JAVA_EXE()

Resolve the Java launcher used to spawn JMT (and any other JVM tool driven out of MATLAB). Returns ‘’ when no JVM is reachable, so a caller can raise a diagnosis of its own instead of letting the failure surface as a raw java.io.IOException (“Cannot run program “”java””: CreateProcess error=2”).

Search order:
  1. LINE_JAVA - full path of a launcher chosen by the user

  2. JAVA_HOME/bin - the conventional JDK/JRE variable

  3. MATLAB_JAVA/bin - the JVM MATLAB itself was pointed at

  4. the JRE bundled with MATLAB (layout varies across releases)

  5. “java” on PATH

Step 4 is what keeps the JMT viewers usable on a host with no system-wide Java, the common case on Windows: MATLAB ships its own JRE, so a missing PATH entry is not a missing JVM.

The result is cached per session: the PATH probe costs a process launch, and a JVM neither appears nor vanishes mid-session.

Copyright (c) 2012-2026, Imperial College London All rights reserved.

jsimwView(filename)

JSIMWVIEW(FILENAME) Open model in JSIMwiz

line_citations(tokens)

ENTRIES = LINE_CITATIONS(TOKENS)

Bibliographic references for the algorithms named by TOKENS, as a struct array with fields:

.key - bibliography key, as used in doc/latex/biblio.bib .ref - short reference, author-title-venue-year .covers - one line saying which part of the solution process it covers

METHOD NAMES is a cell array of algorithm or feature names (solver methods such as ‘bs’ or ‘comom’, transformations such as ‘mmt’, percentile methods such as ‘forktail’). Unknown tokens are ignored, so a caller may pass whatever it knows about a run. The registry is the manual’s method-to-citation table (doc/latex/manual.tex) and its bibliography; keep the two in step.

Attribution in LINE is pull-based: nothing is printed during a solve, and a user asks for the references with solver.citations() when writing them up.

Copyright (c) 2012-2026, Imperial College London All rights reserved.

LQN2QN(lqn, replication)

LQN2QN Convert a LayeredNetwork (LQN) to a Network (QN) using REPLY signals

model = LQN2QN(lqn) flattens a LayeredNetwork into a single queueing network in which synchronous call blocking is represented by REPLY signals.

model = LQN2QN(lqn, replication) selects how task and processor replication is represented: ‘auto’ (default) materialises the replicas while the expansion stays within the instantiation budget and pools them otherwise, ‘materialize’ always materialises, ‘pool’ always pools.

Construction - One station per host processor (scheduling and multiplicity taken from

the processor). Tasks sharing a processor share the station, as in the LQN semantics where the processor is the contended resource.

  • One Delay per reference task, holding its think time.

  • One closed class per step of the expanded activity graph. A step is an activity, or one call stage of an activity that issues synchronous calls. Steps of the reference task chain carry population 0 except the think class, which carries the reference task multiplicity.

  • A synchronous call site blocks its caller: the step class has a REPLY signal bound to it (sn.syncreply), the token proceeds to the callee, and the callee’s replying activity class-switches to that signal, which returns to the caller station and unblocks it.

  • Call multiplicity mean m is unrolled into floor(m) mandatory call stages plus, if m is not integer, one further stage entered with probability m-floor(m). This preserves both the mean number of calls and the blocking of each individual call.

  • OR-branch and loop probabilities are read from the activity graph weights lsn.graph(a,b). Multiple entries per task and branching call trees are expanded, each call site receiving its own copy of the callee subgraph so that per-call-path response times remain distinguishable.

  • AND precedences become Fork and Join nodes: a POST_AND successor set is entered through a Fork and one Router per branch, since a Fork cannot switch class per output link, and the PRE_AND branch tails switch back to the class that entered the Fork, which is the class the Join matches its siblings on. A branch tail that issues a synchronous call is given a merge step, so that it reaches the Join in an ordinary class rather than as a REPLY signal, which carries no forked-task identity.

  • A CacheTask becomes a Cache node. The activity bound to an ItemEntry is the read step and sits on that node, carrying the item popularity and cardinality of the entry; its two CacheAccess successors become the hit and the miss class, and the class switch is performed by the Cache node itself, so the routes leaving it are written in the successor class.

  • An asynchronous call is lowered to a non-blocking visit: the caller does not hold its server for the duration of the call, but it is serialised behind it, since a closed network has no means of creating the second token that a truly concurrent send would require.

  • Entry forwarding splits the reply exits of the forwarding entry: with the forwarding probability the request is handed to the target entry, which replies to the original caller, so the forwarder is released while the caller stays blocked.

  • The multiplicity of a non-reference task is its thread pool: at most that many requests may be inside the task at once, where inside spans the task’s own steps and those of its nested callees, since a thread is held for the whole of a synchronous call. It is enforced by a finite capacity region with one linear admission constraint per task, and the calls of such a task do not hold the caller’s server, since the processor is released while a thread waits for a reply.

  • An entry with an open arrival process receives requests from a Source: they traverse the entry’s subgraph in open classes and leave through a Sink where the entry would reply. Calls on an open chain do not hold the caller’s server, since a REPLY signal is a closed class that cannot be woven into an open chain; the thread pools of the traversed tasks are still enforced by the finite capacity region.

  • Phase-2 activities, the successors of a replying activity, run after the reply: the replying step’s exit routes back to the caller, and each of its service completions spawns the continuation at the host station (sn.classspawn). The spawned token walks the phase-2 subgraph holding only the task’s own thread and is destroyed at the chain end, through a NEGATIVE signal that always misses on a closed chain, or through the Sink on an open one. A boundary that ends on a call site is normalised through a merge step at the host station; a phase 2 that opens with an AND-fork spawns into an immediate head that feeds the Fork; at an AND-join branch tail the spawned token inherits the fork identity of the trigger and stands in for it at the Join; at a cache read the reply is emitted by an immediate trigger step per hit/miss outcome, whose completion spawns the matching branch continuation.

  • An AND-join quorum k of n is applied to the Join node in the class that entered the Fork; k equal to the branch count is the default wait-for-all and is left alone. An activity think time becomes an extra step on a shared ActivityThink delay, in series with the host demand, so the task keeps its thread for it while its processor is released.

  • Replication is represented in one of two ways. Under materialisation each replica of a processor is a station of its own and each replica of a task carries its own copy of the expanded step graph, its own reply signals and its own admission row; a call from replica i of the caller reaches the fan-out block {(i*f+k) mod r} of the callee replicas and splits its call mean uniformly over them, which is deterministic pairing at f=1 and a uniform broadcast at f=r. Under pooling the replicas of a processor collapse into one station of r times the servers, a replicated thread pool into one admission row of r times the bound, and a replicated reference task into one class of r times the population; that is exact at an infinite-server host and optimistic elsewhere, since pooled servers share one queue while the replicas hold r separate ones.

  • A CacheTask with delayed-hit retrieval gets a retrieval system, which is an ordinary queueing network: one PS fetch station per cache replica, entered and left by the read class, with the Cache node coalescing concurrent misses of the same item. The fetch is what the miss branch does, so the miss activity’s host demand moves onto that station; calls issued by the miss activity stay outside the retrieval system and are warned.

  • A SetupTask carries its setup and delay-off times onto its host station as the Queue setup/delay-off pair, per step class: the server shuts down after the delay-off idle period and pays the setup on the next arrival. An infinite-server processor never shuts down, so the pair is dropped there with a warning, as is a setup with no delay-off time.

Not yet represented: retrieval on a cache read with phase-2 successors and the thread pool of a task with an internal AND-fork. Each is reported through line_warning.

Example

lqn = LayeredNetwork(‘MyLQN’); % … define LQN model … model = LQN2QN(lqn); SolverLDES(model).getAvgTable()

Copyright (c) 2012-2026, Imperial College London All rights reserved.

class WfCommonsLoader

Bases: handle

WfCommonsLoader - Load WfCommons JSON workflows into LINE Workflow objects.

WfCommonsLoader provides static methods to load workflow traces from the WfCommons format (https://github.com/wfcommons/workflow-schema) into LINE Workflow objects for queueing analysis.

Supported schema versions: 1.4, 1.5

Example

wf = WfCommonsLoader.load(‘workflow.json’); ph = wf.toPH();

Example with options:

options.distributionType = ‘exp’; options.defaultRuntime = 1.0; wf = WfCommonsLoader.load(‘workflow.json’, options);

Copyright (c) 2012-2026, Imperial College London All rights reserved.

Property Summary
SUPPORTED_SCHEMA_VERSIONS
Method Summary
static load(jsonFile, options)

LOAD Load a WfCommons JSON file into a Workflow object.

WF = WFCOMMONSLOADER.LOAD(JSONFILE) WF = WFCOMMONSLOADER.LOAD(JSONFILE, OPTIONS)

Parameters:
  • jsonFile - Path to WfCommons JSON file

  • options - Optional struct with – .distributionType - ‘exp’ (default), ‘det’, ‘aph’, ‘hyperexp’ .defaultSCV - Default SCV for APH/HyperExp (default: 1.0) .defaultRuntime - Default runtime when missing (default: 1.0) .useExecutionData - Use execution data if available (default: true) .storeMetadata - Store WfCommons metadata (default: true)

Returns:

wf - Workflow object

static loadFromStruct(data, options)

LOADFROMSTRUCT Load from pre-parsed struct.

WF = WFCOMMONSLOADER.LOADFROMSTRUCT(DATA) WF = WFCOMMONSLOADER.LOADFROMSTRUCT(DATA, OPTIONS)

Parameters:
  • data - Struct from jsondecode

  • options - Options struct (see load method)

Returns:

wf - Workflow object

static loadFromUrl(urlString, options)

LOADFROMURL Load WfCommons JSON from a URL.

WF = WFCOMMONSLOADER.LOADFROMURL(URLSTRING) WF = WFCOMMONSLOADER.LOADFROMURL(URLSTRING, OPTIONS)

Useful for loading workflows directly from repositories like wfcommons/pegasus-instances.

Parameters:
  • urlString - URL pointing to WfCommons JSON file

  • options - Options struct (see load method)

Returns:

wf - Workflow object

static validateFile(jsonFile)

VALIDATEFILE Check if file is valid WfCommons schema.

ISVALID = WFCOMMONSLOADER.VALIDATEFILE(JSONFILE)

Returns:

isValid - true if file is valid WfCommons format

line_warning_always(caller, MSG, varargin)

LINE_WARNING_ALWAYS(CALLER, MSG, …)

Emit a warning without the repeat suppression applied by LINE_WARNING.

LINE_WARNING keeps only the last message and hides an identical repeat for 60 seconds. That is right for configuration notices cast once per model, but wrong for a warning that reports a correctness limitation of the analysis: solving several models in one session would then flag only the first one, and the user would read the silence on the others as a clean bill of health. Warnings that say “these numbers are not exact” must be raised for every model they apply to, so they go through here instead. Verbosity gating is unchanged: SILENT still silences everything.

line_citation(toolName)

BIB = LINE_CITATION(TOOLNAME)

Return the BibTeX entry for the canonical paper of an external tool that a wrapper solver delegates to, so that the acknowledgement printed by LINE_ACK can be turned into a citation without retyping it. With no output argument the entry is printed instead.

TOOLNAME is ‘JMT’, ‘LQNS’ or ‘QNS’ (‘QNS’ shares the LQNS reference, qnsolver being part of that distribution). An unknown tool returns ‘’. Wrapper solvers that live outside this tree carry their own reference: their tools must not be named in this codebase.

The keys match doc/latex/biblio.bib and BIBLIOGRAPHY.md. Mirror any edit in jline.io.InputOutput.line_citation and line_solver.api.io.logging.line_citation.

Example

line_citation(‘JMT’)

See also LINE_ACK.

line_method_type(solvername, method)

LABEL = LINE_METHOD_TYPE(SOLVERNAME, METHOD)

Classification of a solution method, as printed in the solver banner:

<accuracy>, <randomness>

with ACCURACY in {exact, approximate, bound} and RANDOMNESS in {deterministic, randomized}. SOLVERNAME is the banner solver name (‘MVA’, ‘NC’, …) and METHOD the resolved method label, which may carry the ‘default/’ prefix produced by the banner (‘default/exact’).

Conventions, applied uniformly across the four codebases:
  • exact: the algorithm targets the metric with no modeling

    approximation; numerical truncation and floating-point error do not make a method approximate. Integral representations and transform inversions are exact, asymptotic expansions are not.

  • approximate: the algorithm introduces a heuristic, an asymptotic

    expansion, a decomposition, or a statistical estimate.

  • bound: the algorithm returns a formal one-sided bound on the

    metric, not a point estimate: the value is guaranteed to lie on the stated side of the exact one, and the two sides of a family bracket it. The side is read off the method label and printed with it (‘gb.upper’ -> ‘upper bound’).

  • randomized: the algorithm consumes pseudo-random numbers, so two runs

    agree only if the seed does.

Perfect sampling (cftp) is classified by the law it samples from, which is the stationary one, hence exact; the ordinary simulators are approximate because a finite horizon leaves warm-up bias on top of the sampling error.

Lookup order: ‘<solver>.<method>’, ‘<method>’, the tail after each dot of METHOD (longest suffix first), the head before its first dot, ‘<solver>’, then the global default ‘approximate, deterministic’. The unknown-method default is deliberately the conservative one: claiming exactness that a method does not have is the costlier error.

Keep this registry in step with line_citations.m and with its twins MethodType.java, solvers/base.py:method_type and cpp method_type.h.

Copyright (c) 2012-2026, Imperial College London All rights reserved.

line_error(caller, msg, varargin)

LINE_ERROR Display a plain-text error message with file and line info.

LINE_ERROR(CALLER, MSG, …) throws an error with CALLER’s name and message, including the source file and line number, in plain text (no hyperlink). Extra arguments are passed to sprintf to format MSG.

The exception carries NO MATLAB stack, so the console shows the message alone rather than the chain of internal frames that led to it. Every line_error is a diagnostic LINE wrote on purpose and its message already names the throwing function and line, so the frames in between (runAnalyzerChecks -> runAnalyzer -> getAvg -> getAvgTable -> aT …) are noise to a user who only asked a solver for a result. Genuine MATLAB faults (index out of range, undefined function) are untouched and still report in full.

Set the verbosity to VerboseLevel.DEBUG to get the stack back, e.g. SolverNC(model,’verbose’,VerboseLevel.DEBUG) or GlobalConstants.setVerbose(VerboseLevel.DEBUG). dbstop if error stops at the throw site either way, since this is still error().

class LineConsole

LINECONSOLE Running progress log of a LINE solver run.

LineConsole narrates what a solver is doing while it does it: reading the model, compiling the network structure, computing chains, visits and demands, resolving the method, iterating, and closing with the figures of merit. Each line carries the elapsed time since the run started:

[ 0.014s] compiling the network structure (sn) [ 0.031s] computing chains and visit ratios [ 0.052s] closed queueing network: 3 stations, 1 class, 1 chain [ 0.061s] AMVA sweep 10: residual 1.11e-01, X = 1.2225

It prints no tables: the result table stays the caller’s own getAvgTable. THE CONSOLE IS VerboseLevel.DEBUG: it narrates exactly when the run is at DEBUG and is silent at every lower level, and it never alters a numerical result. There is no separate console switch – the console was one, until it became clear that a running progress log IS what a debug verbosity is for, and two switches for one channel only let a session ask for DEBUG and get nothing.

Typical use:

line_verbosity(VerboseLevel.DEBUG) SolverMVA(model).getAvgTable

or, for one run alone:

SolverMVA(model,’verbose’,VerboseLevel.DEBUG).getAvgTable

Nested runs (an inner solver invoked by SolverLN or SolverAUTO) do not narrate: only the outermost run writes, so several layer solves cannot interleave their lines. Use ISACTIVE to suppress a legacy print, OWNSLOG to emit.

See also: line_verbosity(), VerboseLevel, GlobalConstants

Copyright (c) 2012-2026, Imperial College London All rights reserved.

Method Summary
static article(word)

S = ARTICLE(WORD) the indefinite article that fits WORD

static beginRun(options)

GUARD = BEGINRUN(SELF, OPTIONS) opens a console run

Returns an onCleanup handle that closes the run, writing the closing lines, when the caller’s analyzer returns or errors. The caller MUST hold the handle for the whole analysis.

static blankState()

ST = BLANKSTATE() initial console state

static className(sn, r)

S = CLASSNAME(SN, R) display name of a class index

static closeRun()

CLOSERUN(SELF) closes the innermost open run

static closingLines()

CLOSINGLINES(SELF) the figures of merit and the timing split

static compileDetail(fmt, varargin)

COMPILEDETAIL(FMT, …) one stage line of a structure compile

Silenced inside a PUSHQUIET scope, and inside an open run, where the structures being compiled are those of auxiliary models (SolverLN layers) rather than of the model under study. BEGINRUN lifts the second rule while it compiles its own model.

static compileStruct()

COMPILESTRUCT(SELF) compiles sn, narrated by refreshStruct

static compiling(name)

COMPILING(NAME) announces the compilation of a model structure

Inside an open run the model being compiled is an auxiliary one (a SolverLN layer), and saying so keeps it apart from the model the user asked about.

static deferPrint(fmt, varargin)

DEFERPRINT(FMT, …) queues a legacy line for after the run closes

The console owns the log while a run narrates, so a solver’s standard completion message is held here and written once the closing DONE line has gone out, reading as it would with the console off.

static delayMask(sn)

MASK = DELAYMASK(SN) stations that serve without queueing

static detail(text, reset)

DETAIL(TEXT) reports a solver’s own debug message as a substep

LINE_DEBUG routes here while a run narrates. Consecutive repeats are dropped, at most three messages of the same SHAPE (the text with its numbers masked) are reported, and the channel is capped per run, since a message inside a loop would bury the narration.

DETAIL(‘’, TRUE) resets the bookkeeping at the start of a run. It is held in persistent variables of this method rather than in the shared state struct: growing a cell array nested inside that struct, thousands of times through a static method, crashed the MATLAB JIT thread outright (segmentation violation) on a long SolverENV run.

static emitLine(indent, text)

EMITLINE(INDENT, TEXT) writes one timestamped line

The timestamp is the elapsed time since the opening line of the current run. Each run also reports its own duration in its closing line. A top-level row opens with a capital, an indented substep stays lowercase.

static ensembleClosing(st)

ENSEMBLECLOSING(SELF, ST) closing lines of an ensemble run

static featureList()

S = FEATURELIST(SELF) the model’s used language features

static field(s, name, dflt)

V = FIELD(S, NAME, DFLT) struct field with a default

static has(sn, name)

TF = HAS(SN, NAME) field test that works on structs and objects

static hasCompiledStruct(model)

TF = HASCOMPILEDSTRUCT(MODEL) true when sn is already built

static isActive()

TF = ISACTIVE() true while a run is narrating

static isDebug(level)

TF = ISDEBUG(LEVEL) true when a verbosity LEVEL asks for DEBUG

A logical is not a level (see WANTED) and never reaches here from that path; treated as false so a stray one cannot switch the console on by itself.

static iter(k, fmt, varargin)

ITER(K, FMT, …) reports iteration K of the current loop

Lines are decimated: the first 20 iterations report in full, then every 10th, and the loop stops reporting after 30 lines, so that a long run cannot bury the rest of the narration.

static loop(fmt, varargin)

LOOP(FMT, …) announces an iteration loop and resets its budget

Each loop of a run gets its own budget of reported iterations, so a solver that restarts a loop (the fluid integration passes) does not exhaust the budget of the next one.

static lowerFirst(s)

S = LOWERFIRST(S) lowercase the first letter of a sentence

The routed messages were written as standalone sentences; the console reads as one narration, so they join it in lower case unless they open with an acronym (CTMC, AMVA, LQN, …).

static modelKind(sn)

S = MODELKIND(SN) the phrase that names this kind of model

static modelName()

NAME = MODELNAME(SELF) the analyzed model’s name

static modelOf()

MODEL = MODELOF(SELF) the analyzed model, or []

static openRate(sn, c)

LAMBDA = OPENRATE(SN, C) total arrival rate of an open chain

static openingLines(options)

OPENINGLINES(SELF, OPTIONS) narrates the setup of the run

static ownsLog()

TF = OWNSLOG() true only inside the OUTERMOST open run

Analyzer hooks test this rather than ISACTIVE: an inner solver driven by SolverLN or SolverAUTO runs its own loops, and letting it write would interleave several narrations. Use ISACTIVE to suppress a legacy print, OWNSLOG to emit.

static plural(n, singular, plural)

S = PLURAL(N, SINGULAR, PLURAL) count with an agreeing noun

static popQuiet()

POPQUIET() ends one PUSHQUIET scope

static populationLine(sn)

S = POPULATIONLINE(SN) per-class population or open marker

static presolve()

PRESOLVE(SELF) demands, bottleneck and the elementary bounds

static pushQuiet()

GUARD = PUSHQUIET() suppresses detail lines for a bounded scope

Used where many auxiliary models are compiled in a row (the SolverLN layer builders): each keeps its one headline STEP and drops its SUBSTEP breakdown. The caller must hold the handle.

static readModel()

READMODEL(SELF) what the model object declares, before compiling

static recognizeModel()

RECOGNIZEMODEL(SELF) states what kind of model this is

static reportMethod(options)

REPORTMETHOD(SELF, OPTIONS) the method that will actually run

static reset()

RESET() forgets any open run (used after an interrupted solve)

static resultLines(res)

RESULTLINES(SELF, RES) one line per figure of merit

static resultOf()

RES = RESULTOF(SELF) the Avg result block, or []

static schedMix(sn)

S = SCHEDMIX(SN) counts of each scheduling strategy in use

static sessionClock(restart)

T = SESSIONCLOCK(RESTART) elapsed seconds on the console clock

The clock is restarted by the opening line of each run, so that every run’s timeline starts at zero rather than accumulating across the session.

static solverTag()

TAG = SOLVERTAG(SELF) short solver name, e.g. MVA

static sourceMask(sn)

MASK = SOURCEMASK(SN) stations that inject rather than serve

static state(newst)

ST = STATE(NEWST) reads, and optionally replaces, console state

static stationName(sn, i)

S = STATIONNAME(SN, I) display name of a station index

static step(fmt, varargin)

STEP(FMT, …) writes one progress line

static structOf()

SN = STRUCTOF(SELF) the model’s struct, or [] when it has none

static substep(fmt, varargin)

SUBSTEP(FMT, …) writes one indented progress line

static unmute()

UNMUTE() ends one muted scope opened by a silenced run

static vec(v)

S = VEC(V) compact rendering of a numeric row

static versionString()

S = VERSIONSTRING() the running LINE version

static wanted(options)

TF = WANTED(OPTIONS) resolves whether this run should narrate

THE CONSOLE IS DEBUG, and this is the whole rule: a run narrates when it is at VerboseLevel.DEBUG and at no lower level. The run’s own options.verbose decides when it carries one, otherwise the session level does; there is no third switch that could put the two out of step.

static writes()

TF = WRITES() true when a progress line should be printed

Either the outermost run is narrating, or no run is open and the session is at DEBUG – the second case is what lets model construction (refreshStruct) narrate before any solver exists.

JSIM2LINE(filename, modelName)

MODEL = JSIM2LINE(FILENAME,MODELNAME)

class CPPLINE

CPPLINE MATLAB-to-C++ bridge (lang=’cpp’).

Static helpers that serialize a LINE model to the canonical model.json interchange (linemodel_save), run the C++ solver binary (line-cli) as a subprocess, and marshal the JSON answer back into MATLAB matrices. Mirrors PYLINE.m (lang=’python’) and the JLINE.m dispatch (lang=’java’), with the transport being subprocess + JSON rather than py.* or the JVM. It is the MATLAB twin of python/line_solver/solvers/cpp_dispatch.py and follows the same rules, flag for flag.

WHAT FALLS BACK AND WHAT DOES NOT. lang=’cpp’ is an assertion about what produced the numbers, so this bridge never substitutes another engine: a missing binary, a construct the C++ analyzer refuses, a non-zero exit or unparseable output are all errors. Silently answering from the MATLAB solver would hide exactly the class of defect cross-language comparison exists to find.

LayeredNetwork models reach the C++ layered solver through the .lqnx interchange (writeXML), which is the LQNS file format and covers every construct that reader carries, including fan-in/fan-out replication. A model whose think time sits on a non-reference task cannot be written as .lqnx at all, so it goes over model.json (linemodel_save) instead.

Copyright (c) 2012-2026, Imperial College London All rights reserved.

Property Summary
CLI_PSTAR

The exponent line-cli smooths min(n_i, S_i) with under –method pnorm: FluidOptions::pstar in cpp/include/line/solvers/fluid/solver_fluid.h. The CLI exposes no flag for it, so it is the ONLY exponent this bridge can ask for, and pnormMethod refuses any other rather than answering at this one.

Method Summary
static analysisViaCpp(solverName, model, options, analysis, flags)

PAYLOAD = ANALYSISVIACPP(SOLVERNAME, MODEL, OPTIONS, ANALYSIS, FLAGS) Run one non-average line-cli analysis and return ITS payload.

Every line-cli answer is keyed by its own -a, so reading the key back is also the check that the process answered the question that was put: a payload arriving under another key is a wrong answer, and taking whatever object came would turn that into wrong numbers instead of an error.

static aoiResults(solverName, model, options)

AOIRESULTS = AOIRESULTS(SOLVERNAME, MODEL, OPTIONS) The -a aoi payload in solver_mfq_aoi’s OWN struct shape, the one @SolverFLD/getAvgAoI and getCdfAoI already read out of self.result.solverSpecific.aoiResults.

Returning THAT struct rather than the finished answers is what keeps the port whole: both getters then run their existing bodies, so the summary table and the survival-function algebra have one implementation instead of a MATLAB one and a bridge one that can round differently. The (g,A,h) triples ride along for exactly this reason – getCdfAoI takes a caller’s own t_values, and a fixed grid could not answer that.

static assertSingleState(solverName, method, model)

ASSERTSINGLESTATE(SOLVERNAME, METHOD, MODEL) Refuse a state-dependent analysis whose initial state the wire cannot carry.

THE SINGLE STATE NOW TRAVELS. linemodel_save emits the (stateSpace, statePrior) pair for every stateful node carrying a state, INCLUDING the one-row space with prior [1] that setState and initFromMarginal leave, and the C++ network_reader stores it; analyzer_detail::default_init_state takes that row in preference to the default marking, and api::sn_declared_marginal decodes it for the -a prob arms. So -a prob, the transient arms and a sampled trajectory all start where the caller put the jobs, and this used to refuse them all – unconditionally, because MATLAB has no marker telling a setState apart from an initDefault. That marker is no longer needed: the two are now sent alike, and where the state IS the default the C++ reconstructs the same row.

WHAT IS STILL REFUSED IS A GENUINE DISTRIBUTION over several rows, AND ONLY ON THE ARMS THAT SEED ONE STATE. default_init_state and sn_declared_marginal take row 0 only when the space has exactly ONE row and rebuild the default marking otherwise, so a prior over k > 1 states would be answered as one state under the name of a question about a mixture.

IT DOES NOT APPLY TO THE TRANSIENT ARMS. solver_ctmc_transient_analyzer seeds init_state_distribution, the product of the declared per-node priors over the enumerated space, and integrates ONCE from that mixture, which IS the reference’s weighted sum over the support. -a tran, -a tranprob and -a tranreward therefore take a mixture and are not gated here. See _kb/07-cross-language-parity.md.

static auxFiltFromPayload(gen, key, n)

F = AUXFILTFROMPAYLOAD(GEN, KEY, N) Rebuild an {nstations x nclasses} cell of sparse matrices from the wire’s per-(station,class) triplet blocks. Each block carries its own Station and Class fields, 0-based like every other index on this transport.

static avgMatrices(results, sn)

[QN,UN,RN,TN,AN,WN] = AVGMATRICES(RESULTS, SN) Reduce a line-cli avg payload to station x class metric matrices.

ROWS ARE KEYED BY NAME, never by position: the CLI emits the stations in the order the model.json declares them, which is the NODE order, while the matrices are indexed by STATION. Reading them positionally would put a station’s numbers on another station’s row on any model with a non-station node before a station (a Source, a Router, a ClassSwitch).

static avgReward(solverName, model, options)

[R, NAMES] = AVGREWARD(SOLVERNAME, MODEL, OPTIONS) The steady-state expectation of every declared reward, from -a reward, in @SolverCTMC/getAvgReward’s contract.

The serializability precondition is shared with getTranReward; see unbridgeableRewards for why a dropped reward must stop the -a reward route rather than shorten its answer, and rewardsLocally for the route it takes instead.

static avgRewardLocally(solverName, model, options)

[R, NAMES] = AVGREWARDLOCALLY(SOLVERNAME, MODEL, OPTIONS) E[r] for rewards no wire format can carry, against the C++ law.

A FUNCTION HANDLE CANNOT CROSS THE WIRE, and there is nothing to serialize it into – but the reward is a function OF THE STATIONARY LAW, and that law is line-cli’s: pi and the aggregated state space arrive through CPPLINE.generator. Applying the caller’s own handle to them therefore still answers under lang=’cpp’: the engine that computed the chain, its stationary distribution and its enumeration is the C++ one, and this method only evaluates a map that no wire format can express. Refusing instead left rewardModel_aggregation, rewardModel_mm1k and rewardModel_multiclass – whose rewards are handles – unsolvable.

EVERY reward is evaluated here, not just the unbridgeable ones: -a reward answers for the serializable subset only, and splicing two vectors computed over the same law adds nothing but a pairing that can rotate.

static cacheField(e, name)

V = CACHEFIELD(E, NAME) A per-class cache result as a row vector, [] when the solver computed none. The empty is the point: it CLEARS a stale value.

static cdfFirstPassT(solverName, model, options, Arows, Brows)

[RD, OUT] = CDFFIRSTPASST(SOLVERNAME, MODEL, OPTIONS, AROWS, BROWS) The state-set first passage law from -a firstpasst, in @SolverCTMC/getCdfFirstPassT’s own contract: RD an [n x 2] matrix of [F(t), t], OUT carrying tset and the density. AROWS/BROWS are STATE ROWS (already resolved by the caller against its own space); an empty AROWS selects the conditional stationary law.

static cdfRespT(solverName, model, options)

RD = CDFRESPT(SOLVERNAME, MODEL, OPTIONS) The per-station, per-class response-time CDF from -a cdf, in @SolverCTMC/getCdfRespT’s own contract: an (nstations x nclasses) cell whose entries are [F(t), t] two-column matrices.

This is the EXACT tagged-chain law, the same quantity the MATLAB getter builds, so the two are comparable value for value. A (station, class) pair the tagged chain never visits carries no curve on the wire and stays [] here, which is how the MATLAB getter also leaves it.

static cdfSysRespT(solverName, model, options)

RD = CDFSYSRESPT(SOLVERNAME, MODEL, OPTIONS) The PER-CHAIN system response-time CDF, in @SolverCTMC’s getCdfSysRespT contract: a (1 x nchains) cell of [F(t), t]. The ROW shape is that getter’s, not a convention: it builds cell(1, nchains) and callers index it linearly.

It rides in the SAME -a cdf payload as the per-station curves, under ‘sysrespt’, because line-cli’s solve_ctmc_cdf computes both from one tagged-chain solve. Asking for it separately would solve the chain twice and report the second one.

static cppUnsupported(solverName, method, reason)

CPPUNSUPPORTED(SOLVERNAME, METHOD, REASON) Refuse a getter this bridge cannot serve, naming the getter and the reason. THE REASON IS THE POINT: what is unreachable is unreachable for a specific, per-getter cause, and collapsing those into one message would tell a caller to stop asking for something the port may be one arm away from answering.

static denseFromTriplets(payload, n)

M = DENSEFROMTRIPLETS(PAYLOAD, N) Rebuild an (n x n) sparse matrix from the wire’s From/To/Rate triplets, whose indices are 0-based.

static exportODEs(solverName, model, options, notation)

DOC = EXPORTODES(SOLVERNAME, MODEL, OPTIONS, NOTATION) The LaTeX document of the fluid drift, from -a odes. NOTATION is ‘scalar’ or ‘matrix’ and REACHES the C++ exporter, so ‘matrix’ returns the matrix document and not the scalar one under another name.

static extractJsonObject(text, binary)

OBJ = EXTRACTJSONOBJECT(TEXT, BINARY) Decode the first complete JSON object in TEXT, ignoring the solver banner line-cli prints ahead of it.

static findLineCli()

BINARY = FINDLINECLI() Locate the line-cli executable. Search order, most explicit first: the LINE_CLI_BINARY environment variable, the checkout’s common/ directory, the in-tree cpp/build directories, then PATH. Errors rather than returning ‘’ so a missing binary cannot be mistaken for a solver refusal further down.

static firstPassTMoments(solverName, model, options, Arows, Brows, nmax)

[M, MALL] = FIRSTPASSTMOMENTS(SOLVERNAME, MODEL, OPTIONS, AROWS, BROWS, NMAX) The state-set first passage MOMENTS from -a firstpasstmom, in @SolverCTMC/getFirstPassTMoments’s own contract: M the (1 x NMAX) moment vector, MALL the (nstates x NMAX) per-source matrix. AROWS/BROWS are STATE ROWS, as in CDFFIRSTPASST.

No –passage-method here, and that is not an omission: the moments come from one linear solve per order and invert no transform, so there is no inversion for the flag to select. line-cli refuses it on this arm for the same reason.

static generator(solverName, model, options)

G = GENERATOR(SOLVERNAME, MODEL, OPTIONS) The CTMC generator, state spaces and stationary law, as a struct with fields infGen, space, spaceAggr, pi and eventFilt.

TWO INVOCATIONS, ONE PER GETTER: -a gen is getGenerator and -a states is getStateSpace, and line-cli keeps them apart because they are different getters. The bridge asks for both and CHECKS THAT THEY AGREE on the state count before pairing Q with pi. An unchecked pairing is the one way this goes wrong in silence: a Q from one enumeration indexed by another’s states is not a chain, and every consumer reading a row of the space would read the wrong state rather than get an error.

static getAvg(solverName, model, options)

[QN,UN,RN,TN,AN,WN,RUNTIME] = GETAVG(SOLVERNAME, MODEL, OPTIONS) Run the C++ avg analysis and reduce it to (nstations x nclasses) matrices in the MATLAB struct’s own indexing.

static getEnsembleAvg(solver, options)

[QN,UN,RN,TN,AN,WN,RUNTIME] = GETENSEMBLEAVG(SOLVER, OPTIONS) Delegate a layered getEnsembleAvg() to line-cli, returning the per-LQN-element metric column vectors the MATLAB SolverLN uses.

ONE subprocess solves the whole ensemble: letting the MATLAB fixed point run and dispatching each layer separately would spawn one process per layer per iteration.

static getEnvAvg(env, refModel, options)

[QN,UN,TN,RUNTIME] = GETENVAVG(ENV, REFMODEL, OPTIONS) Delegate a random-environment solve to line-cli’s -s env arm. ENV is the Environment, REFMODEL the stage model whose station and class indexing the returned matrices carry.

RN and AN are NOT returned: @SolverENV/getEnsembleAvg defines them as NaN because the environment analyzer computes no response time, and the CLI emits nan for the same reason.

static jacobian(solverName, model, options, wantEquilibria)

[J, RHS, VARS, EQUILIBRIA] = JACOBIAN(SOLVERNAME, MODEL, OPTIONS, WANTEQUILIBRIA) The SYMBOLIC drift Jacobian from -a jacobian, in @SolverFLD/getJacobian’s own contract.

This is symbolic on both sides, which is why it can be bridged at all: line-cli’s arm builds the drift with fluid_symodes and differentiates it through the SAME symbolic backend the MATLAB getter reaches through SAGE, so the entries are expressions and not a numeric evaluation of them.

EQUILIBRIA are only computed when asked for – solving the system is the expensive part – so WANTEQUILIBRIA carries the CALLER’s nargout, not this function’s: called as [J,rhs,vars,eq] = …, nargout here is always 4 and would ask for them every time. hasEquilibria on the wire separates “asked and there are none” from “never asked”, so an empty list is never silently reported as “this system has none”.

static jsonNumericList(v)

X = JSONNUMERICLIST(V) Normalize a jsondecode’d JSON array of numbers to a row vector, mapping a JSON null (which arrives as an empty cell entry) to 0. An INFINITY crosses as the string ‘Infinity’/’-Infinity’, since JSON has no infinite literal; read as a name and defaulted to 0 it turns a queue at its stability boundary into an idle one.

static jsonNumericScalar(x)

V = JSONNUMERICSCALAR(X) One wire value as a double: the non-finite string spellings both CLIs emit, or str2double for anything else numeric-looking.

A NUMBER IS RETURNED AS ITSELF, and reaching char() with one was a silent zero: jsondecode gives a double for an ordinary JSON number, char(0.2231) is the character at CODE POINT 0, str2double of that empty string is NaN, and the NaN branch below floored it to 0. Every scalar read through here – ProbSys, ProbSysAggr, logNormConstAggr – therefore came back 0 whatever the engine said, and 0 is a legal probability, so nothing complained.

static jsonScalar(s, field)

V = JSONSCALAR(S, FIELD) A numeric field of a jsondecode’d row, NaN when absent or null.

static jsonStringList(v)

C = JSONSTRINGLIST(V) Normalize a jsondecode’d JSON array of strings to a cellstr. A one-element array decodes to a char row and an all-equal-length array may decode to a char matrix, so neither shape may be assumed.

static lnKnobs(options)

ARGS = LNKNOBS(OPTIONS) Translate the SolverLN options into layered-path CLI flags, refusing every setting the flag set cannot carry. A knob with no CLI counterpart is an error and not a silent drop: relax, relax_factor and layering all change the fixed point the MATLAB solver converges to.

static lnLayerSolver(solver)

LAYERSOLVER = LNLAYERSOLVER(SOLVER) Resolve –layer-solver from the layer solver the MATLAB SolverLN was built with, refusing anything the port has no layer engine for. The layer solver is what the fixed point is a fixed point of, so substituting MVA for a CTMC layer answers a different question. NC and SSA layers ARE carried (solve_layer_nc / solve_layer_ssa in solver_ln.h); refusing them here was a stale claim that left lqn_twotasks and lqn_ofbiz with no table at all.

static lqnxLossyTasks(solver)

OFFENDERS = LQNXLOSSYTASKS(SOLVER) Name the tasks whose think time an .lqnx serialization of this model would drop, i.e. the non-reference ones that carry one.

The lqnx schema accepts think time on a REFERENCE task only, and writeXML reports the loss and writes the file anyway (lqns would reject it otherwise). LINE nonetheless gives a non-reference task’s think time to its callers as a delay, so a model carrying one solves to DIFFERENT numbers through that transport: on gallery_lqn_basic, T3’s think time caps its throughput at multiplicity/think = 25/4 and drops it from 66.4 to 6.22. A non-empty return therefore does not refuse the solve – it routes it through linemodel_save, whose reader takes think time on any task.

static mamCdfRespT(solverName, model, options)

RD = MAMCDFRESPT(SOLVERNAME, MODEL, OPTIONS) The MAM response-time CDF from -a cdf, in solver_mam_passage_time’s own contract: an (nstations x nclasses) cell whose QUEUE row holds [F(t), t] and whose Source row stays [].

The wire entries carry a class and NO station, and that is not an omission: solver_mam_passage_time covers exactly two stations, a Source and one queue, so the station is determined by the model rather than by the class. This resolves it the same way, and errors if the model is not of that shape rather than guessing a row.

static mamProb(solverName, model, options, nodeIndex)

[JOINT, MARGINAL] = MAMPROB(SOLVERNAME, MODEL, OPTIONS, NODEINDEX) The queue-length law of ONE node under -s mam -a prob: JOINT is the (level x phase) table getProb returns and MARGINAL{r} is the per-class vector getProbMarg returns.

BOTH COME OFF ONE SOLVE, because they are two views of the same QBD law and the arm reports them together. The multi-queue refusal is the REFERENCE’S, raised there rather than here, so the two backends decline the same models for the same stated reason.

static matchingBrace(s)

STOP = MATCHINGBRACE(S) Index of the ‘}’ closing the ‘{’ at S(1), scanning outside string literals so a brace inside a name cannot end the object early.

static nodeSamplePath(solverName, model, options, node, numEvents, aggregate)

S = NODESAMPLEPATH(SOLVERNAME, MODEL, OPTIONS, NODE, NUMEVENTS, AGGREGATE) One node’s sample path in the contract of @@SolverCTMC/sample and sampleAggr: handle, t, state, event, isaggregate.

BOTH VIEWS COME OFF THE WIRE (nodeState, nodeAggr). The aggregate one is the port’s own marginal_of rather than a State.toMarginal applied here, so the reported counts are the engine’s reading of its own states.

static normConstAggr(solverName, model, options)

LG = NORMCONSTAGGR(SOLVERNAME, MODEL, OPTIONS) getProbNormConstAggr: log G, from -a normconst.

static num2arg(x)

S = NUM2ARG(X) Round-trip-exact decimal form of a scalar knob.

static optionFlags(options, token)

ARGS = OPTIONFLAGS(OPTIONS, TOKEN) The subset of SolverOptions line-cli honours for METHOD NAME, forwarded only where the caller overrode the MATLAB default.

static overridden(options, field, defaultValue)

V = OVERRIDDEN(OPTIONS, FIELD, DEFAULTVALUE) The option value when the caller moved it off the MATLAB default, [] otherwise.

static passageSetSpec(S)

SPEC = PASSAGESETSPEC(S) A state set as the –passage-from/–passage-into wire syntax: rows joined by ‘;’, entries by ‘,’. STATE ROWS travel rather than row indices, because the two enumerations need not order (or even purge) states identically – line-cli resolves rows by content against the space its own engine enumerated.

static pnormMethod(options)

OPTIONS = PNORMMETHOD(OPTIONS) Restate MATLAB’s p-norm smoothing request in line-cli’s own terms.

THE TWO SIDES SELECT THE SMOOTHING DIFFERENTLY, and only the symbolic getters notice, because a min-scaled drift has no Jacobian and so must be smoothed before it can be differentiated at all. MATLAB turns the smoothing on whenever options.config.pstar is set, leaving the METHOD name alone (solver_fluid_symodes, use_pnorm); line-cli turns it on only under –method pnorm and has no flag carrying the exponent, so it always smooths at its own p = 20. Forwarding ‘matrix’ would therefore have line-cli refuse a model MATLAB answers for, under a message about a kink the caller had already smoothed away.

A DIFFERENT EXPONENT IS REFUSED RATHER THAN ROUNDED TO 20: p sets how sharply the smoothed min approaches the kink, so answering at another p is answering about another drift.

static probAggr(solverName, model, options, flags)

P = PROBAGGR(SOLVERNAME, MODEL, OPTIONS, FLAGS) The -a prob payload as a struct with the fields the four probability getters read: ProbSys, ProbSysAggr and the per-station Prob / ProbAggr vectors, in station order.

ALL FOUR COME OFF ONE SOLVE, which is why they share one helper: the CLI computes the stationary law once and reports every view of it, so a getter that ran its own process would pay for the chain again to read another column of the same answer.

-s mva emits no ProbSys / Prob: the binomial fit of Schmidt (1997) is an AGGREGATE law with no detailed counterpart, so those fields come back empty and the detailed getters refuse rather than quoting the aggregate. -s nc does emit both, from solver_nc_marg and solver_nc_joint, which carry the class-within-chain split the aggregate pair sums out. FLAGS is getProb’s second argument on the wire: –node and –state name one node’s encoded row, which the arms that accept it substitute for that node’s declared state before evaluating.

static probEntry(p, field, ist, solverName, method)

V = PROBENTRY(P, FIELD, IST, SOLVERNAME, METHOD) One station’s entry of a -a prob vector, or a named refusal when the solver’s arm does not report that view at all.

static probMarg(solverName, model, options, ist, jobclass, state_m)

[PMARG, LOGPMARG] = PROBMARG(SOLVERNAME, MODEL, OPTIONS, IST, JOBCLASS, STATE_M) getProbMarg’s curve from -a marg, for the station IST.

THE TWO SOLVERS ANSWER DIFFERENT QUESTIONS UNDER ONE NAME, which is why the payload key differs and this reads both: SolverMVA’s getProbMarg is P(n jobs OF CLASS r), so its curves are keyed by (station, class) and it takes –class and –marg-states; SolverNC’s is P(n jobs IN TOTAL at the station), so its curves are keyed by station alone and neither flag applies. Reporting one under the other’s name would swap a per-class law for a total-occupancy one.

static probSysMarg(solverName, model, options, nvec, engine)

[PN, LPN] = PROBSYSMARG(SOLVERNAME, MODEL, OPTIONS, NVEC, ENGINE) getProbSysMarg: the joint law of the per-station TOTALS, read at NVEC out of the whole lattice -a sysmarg reports.

LPN IS log(PN) HERE and not an independently accumulated log: the port’s solver_nc_jointmarg returns the probability alone, where the reference also carries the logarithm through. On a population whose probability underflows, this reports -Inf where the native getter still has a finite log.

static probeNetwork()

MODEL = PROBENETWORK() A minimal but valid closed network, used only to ask a SolverLN layer factory which solver class it builds. An adaptive factory inspects the layer it is given, so an empty Network would not survive the call.

static qrfParamsJson(qp)

TXT = QRFPARAMSJSON(QP) options.config.qrf_params as the –qrf-params document. ZM is not sent: the CLI derives it from ZZ, as every codebase now does.

static restoreCacheResults(results, model)

RESTORECACHERESULTS(RESULTS, MODEL) Write the C++ per-Cache results back onto the MATLAB Cache nodes.

A cache’s hit, miss and delayed-hit fractions and its retrieval latency are SOLVER RESULTS, not model state: getAvgCacheTable reads them off the node. Solving in the C++ engine used to leave the node untouched, so the table reported whatever the PREVIOUS solver had written – on retrieval_simple the MVA and NC tables were both the LDES table, identical to five digits, and nothing in those two rows was being measured at all.

ABSENT MUST CLEAR, NOT KEEP. Every field is written on every solve, [] included, mirroring the lang=’java’ path (SolverMVA/runAnalyzer.m:68-75). Writing only the fields the engine filled is what let one solver’s delayed-hit fraction survive into another solver’s row. The block rides INSIDE the “avg” payload, not beside it: line-cli merges extra into the analysis object, the way it already does for ListCost (line_cli.cpp:448). Reading it at envelope level finds nothing and silently restores no cache result at all.

static rewardMatrixOver(model, spaceAggr)

[R, NAMES] = REWARDMATRIXOVER(MODEL, SPACEAGGR) The (nrewards x nstates) matrix of the declared rewards evaluated on the rows of SPACEAGGR, and their names in DECLARATION order.

The reward map is a function of the AGGREGATE state row and of nothing else, so the same evaluation serves whichever engine enumerated the space. This is solver_ctmc_reward’s inner loop, kept identical down to the two calling conventions it accepts, so a handle answers the same number under lang=’cpp’ as it does natively – the only thing that differs is which engine built the space and the law it is weighted against.

static rewardWireOrder(model, names)

[ORDER, OK] = REWARDWIREORDER(MODEL, NAMES) The permutation taking line-cli’s answer back into DECLARATION order.

THE WIRE SORTS THE REWARDS; THE MODEL DOES NOT. linemodel_save emits them in NAME order so the three writers byte-match, so line-cli answers in that order while the native getter answers in DECLARATION order. A caller pairing the values with its own declaration list then reads every reward under the wrong name – on rewardModel_templates a three-way rotation, not a numeric error.

static rmdirQuiet(d)

RMDIRQUIET Best-effort recursive temp-directory removal.

static rowCells(A)

C = ROWCELLS(A) A numeric matrix as a cell of its rows, so that jsonencode always emits an array of arrays. A bare one-row matrix encodes FLAT, which the CLI reads as a vector and not as a one-row table.

static rowsToMatrix(v)

M = ROWSTOMATRIX(V) A matrix sent as an array of row arrays. jsondecode already collapses a rectangular one into a numeric matrix, so this only has to reassemble the cell form and keep the empty case 0x0.

static runLineCli(binary, args)

RESULTS = RUNLINECLI(BINARY, ARGS) Run one line-cli invocation (ARGS a cellstr of argv entries after the binary) and return the jsondecode’d object. This is the seam an in-process binding would replace: nothing above it knows a process was involved. MATLAB’s own libstdc++ shadows the host one on LD_LIBRARY_PATH, so a binary built against this toolchain fails to load; see line_native_env.

static sampleEventIndices(v)

IDX = SAMPLEEVENTINDICES(V) The event column of -a sample as one-based synchronization indices, with NaN where the walk absorbed.

NOT jsonNumericList: that maps a JSON null to 0, and a 0 here becomes index 1 after the base shift – naming a synchronization that did not fire, at the one step where none did.

static samplePath(solverName, model, options, numEvents, nodeIndex)

S = SAMPLEPATH(SOLVERNAME, MODEL, OPTIONS, NUMEVENTS, NODEINDEX) The -a sample payload, decoded into the pieces the four sample getters read: the epoch of each step, the system state in both views, the node’s own two views, and the synchronization that fired.

ONE WALK ANSWERS ALL FOUR, as with probAggr. A trajectory is not a mean: two walks of the same chain are two different answers, so a getter that ran its own process would report a trace that never lines up with the one its sibling reported.

THE STATE SPACE COMES BACK WITH IT (space, NodeWidths), and the visited rows are indices into THAT enumeration. Resolving them against a state space built here instead would pair two orderings that have no reason to agree, and the mismatch would read as a trajectory rather than as an error.

static shellQuote(tok)

Q = SHELLQUOTE(TOK) Quote one argv entry for the platform shell.

static solveLqnViaCpp(solver, options)

RESULTS = SOLVELQNVIACPP(SOLVER, OPTIONS) Serialize the SolverLN’s LayeredNetwork, run line-cli on it and return the parsed object. The wire format is .lqnx unless that would drop a non-reference task’s think time, in which case the model.json interchange carries it instead – see lqnxLossyTasks.

static solveViaCpp(solverName, model, options, analysis, flags)

RESULTS = SOLVEVIACPP(SOLVERNAME, MODEL, OPTIONS, ANALYSIS, FLAGS) Serialize MODEL to model.json, run line-cli on it and return the parsed answer.

Only the knobs line-cli actually honours are forwarded, and only when the caller overrode the MATLAB default: an untouched option lets the port apply its own SolverOptions default rather than having this bridge’s default imposed on it. KNOBS ARE ALSO GATED BY METHOD NAME, because line-cli REFUSES an option the chosen solver does not have (–tol on -s ba, –samples outside ssa, –cutoff outside ctmc), so a solver whose default merely differs from the generic one would otherwise be refused for an option nobody set.

FLAGS is an optional cellstr of the analysis’s OWN argv entries (–tspan for a transient query, –node for a per-node one, –notation for the ODE export). They are arguments of the CALL and not settings of the solver, so the caller that knows the getter assembles them.

static solverToken(solverName)

TOKEN = SOLVERTOKEN(SOLVERNAME) The line-cli -s method name for a LINE solver name. The C++ model-solving path’s token set is NOT the JAR’s: it has ‘ag’ and ‘ba’, which the JAR CLI lacks. It does carry ‘jmt’, which drives the same JMT.jar the MATLAB wrapper drives, and lacks lqns and qns’s layered binaries.

THE TABLE IS THE AUTHORITY FOR WHAT lang=’cpp’ ACCEPTS, so a token missing here makes a CLI arm unreachable however complete the port is – ag was: line-cli -s ag served -a avg, its four views and -a cdf, and native Python’s _CPP_SOLVER_TOKENS already carried it, while this switch refused SolverAG outright and every ag_* [M2C] parity row read as a named gap in the C++ solver.

static stateFlags(nodeIndex, state)

FLAGS = STATEFLAGS(NODEINDEX, STATE) getProb(node, state)’s second argument as argv: the node it belongs to and its ENCODED ROW, comma separated. Empty when no state was passed, which leaves the model’s declared one standing.

static syncEvents(sn, eventIdx, t)

E = SYNCEVENTS(SN, EVENTIDX, T) The sample getters’ event cell: every active and passive arm of each fired synchronization, stamped with the epoch it fired at.

The synchronization DESCRIPTORS are model structure and are read from sn, as the native getters read them; what the C++ decided is WHICH one fired and WHEN, and that is what eventIdx and t carry.

static sysSamplePath(solverName, model, options, numEvents, aggregate)

S = SYSSAMPLEPATH(SOLVERNAME, MODEL, OPTIONS, NUMEVENTS, AGGREGATE) The system sample path in the contract of @@SolverCTMC/sampleSys and sampleSysAggr: state is a CELL PER STATEFUL NODE.

The blocks are cut out of the wire’s own space with its own NodeWidths – the local-space widths, which is the padding rule sampleSys uses and sample.m does not.

THE AGGREGATE IS TAKEN HERE, with State.toMarginal, and not read off the payload’s sysAggr: that matrix is (nstations x nclasses) in station order with Sources dropped, while this getter’s contract is one entry per STATEFUL node. The two index different things, so quoting one under the other’s name would misattribute whole columns. What the engine decided – which state, at what epoch – is what P carries; only the aggregation is done here, by the same function the native getter calls.

static tranAvg(solverName, model, options)

[QNT, UNT, TNT] = TRANAVG(SOLVERNAME, MODEL, OPTIONS) The transient means from -a tran, in the contract of self.result.Tran.Avg: (nstations x nclasses) cells whose entries are [value, t] two-column matrices.

THAT IS THE RESULT TABLE, NOT THE GETTER’S ANSWER. getTranAvg returns a metricVal STRUCT per cell (handle, t, metric, isaggregate) and NaN where a handle is disabled, and it builds those in @NetworkSolver/getTranAvg from exactly these tables. The callers therefore store what comes back and delegate there, so the wrapping has one implementation rather than a native one and a bridge one that can disagree on the disabled case.

THE HORIZON IS THE CALLER’S, not this bridge’s. line-cli refuses -a tran without –tspan rather than inventing one, so the timespan is read from options here and a model asked for a transient without one is refused on this side, where the option can be named.

static tranAvgVar(solverName, model, options)

[T, QVART, SIGMAT] = TRANAVGVAR(SOLVERNAME, MODEL, OPTIONS) The transient second moments from -a tranvar, in @SolverFLD/getTranAvgVar’s contract: T the abscissae, QVART an (nstations x nclasses) cell of variance series, and SIGMAT the (dim x dim x numel(t)) covariance trajectory.

THE HORIZON IS ALREADY RESOLVED by the caller, which applies the same slowest-rate rule the native path uses for an unbounded timespan; this only transmits it. line-cli enforces the kp precondition on its own side, so both agree that the other fluid methods integrate the mean alone.

static tranProb(solverName, model, options, nodeIndex, aggregate)

[PI_T, SS] = TRANPROB(SOLVERNAME, MODEL, OPTIONS, NODEINDEX, AGGREGATE) The four getTranProb* answers from -a tranprob: PI_T is [t, pi(t)] and SS is the labelling of the columns of pi(t).

PI_T IS THE SAME OBJECT IN ALL FOUR. Every one of the reference getters takes the transient analyzer’s DETAILED law and differs only in which labelling it returns beside it, so the aggregate ones read labelsAggr and NOT pitAggr – that is the law over aggregate states, a different quantity that would sum to one just as convincingly under the wrong name.

SCOPE IS THE –node FLAG: without it the labels span the system, with it they are that node’s block, which is the slice the node getters cut out of SS themselves.

static tranReward(solverName, model, options, rewardName)

[RT, T, NAMES] = TRANREWARD(SOLVERNAME, MODEL, OPTIONS, REWARDNAME) The transient expectation of every declared reward, from -a tranreward, in @SolverCTMC/getTranReward’s contract: RT a (nrewards x 1) cell of time series, T the shared abscissae, NAMES the matching (nrewards x 1) cell. A named reward returns that one alone, as the native getter does.

The serializability precondition and the wire-order correction are the SAME as getAvgReward’s, and are shared rather than restated: a bare function handle still has no serializable form here, and the wire still sorts by name where the model declares in its own order.

static tranRewardLocally(solverName, model, options, tspan)

[RT, T, NAMES] = TRANREWARDLOCALLY(SOLVERNAME, MODEL, OPTIONS, TSPAN) E[r(X(t))] for rewards no wire format can carry, against the C++ transient law.

The twin of avgRewardLocally, over pi(t) instead of pi: -a tranprob integrates the forward equation ONCE from the model’s own initial distribution and returns the trajectory beside the AGGREGATED labels its columns are indexed by, which is exactly the pair the reward map needs.

static tryDelete(f)

TRYDELETE Best-effort temp-file removal.

static unbridgeableRewards(model)

UNBRIDGEABLE = UNBRIDGEABLEREWARDS(MODEL) The declared rewards that no wire format can carry, by name.

A REWARD THE WRITER DROPS MUST STOP THE -a reward ROUTE, NOT SHORTEN ITS ANSWER. linemodel_save omits a reward whose descriptor is absent or Custom – a bare function handle has no serializable form – and only warns. Taking that route anyway hands line-cli the SURVIVING rewards, whose shorter vector the caller then pairs with its own full declaration list; where every reward is a handle it hands over none and line-cli exits 2. This list is what sends the solve down rewardsLocally instead, and is shared by the steady-state and the transient arm so the two cannot disagree about which model is bridgeable.

QN2MATLAB(MODEL, MODELNAME, FID)
QN2LQN(model)
QN2LINE(sn, modelName)

MODEL = QN2LINE(QN, MODELNAME)

QN2JSIMG(model, outputFileName, options)

QN2JSIMG Writes a Network model to JMT JSIMG format

OUTPUTFILENAME = QN2JSIMG(MODEL) exports the Network MODEL to a temporary JSIMG file and returns the file path.

OUTPUTFILENAME = QN2JSIMG(MODEL, OUTPUTFILENAME) exports to the specified file path.

OUTPUTFILENAME = QN2JSIMG(MODEL, OUTPUTFILENAME, OPTIONS) uses the specified solver options for export configuration.

Parameters:
  • model - Network model to export

  • outputFileName - Optional output file path (default – temp file)

  • options - Optional SolverOptions with simulation parameters

Returns:

outputFileName - Path to the created JSIMG file

Example

model = Network(‘example’); % … define model … fname = QN2JSIMG(model); jsimgView(fname); % View in JMT

See also: JMTIO, jsimgView()

Copyright (c) 2012-2026, Imperial College London All rights reserved.

QN2JAVA(MODEL, MODELNAME, FID, HEADERS)
LQN2MATLAB(filename, modelName)

MODEL = LQN2MATLAB(FILENAME, MODELNAME)

LINE2MATLAB(model, filename)

MODEL = LINE2MATLAB(MODEL, FILENAME)

LINE2JLINE(line_model)
LINE2JAVA(MODEL, FILENAME)
JMVA2LINE(filename, modelName)

MODEL = JMVA2LINE(FILENAME,MODELNAME)

JMT2LINE(filename, modelName)

MODEL = JMT2LINE(FILENAME,MODELNAME)

class IndexedTable

Bases: handle

INDEXEDTABLE Generic enhanced table wrapper with flexible object-based filtering

This class wraps any MATLAB table to enable filtering using Station, Node, JobClass, and/or Chain objects, while maintaining full backward compatibility with standard table operations.

Supports all NetworkSolver result tables: - AvgTable (Station + JobClass) - AvgNodeTable (Node + JobClass) - AvgChainTable (Station + Chain) - AvgNodeChainTable (Node + Chain) - AvgSysTable (Chain only) - Any custom table with Station/Node/JobClass/Chain columns

Usage:

% Create from any MATLAB table t_native = solver.avgTable(); t = IndexedTable(t_native);

% Standard table operations (works as normal) rows = t.data(1:5, :) value = t.data.QLen(1)

% Object-based filtering (four equivalent syntaxes) rows = t(station_obj) % Direct indexing rows = t.filterBy(station_obj) % filterBy method rows = t.get(station_obj) % get alias (shorthand) rows = t.tget(station_obj) % tget alias

% Filter by class object rows = t(class_obj) rows = t.filterBy(class_obj) rows = t.get(class_obj) rows = t.tget(class_obj)

% Filter by both station/node and class/chain (any order) rows = t(station_obj, class_obj) rows = t.filterBy(station_obj, class_obj) rows = t.get(station_obj, class_obj) rows = t.tget(station_obj, class_obj)

Example (AvgTable):

solver = SolverJMT(model); solver.runAnalyzer(); avgTable = IndexedTable(solver.avgTable()); metrics = avgTable(queue, jobclass);

Example (AvgNodeTable):

nodeTable = IndexedTable(solver.avgNodeTable()); metrics = nodeTable(sink, jobclass);

Example (AvgChainTable):

chainTable = IndexedTable(solver.avgChainTable()); metrics = chainTable(queue, chain);

Example (AvgSysTable):

sysTable = IndexedTable(solver.avgSysTable()); metrics = sysTable(chain);

Constructor Summary
IndexedTable(table_data)

INDEXEDTABLE(table_data) Create wrapper around any MATLAB table

Parameters:

table_data – A MATLAB table object with object-filterable columns

Property Summary
data

Underlying MATLAB table

Method Summary
disp()

DISP Display the table

filterBy(varargin)

FILTERBY Filter table by Station/Node and/or JobClass/Chain objects

Intelligently filters based on table structure and object types

Usage:

rows = t.filterBy(station_obj) rows = t.filterBy(class_obj) rows = t.filterBy(station_obj, class_obj)

Parameters:

varargin – 1 or 2 arguments - Station/Node/JobClass/Chain objects

Returns:

result – Filtered MATLAB table

get(varargin)

GET Alias for filterBy - convenient shorthand

getTable()

GETTABLE Return the native MATLAB table

head(n)

HEAD Get first n rows

height()

HEIGHT Get number of rows

numArgumentsFromSubscript(s, indexingContext)

NUMARGUMENTSFROMSUBSCRIPT Specify number of outputs for indexing This tells MATLAB to always expect exactly 1 output from subsref operations, which is appropriate for table data access.

numel(varargin)

NUMEL Support numel() function When called with extra arguments (during indexing like obj(a,b)), return 1 to tell MATLAB we expect a single output from subsref. When called directly as numel(obj), return actual element count.

size(varargin)

SIZE Support size() function

subsref(s)

SUBSREF Support both standard indexing and object-based filtering

Intelligently detects object types and filters appropriate columns

summary()

SUMMARY Display table summary

table2array()

TABLE2ARRAY Convert IndexedTable to array Delegates to the underlying MATLAB table

tail(n)

TAIL Get last n rows

tget(varargin)

TGET Alias for filterBy - backward compatibility

varfun(func, obj, varargin)

VARFUN Apply function to table variables Delegates to the underlying MATLAB table

width()

WIDTH Get number of columns

class Copyable

Bases: handle

Copyable allows to perform deep-copy of objects via the copy() method.

Copyright (c) 2012-2026, Imperial College London All rights reserved.

Method Summary
copy()

NEWOBJ = COPY(OBJ)

class PYLINE

PYLINE MATLAB-to-native-Python bridge (lang=’python’).

Static helpers that construct a native line_solver (pure Python, no JVM) model element-by-element through MATLAB’s in-process Python interface (py.*), run a native Python solver, and marshal results back. Mirrors the structure of JLINE.m (line_to_jline/from_line_network/from_line_node/…) with the jline.* Java backend replaced by py.line_solver.* and the Java Matrix class replaced by numpy arrays.

Requirements: MATLAB pyenv must point at a CPython where the in-tree python/ line_solver package is importable. No JPype/JVM is used.

Copyright (c) 2012-2026, Imperial College London All rights reserved.

Method Summary
static Solver(name, pynet, options, L)

SOLVER Dispatch to the native Python solver constructor by name.

static SolverLN(pynet, options, L, layerSolverName)

SOLVERLN Native SolverLN over an LQN model.

LAYERSOLVERNAME is the MATLAB class of the LAYER solvers the caller built (SolverLN holds one instance per layer). Without it the native side falls back to its own default layer solver, so LN(model, @(l)NC(l,…)) under lang=’python’ silently answered with AMVA layers: on lqn_twotasks that is E1 RespT 390.1 against the exact 390. The native constructor takes the factory as its second positional argument, the same shape as MATLAB’s.

static acceptedKwargs(solverName)

ACCEPTEDKWARGS Native SolverXOptions constructor parameter names.

READ OFF THE NATIVE DATACLASS, not transcribed. A hand-kept list drifts the moment a native option is added or renamed, and the failure is silent: the field is dropped and the default answers under the caller’s name. dataclasses.fields is the same source the constructor itself uses, so the two cannot disagree.

static alignLNVector(v, nElem, pyNames, mlNames)

ALIGNLNVECTOR One native LQN metric vector in MATLAB’s element order. With names on both sides the values are looked up BY NAME; with none (a numeric nElem was passed) it falls back to dropping the index-0 placeholder positionally.

static asRow(v)

ASROW A per-class result as a row vector, [] when absent. setResult* stores what it is given, so an empty here is what CLEARS a stale value from an earlier solve on the same model.

static assertPythonReady()

ASSERTPYTHONREADY Verify the native line_solver is importable and pin the embedded interpreter’s BLAS/OpenMP thread pools to a single thread. Running numpy/scipy in-process (py.*) alongside MATLAB’s own worker threads otherwise causes BLAS oversubscription that spins or deadlocks the session on heavy linear-algebra algorithms (e.g. CTMC).

static bindSupportedInterpreter()

BINDSUPPORTEDINTERPRETER Ensure pyenv names a CPython that this MATLAB release can actually load, and bind one that it can when it does not.

TWO THINGS WENT WRONG HERE AND EITHER ALONE IS ENOUGH TO LOSE THE DIAGNOSIS. First, with nothing configured MATLAB auto-discovers python3 from PATH, and that is routinely NEWER than the release supports – every host of this cluster answers python3 -V with 3.14 while R2026a loads at most 3.13, so the bridge had no usable interpreter at all. Second, the guard meant to say so was DEAD CODE: pyenv returns Executable as a STRING SCALAR, and isempty(“”) is false for a 1x1 string, so isempty(pe.Executable) never fired however unconfigured the environment was. Hence the clear message was never reached and the first py.* call died with MATLAB’s own “Python commands require a supported version of CPython” – an error naming neither the interpreter nor the bridge, which reached the [M2P] parity rows as “solver missing from output”. Emptiness is therefore tested on char(), never on the string property.

So the interpreter is asserted the way sys.path is asserted in assertPythonReady: probe it, and when it cannot serve, bind one that can. LINE_PYTHON pins an executable and is tried first. pyenv PERSISTS its choice across MATLAB sessions, so a switch is announced rather than made silently.

static cacheSplitFromStruct(pysn, nodeIndex0)

CACHESPLITFROMSTRUCT sn.nodeparam[i].actualhitprob / actualmissprob / actualdelayedhitprob / actualresidt of the native struct, each [] when this solver wrote it nowhere.

static candidateInterpreters()

CANDIDATEINTERPRETERS Executables to try, newest first, when the pyenv default cannot serve. No MATLAB-release-to-CPython table is kept: a version this release refuses simply fails its probe in bindSupportedInterpreter and the next candidate is tried, so the list stays correct as both sides move.

static canonicalSolverName(solverName)

CANONICALSOLVERNAME Short alias class -> canonical Solver* name. class(FLD(model)) is ‘FLD’, not ‘SolverFluid’: the short classes SUBCLASS the canonical ones, so anything keyed on class() must canonicalize first or a perfectly valid solver reads as unported.

static from_line_class(line_class, pynet, L, pynodes)

FROM_LINE_CLASS LINE job class -> py.line_solver class. THE SIGNAL BRANCHES MUST COME FIRST. OpenSignal derives from OpenClass and ClosedSignal from ClosedClass, so testing the bases first matches a G-network signal and marshals it as an ORDINARY class: the negative customers stop removing jobs and the bridge silently solves a different model (on ag_gnetwork the signal class picked up a queue length, a utilization and a throughput where MATLAB has zero). A silent downgrade is worse than a refusal, and nothing downstream can detect it.

static from_line_distribution(line_dist, L)

FROM_LINE_DISTRIBUTION LINE distribution -> py.line_solver process.

static from_line_layered_network(model)

FROM_LINE_LAYERED_NETWORK LINE LayeredNetwork -> native LQN. Bridged through the canonical LQN XML interchange format: the element-by-element LQN graph (processors/tasks/entries/activities/ calls/precedences) is large and error-prone to marshal over py.*, whereas writeXML/parseXML is a faithful, well-established round-trip.

FROM_LINE_LINKS Reconstruct routing on the Python model.

static from_line_matrix(matrix)

FROM_LINE_MATRIX MATLAB double matrix -> numpy ndarray.

static from_line_network(model)

FROM_LINE_NETWORK Build a native Python Network from a LINE Network.

static from_line_node(line_node, pynet, L, forkNode)

FROM_LINE_NODE LINE node -> py.line_solver node.

static from_line_via_json(model)

FROM_LINE_VIA_JSON Bridge a model through the canonical line-model JSON schema: MATLAB linemodel_save -> temp .json -> native load_model. Used for models whose element-by-element construction is impractical over py.* (Cache, SPN Place/Transition, Environment).

static from_pyline_matrix(pyarr)

FROM_PYLINE_MATRIX numpy ndarray / scalar -> MATLAB double.

static getAvg(solverName, model, options)

GETAVG Build the native model+solver, run getAvg(), marshal back. Guarded import: line_to_pyline below calls assertPythonReady, but this line runs FIRST, so an unconfigured pyenv escaped as a raw MATLAB:Python:PyException instead of the classified message. That is not cosmetic – the parity harness reads the message to tell a missing environment (a named SKIP) from a wrong answer (a FAIL), so every [M2P] row on a host without the native package was scored as a failure of the solver under test.

static getEnsembleAvg(model, options, elemNames, layerSolverName)

GETENSEMBLEAVG Build the native LQN + SolverLN, run get_ensemble_avg(), marshal the per-element metric vectors back. Native get_ensemble_avg() returns (Q,U,R,T,A,W) as column vectors positionally aligned with the NATIVE LQN element index, and the native struct keeps a leading index-0 placeholder (empty name).

THE TWO ELEMENT ORDERS ARE NOT THE SAME ORDER. MATLAB indexes the activities in CREATION order, while the .lqnx interchange this bridge goes through writes them grouped per task, so parseXML rebuilds them in DOCUMENT order. On lcq_threehosts, which creates A2 before A1, MATLAB’s names run [… A2 A1 Ac Ac_hit Ac_miss] and the native ones [… A1 Ac Ac_hit Ac_miss A2]: a positional copy rotated the whole activity block by one and reported Ac_hit’s answer under Ac. Marshal by NAME, so the two orders never have to agree. elemNames is self.lqn.names; a numeric argument is still accepted and means “positional, nElem elements”.

static getEnvAvg(model, options, stageSolverName, stageOptions)

GETENVAVG Build a native Environment (via JSON) + SolverENV and marshal the environment-weighted (Q,U,T) metrics back.

STAGESOLVERNAME is the MATLAB class of the stage solvers the caller built (SolverENV holds one instance per stage) and STAGEOPTIONS is that solver’s own options struct. Both must be forwarded: the stage solver is what decides the cost and the accuracy of the whole ensemble, and substituting one here makes lang=’python’ answer a different question from lang=’matlab’. Hardcoding SolverCTMC once turned the MVA random-environment image of a two-class open FCFS model (mapEnvApprox, gallery mmap1_multiclass) into a truncated CTMC at the default cutoff, which exhausted the host. Dropping the stage options is just as destructive on the other side: a stage FLD built here without the caller’s timespan integrates an UNSTABLE down-stage to the default horizon instead of to 1e3, and renv_node_breakdown came back with QLen 475.78 against MATLAB’s 0.462.

static getLNSensitivityTable(model, options, varargin)

GETLNSENSITIVITYTABLE Build the native LQN + SolverLN and marshal get_sensitivity_table() back into the MATLAB layer-wise table. The native call runs the fixed-point loop itself when the layers have not been solved, so the layer derivatives are taken at the converged parameterization (see solver_ln.py getSensitivityTable).

The name-value options are those of @NetworkSolver/getSensitivityTable (‘method’, ‘step’, ‘scheme’); an empty step forwards as None, which selects the native default.

static interpreterUsable()

INTERPRETERUSABLE True when py.* commands actually execute. Loading is the only reliable test: pyenv reports the CONFIGURED executable, not whether MATLAB can bind it.

THE PROBE MUST BE A PLAIN FUNCTION CALL, and py.sys.version_info is not one. sys.version_info is a structseq TYPE, so MATLAB is free to read py.sys.version_info as a CONSTRUCTOR and call it, which raises TypeError: cannot create ‘sys.version_info’ instances against a perfectly healthy interpreter. Which reading MATLAB takes is not stable across a session: measured 2026-09-08, every probe below answered before dispatch_closed ran and only this one failed after it, with py.str, py.list and importlib.import_module all still fine.

The cost of getting it wrong is the whole rest of the session: bindSupportedInterpreter reports “cannot run py.* commands”, and MATLAB cannot swap a loaded interpreter, so every later row sees the same refusal. That is what skipped all 174 [M2P] parity rows of run 20260908_104240 from dispatch_closed onwards, against an interpreter that was answering the whole time.

static line_to_pyline(model)

LINE_TO_PYLINE Top-level entry: LINE model -> py.line_solver model.

static lnLayerSolverName(solver)

NAME = LNLAYERSOLVERNAME(SOLVER) Class of the layer solver a MATLAB SolverLN was built with, or ‘’ when it kept the default. Under lang=’python’ no layer is constructed, so the factory is probed on a throwaway network rather than read off self.solvers – the same resolution CPPLINE.lnLayerSolver performs for –layer-solver. The layer solver is what the fixed point is a fixed point OF, so leaving it unnamed hands the native side a different question to answer. CANONICALIZE: class(MVA(model)) is ‘MVA’, not ‘SolverMVA’, because the short solver classes SUBCLASS the canonical ones. Both spellings happen to resolve today (line_solver exports the short aliases too), so this is fragility rather than a live fault – but every other consumer of a solver name on this bridge goes through canonicalSolverName, and one that does not is the shape that made PYLINE.Solver refuse a valid FLD as “not supported yet”.

static nativeOptionsClass(canon)

NATIVEOPTIONSCLASS Canonical solver name -> native options class.

static needsJsonBridge(model, line_nodes)

TF = NEEDSJSONBRIDGE(MODEL, LINE_NODES) True when a station carries a feature the element-by-element py.* construction below does not marshal.

SILENTLY DROPPING ONE IS THE WORST OUTCOME: from_line_node carries the discipline, the server count, the capacity and the load-dependent scaling and NOTHING ELSE, so a setup time, a server breakdown, a switchover, a balking rule or a finite capacity region crossed as a model that simply did not have it – lqn_setup came back with processor Util 0.75 against 0.436 because the setup layer lost its setup. linemodel_save serializes every one of these, so the whole model goes through the JSON bridge instead, exactly as a Cache, an SPN or an OI/PAS station already does.

static parseSolverOptions(options, solverName)

PARSESOLVEROPTIONS MATLAB options struct -> native solver kwargs. The native SolverXOptions constructors have per-solver parameter lists, so build a candidate native-name->value map and forward only the keys the target options class accepts.

static pyNameList(pyval)

PYNAMELIST A native LQN struct name vector -> column cellstr. The native struct stores it as a numpy array of str, whose first entry is the empty index-0 placeholder.

static pyNumericColumn(T, name)

PYNUMERICCOLUMN pandas column of float -> column double vector.

static pySensStruct(pyobj)

PYSENSSTRUCT native pfqn_sens result -> the MATLAB pfqn_sens struct returned as the second output of getSensitivityTable. Empty for a layer that took the finite-difference branch, which carries no analytic Jacobian.

static pySeries(x)

V = PYSERIES(X) One native 1-D series as a MATLAB row vector, WITHOUT crossing the buffer protocol.

double(numpy_array) binds MATLAB’s own libmwbuffer, and inside the embedded interpreter that import can fail outright – “ImportError: PyCapsule_Import could not import module libmwbuffer”, raised where a transient should have been. It is also dtype-sensitive: this interpreter warns that its numpy has “broken support” for longdouble, and a series carrying one converts for t and dies for metric in the same call. Going through tolist() hands MATLAB native Python floats and no buffer at all, so neither failure is reachable. A caller that already has a plain list (the native getters return either) is served by the same path.

static pyStringColumn(T, name)

PYSTRINGCOLUMN pandas column of str -> column cell of char.

static pyUnsupported(solverName, method, reason)

PYUNSUPPORTED(SOLVERNAME, METHOD, REASON) Refuse a getter this bridge cannot serve, naming the getter and the reason, exactly as CPPLINE.cppUnsupported does for lang=’cpp’. A getter that is not bridged must say so: the delegation in runAnalyzerPreamble sets the AVERAGE results only, so a transient getter that fell through found no self.result.Tran and died on a dot-index rather than on a statement about the port.

static restoreCacheResults(model, pynet)

RESTORECACHERESULTS Copy each Cache node’s solved hit/miss split back from the native model onto the LINE node.

A cache’s hit and miss probabilities are a SOLVER RESULT, not model state: the analyzer computes them and writes them onto the node, and getAvgNode then rebuilds the hit-class and miss-class node throughputs from them (sn_get_node_tput_from_tput). Solving in the native engine leaves the LINE node’s copy empty, so that reconstruction silently falls back to the nodevisits split, which is the 0.5/0.5 guess link() laid down before any cache was analysed: on cache_replc_fifo (5 items, capacity 2) the hit and miss throughputs came back 0.5/0.5 instead of 0.4/0.6. getHitRatio on the LINE node returned nothing for the same reason.

The struct is then refreshed HARD, because the visits that carry the hit and miss classes are derived from the split: without it a Router downstream of the cache still saw the 0.5/0.5 routing (cache_replc_routing Router ArvR 1/1 instead of 0.8/1.2, and the residence times scaled by the same ratio). Every MATLAB solver that writes a hit probability does the same, see SolverNC/runAnalyzer.m (refreshChains / refreshStruct(true)).

static restoreNonRefThinkTimes(model, pynet)

RESTORENONREFTHINKTIMES Reapply the one piece of LINE state that the .lqnx interchange cannot carry. lqns rejects think-time on a non-reference task, so writeXML omits it there; LINE nonetheless gives such a think time to the task’s callers as a delay, and without this the bridged model would be a DIFFERENT model (the layer of the called task loses its delay, which moves that layer’s throughput and every quantity derived from it).

static set_drop_rules(line_node, pynode, pyclasses, L)

SET_DROP_RULES Transfer Station.dropRule, the per-class rule that says what a full buffer does to an arriving job. It is user state with no derivation, so a rule that fails to cross is invisible: on cqn_bas_blocking the native SolverMVA then sees a finite capacity with no blocking policy attached and REFUSES the model, because its finite-capacity gate exempts Blocking-After-Service alone.

static set_service(line_node, pynode, line_classes, pyclasses, L)

SET_SERVICE Transfer arrival/service processes.

static set_signal_removal(line_class, pyclass, L)

SET_SIGNAL_REMOVAL Transfer how many jobs a signal removes, and which. Empty removalDistribution means “exactly one”, which is the native default, so it is left alone rather than encoded.

static to_py_drop_rule(dropId, L)

TO_PY_DROP_RULE MATLAB DropStrategy id -> py DropStrategy member. Resolved through the member NAME. The two enums happen to agree numerically today, but DropStrategy.toText returns prose (‘BAS blocking’) that no python member is keyed by, so an ordinal is the only other option and an ordinal is exactly what silently picks the wrong member the day one side renumbers.

static to_py_sched(schedId, L)

TO_PY_SCHED SchedStrategy id -> py.line_solver.SchedStrategy enum.

static to_py_signal_type(signalType, L)

TO_PY_SIGNAL_TYPE MATLAB SignalType constant -> py SignalType. Bridged through the LOWERCASE TEXT both enums already use on the JSON wire, never through the ordinal: MATLAB numbers REPLY 0 and NEGATIVE 1, while the python member values are the strings, so an ordinal handed over directly would silently pick another signal.

static tranAvg(solverName, model, options)

[QNT, UNT, TNT] = TRANAVG(SOLVERNAME, MODEL, OPTIONS) The transient means from the native getTranAvg(), in the contract of self.result.Tran.Avg: (nstations x nclasses) cells whose entries are [value, t] two-column matrices.

THAT IS THE RESULT TABLE, NOT THE GETTER’S ANSWER, exactly as in CPPLINE.tranAvg: getTranAvg returns a metricVal struct per cell and builds it in @NetworkSolver/getTranAvg from these tables, so the callers store what comes back and delegate there. One wrapping implementation, not a native one and a bridge one that can differ on the disabled case.

A DISABLED PAIR STAYS EMPTY. The native getter returns None for a (station, class) it has no series for, and filling it with zeros here would report an idle station where there is no measurement.

static tranGrid(pygrid)

G = TRANGRID(PYGRID) One [M][K] nested list of native TranResult(t, metric) objects as an (M x K) cell of [value, t] matrices.

static trimLNVector(v, nElem)

TRIMLNVECTOR Align a native LQN metric vector to nElem MATLAB elements: drop the leading index-0 placeholder when present.

static tryDelete(f)

TRYDELETE Best-effort temp-file removal.

static verboseFlag(options)

VERBOSEFLAG options.verbose (VerboseLevel or logical) -> logical.

line_verbosity(level)

LINE_VERBOSITY Sets the global verbosity level for the LINE toolbox.

LINE_VERBOSITY(LEVEL) sets the verbosity of LINE’s output and configures MATLAB’s warning behavior accordingly.

LEVEL should be one of the following (defined in VerboseLevel):
  • VerboseLevel.SILENT : Disables warnings and suppresses all output.

  • VerboseLevel.STD : Enables standard verbosity and warning backtrace.

  • VerboseLevel.DEBUGTurns the SOLVER CONSOLE on – a running

    progress log of every solver run (LineConsole).

DEBUG is the only way to switch the console on; there is no separate console switch and no ‘console’ solver option. A single run can ask for it on its own through the ‘verbose’ option:

SolverMVA(model,’verbose’,VerboseLevel.DEBUG).getAvgTable

If LEVEL is not provided, it defaults to VerboseLevel.STD.

Example

line_verbosity(VerboseLevel.SILENT);

See also: VerboseLevel, LineConsole, warning

class JLINE

JLINE Conversion utilities for JLINE format models

JLINE provides static methods to convert between LINE MATLAB models and JLINE Java models. This class serves as the primary interface for interoperability between the MATLAB and Java implementations of LINE.

@brief JLINE format conversion and Java interoperability utilities

Main functionality: - Convert LINE MATLAB models to JLINE Java models - Convert JLINE Java models back to LINE MATLAB format - Access JLINE solvers from MATLAB - Handle serialization between MATLAB and Java representations

Example: @code % Convert a LINE model to JLINE format jnetwork = JLINE.from_model(network); % Get a JLINE solver jssa = JLINE.get_solver(jnetwork, ‘ssa’); @endcode

Method Summary
static SolverAG(network_object, options)

The agent-based (RCAT) solver. Its options carry the truncation level of an open agent and the execution backend, neither of which SolverOptions(‘MAM’) has, so it builds AGOptions rather than the generic container.

static SolverAuto(network_object, options)
static SolverBA(network_object, options)
static SolverCTMC(network_object, options)
static SolverFluid(network_object, options)
static SolverJMT(network_object, options)
static SolverLDES(network_object, options)

Create LDES-specific options object

static SolverLN(layered_network_object, options, layerSolverType)

LN = SOLVERLN(LAYERED_NETWORK_OBJECT, OPTIONS, LAYERSOLVERTYPE)

LAYERSOLVERTYPE is the JAR SolverType of the LAYER solvers the caller asked for. Without it the JAR falls back to its own DefaultSolverFactory, which is SolverMVA at every layer, so LN(model, @(l)NC(l,…)) under lang=’java’ silently answered with MVA layers – the same defect the python bridge carries a fix for in PYLINE.SolverLN. The layer solver is what the fixed point is a fixed point OF, so substituting one answers a different question.

static SolverMAM(network_object, options)
static SolverMVA(network_object, options)
static SolverNC(network_object, options)
static SolverQNS(network_object, options)
static SolverSSA(network_object, options)
static StreamingOptions(varargin)

STREAMINGOPTIONS Create Java StreamingOptions for SSA/LDES stream() method

@brief Creates StreamingOptions for streaming simulation metrics

@param varargin Name-value pairs for options:

‘transport’ - ‘http’ (recommended) or ‘grpc’ (default: ‘http’) ‘endpoint’ - Receiver endpoint (default: ‘localhost:8080/metrics’ for HTTP) ‘mode’ - ‘sampled’ or ‘time_window’ (default: ‘sampled’) ‘sampleFrequency’ - Push every N events in sampled mode (default: 100) ‘timeWindowSeconds’ - Window duration in time_window mode (default: 1.0) ‘serviceName’ - Service identifier (default: ‘line-stream’) ‘includeQueueLength’ - Include queue length metrics (default: true) ‘includeUtilization’ - Include utilization metrics (default: true) ‘includeThroughput’ - Include throughput metrics (default: true) ‘includeResponseTime’ - Include response time metrics (default: true) ‘includeArrivalRate’ - Include arrival rate metrics (default: true)

@return streamOpts Java StreamingOptions object

Example: @code streamOpts = JLINE.StreamingOptions(‘transport’, ‘http’, ‘sampleFrequency’, 50); @endcode

static arrayListToResults(alist)
static arraylist_to_matrix(jline_matrix)
static call_java_cdscaling(jfun, ni)

CALL_JAVA_CDSCALING Call a Java SerializableFunction for class dependence

This function converts a MATLAB vector to a Java Matrix and calls the Java function’s apply() method.

@param jfun Java SerializableFunction<Matrix, Double> object @param ni MATLAB vector representing the state (jobs per class) @return result The scaling factor returned by the Java function

static convertSampleResult(jresult)

CONVERTSAMPLERESULT Convert Java sample result to MATLAB struct

@brief Converts Java SampleNodeState to MATLAB structure

@param jresult Java SampleNodeState object @return result MATLAB struct with fields: t, state, isaggregate

static enumerate_states(handle, serfun, maxPop, currentState, classIdx)

ENUMERATE_STATES Recursively enumerate all state combinations

@param handle MATLAB function handle @param serfun Java PrecomputedCDFunction object to populate @param maxPop Maximum population per class @param currentState Current state being built @param classIdx Current class index being enumerated

static eventFromJava(jev)

EV = EVENTFROMJAVA(JEV) build the MATLAB Event of a jline.lang.Event.

The Java and MATLAB EventType numberings disagree (Java ordinals start at INIT = 0, MATLAB at INIT = -1), so the two are matched by NAME, as the rest of the codebase does. Every member of the Java enum is listed: an unmapped one must raise rather than leave the synchronization silently unassigned.

static from_jline_class(jclass, model)

Check signal classes first (their base classes would match below)

static from_jline_distribution(jdist)
static from_jline_matrix(jline_matrix)
static from_jline_matrix_list(jlist)

java.util.List<Matrix> -> 1-by-n cell of double matrices

static from_jline_matrixcell(jcell)

jline.util.matrix.MatrixCell -> 1-by-n cell of double matrices

static from_jline_node(jline_node, model, job_classes)
static from_jline_routing(model, jnetwork)
static from_jline_signal_removal(jclass)

FROM_JLINE_SIGNAL_REMOVAL Read back a JAR signal’s batch-removal distribution and policy for the MATLAB signal constructors.

static from_jline_struct(jnetwork, jsn)

lst and rtfun are not implemented Due to the transformation of Java lambda to matlab function

static from_jline_struct_layered(jlayerednetwork, jlsn)

JLINE indexes LayeredNetworkStruct elements from 0 and carries no padding row or column; MATLAB indexes them from 1. This is the only seam between the two conventions, so every element index crossing it is shifted by one here: map keys are looked up at key-1, and index VALUES (parent, callpair, tasksof/entriesof/actsof/callsof) come back +1. Java’s -1 “unset” parent becomes MATLAB’s 0. See _kb/04-networkstruct.md and _kb/07-cross-language-parity.md.

static from_line_class(line_class, jnetwork)

Check signal classes first (before their base classes)

static from_line_distribution(line_dist)
static from_line_environment(line_env)

Convert a MATLAB Environment to a JAR Environment.

static from_line_layered_network(line_layered_network)
static from_line_lqn_dist(ptype, dmean, dscv, dparams, dproc)

JDIST = FROM_LINE_LQN_DIST(PTYPE, DMEAN, DSCV, DPROC) Rebuild a JAR distribution from the LayeredNetworkStruct fields of an LQN distribution, keeping its variability. Passing the mean alone to a setSomething(double) overload rebuilds it as Exp(1/mean) with SCV 1, which silently discards everything above the first moment: an Erlang setup and an exponential one of the same mean would reach the JAR as the same process.

static from_line_matrix(matrix)
static from_line_network(model)

w = warning; warning(‘off’);

static from_line_node(line_node, jnetwork, ~, forkNode, sn)

Handle optional sn argument

static from_line_signal_removal(line_class)

FROM_LINE_SIGNAL_REMOVAL Marshal a signal class’s batch-removal distribution and removal policy. Returns empties when the class uses the defaults (remove exactly 1, RANDOM policy), so callers can keep using the short JAR constructors in that case.

static from_line_workflow(line_wf)

Convert a MATLAB Workflow to a JAR Workflow.

static getFeatureSet()

FEATSUPPORTED = GETFEATURESET()

static getSymbolicGenerator(ctmc, invertSymbol)

[INFGEN, EVENTFILT, SYNCINFO, STATESPACE, NODESTATESPACE] = GETSYMBOLICGENERATOR(CTMC, INVERTSYMBOL) Symbolic infinitesimal generator of a JLINE SolverCTMC object, with each event filtration normalized by its minimum positive rate and scaled by a symbolic variable x1..xE, as in the native SolverCTMC.getSymbolicGenerator. Coefficient matrices are computed by the JAR; symbolic objects are rebuilt with the Symbolic Toolbox.

static get_jar_location()

Get jline.jar location, downloading if necessary. Re-checks on each call, so deleted JAR triggers re-download. Treats lang=’java’ as a wrapper that auto-downloads jline.jar if absent.

static handle_to_serializablefun(handle, sn)

HANDLE_TO_SERIALIZABLEFUN Convert MATLAB function handle to Java SerializableFunction

This function pre-computes the function values for all possible state combinations and creates a Java PrecomputedCDFunction object.

@param handle MATLAB function handle that takes a vector ni and returns a scalar @param sn Network struct containing njobs (population per class) @return serfun Java PrecomputedCDFunction object

static is_custom_handle(funCell, e, h, defaultStr)

IS_CUSTOM_HANDLE True if funCell{e,h} is a function handle that differs from the given identity default (whitespace-insensitive func2str comparison).

static jline_to_line(jnetwork)

True when jline_to_line restores the routing with link(rtorig). State-dependent routing (RROBIN, WRROBIN, JSQ, SQ) cannot go through link(P), which overwrites every strategy with PROB, and a model with no rtorig was never linked in the first place.

static line_to_jline(model)
static lnLayerSolverType(solver)

LAYERSOLVERTYPE = LNLAYERSOLVERTYPE(SOLVER) Resolve the JAR SolverType of the layer solver a MATLAB SolverLN was built with, refusing anything the JAR has no layer factory for. Empty means “no factory recorded”, i.e. the caller kept the default and the JAR may keep its own. The factory is probed on a throwaway network, the same resolution CPPLINE.lnLayerSolver and PYLINE.lnLayerSolverName perform: under lang=’java’ no MATLAB layer is constructed, so there is no self.solvers to read it off.

static parseSolverOptions(solverOptions, options)
static pas_enumerate_seqs(handle, serfun, nclasses, cap, prefix)

PAS_ENUMERATE_SEQS Recursively enumerate ordered class sequences (1-based, length 1..cap) and store mu(prefix) under the matching 0-based key expected by the JAR.

static pas_handle_to_serializablefun(handle, nclasses, cap)

PAS_HANDLE_TO_SERIALIZABLEFUN Convert a PAS service rate function mu(c) (MATLAB handle of the ordered class list) to a Java SerializableFunction by pre-computing mu over every ordered prefix up to the queue capacity.

The JAR queries mu(c) with the ordered prefix as a row vector of 0-based class indices (jline.lang.state.AfterEventStation), and the PrecomputedRateFunction keys on the stringified vector, so values are stored under the matching 0-based key.

@param handle MATLAB mu(c) handle taking a 1-based ordered class list @param nclasses Number of classes @param cap Station capacity (max ordered-list length) @return serfun Java PrecomputedCDFunction

static reward_enum_capped(M, caps, budget)

REWARD_ENUM_CAPPED All integer row vectors v (1 x M) with 0 <= v(i) <= caps(i) and sum(v) <= budget.

static reward_handle_to_tabulatedfun(rewardFn, sn)

REWARD_HANDLE_TO_TABULATEDFUN Convert a MATLAB reward function handle to a Java TabulatedRewardFunction by pre-computing its value over an enumerable superset of the aggregated state space.

The domain per class r is: all per-station count vectors with sum <= njobs(r) for closed classes, and per-station counts capped by classcap(i,r) (or the default CTMC cutoff of 100 when infinite) for open classes. The JAR reward analyzer evaluates the function only on reachable stateSpaceAggr rows, which are a subset of this domain; unseen states raise a descriptive error.

@param rewardFn MATLAB reward handle @(state) or @(state, sn) @param sn Network struct @return jfun Java jline.lang.reward.TabulatedRewardFunction

static runFluidAnalyzer(network, options)

RUNFLUIDANALYZER Run JLINE fluid analyzer and return results

[QN, UN, RN, TN, CN, XN, T, QNT, UNT, TNT, XVEC] = JLINE.runFluidAnalyzer(NETWORK, OPTIONS)

Runs the JLINE fluid solver on the given network and converts results back to MATLAB data structures.

Input:

network - LINE Network model options - Solver options structure with fields:

.method - solver method .stiff - use stiff ODE solver

Output:

QN, UN, RN, TN - Steady-state metrics [M x K] CN, XN - System metrics [1 x K] t - Time vector [Tmax x 1] QNt, UNt, TNt - Transient metrics {M x K} cells xvec - State vector structure

static set_csMatrix(line_node, jnode, jclasses)
static set_delayoff(line_node, jnode, job_classes)

Transfer setup and delayoff times from MATLAB Queue to Java Queue

static set_layered_lincon(jelem, sn, eidx)

SET_LAYERED_LINCON(JELEM, SN, EIDX)

Admission constraint A*n <= b of a Host or a Task. SN.LINCON holds the resolved positional form, columns in the tasksof/entriesof order that the JAR uses as well, so the named rows of ADDCONSTRAINT cross already merged into A.

static set_layered_rate_dependence(jelem, sn, eidx)

SET_LAYERED_RATE_DEPENDENCE(JELEM, SN, EIDX)

Load dependence is a numeric vector and crosses to the JAR; the class- and joint-dependent scalings are MATLAB function handles with no Java counterpart, so they are refused rather than dropped.

static set_line_service(jline_node, line_node, job_classes, line_classes)
static set_service(line_node, jnode, job_classes)
static shift_idx(idxs)

Convert JLINE 0-based element (or call) indices to MATLAB 1-based ones. Java marks “unset” with -1, which MATLAB spells as 0, so the same +1 carries both. An empty input stays empty.

static supports(model)

[BOOL, FEATSUPPORTED] = SUPPORTS(MODEL)

static to_jline_sched_strategy(schedId)

Convert MATLAB SchedStrategy id to jline SchedStrategy enum

linemodel_save(model, filename)

LINEMODEL_SAVE Save a LINE model to JSON.

LINEMODEL_SAVE(MODEL, FILENAME) saves the model to the specified JSON file, conforming to the line-model.schema.json specification.

Parameters:
  • model - Network, LayeredNetwork, Workflow, or Environment object

  • filename - output file path (should end in .json)

Example

model = Network(‘M/M/1’); source = Source(model, ‘Source’); queue = Queue(model, ‘Queue’, SchedStrategy.FCFS); sink = Sink(model, ‘Sink’); oclass = OpenClass(model, ‘Class1’); source.setArrival(oclass, Exp(1.0)); queue.setService(oclass, Exp(2.0)); P = model.initRoutingMatrix(); P{1}(1,2) = 1; P{1}(2,3) = 1; model.link(P); linemodel_save(model, ‘mm1.json’);

Copyright (c) 2012-2026, Imperial College London All rights reserved.

pnml_save(model, filename)

PNML_SAVE Write a LINE Petri net to a PNML (ISO/IEC 15909-2) file.

PNML_SAVE(MODEL, FILENAME) writes the Place/Transition net held by MODEL to FILENAME in the PNML place/transition grammar http://www.pnml.org/version-2009/grammar/ptnet, so that a LINE net can be read by the tools built around that corpus (GreatSPN, TINA, the Model Checking Contest harnesses).

THE P/T GRAMMAR IS UNCOLOURED, so what it can carry is narrower than what LINE can express, and the difference is REFUSED rather than approximated:

  • more than one job class – a LINE net whose tokens carry a class is a coloured net, and flattening the colours would change the model;

  • an open class, a Source or a Sink – the P/T grammar has no unbounded token source;

  • a queueing place, which is a station rather than a place;

  • a firing-rate dependence, which no PNML element can carry;

  • a distribution outside the scalar-parameter families listed in PNML_DIST_TO_XML below, e.g. a phase-type or a Markovian arrival process given by matrices.

TIMING RIDES IN A TOOLSPECIFIC BLOCK, which is where the grammar puts anything it does not define. Each LINE MODE becomes one PNML transition, so that the arcs of a mode are the arcs of a transition as the grammar requires; the block records which LINE transition and mode the PNML transition came from, so PNML_LOAD regroups the modes it split. A reader that ignores the block still sees a correct untimed P/T net.

See also PNML_LOAD, LINEMODEL_SAVE.

Copyright (c) 2012-2026, Imperial College London All rights reserved.

line_native_env()

PREFIX = LINE_NATIVE_ENV()

Shell prefix that restores a SYSTEM loader environment for a native binary spawned out of MATLAB (line-cli, the ldes native binary).

MATLAB exports its own bin/glnxa64 and sys/os/glnxa64 to every child process, and the libstdc++ shipped there is older than the one a current toolchain links against. A binary this checkout compiled then fails to load:

common/line-cli: …/sys/os/glnxa64/libstdc++.so.6: version `GLIBCXX_3.4.32’ not found (required by common/line-cli)

which surfaces as “line-cli exited with code 1” and, one level up, as every lang=’cpp’ row of the model reporting “solver X missing from output”. Neither the binary nor the model is at fault, so the fix belongs here.

Only the entries under matlabroot (and MATLAB’s per-user .MathWorks tree) are dropped: a path the user set before starting MATLAB is theirs and is kept. A JVM runner must NOT be wrapped – it is MATLAB’s own JRE and wants those directories.

Returns ‘’ on Windows and macOS, where no such shadowing occurs.

Copyright (c) 2012-2026, Imperial College London All rights reserved.

line_java_cmd(caller)

JAVACMD = LINE_JAVA_CMD(CALLER)

Quoted Java launcher to prefix a JVM command line with, or a clear error naming the missing runtime when none is reachable. CALLER is the function reported in that error, usually mfilename.

Callers must go through here rather than hardcoding ‘java’: on a host with no Java on PATH, the bare string produces “‘java’ is not recognized” from the shell and, if the failure is then retried through java.lang.Runtime.exec, an unhandled java.io.IOException with CreateProcess error=2 that says nothing about what is actually missing.

Copyright (c) 2012-2026, Imperial College London All rights reserved.

line_debug(varargin)

LINE_DEBUG(MSG, VARARGIN) LINE_DEBUG(OPTIONS, MSG, VARARGIN)

Print debug message if verbose level is DEBUG. If OPTIONS struct is passed as first argument, checks options.verbose. Otherwise checks GlobalConstants.Verbose.

jsimgView(filename)

JSIMGVIEW(FILENAME) Open model in JSIMgraph

map2renv(model, options)

[ENVMODEL, INFO] = MAP2RENV(MODEL, OPTIONS)

Markov-modulated image of a network with MAP/MMPP/MMAP arrival or service processes as a queueing network in a random environment.

Every non-renewal process is a point process modulated by the CTMC with generator Q = D0 + D1, whose conditional intensity in phase k is lambda(k) = sum_j D1(k,j). The transformation freezes each phase into an environment stage in which the process is the Poisson process of that intensity, i.e. an exponential arrival or service time, and lets the environment switch stages at the rates of Q. With P modulated processes the stage set is the Cartesian product of their phase spaces and the environment generator is the Kronecker sum of the individual Q’s, so only one process changes phase at a time, as in the original model.

The image is exact in structure for an MMPP (diagonal D1): the modulating chain, its stationary distribution and the phase-conditional intensities are all preserved. For a general MAP the phase jumps that occur AT an event epoch (off-diagonal D1) are aggregated into Q and their correlation with the event stream is lost, so the image matches the modulating chain and the conditional intensities but not the full inter-event autocorrelation.

Populations are carried across stage switches unchanged (identity reset), as a phase switch moves no job.

OPTIONS is a solver options structure; OPTIONS.config.map_env_maxstages caps the number of environment stages (default 64).

INFO reports nstages, the per-process phase orders, whether every process was an MMPP, and the modulation records themselves.

Copyright (c) 2012-2026, Imperial College London All rights reserved.

MAPQN2RENV(model, options)

ENVMODEL = MAPQN2RENV(MODEL, OPTIONS) Random-environment image of a network with MAP/MMPP service or arrival processes.

Retained name for the transformation now implemented by MAP2RENV, which generalizes it from a single MMPP2 service process to any number of MAP, MMPP2 or MMAP arrival and service processes of arbitrary phase order (the stage set is then the Cartesian product of the phase spaces). New code should call MAP2RENV directly.

Copyright (c) 2012-2026, Imperial College London All rights reserved.

class SAGE

SAGE MATLAB-to-SageMath bridge for symbolic analysis.

Static helpers that talk to the line-sage-rest service, the same JSON protocol the JAR (jline.api.sym) and native Python (line_solver.api.sym) use. The service is SageMath in a container; see io/sage/server.py for the endpoints.

It exists for two reasons. The Symbolic Math Toolbox is licence-gated, so a session without it cannot run any symbolic analysis at all; and where both are present, routing through one engine gives all three codebases the same normal form, which is what makes a symbolic result comparable across them.

Backend resolution, in order:
  1. an explicit URL in options.config.symbolic;

  2. the LINE_SAGE_URL environment variable;

  3. a line-sage-rest service already listening on a conventional port;

  4. a container started here from a locally present image;

  5. nothing, in which case the caller falls back to the toolbox.

Step 3 checks identity through /api/v1/info rather than trusting the port: every imperialqore line-*-rest service listens on 8080 by convention, so a health probe alone would accept the LQNS service.

Expressions cross as plain char infix strings, e.g. ‘2*x1 - 3*x2’. Numeric coefficients are read server side as exact rationals, so ‘0.1’ is 1/10 and not the binary double nearest to it.

Copyright (c) 2012-2026, Imperial College London All rights reserved.

Property Summary
CANARY_ARG
CANARY_EXPR

A service that answers /health can still be unable to COMPUTE. The line-sage-rest image ships a FLINT built for CPUs that have BMI2 and ADX; on an older host the first multi-limb exact operation raises SIGILL, the worker dies mid-request and the call returns no bytes at all. Health is pure Python and keeps answering, so it cannot see this. The canary is the weighted-average softmin form – what the fluid export actually sends – and is the smallest expression observed to trigger it; see _kb/11-conventions-and-gotchas.md.

CANARY_VALUE
DEFAULT_TIMEOUT

seconds per request

DOCKER_IMAGES
PROBE_PORTS
STARTUP_TIMEOUT

seconds to wait for a container to answer

Method Summary
static asCellMatrix(x)

ASCELLMATRIX Nested JSON array to a cell matrix.

static asCellstr(x)
static diff(exprs, variable, order, url, timeout)

DIFF Differentiate expressions with respect to VARIABLE.

static eval(exprs, assignment, url, timeout)

EVAL Substitute values for symbols and evaluate.

ASSIGNMENT is a struct whose fields are symbol names. This is how symbolic results are compared across codebases: symbol numbering follows event enumeration order, so comparing expression text is unsound while comparing substituted values is not.

static exprToChar(e)
static fluidODEs(rhs, vars, want, url, timeout)

FLUIDODES Jacobian, LaTeX form and equilibria of a fluid drift.

static freePort()

FREEPORT An ephemeral port nothing is listening on.

There is a race between finding the port and docker binding it; it is the same race the JAR and Python clients run, and losing it fails the start loudly rather than silently sharing a port.

static getDockerImage()

GETDOCKERIMAGE First locally present image tag, or ‘’ if none.

static hasLocalImage(image)

HASLOCALIMAGE True if IMAGE is present in the local Docker store.

static hasSymbolicToolbox()

HASSYMBOLICTOOLBOX True if sym objects can be constructed here.

static isAvailable(requested)

ISAVAILABLE True if a symbolic backend can be resolved.

static isReachable(url)

ISREACHABLE True if the service answers a health probe.

static isSageService(url)

ISSAGESERVICE True if the service is line-sage-rest and not another line-*-rest service sharing the conventional port.

static isUsable(url)

ISUSABLE True if the service answers AND can evaluate.

Verdicts are cached per URL: this costs one small request the first time a service is considered, and nothing after.

static numToChar(v)

NUMTOCHAR Decimal text the service reads as an exact rational.

static post(url, endpoint, payload, timeout)

POST One JSON request, with the service’s own errors raised here.

static pullDockerImage(target)

PULLDOCKERIMAGE Pull TARGET if Docker is usable and there is room.

Returns the tag on success, else ‘’. Storage-guarded via lineDockerHasStorageFor (the same guard as the LQNS/QNS/JMT wrappers), so an opt-in symbolic request never silently fills the Docker disk; on refusal the caller keeps its native algebra.

static require()

REQUIRE Resolve a backend or error with how to get one.

static resolve(requested)

RESOLVE Base URL of a usable service, or ‘’ if there is none.

REQUESTED is ‘’ or ‘auto’ to search, a URL to use a specific service, ‘none’ to disable the backend, or an image name.

static scalarSymOrChar(item)
static sensitivity(Q, symbols, theta, reward, url, timeout)

SENSITIVITY Exact d(pi)/d(theta) and, given a reward, d(E[r])/d(theta).

Exact where @SolverCTMC/getSensitivity uses a central difference accurate to O(h^2). As there, dr/dtheta is taken to be zero.

static simplify(exprs, form, url, timeout)

SIMPLIFY Rewrite expressions: simplify, factor, together, cancel, expand or latex.

static solveCTMC(Q, symbols, url, timeout)

SOLVECTMC Symbolic stationary distribution, pi*Q = 0, sum(pi) = 1.

Q is a cell array of expression strings or a sym matrix, SYMBOLS a cellstr of the symbols occurring in it. PI comes back as a cellstr, or as a sym vector when the Symbolic Toolbox is present.

static startContainer(image)

STARTCONTAINER Run the service and wait for it to answer.

The container is bound to a free host port, so several MATLAB sessions, or a session next to a hand-started service, do not collide. It stays warm for the session (repeated symbolic calls are then cheap) and is best-effort stopped when MATLAB exits or SAGE is cleared, via the onCleanup guard below. Stop it sooner with SAGE.stopContainer. Held only for its destructor: cleared at MATLAB exit / clear SAGE, which fires the onCleanup below and stops the container.

static startedContainer(name)

STARTEDCONTAINER Get/set the container this session started.

Call with a name to set, ‘’ to clear, or no argument to get the tracked name (parity with the Java/Python startedContainer state).

static stopContainer()

STOPCONTAINER Stop the container this session started, if any.

Parity with the Java (shutdown hook) and Python (atexit) clients: it stops only the container started here, not every line-sage-rest-* on the host.

static stopContainerByName(name)

STOPCONTAINERBYNAME Stop a single named container, best effort.

static toExpressionList(exprs)

TOEXPRESSIONLIST Expressions as a cellstr, whatever came in.

static toExpressionMatrix(Q, symbols)

TOEXPRESSIONMATRIX Generator as a cell array of expressions.

static toSymOrChar(items)

TOSYMORCHAR Expressions as sym when the toolbox is present.

Returning sym where possible keeps every existing caller working unchanged; without the toolbox the same result is still usable as text, which is the whole point of the Sage backend.

static trimUrl(url)
line_warning(caller, MSG, varargin)

LINE_WARNING(CALLER, ERRMSG)

line_ack(toolName, verbose, msg, cite)

LINE_ACK(TOOLNAME, VERBOSE, MSG, CITE)

Print, once per session, the acknowledgement of the external tool that a wrapper solver delegates to, together with the pointer to its official website and the canonical paper to cite. The acknowledgement is pull-based, like the library attribution: nothing is printed at the default verbosity, and only a caller that asks for it explicitly, by running at VerboseLevel.DEBUG, gets the line. It is printed at most once per TOOLNAME per session. SOLVER.CITATIONS and LINE_CITATION are the quiet ways to obtain the same reference.

MSG and CITE supply the acknowledgement text and the reference for a solver that lives outside this tree; when omitted both come from the table below, which covers the in-tree wrapper solvers only. Mirror any edit to that table in the Java (jline.io.InputOutput.line_ack) and Python (line_solver.api.io.logging.line_ack) tables.

The machine-readable form of the same reference is LINE_CITATION(TOOLNAME), which returns the BibTeX entry.

See also LINE_CITATION.

LQN2JAVA(MODEL, MODELNAME, FID)
lineTimeoutExceeded(options)

TF = LINETIMEOUTEXCEEDED(OPTIONS) Cooperative wall-clock checkpoint for iterative solvers. Returns true if the elapsed time since the solver launch (options.timeout_tic, a tic handle set at the top of the per-solver runAnalyzer) exceeds options.timeout (seconds). A missing/non-finite/non-positive budget, or a missing tic, means no budget.

lineDownloadJAR(verbose)

LINEDOWNLOADJAR Download jline.jar from SourceForge if not found locally.