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 not finite
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
75template <class T>
76struct FpiResult {
77 std::vector<T> x;
78 std::size_t iterations = 0;
79 bool converged = false;
80};
81
82/**
83 * @brief Damped fixed-point iteration, the shared driver of the decomposition
84 * algorithms.
85 *
86 * @param iterfun (x, iteration) -> (xnew, xref); xref is the point the damping
87 * and the increment norm are taken against
88 * @param x0 initial iterate
89 * @param options fixed-point options (tolerance, iteration cap, damping)
90 */
91template <class T>
93 const std::function<std::pair<std::vector<T>, std::vector<T>>(const std::vector<T>&, std::size_t)>&
94 iterfun,
95 const std::vector<T>& x0, const FpiOptions& options = FpiOptions()) {
97 "da_fpi requires transcendental arithmetic: it stops on a tolerance, so its "
98 "result is the fixed point only to within iter_tol whatever the arithmetic");
99 if (x0.empty()) throw InputError("da_fpi: empty initial iterate");
100 const T omega = num_traits<T>::from_double(options.damping);
101 const T one = num_traits<T>::from_int(1);
102
103 FpiResult<T> r;
104 r.x = x0;
105 for (std::size_t it = 1; it <= options.iter_max; ++it) {
106 r.iterations = it;
107 std::pair<std::vector<T>, std::vector<T>> step = iterfun(r.x, it);
108 std::vector<T>& xnew = step.first;
109 const std::vector<T>& xref = step.second;
110 if (xnew.size() != r.x.size() || xref.size() != r.x.size())
111 throw InputError("da_fpi: the iteration function changed the vector length");
112
113 if (options.damping != 1.0)
114 for (std::size_t i = 0; i < xnew.size(); ++i)
115 xnew[i] = (one - omega) * xref[i] + omega * xnew[i];
116
117 // MATLAB's max() OMITS NaN and answers NaN only when every entry is one,
118 // which is what lets the relative norm survive a zero reference entry on
119 // the first sweep instead of stopping the loop there.
120 double delta = 0.0;
121 bool anynum = false;
122 for (std::size_t i = 0; i < xnew.size(); ++i) {
123 double d = std::fabs(num_traits<T>::to_double(T(xnew[i] - xref[i])));
124 if (options.relative_norm)
125 d /= num_traits<T>::to_double(xref[i]) + options.relative_eps;
126 if (std::isnan(d)) continue;
127 if (!anynum || d > delta) delta = d;
128 anynum = true;
129 }
130 if (!anynum && !xnew.empty()) delta = std::numeric_limits<double>::quiet_NaN();
131 r.x = xnew;
132
133 if (it >= options.miniter) {
134 if (delta < options.iter_tol) {
135 r.converged = true;
136 break;
137 }
138 if (options.nanstop && !std::isfinite(delta)) break;
139 }
140 }
141 return r;
142}
143
144} // namespace da
145} // namespace line
146
147#endif // LINE_API_DA_DA_FPI_H
InputError(const std::string &what)
Definition error.h:39
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:92
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::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
bool nanstop
stop when the increment norm is not finite
Definition da_fpi.h:55
std::size_t iterations
Definition da_fpi.h:78
std::vector< T > x
Definition da_fpi.h:77