LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Toggle main menu visibility
Loading...
Searching...
No Matches
lu.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_UTIL_LU_H
6
#define LINE_UTIL_LU_H
7
8
/**
9
* @file
10
* @ingroup line_util
11
* LU factorization with partial pivoting, templated on the number type.
12
*
13
* Port of mp_pfqn's gmpla/mpq_ludcmp.c + mpq_lubksb.c, generalized from mpq_t
14
* to any field type and with the 1-based Numerical-Recipes indexing removed.
15
* In exact arithmetic pivoting is not needed for stability, only to avoid a
16
* zero pivot, but the same largest-magnitude rule is kept so that the exact
17
* and double paths take identical elimination orders and can be diffed.
18
*/
19
20
#include <cstddef>
21
#include <vector>
22
23
#include "
line/num/number.h
"
24
#include "
line/util/error.h
"
25
#include "
line/util/matrix.h
"
26
27
namespace
line
{
28
29
/**
30
* The ordered type the pivot search compares in.
31
*
32
* It is T itself for every ordered field, so the real instantiations compile to
33
* the same code as before. A complex element type has no order, and its
34
* specialization (in line/num/complex_number.h) reduces to the real modulus --
35
* which is what MATLAB's own pivot rule uses on a complex matrix.
36
*/
37
template
<
class
T>
38
struct
pivot_mag
{
39
using
type
= T;
40
static
type
of
(
const
T& x) {
return
num_abs
(x); }
41
};
42
43
/**
44
* In-place LU of A (n x n). Returns the row permutation; A holds L (unit
45
* diagonal, implicit) below and U on and above the diagonal.
46
*/
47
template
<
class
T>
48
std::vector<std::size_t>
lu_factor
(
Matrix<T>
& A) {
49
using
Mag =
typename
pivot_mag<T>::type
;
50
const
std::size_t n = A.
rows
();
51
if
(A.
cols
() != n)
throw
InputError
(
"lu_factor: matrix is not square"
);
52
std::vector<std::size_t> piv(n);
53
for
(std::size_t k = 0; k < n; ++k) {
54
std::size_t p = k;
55
Mag amax =
pivot_mag<T>::of
(A(k, k));
56
for
(std::size_t i = k + 1; i < n; ++i) {
57
Mag a =
pivot_mag<T>::of
(A(i, k));
58
if
(a > amax) {
59
amax = a;
60
p = i;
61
}
62
}
63
if
(amax ==
num_traits<Mag>::from_int
(0))
throw
NumericError
(
"lu_factor: singular matrix"
);
64
piv[k] = p;
65
if
(p != k) {
66
for
(std::size_t j = 0; j < n; ++j) std::swap(A(k, j), A(p, j));
67
}
68
const
T d = A(k, k);
69
for
(std::size_t i = k + 1; i < n; ++i) {
70
if
(A(i, k) ==
num_traits<T>::from_int
(0))
continue
;
71
T f = A(i, k) / d;
72
A(i, k) = f;
// store the multiplier in place, as in Crout
73
for
(std::size_t j = k + 1; j < n; ++j) A(i, j) -= f * A(k, j);
74
}
75
}
76
return
piv;
77
}
78
79
/**
80
* Solve LUx = Pb in place on b, using the factors from lu_factor.
81
*
82
* The whole permutation must be applied to b BEFORE any elimination.
83
* lu_factor swaps entire rows, multiplier columns included, so LU(i,k) is the
84
* multiplier of the row that ends up at position i. Interleaving the swaps
85
* with the forward updates -- swap b[k], then update b[i] with LU(i,k) --
86
* pairs the row identity at step k with a final-order multiplier whenever a
87
* later step moves that row, and silently returns a wrong solution. It does
88
* so without any warning sign: the factorization still satisfies LU = PA and
89
* the pivots are all healthy. The failure needs a pivot sequence that moves an
90
* already-eliminated row, which diagonally dominant matrices rarely produce,
91
* so it hides until a generator with a small leading diagonal is solved.
92
*/
93
template
<
class
T>
94
void
lu_solve
(
const
Matrix<T>
& LU,
const
std::vector<std::size_t>& piv, std::vector<T>& b) {
95
const
std::size_t n = LU.
rows
();
96
if
(b.size() != n)
throw
InputError
(
"lu_solve: rhs length mismatch"
);
97
for
(std::size_t k = 0; k < n; ++k)
98
if
(piv[k] != k) std::swap(b[k], b[piv[k]]);
99
for
(std::size_t i = 0; i < n; ++i)
100
for
(std::size_t k = 0; k < i; ++k) b[i] -= LU(i, k) * b[k];
101
for
(std::size_t i = n; i-- > 0;) {
102
T s = b[i];
103
for
(std::size_t j = i + 1; j < n; ++j) s -= LU(i, j) * b[j];
104
b[i] = s / LU(i, i);
105
}
106
}
107
108
/**
109
* Determinant of a square matrix, by the same partial-pivoting elimination.
110
*
111
* SINGULAR IS A VALUE HERE, NOT AN ERROR, which is why this does not go through
112
* `lu_factor`: that one throws on a zero pivot because every caller of it is
113
* solving a system, where a zero pivot means the question has no answer. A
114
* determinant of a singular matrix is zero, a perfectly good answer, and
115
* Cramer's rule needs it -- `MarkovProcess.getProbState` forms a numerator
116
* matrix that IS singular whenever the state has probability zero.
117
*
118
* The sign comes from the parity of the row swaps, so it is exact even where
119
* the product of the pivots is not.
120
*/
121
template
<
class
T>
122
T
lu_det
(
const
Matrix<T>
& A) {
123
using
Mag =
typename
pivot_mag<T>::type
;
124
const
std::size_t n = A.
rows
();
125
if
(A.
cols
() != n)
throw
InputError
(
"lu_det: matrix is not square"
);
126
if
(n == 0)
return
num_traits<T>::from_int
(1);
127
Matrix<T>
U = A;
128
const
T zero =
num_traits<T>::from_int
(0);
129
T det =
num_traits<T>::from_int
(1);
130
for
(std::size_t k = 0; k < n; ++k) {
131
std::size_t p = k;
132
Mag amax =
pivot_mag<T>::of
(U(k, k));
133
for
(std::size_t i = k + 1; i < n; ++i) {
134
Mag a =
pivot_mag<T>::of
(U(i, k));
135
if
(a > amax) {
136
amax = a;
137
p = i;
138
}
139
}
140
if
(amax ==
num_traits<Mag>::from_int
(0))
return
zero;
141
if
(p != k) {
142
for
(std::size_t j = 0; j < n; ++j) std::swap(U(k, j), U(p, j));
143
det = T(zero - det);
144
}
145
const
T d = U(k, k);
146
det = T(det * d);
147
for
(std::size_t i = k + 1; i < n; ++i) {
148
if
(U(i, k) == zero)
continue
;
149
const
T f = U(i, k) / d;
150
for
(std::size_t j = k + 1; j < n; ++j) U(i, j) -= f * U(k, j);
151
}
152
}
153
return
det;
154
}
155
156
/** Convenience: solve Ax = b, leaving A and b untouched. */
157
template
<
class
T>
158
std::vector<T>
solve
(
const
Matrix<T>
& A,
const
std::vector<T>& b) {
159
Matrix<T>
LU = A;
160
std::vector<T> x = b;
161
std::vector<std::size_t> piv =
lu_factor
(LU);
162
lu_solve
(LU, piv, x);
163
return
x;
164
}
165
166
}
// namespace line
167
168
#endif
// LINE_UTIL_LU_H
line::InputError::InputError
InputError(const std::string &what)
Definition
error.h:39
line::Matrix
Definition
matrix.h:56
line::Matrix::cols
std::size_t cols() const
Definition
matrix.h:90
line::Matrix::rows
std::size_t rows() const
Definition
matrix.h:89
line::NumericError::NumericError
NumericError(const std::string &what)
Definition
error.h:45
error.h
The exception types the port throws.
matrix.h
Dense matrix and non-owning view.
line
Definition
aoi_dist2ph.h:52
line::lu_solve
void lu_solve(const Matrix< T > &LU, const std::vector< std::size_t > &piv, std::vector< T > &b)
Solve LUx = Pb in place on b, using the factors from lu_factor.
Definition
lu.h:94
line::num_abs
T num_abs(const T &v)
Definition
number.h:172
line::lu_factor
std::vector< std::size_t > lu_factor(Matrix< T > &A)
In-place LU of A (n x n).
Definition
lu.h:48
line::solve
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
line::lu_det
T lu_det(const Matrix< T > &A)
Determinant of a square matrix, by the same partial-pivoting elimination.
Definition
lu.h:122
number.h
Number-type abstraction for the templated API port.
line::num_traits
Definition
number.h:111
line::pivot_mag
The ordered type the pivot search compares in.
Definition
lu.h:38
line::pivot_mag::of
static type of(const T &x)
Definition
lu.h:40
line::pivot_mag::type
T type
Definition
lu.h:39
include
line
util
lu.h
Generated by
1.18.0