LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
fj_delay_opt.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_FJ_DELAY_OPT_H
6#define LINE_API_FJ_DELAY_OPT_H
7
8/**
9 * @file
10 * @ingroup api_fj
11 * Deterministic subtask delays that minimise mean dispersion.
12 *
13 * Templated port of matlab/src/api/fj/fj_delay_opt.m.
14 *
15 * Holding back a fast branch costs little at the last completion and buys a
16 * great deal at the first, so the minimiser of the dispersion of fj_dispersion
17 * is generally interior and strictly positive on every branch but the slowest.
18 *
19 * The objective is minimised by cyclic coordinate descent with a golden section
20 * line search on each coordinate: deterministic, derivative-free, and the same
21 * sequence of evaluations in all four codebases. Adding a constant to every
22 * delay shifts both order statistics equally, so the search returns the
23 * representative with min(d) = 0.
24 */
25
26#include <cstddef>
27#include <vector>
28
31#include "line/num/number.h"
32#include "line/util/error.h"
33
34namespace line {
35namespace fj {
36
37/** [d, Edisp, Emax] of fj_delay_opt. */
38template <class T>
40 std::vector<T> d;
43};
44
45/**
46 * @brief Deterministic subtask delays that minimise mean dispersion.
47 *
48 * @param shape Erlang stage counts, one per branch
49 * @param rate Erlang stage rates, one per branch
50 * @param maxsweeps coordinate-descent sweep cap
51 * @param dtol relative convergence tolerance
52 * @param npanels Simpson panel count passed to fj_dispersion
53 * @return the optimal delays and the dispersion and last-completion mean there
54 */
55template <class T>
56FJDelayOptResult<T> fj_delay_opt(const std::vector<unsigned>& shape, const std::vector<T>& rate,
57 unsigned maxsweeps = 40,
58 const T& dtol = num_traits<T>::from_double(1e-8),
59 unsigned npanels = 2000) {
60 const std::size_t N = shape.size();
61 if (rate.size() != N)
62 throw InputError("fj_delay_opt: shape and rate must have the same length");
63 if (N < 1) throw InputError("fj_delay_opt: at least one branch is required");
64 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
65 const T tol = num_traits<T>::from_double(1e-10);
66
68 out.d.assign(N, zero);
69 if (N < 2) {
70 const FJDispersionResult<T> r = fj_dispersion<T>(shape, rate, out.d, tol, npanels);
71 out.Edisp = r.Edisp;
72 out.Emax = r.Emax;
73 return out;
74 }
75
76 // Delaying past the slowest branch never helps, so that is the search ceiling
77 T mmax = zero, smax = zero;
78 for (std::size_t i = 0; i < N; ++i) {
79 const T mi = num_traits<T>::from_int(static_cast<long>(shape[i])) / rate[i];
80 const T si = detail::num_sqrt<T>(num_traits<T>::from_int(static_cast<long>(shape[i]))) / rate[i];
81 if (mi > mmax) mmax = mi;
82 if (si > smax) smax = si;
83 }
84 const T ub = mmax + num_traits<T>::from_int(8) * smax;
85
86 const T invphi = (detail::num_sqrt<T>(num_traits<T>::from_int(5)) - one) /
88 T fcur = fj_dispersion<T>(shape, rate, out.d, tol, npanels).Edisp;
89 for (unsigned sweep = 0; sweep < maxsweeps; ++sweep) {
90 const T fprev = fcur;
91 for (std::size_t i = 0; i < N; ++i) {
92 // Golden section on coordinate i, the other delays held fixed
93 T a = zero, b = ub;
94 T c = b - invphi * (b - a), dd = a + invphi * (b - a);
95 std::vector<T> probe = out.d;
96 probe[i] = c;
97 T fc = fj_dispersion<T>(shape, rate, probe, tol, npanels).Edisp;
98 probe[i] = dd;
99 T fd = fj_dispersion<T>(shape, rate, probe, tol, npanels).Edisp;
100 for (unsigned it = 0; it < 60; ++it) {
101 if (fc < fd) {
102 b = dd; dd = c; fd = fc;
103 c = b - invphi * (b - a);
104 probe[i] = c;
105 fc = fj_dispersion<T>(shape, rate, probe, tol, npanels).Edisp;
106 } else {
107 a = c; c = dd; fc = fd;
108 dd = a + invphi * (b - a);
109 probe[i] = dd;
110 fd = fj_dispersion<T>(shape, rate, probe, tol, npanels).Edisp;
111 }
112 if ((b - a) <= dtol * (ub > one ? ub : one)) break;
113 }
114 out.d[i] = (fc < fd) ? c : dd;
115 }
116 // Normalise so that the smallest delay is zero
117 T dmin = out.d[0];
118 for (std::size_t i = 1; i < N; ++i)
119 if (out.d[i] < dmin) dmin = out.d[i];
120 for (std::size_t i = 0; i < N; ++i) out.d[i] -= dmin;
121 fcur = fj_dispersion<T>(shape, rate, out.d, tol, npanels).Edisp;
122 const T gap = (fprev > fcur) ? (fprev - fcur) : (fcur - fprev);
123 const T mag = (fprev > one || fprev < -one) ? (fprev > zero ? fprev : -fprev) : one;
124 if (gap <= dtol * mag) break;
125 }
126
127 const FJDispersionResult<T> r = fj_dispersion<T>(shape, rate, out.d, tol, npanels);
128 out.Edisp = r.Edisp;
129 out.Emax = r.Emax;
130 return out;
131}
132
133} // namespace fj
134} // namespace line
135
136#endif // LINE_API_FJ_DELAY_OPT_H
InputError(const std::string &what)
Definition error.h:39
The exception types the port throws.
Mean subtask dispersion of a split-merge system with Erlang branches.
Shared return types and arithmetic helpers for the templated fork-join port.
FJDelayOptResult< T > fj_delay_opt(const std::vector< unsigned > &shape, const std::vector< T > &rate, unsigned maxsweeps=40, const T &dtol=num_traits< T >::from_double(1e-8), unsigned npanels=2000)
Deterministic subtask delays that minimise mean dispersion.
FJDispersionResult< T > fj_dispersion(const std::vector< unsigned > &shape, const std::vector< T > &rate, const std::vector< T > &d, const T &tol=num_traits< T >::from_double(1e-10), unsigned npanels=4000)
Mean subtask dispersion of a split-merge system with Erlang branches.
Number-type abstraction for the templated API port.
[d, Edisp, Emax] of fj_delay_opt.
[Edisp, Emax, Emin] of fj_dispersion.