5#ifndef LINE_SOLVERS_MVA_FJ_DRIVER_H
6#define LINE_SOLVERS_MVA_FJ_DRIVER_H
58 const std::vector<std::size_t>& merge,
const Matrix<T>& QN,
61 const std::size_t st = V.
nodes[nd - 1].station;
62 if (st == 0)
return zero;
64 for (std::size_t k : merge) {
65 q += QN(st - 1, k - 1);
66 t += TN(st - 1, k - 1);
68 if (!(t > zero))
return zero;
81 std::vector<std::vector<bool>>
set;
82 void init(std::size_t nnodes, std::size_t nclasses) {
84 set.assign(nnodes, std::vector<bool>(nclasses,
false));
87 void put(std::size_t node, std::size_t cls,
const T& d0) {
88 if (node == 0 || cls == 0 || node >
R.
rows() || cls >
R.
cols())
return;
89 R(node - 1, cls - 1) = d0;
90 set[node - 1][cls - 1] =
true;
113 for (std::size_t p = 0; p < out.
npairs(); ++p) {
114 const std::size_t f = out.
forks[p];
115 const std::size_t jst = L.
nodes[out.
joins[p] - 1].station;
116 for (std::size_t r = 0; r < L.
nclasses; ++r) {
118 if (jst != 0 && jst <= TN.
rows() && r < TN.
cols()) x = TN(jst - 1, r);
119 const bool has_d0 = f <= acc.
set.size() && r < acc.
set[f - 1].size() &&
122 if (x > zero && has_d0) {
124 out.
RespT(p, r) = acc.
R(f - 1, r);
125 out.
QLen(p, r) = T(x * acc.
R(f - 1, r));
158 const T& t0, std::vector<T>& out,
159 std::vector<std::pair<std::size_t, std::size_t>> visited,
163 if (curNode == endNode) {
164 out.push_back(T(t0 -
fj_node_time(V, curNode, merge, QN, TN)));
167 const std::pair<std::size_t, std::size_t> here(curNode, curClass);
168 if (std::find(visited.begin(), visited.end(), here) != visited.end())
return;
169 visited.push_back(here);
171 for (std::size_t s = 1; s <= V.
classes.size(); ++s)
172 for (std::size_t nd = 1; nd <= V.
nodes.size(); ++nd) {
176 if (!(detail::fj_route_cs(V, curClass, s, curNode, nd) > zero))
continue;
177 std::vector<std::size_t> m2 = merge;
182 std::size_t inner =
tr.forks.size();
183 for (std::size_t b = 0; b <
tr.forks.size(); ++b)
184 if (
tr.forks[b].node == nd &&
tr.forks[b].joinNode != 0) inner = b;
186 if (inner <
tr.forks.size()) {
187 const std::size_t ijoin =
tr.forks[inner].joinNode;
188 const std::size_t istat =
tr.forks[inner].joinStation;
190 std::size_t iaux = 0;
191 for (std::size_t x :
tr.auxclasses)
192 if (
tr.fjforkmap[x] == inner &&
tr.fjclassmap[x] == s) iaux = x;
193 std::vector<std::size_t> m3 = m2;
194 if (iaux != 0) m3.push_back(iaux);
196 std::vector<T> paths;
197 fj_find_paths(
tr, nd, ijoin, s, m3, QN, TN, zero, paths, visited, inner_d0);
199 if (!paths.empty()) {
201 for (
const T& x : paths) mean += x;
214 for (std::size_t cls : m3) V.
set_service(istat, cls, d);
218 if (inner_d0 !=
nullptr) {
223 inner_d0->push_back(rec);
225 fj_find_paths(
tr, ijoin, endNode, s, m2, QN, TN, T(t0 + d0), out, visited,
229 T(t0 +
fj_node_time(V, nd, m2, QN, TN)), out, visited, inner_d0);
238 for (std::size_t i = 0; i < A.
rows(); ++i)
239 for (std::size_t j = 0; j < n && j < A.
cols(); ++j) out(i, j) = A(i, j);
247 for (std::size_t i = 0; i < A.
rows(); ++i)
248 for (std::size_t j = 0; j < A.
cols(); ++j) {
286 std::vector<T> tnfork(L.
nclasses, zero);
287 const std::size_t pnode =
tr.forks[
tr.forks[fa].parent].node;
288 for (std::size_t c = 0; c < L.
nchains; ++c) {
289 const std::vector<std::size_t>& ic = L.
inchain[c];
290 for (std::size_t r : ic) {
291 const std::size_t rs = L.
classes[r - 1].refstat;
293 T den = zero, tsum = zero;
294 for (std::size_t k : ic) {
295 den += L.
visits[c](isf - 1, k - 1);
296 tsum += TN(rs - 1, k - 1);
298 if (!(den > zero))
continue;
299 tnfork[r - 1] = T(T(L.
nodevisits[c](pnode - 1, r - 1) / den) * tsum);
345 for (std::size_t fa = 0; fa <
tr.forks.size(); ++fa) {
346 const std::size_t fnode =
tr.forks[fa].node;
347 const std::size_t jnode =
tr.forks[fa].joinNode;
348 if (jnode == 0)
continue;
349 const std::size_t jstat =
tr.forks[fa].joinStation;
350 const std::size_t adstat =
tr.auxDelayStation[jnode];
351 for (std::size_t c = 0; c < L.
nchains; ++c) {
352 const std::vector<std::size_t>& ic = L.
inchain[c];
353 for (std::size_t xi = 0; xi < ic.size(); ++xi) {
354 const std::size_t r = ic[xi];
372 std::vector<std::size_t> br, foreign;
374 for (std::size_t s :
tr.auxclasses) {
375 if (
tr.fjclassmap[s] != r)
continue;
378 foreign.push_back(s);
382 for (std::size_t i = 0; i < out.
R.rows(); ++i)
386 acc = T(acc - out.
R(adstat - 1, s - 1));
388 ri.push_back(T(acc - out.
R(jstat - 1, s - 1)));
390 if (br.empty())
continue;
392 if (fjacc !=
nullptr) fjacc->put(fnode, r, d0);
395 for (std::size_t i = 0; i < out.
R.rows(); ++i) {
397 for (std::size_t yi = 0; yi < ic.size(); ++yi) row += out.
R(i, ic[yi] - 1);
400 r0 = T(r0 - out.
R(jstat - 1, r - 1));
404 tr.forks[fa].fanOut))));
406 for (std::size_t b = 0; b < br.size(); ++b) {
407 T di = T(d0 - ri[b]);
408 if (di < zero) di = zero;
427 for (std::size_t s : foreign) {
456 std::vector<bool> orig(
tr.V.classes.size() + 1,
false);
457 for (std::size_t s :
tr.auxclasses) orig[
tr.fjclassmap[s]] =
true;
459 std::vector<std::vector<T>> tnJoin(
tr.joinStations.size(),
460 std::vector<T>(L.
nclasses + 1, zero));
461 for (std::size_t a = 0; a <
tr.joinStations.size(); ++a) {
462 const std::size_t js =
tr.joinStations[a];
463 for (std::size_t r = 1; r <= L.
nclasses; ++r) {
464 if (!orig[r])
continue;
465 tnJoin[a][r] = out.
Tp(js - 1, r - 1);
466 out.
Q(js - 1, r - 1) = zero;
467 out.
R(js - 1, r - 1) = zero;
468 out.
Tp(js - 1, r - 1) = zero;
469 out.
U(js - 1, r - 1) = zero;
472 const std::size_t nrows = out.
Tp.rows();
473 for (std::size_t s :
tr.auxclasses) {
474 const std::size_t r =
tr.fjclassmap[s];
475 for (std::size_t i = 0; i < nrows; ++i) {
476 out.
Q(i, r - 1) = T(out.
Q(i, r - 1) + out.
Q(i, s - 1));
477 out.
U(i, r - 1) = T(out.
U(i, r - 1) + out.
U(i, s - 1));
478 out.
Tp(i, r - 1) = T(out.
Tp(i, r - 1) + out.
Tp(i, s - 1));
480 out.
Tp(i, r - 1) != zero ? T(out.
Q(i, r - 1) / out.
Tp(i, r - 1)) : zero;
483 for (std::size_t a = 0; a <
tr.joinStations.size(); ++a) {
484 const std::size_t js =
tr.joinStations[a];
485 for (std::size_t r = 1; r <= L.
nclasses; ++r)
486 if (orig[r]) out.
Tp(js - 1, r - 1) = tnJoin[a][r];
511template <
class T,
class InnerSolve>
514 InnerSolve inner, std::string* warning =
nullptr) {
525 if (!
tr.heidelberger_trivedi) {
532 for (std::size_t i = 1; i <= L.
stations.size(); ++i)
533 for (std::size_t k = 1; k <= L.
classes.size(); ++k)
535 for (std::size_t s :
tr.auxclasses) {
536 const std::size_t r =
tr.fjclassmap[s];
537 for (std::size_t i = 1; i <= L.
stations.size(); ++i) {
538 if (
tr.is_join_station(i))
continue;
542 for (std::size_t js :
tr.joinStations)
543 for (std::size_t k = 1; k <= V.
classes.size(); ++k)
545 for (std::size_t s :
tr.auxclasses)
561 bool forkLoop =
true;
563 while (forkLoop && forkIter <
opt.iter_max) {
568 if (forkIter > 1 && !
tr.heidelberger_trivedi) {
571 for (std::size_t s :
tr.auxclasses) {
572 if (
tr.auxdisabled[s] || !(
tr.fanout[s] > 0.0))
continue;
582 if (QN_1.
rows() == QN.rows() && QN_1.
cols() == QN.cols()) {
584 for (std::size_t i = 0; i < QN.rows(); ++i)
585 for (std::size_t j = 0; j < QN.cols(); ++j)
589 "fork-join iteration %d: queue lengths moved by at most %.3e", forkIter, moved);
592 "fork-join iteration %d: transformed model rebuilt", forkIter);
603 if (
tr.heidelberger_trivedi) {
613 for (std::size_t fa = 0; fa <
tr.forks.size(); ++fa) {
614 const std::size_t fnode =
tr.forks[fa].node;
615 const std::size_t jstat =
tr.forks[fa].joinStation;
624 for (std::size_t s :
tr.auxclasses) {
625 if (
tr.fjforkmap[s] != fa)
continue;
626 lam[s] = T((lam[s] + tnfork[
tr.fjclassmap[s] - 1]) / two);
631 for (std::size_t s :
tr.auxclasses) {
632 if (
tr.fjforkmap[s] != fa)
continue;
633 const std::size_t r =
tr.fjclassmap[s];
635 for (std::size_t s2 :
tr.auxclasses)
636 if (
tr.fjclassmap[s2] == r) acc += out.
Tp(jstat - 1, s2 - 1);
637 out.
Tp(jstat - 1, r - 1) =
638 T(out.
Tp(jstat - 1, r - 1) + acc - out.
Tp(jstat - 1, s - 1));
639 lam[s] = T((lam[s] + out.
Tp(jstat - 1, r - 1)) / two);
644 if (!
tr.forks[fa].outer[r])
continue;
647 std::vector<std::size_t> merge{r, s};
648 std::vector<FjInnerD0<T>> innerFJ;
654 std::size_t cq = q.cls;
655 if (cq > L.
nclasses) cq = cq <
tr.fjclassmap.size() ?
tr.fjclassmap[cq] : 0;
656 if (cq >= 1 && cq <= L.
nclasses) fjR.
put(q.node, cq, q.d0);
667 double wd =
tr.forks[fa].fanOut;
668 std::size_t w = (wd >= 1.0) ?
static_cast<std::size_t
>(wd + 0.5) : 1;
670 const std::vector<T> base = ri;
671 for (std::size_t k = 1; k < w; ++k)
672 ri.insert(ri.end(), base.begin(), base.end());
675 for (
const T& x : ri) mean += x;
683 fjR.
put(fnode, r, d0);
684 const T raw = T(d0 - mean);
693 sync = raw > zero ? raw : zero;
706 const std::size_t nrows = out.
Tp.rows();
707 std::vector<std::vector<T>> tnJoin(
tr.joinStations.size(),
709 std::vector<T> tnSource(L.
nclasses, zero);
710 for (std::size_t a = 0; a <
tr.joinStations.size(); ++a)
711 for (std::size_t k = 0; k < L.
nclasses; ++k)
712 tnJoin[a][k] = out.
Tp(
tr.joinStations[a] - 1, k);
713 for (std::size_t k = 0; k < L.
nclasses; ++k)
714 tnSource[k] = out.
Tp(
tr.sourceStation - 1, k);
715 for (std::size_t s :
tr.auxclasses) {
716 const std::size_t r =
tr.fjclassmap[s];
717 for (std::size_t i = 0; i < nrows; ++i) {
718 out.
Q(i, r - 1) = T(out.
Q(i, r - 1) + out.
Q(i, s - 1));
719 out.
U(i, r - 1) = T(out.
U(i, r - 1) + out.
U(i, s - 1));
720 out.
Tp(i, r - 1) = T(out.
Tp(i, r - 1) + out.
Tp(i, s - 1));
721 out.
R(i, r - 1) = out.
Tp(i, r - 1) != zero
722 ? T(out.
Q(i, r - 1) / out.
Tp(i, r - 1))
726 for (std::size_t a = 0; a <
tr.joinStations.size(); ++a)
727 for (std::size_t k = 0; k < L.
nclasses; ++k)
728 out.
Tp(
tr.joinStations[a] - 1, k) = tnJoin[a][k];
729 for (std::size_t k = 0; k < L.
nclasses; ++k)
730 out.
Tp(
tr.sourceStation - 1, k) = tnSource[k];
742 if (forkLoop && forkIter >=
opt.iter_max) {
744 if (warning !=
nullptr)
745 *warning = std::string(
"The fork-join (") +
746 (
tr.heidelberger_trivedi ?
"ht" :
"mmt") +
747 ") fixed point did not converge in options.iter_max=" +
748 std::to_string(
opt.iter_max) +
749 " iterations; returning the interim solution.";
755 for (std::size_t i = 0; i < out.
Q.rows(); ++i)
756 for (std::size_t k = 0; k < out.
Q.cols(); ++k)
762 "SolverMVA: the fork-join fixed point of model '" + L.
name +
763 "' returned a non-finite metric; the auxiliary open classes the transform "
764 "adds have saturated one of the branch stations");
NumericError(const std::string &what)
A network plus its refreshed NetworkStruct.
std::vector< Matrix< T > > nodevisits
(nchains) each (nnodes x nclasses)
std::size_t stateful_of_station(std::size_t st) const
std::vector< std::vector< Distrib< T > > > service
service[i][r], 0-based station and class; a disabled entry marks a pair never visited.
void refresh_chains()
Port of MNetwork.refreshChains followed by sn_refresh_visits.
std::vector< JobClass > classes
std::vector< Station< T > > stations
stations[k-1] is the k-th station
void set_service(std::size_t station, std::size_t cls, const Distrib< T > &d)
std::vector< std::vector< std::size_t > > inchain
1-based class indices per chain
std::vector< NodeDef > nodes
every node, in creation order
std::vector< Matrix< T > > visits
(nchains) each (nstateful x nclasses)
static bool owns_log()
True only inside the OUTERMOST open run; gates EMISSION.
static void step(const char *fmt,...)
Write one progress line.
The exception types the port throws.
Fork-join section metrics: FJQLen, FJRespT and FJTput of every Fork/Join pair.
The fork-join transform SolverMVA applies before solving a layer that contains a Fork.
Running progress log of a LINE solver run (the "solver console").
void fj_ht_sync_delays(const qn::NetworkStruct< T > &L, FjMmt< T > &tr, const MvaSolution< T > &out, FjSectionAcc< T > *fjacc=nullptr)
Port of the heidelberger-trivedi arm of fjFixedPoint.m:212-255: the synchronisation delays of one pas...
Matrix< T > fj_leading_cols(const Matrix< T > &A, std::size_t n)
The leading n columns of a class-indexed metric.
Distrib< T > fj_exp_fit_mean(const T &mean)
Exp.fitMean(m), including its clamp.
T 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,...
bool fj_converged(const Matrix< T > &A, const Matrix< T > &B, double iter_tol)
The mixed absolute/relative stopping test of the fork-join loop.
std::size_t 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.
void fj_ht_merge(const qn::NetworkStruct< T > &L, const FjMmt< T > &tr, MvaSolution< T > &out)
Port of the heidelberger-trivedi arm of fjFixedPoint.m:263-291: fold the auxiliary columns back into ...
MvaSolution< T > fj_fixed_point(const qn::NetworkStruct< T > &L, FjMmt< T > &tr, std::vector< T > &lam, const MvaOptions &opt, InnerSolve inner, std::string *warning=nullptr)
Drive the fork-join fixed point of a transformed model to convergence.
std::vector< T > fj_fork_tput(const qn::NetworkStruct< T > &L, const FjMmt< T > &tr, const Matrix< T > &TN, std::size_t fa)
Port of fjFixedPoint.m:130-136: the firing throughput of fork fa, per class of the BASE model.
T fj_finite_or_zero(const T &x)
A metric read out of a solve, with a non-finite entry read as zero.
void fj_find_paths(FjMmt< T > &tr, std::size_t curNode, std::size_t endNode, std::size_t curClass, const std::vector< std::size_t > &merge, const Matrix< T > &QN, const Matrix< T > &TN, const T &t0, std::vector< T > &out, std::vector< std::pair< std::size_t, std::size_t > > visited, std::vector< FjInnerD0< T > > *inner_d0=nullptr)
Port of ModelAdapter.findPathsCS: the response time along every path from a fork to ITS join,...
solvers::FjMetrics< T > fj_section_metrics(const qn::NetworkStruct< T > &L, const Matrix< T > &TN, const FjSectionAcc< T > &acc)
fjSectionMetrics of fjFixedPoint.m: one row per Fork/Join pair of the BASE model.
T fj_node_time(const qn::NetworkStruct< T > &V, std::size_t nd, const std::vector< std::size_t > &merge, const Matrix< T > &QN, const Matrix< T > &TN)
Port of ModelAdapter.findPathsCS' per-node charge: the queue length of the merge set at this station ...
FjMetrics< T > fj_metrics_zero(const S &sn, std::size_t nclasses)
An empty-but-shaped result: every pair of sn, all three matrices zero.
Conservation laws of a layered queueing network, enumerated from its structure.
A queueing network and its refreshed NetworkStruct.
SolverMVA over a SolverLN layer.
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
The MATLAB GlobalConstants, as reported by lineStart at its defaults.
static Distrib exp_rate(const T &r)
static Distrib disabled_dist()
static Distrib immediate()
The point mass at zero.
One nested fork walked by fj_find_paths: the second output inner of findPathsCS.
T d0
the order statistic the outer path is charged for it
std::size_t node
1-based fork node
std::size_t cls
1-based class arriving at it, possibly auxiliary
The transformed layer and the bookkeeping the fixed point needs to drive it and to merge its results ...
fjR of fjFixedPoint.m: the order-statistic sojourn d0 the fixed point charges each fork-join section,...
void init(std::size_t nnodes, std::size_t nclasses)
std::vector< std::vector< bool > > set
whether R(node, class) was ever written
void put(std::size_t node, std::size_t cls, const T &d0)
Record d0 for a 1-based (node, class); a class outside the base set is ignored.
Matrix< T > R
(nnodes x nclasses) d0, 0-based storage
static constexpr double Immediate
Rate of an Immediate distribution; its mean is 1/Immediate = 1e-8.
static constexpr double FineTol
static constexpr double Zero
The options SolverMVA reads.
Class-level results, the [Q,U,R,T,C,X] of the MATLAB analyzers.
std::optional< bool > converged
Whether the fixed point met its tolerance, or empty when the handler reports none.
solvers::FjMetrics< T > fj
Fork-join section metrics set by the fork-join fixed point (fj_driver.h); empty otherwise.
self.result.FJ: one row per Fork/Join pair, one column per class.
std::vector< std::size_t > forks
1-based fork node of each pair
Matrix< T > Tput
FJTput, P x R.
Matrix< T > QLen
FJQLen, P x R.
std::size_t npairs() const
std::vector< std::size_t > joins
1-based join node of each pair
Matrix< T > RespT
FJRespT, P x R.