LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
fj_xmax_hz_het.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_XMAX_HZ_HET_H
6#define LINE_API_FJ_XMAX_HZ_HET_H
7
8/**
9 * @file
10 * @ingroup api_fj
11 * Harrison-Zertal approximation of the maximum of general variables.
12 *
13 * Templated port of matlab/src/api/fj/fj_xmax_hz_het.m.
14 *
15 * I(S) = (1/|S|) sum_{i in S} [ I(S \\ i) + (m2_i/(2 m1_i)) L*_{S\\i}(alpha_i) ]
16 *
17 * anchored at I({i}) = m1_i, with alpha_i = 1/m1_i. The transform of the
18 * maximum over a sub-collection is recovered from the distribution functions,
19 *
20 * L*_T(s) = s integral_0^inf exp(-s t) prod_{j in T} F_j(t) dt,
21 *
22 * by composite Simpson quadrature on a horizon widened until the product of the
23 * distribution functions is within TOL of one. For identical branches the
24 * recurrence collapses onto fj_xmax_hz, and for identical exponential branches
25 * it is exact at H_K/lambda.
26 */
27
28#include <cstddef>
29#include <functional>
30#include <vector>
31
33#include "line/num/number.h"
34#include "line/util/error.h"
35
36namespace line {
37namespace fj {
38
39namespace detail {
40
41/** L*_T(s) for the sub-collection selected by mask, by composite Simpson. */
42template <class T>
43inline T hz_lst_max(const std::vector<std::function<T(const T&)> >& cdf, std::size_t mask,
44 std::size_t K, const T& s, const T& U, unsigned npanels) {
45 if (mask == 0) return num_traits<T>::from_int(1);
46 const T zero = num_traits<T>::from_int(0);
47 const T h = (U - zero) / num_traits<T>::from_int(static_cast<long>(npanels));
48 const T two = num_traits<T>::from_int(2), four = num_traits<T>::from_int(4);
49 T acc = zero;
50 for (unsigned i = 0; i <= npanels; ++i) {
51 const T t = h * num_traits<T>::from_int(static_cast<long>(i));
52 T g = num_exp<T>(-s * t);
53 for (std::size_t j = 0; j < K; ++j)
54 if (mask & (static_cast<std::size_t>(1) << j)) g *= cdf[j](t);
55 T w;
56 if (i == 0 || i == npanels) w = num_traits<T>::from_int(1);
57 else w = (i % 2 == 1) ? four : two;
58 acc += w * g;
59 }
60 return s * (h / num_traits<T>::from_int(3)) * acc;
61}
62
63} // namespace detail
64
65/**
66 * @brief Harrison-Zertal approximation of the maximum of general variables.
67 *
68 * @param m1 the K branch means, all positive
69 * @param m2 the K branch second moments, m2[i] >= m1[i]^2
70 * @param cdf the K distribution functions, cdf[i](t) = P(X_i <= t)
71 * @param tol completion tolerance used to pick the quadrature horizon
72 * @param npanels Simpson panel count, forced even
73 * @return the approximate expected maximum
74 */
75template <class T>
76T fj_xmax_hz_het(const std::vector<T>& m1, const std::vector<T>& m2,
77 const std::vector<std::function<T(const T&)> >& cdf,
78 const T& tol = num_traits<T>::from_double(1e-10), unsigned npanels = 2000) {
79 const std::size_t K = m1.size();
80 if (m2.size() != K || cdf.size() != K)
81 throw InputError("fj_xmax_hz_het: m1, m2 and cdf must have the same length");
82 if (K < 1) throw InputError("fj_xmax_hz_het: at least one branch is required");
83 if (K > 14)
84 throw InputError(
85 "fj_xmax_hz_het: the recurrence enumerates 2^K sub-collections with a quadrature each");
86 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1),
88 for (std::size_t i = 0; i < K; ++i) {
89 if (!(m1[i] > zero)) throw InputError("fj_xmax_hz_het: all branch means must be positive");
90 if (m2[i] < m1[i] * m1[i])
91 throw InputError("fj_xmax_hz_het: a second moment is below the square of its mean");
92 }
93 if (npanels % 2 != 0) ++npanels;
94
95 std::vector<T> alpha(K), resid(K);
96 T mmax = m1[0];
97 for (std::size_t i = 0; i < K; ++i) {
98 alpha[i] = one / m1[i];
99 resid[i] = m2[i] / (two * m1[i]);
100 if (m1[i] > mmax) mmax = m1[i];
101 }
102
103 // Horizon: widen until every branch is essentially complete
104 T U = num_traits<T>::from_int(8) * mmax;
105 for (unsigned it = 0; it < 60; ++it) {
106 T prodF = one;
107 for (std::size_t j = 0; j < K; ++j) prodF *= cdf[j](U);
108 if (one - prodF < tol) break;
109 U = two * U;
110 }
111
112 const std::size_t nmask = static_cast<std::size_t>(1) << K;
113 std::vector<T> Ival(nmask, zero);
114 for (std::size_t mask = 1; mask < nmask; ++mask) {
115 std::vector<std::size_t> members;
116 for (std::size_t i = 0; i < K; ++i)
117 if (mask & (static_cast<std::size_t>(1) << i)) members.push_back(i);
118 if (members.size() == 1) {
119 Ival[mask] = m1[members[0]];
120 continue;
121 }
122 T acc = zero;
123 for (std::size_t idx = 0; idx < members.size(); ++idx) {
124 const std::size_t i = members[idx];
125 const std::size_t rest = mask ^ (static_cast<std::size_t>(1) << i);
126 acc += Ival[rest] +
127 resid[i] * detail::hz_lst_max<T>(cdf, rest, K, alpha[i], U, npanels);
128 }
129 Ival[mask] = acc / num_traits<T>::from_int(static_cast<long>(members.size()));
130 }
131 return Ival[nmask - 1];
132}
133
134} // namespace fj
135} // namespace line
136
137#endif // LINE_API_FJ_XMAX_HZ_HET_H
InputError(const std::string &what)
Definition error.h:39
The exception types the port throws.
Shared return types and arithmetic helpers for the templated fork-join port.
T fj_xmax_hz_het(const std::vector< T > &m1, const std::vector< T > &m2, const std::vector< std::function< T(const T &)> > &cdf, const T &tol=num_traits< T >::from_double(1e-10), unsigned npanels=2000)
Harrison-Zertal approximation of the maximum of general variables.
Number-type abstraction for the templated API port.