LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
fj_mmt.h File Reference

The fork-join transform SolverMVA applies before solving a layer that contains a Fork. More...

#include <algorithm>
#include <cmath>
#include <limits>
#include <map>
#include <string>
#include <utility>
#include <vector>
#include "line/api/fj/fj_ordstat_exp.h"
#include "line/lang/qn/network_struct.h"
#include "line/util/error.h"
Include dependency graph for fj_mmt.h:

Go to the source code of this file.

Classes

struct  line::mva::FjMmt< T >
 The transformed layer and the bookkeeping the fixed point needs to drive it and to merge its results back. More...
struct  line::mva::FjMmt< T >::ForkRec
 One record per Fork of the base layer, ascending in node index. More...

Namespaces

namespace  line
namespace  line::mva

Enumerations

enum class  line::mva::JobClassType
 Job class kinds, with the values of MATLAB JobClassType. More...
enum class  line::mva::NodeType
 Node kinds, with the values of MATLAB NodeType. More...
enum class  line::mva::SchedStrategy
 Scheduling disciplines, with the values of MATLAB SchedStrategy. More...

Functions

template<class T>
line::mva::fj_expected_ordstat (const std::vector< T > &means, std::size_t k)
 The instant the join fires: E[X_(k)] of independent exponentials with the given means, by inclusion-exclusion.
template<class T>
line::mva::fj_expected_max (const std::vector< T > &means)
 The AND-join case, i.e.
template<class T>
std::size_t line::mva::fj_join_quorum (const qn::NetworkStruct< T > &sn, std::size_t joinNode, std::size_t nbranches)
 The number of siblings the Join node fires on, out of nbranches forked.
template<class T>
Distrib< T > line::mva::fj_exp_fit_mean (const T &mean)
 Exp.fitMean(m), including its clamp.
template<class T>
void line::mva::fj_sort_forks (FjMmt< T > &tr)
 Port of ModelAdapter.sortForks: fill in outer and parent on every fork record.
template<class T>
void line::mva::fj_widen_csmatrix (FjMmt< T > &tr)
 Widen every explicit ClassSwitch matrix to the auxiliary-expanded class set.
template<class T>
FjMmt< T > line::mva::fj_mmt (const qn::NetworkStruct< T > &L)
 Build the transformed layer.

Detailed Description

The fork-join transform SolverMVA applies before solving a layer that contains a Fork.

Port of matlab/src/io/@ModelAdapter/mmt.m. The transform turns a layer with a fork into a plain queueing network with no fork at all, in three moves:

  1. THE FORK BECOMES A ROUTER. Its outgoing routing is divided by its fan-out, so a circulating job takes ONE branch chosen at random rather than all of them, and the node stops being a Fork – which also stops the fork corrections in chain_visits from firing, since the reference transform leaves no Fork in the model for them to key on.
  2. THE JOIN BECOMES A DELAY whose per-class service time is the synchronisation delay E[X_(k) over branches] * fanOut - mean(branch), re-set on every pass of the fixed point from the branch response times the previous pass measured. k is the branch count on a standard join, which makes X_(k) the maximum, and the declared quorum on a PARTIAL one; the floor at zero the quorum needs is in fj_driver.h.
  3. THE BRANCHES THE JOB DID NOT TAKE ARE CARRIED BY AUXILIARY OPEN CLASSES. A Source and a Sink are added, and one auxiliary open class is minted per class of every chain that reaches the fork. Auxiliary tokens arrive at rate (fanout - 1) * forkLambda, enter at the fork, take one branch each by the same split, and leave at the Sink after the Join. With the circulating class contributing 1/fanout of a visit to each branch and the auxiliary stream the remaining (fanout-1)/fanout, every branch station sees the load of one branch traversal per fork event, which is what the fork actually generates.

Moves 1 and 2 alone are exact only when every station of the layer is an infinite server, because auxiliary tokens then add no waiting. Move 3 is what makes the transform correct at a station that can QUEUE, and it is also what makes the layer MIXED open-closed – so solver_amvald and solver_mva both have to accept open chains before any of this can be solved.

WHAT IS DELIBERATELY REPRODUCED RATHER THAN IMPROVED:

  • An auxiliary class is minted for EVERY class of the forked chain, not only for the classes that reach the fork. The ones that do not get a disabled arrival and an inert Source -> Sink route; they exist so that the class indexing of the auxiliary block mirrors the original block one-for-one, which is what the merge-back keys on.
  • The auxiliary routing is CONFINED to the fork-join scope by a class-aware BFS from the fork that stops at the join. Without it the copied return-path cycles form recurrent components disconnected from the Source, which capture the whole stationary mass of the auxiliary chain and destroy the visit ratios (on lqn_bpmn the reference class switch is amplified to 27 instead of 1, saturating the layer beyond solvability).

SEVERAL FORKS, AND NESTED ONES (2026-07-30). Every Fork of the layer gets its own record: its own join, its own per-class fan-out, and its own block of auxiliary classes, so fjforkmap[s] is needed alongside fjclassmap[s] to say WHICH fork an auxiliary class stands in for. Two properties then have to be derived rather than assumed, and fj_sort_forks derives them (the port of ModelAdapter.sortForks):

outer_forks(f, r) fork f is the OUTERMOST fork on class r's path, i.e. no other fork encloses it. Only an outer fork writes a synchronisation delay onto the ORIGINAL class r: an inner fork's delay is charged while the outer fork's branch is being walked, by fj_find_paths itself, and writing it again on the original class would count it twice. parent_forks(f) the fork whose node visits measure how often f fires. For an outer fork that is f; for a nested one it is the enclosing fork, because a nested fork fires once per traversal of the enclosing branch, not once per class completion.

A fork with NO join is admitted (the reference allows it): there is no synchronisation point, so no delay is computed and forkLambda is driven from the fork's own throughput instead of a join's.

Definition in file fj_mmt.h.