LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
smp_stage_cif.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_API_MC_SMP_STAGE_CIF_H
6#define LINE_API_MC_SMP_STAGE_CIF_H
7
8/**
9 * @file
10 * @ingroup api_mc
11 * Cumulative incidence functions of one semi-Markov stage.
12 *
13 * Port of `matlab/src/api/mc/smp_stage_cif.m`. A stage left by E possible
14 * transitions is described in one of two ways, which are the two
15 * parameterizations of a semi-Markov kernel compared by Asanjarani, Liquet and
16 * Nazarathy (arXiv:2005.14462, 2020):
17 *
18 * COMPETING arcs (transition intensities, `probs` empty): every arc h is a
19 * latent clock with CDF F_h started on entry, the first to ring picks the
20 * destination, and
21 * CIF_h(t) = int_0^t prod_{k!=h} (1 - F_k(s)) dF_h(s),
22 * Fhold(t) = 1 - prod_k (1 - F_k(t)).
23 *
24 * KERNEL arcs (sojourn times, `probs` the jump probabilities): the
25 * destination is drawn on entry and the sojourn then has the law F_h, so
26 * CIF_h(t) = p_h F_h(t), Fhold(t) = sum_h p_h F_h(t).
27 *
28 * The competing integral is the Riemann-Stieltjes sum on the grid `t` with the
29 * other clocks' survival read at the interval midpoints, which needs no
30 * density and so admits a deterministic clock. When `t(1) > 0` the sum starts
31 * at zero on 200 uniform intervals below `t(1)`, whose values are dropped.
32 * Every CDF is clamped to [0, 1] before use.
33 */
34
35#include <algorithm>
36#include <cstddef>
37#include <vector>
38
41#include "line/util/error.h"
42#include "line/util/matrix.h"
43
44namespace line {
45namespace mc {
46
47/** What `smp_stage_cif` returns: `C(h, j)` = CIF_h(t_j) and `Fhold[j]`. */
50 std::vector<double> Fhold;
51};
52
53namespace smp_detail {
54
55/** `linspace(a, b, n)`, with the last point exactly `b`. */
56inline std::vector<double> linspace(double a, double b, std::size_t n) {
57 std::vector<double> y(n, a);
58 if (n == 0) return y;
59 if (n == 1) {
60 y[0] = b;
61 return y;
62 }
63 const double n1 = static_cast<double>(n - 1);
64 for (std::size_t k = 0; k < n; ++k) y[k] = a + static_cast<double>(k) * (b - a) / n1;
65 y[n - 1] = b;
66 return y;
67}
68
69/** The CDF of `d` at every instant of `t`, clamped to [0, 1] (`cdfRow`). */
70template <class T>
71std::vector<double> cdf_row(const lang::Distrib<T>& d, const std::vector<double>& t) {
72 std::vector<double> F(t.size(), 0.0);
73 for (std::size_t j = 0; j < t.size(); ++j) {
74 const double v =
76 F[j] = std::min(std::max(v, 0.0), 1.0);
77 }
78 return F;
79}
80
81} // namespace smp_detail
82
83/**
84 * CIF and holding-time CDF of a stage at the ascending instants `t`.
85 *
86 * `arcs[h]` is the law of the transition to stage h, DISABLED where there is
87 * none; `probs` is empty for competing arcs, or holds the E kernel
88 * probabilities.
89 */
90template <class T>
92 const std::vector<double>& probs, const std::vector<double>& t) {
93 const std::size_t E = arcs.size(), nt = t.size();
94 SmpStageCif out;
95 out.C = Matrix<double>(E, nt, 0.0);
96 out.Fhold.assign(nt, 0.0);
97 std::vector<std::size_t> live;
98 for (std::size_t h = 0; h < E; ++h)
99 if (!arcs[h].disabled) live.push_back(h);
100 if (nt == 0 || live.empty()) return out;
101 if (!probs.empty()) {
102 if (probs.size() != E)
103 throw InputError("smp_stage_cif: the kernel probabilities need one entry per arc");
104 for (std::size_t h : live) {
105 const std::vector<double> Fh = smp_detail::cdf_row(arcs[h], t);
106 for (std::size_t j = 0; j < nt; ++j) {
107 out.C(h, j) = probs[h] * Fh[j];
108 out.Fhold[j] += probs[h] * Fh[j];
109 }
110 }
111 return out;
112 }
113 std::size_t npre = 0;
114 std::vector<double> g;
115 if (t[0] > 0.0) {
116 const std::vector<double> pre = smp_detail::linspace(0.0, t[0], 201);
117 g.assign(pre.begin(), pre.end() - 1);
118 npre = 200;
119 }
120 g.insert(g.end(), t.begin(), t.end());
121 const std::size_t ng = g.size(), nl = live.size();
122 std::vector<double> gm(ng - 1);
123 for (std::size_t j = 0; j + 1 < ng; ++j) gm[j] = (g[j] + g[j + 1]) / 2.0;
124 std::vector<std::vector<double>> F(nl), Fm(nl);
125 for (std::size_t k = 0; k < nl; ++k) {
126 F[k] = smp_detail::cdf_row(arcs[live[k]], g);
127 Fm[k] = smp_detail::cdf_row(arcs[live[k]], gm);
128 }
129 for (std::size_t k = 0; k < nl; ++k) {
130 double acc = 0.0;
131 for (std::size_t j = 1; j < ng; ++j) {
132 double Sother = 1.0;
133 for (std::size_t q = 0; q < nl; ++q)
134 if (q != k) Sother *= 1.0 - Fm[q][j - 1];
135 acc += (F[k][j] - F[k][j - 1]) * Sother;
136 if (j >= npre) out.C(live[k], j - npre) = acc;
137 }
138 }
139 for (std::size_t j = 0; j < nt; ++j) {
140 double S = 1.0;
141 for (std::size_t k = 0; k < nl; ++k) S *= 1.0 - F[k][npre + j];
142 out.Fhold[j] = 1.0 - S;
143 }
144 return out;
145}
146
147} // namespace mc
148} // namespace line
149
150#endif // LINE_API_MC_SMP_STAGE_CIF_H
InputError(const std::string &what)
Definition error.h:105
What refreshProcessRepresentations and refreshLST compute FROM a distribution: the (D0,...
The exception types the port throws.
Enumerations and the minimal distribution descriptor shared by the model layer of the C++ port.
Dense matrix and non-owning view.
T dist_cdf(const Distrib< T > &d, const T &x)
F(x) = P{X <= x}, MATLAB's Distribution.evalCDF.
std::vector< double > linspace(double a, double b, std::size_t n)
linspace(a, b, n), with the last point exactly b.
std::vector< double > cdf_row(const lang::Distrib< T > &d, const std::vector< double > &t)
The CDF of d at every instant of t, clamped to [0, 1] (cdfRow).
SmpStageCif smp_stage_cif(const std::vector< lang::Distrib< T > > &arcs, const std::vector< double > &probs, const std::vector< double > &t)
CIF and holding-time CDF of a stage at the ascending instants t.
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
What smp_stage_cif returns: C(h, j) = CIF_h(t_j) and Fhold[j].
Matrix< double > C
std::vector< double > Fhold