LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
da_fpi.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_DA_DA_FPI_H
6#define LINE_API_DA_DA_FPI_H
7
8/**
9 * @file
10 * @ingroup api_da
11 * Damped fixed-point iteration, the shared driver of the decomposition
12 * algorithms.
13 *
14 * Templated port of matlab/src/api/da/da_fpi.m. The iteration is
15 * x_{k+1} = (1 - omega) x_ref + omega f(x_k, k)
16 * stopping when the configured norm of the increment falls below iter_tol, or
17 * after iter_max steps. MATLAB passes the iteration function as a handle
18 * returning both the new iterate and the reference point it should be damped
19 * against; the port takes a std::function with the same contract, so a caller
20 * whose reference differs from its input (as in the Erlang fixed point) is
21 * expressible without special-casing.
22 *
23 * THE INCREMENT NORM FOLLOWS MATLAB'S max(), which OMITS NaN and answers NaN
24 * only when every entry is one. The first form of this port skipped NaN entries
25 * but left `delta` at its initial 0, so an all-NaN increment -- a diverged
26 * iterate -- reported CONVERGENCE, where MATLAB's `NaN < iter_tol` is false and
27 * the loop continues to iter_max. Corrected here; a caller that wants to stop on
28 * a divergence asks for it with `nanstop`.
29 *
30 * A tolerance-driven loop is inexact by construction, whatever the arithmetic:
31 * the answer is the fixed point only to within iter_tol. The static_assert
32 * records that, so nobody instantiates it at exact arithmetic expecting an
33 * exact fixed point.
34 */
35
36#include <cmath>
37#include <cstddef>
38#include <functional>
39#include <limits>
40#include <utility>
41#include <vector>
42
43#include "line/num/number.h"
44#include "line/util/error.h"
45
46namespace line {
47namespace da {
48
49/** Options mirroring the fields MATLAB reads off the options struct. */
50struct FpiOptions {
51 std::size_t iter_max = 10000;
52 double iter_tol = 1e-8;
53 double damping = 1.0; ///< omega; 1 means no damping
54 std::size_t miniter = 1; ///< iterations before the stopping test applies
55 bool nanstop = false; ///< stop when the increment norm is NaN (the reference's isnan test)
56 /**
57 * `config.da_norm`, the increment norm. MATLAB passes a function handle;
58 * the two handles the reference actually installs are both RELATIVE --
59 * `max(|xn-xr|./xr)` in solver_mam.m and `max(|xn-xr|./(xr+FineTol))` in
60 * solver_mam_basic_mmap_inner.m -- so they are expressed here as a flag and
61 * the denominator's offset rather than as a std::function, which would have
62 * to be templated on T and would change the type of every existing caller's
63 * options object.
64 *
65 * FALSE keeps the default absolute max-norm. TRUE with `relative_eps = 0`
66 * reproduces the first handle, including its division by a zero reference
67 * (the reference divides by `xr` unguarded, and an all-zero start therefore
68 * yields NaN on the first sweep; `miniter` is what keeps that from stopping
69 * the loop, exactly as in MATLAB).
70 */
71 bool relative_norm = false;
72 double relative_eps = 0.0;
73 /**
74 * `config.da_norm` as a CALLABLE, the reference's function handle: `norm`
75 * receives the increment xnew - xref (in double) and `norm2` the pair
76 * (xnew, xref), MATLAB's one- and two-argument handles. Either takes
77 * precedence over the flags above when set; `norm2` over `norm`. With this
78 * a caller can install `@(d) norm(d,1)` (da_cacheqn, lossn_erlangfp) as
79 * `[](const std::vector<double>& d) { double s = 0; for (double v : d) s += std::fabs(v); return s; }`.
80 */
81 std::function<double(const std::vector<double>&)> norm;
82 std::function<double(const std::vector<double>&, const std::vector<double>&)> norm2;
83};
84
85template <class T>
86struct FpiResult {
87 std::vector<T> x;
88 std::size_t iterations = 0;
89 bool converged = false;
90};
91
92/**
93 * @brief Damped fixed-point iteration, the shared driver of the decomposition
94 * algorithms.
95 *
96 * @param iterfun (x, iteration) -> (xnew, xref); xref is the point the damping
97 * and the increment norm are taken against
98 * @param x0 initial iterate
99 * @param options fixed-point options (tolerance, iteration cap, damping)
100 */
101template <class T>
103 const std::function<std::pair<std::vector<T>, std::vector<T>>(const std::vector<T>&, std::size_t)>&
104 iterfun,
105 const std::vector<T>& x0, const FpiOptions& options = FpiOptions()) {
107 "da_fpi requires transcendental arithmetic: it stops on a tolerance, so its "
108 "result is the fixed point only to within iter_tol whatever the arithmetic");
109 if (x0.empty()) throw InputError("da_fpi: empty initial iterate");
110 const T omega = num_traits<T>::from_double(options.damping);
111 const T one = num_traits<T>::from_int(1);
112
113 FpiResult<T> r;
114 r.x = x0;
115 for (std::size_t it = 1; it <= options.iter_max; ++it) {
116 r.iterations = it;
117 std::pair<std::vector<T>, std::vector<T>> step = iterfun(r.x, it);
118 std::vector<T>& xnew = step.first;
119 const std::vector<T>& xref = step.second;
120 if (xnew.size() != r.x.size() || xref.size() != r.x.size())
121 throw InputError("da_fpi: the iteration function changed the vector length");
122
123 if (options.damping != 1.0)
124 for (std::size_t i = 0; i < xnew.size(); ++i)
125 xnew[i] = (one - omega) * xref[i] + omega * xnew[i];
126
127 // MATLAB's max() OMITS NaN and answers NaN only when every entry is one,
128 // which is what lets the relative norm survive a zero reference entry on
129 // the first sweep instead of stopping the loop there.
130 double delta = 0.0;
131 if (options.norm2 || options.norm) {
132 // the reference's normfun handle: delta = normfun(xnew, xref) or normfun(xnew - xref)
133 std::vector<double> xn(xnew.size()), xr(xnew.size());
134 for (std::size_t i = 0; i < xnew.size(); ++i) {
135 xn[i] = num_traits<T>::to_double(xnew[i]);
136 xr[i] = num_traits<T>::to_double(xref[i]);
137 }
138 if (options.norm2) {
139 delta = options.norm2(xn, xr);
140 } else {
141 std::vector<double> d(xnew.size());
142 for (std::size_t i = 0; i < xnew.size(); ++i) d[i] = xn[i] - xr[i];
143 delta = options.norm(d);
144 }
145 } else {
146 bool anynum = false;
147 for (std::size_t i = 0; i < xnew.size(); ++i) {
148 double d = std::fabs(num_traits<T>::to_double(T(xnew[i] - xref[i])));
149 if (options.relative_norm)
150 d /= num_traits<T>::to_double(xref[i]) + options.relative_eps;
151 if (std::isnan(d)) continue;
152 if (!anynum || d > delta) delta = d;
153 anynum = true;
154 }
155 if (!anynum && !xnew.empty()) delta = std::numeric_limits<double>::quiet_NaN();
156 }
157 r.x = xnew;
158
159 if (it >= options.miniter) {
160 if (delta < options.iter_tol) {
161 r.converged = true;
162 break;
163 }
164 // the reference tests isnan(delta): an infinite increment keeps iterating
165 if (options.nanstop && std::isnan(delta)) break;
166 }
167 }
168 return r;
169}
170
171} // namespace da
172} // namespace line
173
174#endif // LINE_API_DA_DA_FPI_H
InputError(const std::string &what)
Definition error.h:105
The exception types the port throws.
FpiResult< T > da_fpi(const std::function< std::pair< std::vector< T >, std::vector< T > >(const std::vector< T > &, std::size_t)> &iterfun, const std::vector< T > &x0, const FpiOptions &options=FpiOptions())
Damped fixed-point iteration, the shared driver of the decomposition algorithms.
Definition da_fpi.h:102
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
Number-type abstraction for the templated API port.
Options mirroring the fields MATLAB reads off the options struct.
Definition da_fpi.h:50
std::size_t iter_max
Definition da_fpi.h:51
std::function< double(const std::vector< double > &)> norm
config.da_norm as a CALLABLE, the reference's function handle: norm receives the increment xnew - xre...
Definition da_fpi.h:81
std::size_t miniter
iterations before the stopping test applies
Definition da_fpi.h:54
double damping
omega; 1 means no damping
Definition da_fpi.h:53
double relative_eps
Definition da_fpi.h:72
bool relative_norm
config.da_norm, the increment norm.
Definition da_fpi.h:71
std::function< double(const std::vector< double > &, const std::vector< double > &)> norm2
Definition da_fpi.h:82
bool nanstop
stop when the increment norm is NaN (the reference's isnan test)
Definition da_fpi.h:55
std::size_t iterations
Definition da_fpi.h:88
std::vector< T > x
Definition da_fpi.h:87