LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
dmap_optim_dist.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_MAM_DMAP_OPTIM_DIST_H
6#define LINE_API_MAM_DMAP_OPTIM_DIST_H
7
8/**
9 * @file
10 * @ingroup api_mam
11 * The DISCRETE twins of `map_optim_dist` / `map_optim_dist_acf`: fit a D-MAP's
12 * D1 by minimizing a distance to a reference, with D0 held fixed.
13 *
14 * Port of matlab/lib/kpctoolbox/dmap/dmap_optim_dist.m and
15 * dmap_optim_dist_acf.m (twins in
16 * python/line_solver/api/mapdist/discrete.py). The distances themselves --
17 * `dmap_dist`, `dmap_dist_acf` -- are already in `dmap.h`.
18 *
19 * EVERYTHING IS THE CONTINUOUS CASE WITH `(-D0)` REPLACED BY `(I - D0)`, and
20 * that substitution is the whole of the discrete/continuous difference here. In
21 * continuous time the embedded kernel is `(-D0)^-1 D1` and the row sums of D0
22 * and D1 cancel; in discrete time the phase either moves without an arrival
23 * (D0) or with one (D1) at every SLOT, so the two together form a stochastic
24 * matrix, the kernel is `(I - D0)^-1 D1`, and the row sums are ONE rather than
25 * zero. Both constraint blocks change accordingly:
26 *
27 * - `alB (I - B0)^-1 B1 = alB`, alB stationary at arrivals;
28 * - the row sums of B1 are those of `(I - B0)`.
29 *
30 * There is no convex quadratic branch here. The continuous `map_optim_dist`
31 * takes one at L = 1 because `quadprog` applies; the discrete reference calls
32 * `fmincon` at every lag, so every result is a LOCAL optimum and none claims
33 * otherwise.
34 *
35 * ARITHMETIC: transcendental, inherited from the distances.
36 */
37
38#include <cstddef>
39#include <string>
40#include <vector>
41
42#include "line/api/mam/dmap.h"
44#include "line/num/number.h"
45#include "line/util/error.h"
46#include "line/util/lu.h"
47#include "line/util/matrix.h"
48
49namespace line {
50namespace mam {
51
52namespace dmapoptdetail {
53
54/**
55 * The discrete constraint block.
56 *
57 * `(I - B0)` where the continuous form has `(-B0)`, and the row-sum target is
58 * the row sums of `(I - B0)` rather than of `-B0`.
59 */
60template <class T>
61void build_constraints_d(const Matrix<T>& B0, const std::vector<T>& alB, Matrix<T>* Aeq,
62 std::vector<T>* beq) {
63 const std::size_t n = B0.rows();
64 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
65
66 Matrix<T> ImB0(n, n, zero);
67 for (std::size_t i = 0; i < n; ++i)
68 for (std::size_t j = 0; j < n; ++j) ImB0(i, j) = (i == j ? one : zero) - B0(i, j);
69
70 // (alB (I-B0)^-1)' solves (I-B0)' x = alB'.
71 Matrix<T> tr(n, n, zero);
72 for (std::size_t i = 0; i < n; ++i)
73 for (std::size_t j = 0; j < n; ++j) tr(i, j) = ImB0(j, i);
74 const std::vector<T> row = solve(tr, alB);
75
76 Matrix<T> rowM(1, n, zero);
77 for (std::size_t j = 0; j < n; ++j) rowM(0, j) = row[j];
78 Matrix<T> I(n, n, zero);
79 for (std::size_t i = 0; i < n; ++i) I(i, i) = one;
80 Matrix<T> ones1(1, n, one);
81
82 const Matrix<T> top = optdistdetail::mkron(I, rowM);
83 const Matrix<T> bot = optdistdetail::mkron(ones1, I);
84 *Aeq = Matrix<T>(2 * n, n * n, zero);
85 for (std::size_t i = 0; i < n; ++i)
86 for (std::size_t j = 0; j < n * n; ++j) {
87 (*Aeq)(i, j) = top(i, j);
88 (*Aeq)(n + i, j) = bot(i, j);
89 }
90 beq->assign(2 * n, zero);
91 for (std::size_t i = 0; i < n; ++i) {
92 (*beq)[i] = alB[i];
93 T s = zero;
94 for (std::size_t j = 0; j < n; ++j) s += ImB0(i, j);
95 (*beq)[n + i] = s;
96 }
97}
98
99} // namespace dmapoptdetail
100
101/**
102 * Fit B1 minimizing the lag-L joint-PMF distance to `a`, with B0 fixed.
103 *
104 * Always a LOCAL optimum: the discrete reference has no convex branch.
105 */
106template <class T>
107MapOptimDist<T> dmap_optim_dist(const Dmap<T>& a, const std::vector<T>& alA, const Matrix<T>& B0,
108 const std::vector<T>& alB, unsigned L) {
109 static_assert(num_traits<T>::has_transcendental, "dmap_optim_dist needs the D-MAP distances");
110 const std::size_t n = B0.rows();
111 if (n == 0 || B0.cols() != n) throw InputError("dmap_optim_dist: B0 must be square");
112 if (alB.size() != n) throw InputError("dmap_optim_dist: alB has the wrong length");
113 if (alA.size() != a.D0.rows()) throw InputError("dmap_optim_dist: alA has the wrong length");
114 if (L == 0) throw InputError("dmap_optim_dist: at least one lag is required");
115
116 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
117 const T lo = num_traits<T>::from_double(1e-6);
118 Matrix<T> Aeq;
119 std::vector<T> beq;
120 dmapoptdetail::build_constraints_d(B0, alB, &Aeq, &beq);
121
122 auto obj = [&a, &B0, &alA, &alB, L, n](const std::vector<T>& x) {
123 Dmap<T> b;
124 b.D0 = B0;
125 b.D1 = optdistdetail::unvec(x, n);
126 return dmap_dist(a, b, L, alA, alB);
127 };
128 // Start on the row-sum constraint: (I - B0)'s row sums, spread evenly.
129 Matrix<T> seed(n, n, zero);
130 for (std::size_t i = 0; i < n; ++i) {
131 T s = zero;
132 for (std::size_t j = 0; j < n; ++j) s += (i == j ? one : zero) - B0(i, j);
133 for (std::size_t j = 0; j < n; ++j)
134 seed(i, j) = s / num_traits<T>::from_int(static_cast<long>(n));
135 }
136
137 MapOptimDist<T> out;
138 const std::vector<T> sol =
139 optdistdetail::constrained_min(obj, Aeq, beq, optdistdetail::vec(seed), lo);
140 out.B1 = optdistdetail::unvec(sol, n);
141 out.d = obj(sol);
142 out.global = false;
143 return out;
144}
145
146/**
147 * Fit B1 minimizing the AUTOCORRELATION distance, with B0 fixed.
148 *
149 * As in the continuous twin, the distance is RE-EVALUATED at the returned B1
150 * rather than taken from the optimizer, so the number reported is the distance
151 * of the D-MAP the caller was handed.
152 */
153template <class T>
154MapOptimDist<T> dmap_optim_dist_acf(const Dmap<T>& a, const std::vector<T>& alA,
155 const Matrix<T>& B0, const std::vector<T>& alB) {
157 "dmap_optim_dist_acf needs the D-MAP distances");
158 const std::size_t n = B0.rows();
159 if (n == 0 || B0.cols() != n) throw InputError("dmap_optim_dist_acf: B0 must be square");
160 if (alB.size() != n) throw InputError("dmap_optim_dist_acf: alB has the wrong length");
161 if (alA.size() != a.D0.rows())
162 throw InputError("dmap_optim_dist_acf: alA has the wrong length");
163
164 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
165 const T lo = num_traits<T>::from_double(1e-6);
166 Matrix<T> Aeq;
167 std::vector<T> beq;
168 dmapoptdetail::build_constraints_d(B0, alB, &Aeq, &beq);
169
170 auto obj = [&a, &B0, &alA, &alB, n](const std::vector<T>& x) {
171 Dmap<T> b;
172 b.D0 = B0;
173 b.D1 = optdistdetail::unvec(x, n);
174 return dmap_dist_acf(a, b, alA, alB);
175 };
176 Matrix<T> seed(n, n, zero);
177 for (std::size_t i = 0; i < n; ++i) {
178 T s = zero;
179 for (std::size_t j = 0; j < n; ++j) s += (i == j ? one : zero) - B0(i, j);
180 for (std::size_t j = 0; j < n; ++j)
181 seed(i, j) = s / num_traits<T>::from_int(static_cast<long>(n));
182 }
183
184 MapOptimDist<T> out;
185 const std::vector<T> sol =
186 optdistdetail::constrained_min(obj, Aeq, beq, optdistdetail::vec(seed), lo);
187 out.B1 = optdistdetail::unvec(sol, n);
188 Dmap<T> b;
189 b.D0 = B0;
190 b.D1 = out.B1;
191 out.d = dmap_dist_acf(a, b, alA, alB);
192 out.global = false;
193 return out;
194}
195
196} // namespace mam
197} // namespace line
198
199#endif // LINE_API_MAM_DMAP_OPTIM_DIST_H
InputError(const std::string &what)
Definition error.h:39
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
Discrete-time Markovian arrival processes (D-MAPs).
The exception types the port throws.
LU factorization with partial pivoting, templated on the number type.
Fit the D1 of a MAP by MINIMIZING a distance to a reference MAP.
Dense matrix and non-owning view.
T dmap_dist_acf(const Dmap< T > &a, const Dmap< T > &b, const std::vector< T > &alA, const std::vector< T > &alB)
Squared distance between the autocorrelation structures of two D-MAPs.
Definition dmap.h:261
T dmap_dist(const Dmap< T > &a, const Dmap< T > &b, unsigned L, const std::vector< T > &alA, const std::vector< T > &alB)
Squared L2 distance between the interarrival densities truncated at lag L.
Definition dmap.h:205
MapOptimDist< T > dmap_optim_dist_acf(const Dmap< T > &a, const std::vector< T > &alA, const Matrix< T > &B0, const std::vector< T > &alB)
Fit B1 minimizing the AUTOCORRELATION distance, with B0 fixed.
MapOptimDist< T > dmap_optim_dist(const Dmap< T > &a, const std::vector< T > &alA, const Matrix< T > &B0, const std::vector< T > &alB, unsigned L)
Fit B1 minimizing the lag-L joint-PMF distance to a, with B0 fixed.
std::vector< T > solve(const Matrix< T > &A, const std::vector< T > &b)
Convenience: solve Ax = b, leaving A and b untouched.
Definition lu.h:158
Number-type abstraction for the templated API port.
A discrete-time MAP: substochastic D0 (no arrival) and D1 (one arrival).
Definition dmap.h:48
Matrix< T > D0
Definition dmap.h:49
Matrix< T > D1
Definition dmap.h:50
What an optimizing distance fit returns.
Matrix< T > B1
the fitted D1
bool global
true only on the convex lag-1 path
T d
the distance achieved