Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
gmres.hpp
Go to the documentation of this file.
1#pragma once
2
11
12namespace uni20::krylov
13{
24
26template <Real R> struct GmresOptions
27{
29 std::optional<R> relative_tolerance = {};
31 std::size_t restart = 30;
32 std::size_t max_iterations = 1000;
34};
35
41template <Real R> struct GmresInfo
42{
44 std::size_t iterations = 0;
45 std::size_t cycles = 0;
46 std::size_t operator_applications = 0;
48 std::optional<GmresResidual<R>> residual = {};
49 std::optional<GmresErrorEstimates<R>> errors = {};
50 bool succeeded() const noexcept { return this->status == GmresStatus::converged; }
51};
52
57template <class P, class V>
58concept KrylovPreconditioner = requires(P p, V& output, V const& input) {
59 { p.apply(output, input) } -> std::same_as<void>;
60};
61
63template <class Ops> struct IdentityPreconditioner
64{
65 Ops& ops;
66 template <class V> void apply(V& output, V const& input) { ops.copy(output, input); }
67};
68
69namespace gmres_detail
70{
71template <Real R> struct Tolerances
72{
75};
76
77template <Real R> Tolerances<R> tolerances(GmresOptions<R> const& options, Precision precision)
78{
79 if (options.restart == 0 ||
80 (options.relative_tolerance &&
81 (!uni20::isfinite(*options.relative_tolerance) || *options.relative_tolerance < 0)) ||
83 throw std::invalid_argument("GMRES requires a positive restart and finite nonnegative tolerances");
84 R relative = options.relative_tolerance ? scalar_at_precision<R>(*options.relative_tolerance, precision)
86 R absolute = scalar_at_precision<R>(options.absolute_tolerance, precision);
87 if (!uni20::isfinite(relative) || !uni20::isfinite(absolute))
88 throw std::invalid_argument("GMRES tolerance is not representable at working precision");
89 return {relative, absolute};
90}
91
92template <RealOrComplex S> struct Rotation
93{
95 S s;
96 S r;
97};
98
99// [c s; -conj(s) c] maps [f g] to [r 0]. Neither zero component
100// requires division by zero, and hypot avoids squaring unscaled magnitudes.
101template <RealOrComplex S> Rotation<S> rotation(S const& f, S const& g, Precision p)
102{
103 using R = make_real_t<S>;
104 auto zero = scalar_at_precision<S>(0, p);
105 auto rzero = scalar_at_precision<R>(0, p);
106 auto rone = scalar_at_precision<R>(1, p);
107 if (g == zero) return {rone, zero, f};
108 R af = math::abs(f), ag = math::abs(g);
109 if (f == zero) return {rzero, uni20::conj(g) / ag, scalar_at_precision<S>(ag, p)};
110 R magnitude = math::hypot(af, ag);
111 S phase = f / af;
112 return {af / magnitude, phase * (uni20::conj(g) / magnitude), phase * magnitude};
113}
114
115template <class S> void rotate(Rotation<S> const& rotation, S& x, S& y)
116{
117 S previous = x;
118 x = rotation.c * previous + rotation.s * y;
119 y = -uni20::conj(rotation.s) * previous + rotation.c * y;
120}
121
122template <RealOrComplex S, class Ops, class V> std::optional<make_real_t<S>> norm(Ops& ops, V const& v, Precision p)
123{
124 using R = make_real_t<S>;
125 if constexpr (KrylovNorm<Ops, V, S>)
126 {
127 R value = scalar_at_precision<R>(ops.norm(v), p);
128 if (!uni20::isfinite(value) || value < 0) return {};
129 return value;
130 }
131 else
132 {
133 S square = scalar_at_precision<S>(ops.inner_product(v, v), p);
134 if (!uni20::isfinite(square) || math::real(square) < 0) return {};
135 return math::sqrt(scalar_at_precision<R>(math::real(square), p));
136 }
137}
138
139template <RealOrComplex S, class Ops, class V> void normalize(Ops& ops, V& v, make_real_t<S> const& length, Precision p)
140{
141 using R = make_real_t<S>;
142 auto one = scalar_at_precision<R>(1, p);
143 R inverse = one / length;
144 if (uni20::isfinite(inverse) && inverse != 0)
145 ops.scal(v, scalar_at_precision<S>(inverse, p));
146 else
147 {
148 // A finite vector norm can have an unrepresentable reciprocal. Two
149 // square-root factors avoid overflowing the scale before applying it.
150 S factor = scalar_at_precision<S>(one / math::sqrt(length), p);
151 ops.scal(v, factor);
152 ops.scal(v, factor);
153 }
154}
155
156template <Real R> bool reached(R const& residual, R const& rhs, R const& relative, R const& absolute)
157{
158 // Compare ratios instead of forming a potentially overflowing rtol*||b||.
159 return residual <= absolute || (relative > 0 && rhs > 0 && residual / rhs <= relative);
160}
161} // namespace gmres_detail
162
178template <RealOrComplex S, class Ops, class Op, class P, class V>
179 requires KrylovVectorOps<Ops, V, S> && KrylovOperator<Op, V> && KrylovPreconditioner<P, V>
180GmresInfo<make_real_t<S>> gmres_with_info(Ops& ops, Op& op, P& preconditioner, V& x, V const& b,
181 GmresOptions<make_real_t<S>> const& options, Precision precision)
182{
183 using R = make_real_t<S>;
184 require_precision<S>(precision);
185 (void)precision.bit_count();
186 std::size_t n = ops.problem_dimension();
187 if (ops.vector_dimension(x) != n || ops.vector_dimension(b) != n)
188 throw std::invalid_argument("GMRES vectors do not belong to the operator space");
189 auto [relative, absolute] = gmres_detail::tolerances(options, precision);
190 auto estimates = gmres_detail::operator_estimates_at_precision(options.operator_estimates, precision);
191 auto zero = scalar_at_precision<S>(0, precision);
192 auto one = scalar_at_precision<S>(1, precision);
193 auto epsilon = numeric_limits<R>::epsilon(precision);
194 auto dgks = math::sqrt(scalar_at_precision<R>(2, precision));
195 GmresInfo<R> info;
196 auto norm = [&](V const& v) { return gmres_detail::norm<S>(ops, v, precision); };
197 auto fail = [&](GmresStatus status) {
198 info.status = status;
199 return info;
200 };
201 auto bnorm = norm(b);
202 auto xnorm = norm(x);
203 if (!bnorm || !xnorm) return fail(GmresStatus::nonfinite_input);
204 auto diagnose = [&](R const& residual_norm, R const& solution_norm) {
205 info.residual = GmresResidual<R>{residual_norm, *bnorm, solution_norm};
206 info.errors = gmres_detail::error_estimates(*info.residual, estimates);
207 };
208
209 // Preserve b even if x aliases it, and publish x only after diagnosis.
210 auto rhs = ops.allocate_like(b);
211 ops.copy(rhs, b);
212 auto residual = ops.allocate_like(b);
213 auto work = ops.allocate_like(b);
214 auto evaluate = [&](V const& candidate) {
215 ++info.operator_applications;
216 op.matvec(work, candidate);
217 ops.copy(residual, rhs);
218 ops.axpy(residual, -one, work);
219 return norm(residual);
220 };
221 auto beta = evaluate(x);
222 if (!beta) return fail(GmresStatus::nonfinite_result);
223 diagnose(*beta, *xnorm);
224 auto reached = [&](R const& value) { return gmres_detail::reached(value, *bnorm, relative, absolute); };
225 if (reached(*beta)) return fail(GmresStatus::converged);
226 if (options.max_iterations == 0) return info;
227
228 auto z = ops.allocate_like(b);
229 auto delta = ops.allocate_like(b);
230 auto candidate = ops.allocate_like(b);
231 std::size_t m = std::min({options.restart, options.max_iterations, n});
232 std::vector<S> h((m + 1) * m, zero), g(m + 1, zero);
233 auto H = [&](std::size_t row, std::size_t col) -> S& { return h[col * (m + 1) + row]; };
234 while (info.iterations < options.max_iterations)
235 {
236 ++info.cycles;
237 std::vector<V> basis;
238 basis.reserve(m + 1);
239 basis.push_back(ops.allocate_like(b));
240 ops.copy(basis.back(), residual);
241 gmres_detail::normalize<S>(ops, basis.back(), *beta, precision);
242 std::fill(h.begin(), h.end(), zero);
243 std::fill(g.begin(), g.end(), zero);
244 g[0] = scalar_at_precision<S>(*beta, precision);
245 std::vector<gmres_detail::Rotation<S>> rotations;
246 rotations.reserve(m);
247 std::size_t columns = 0;
248 bool breakdown = false;
249 for (std::size_t j = 0; j < m && info.iterations < options.max_iterations; ++j)
250 {
251 ++info.iterations;
252 ++info.preconditioner_applications;
253 preconditioner.apply(z, basis[j]);
254 if (!norm(z)) return fail(GmresStatus::nonfinite_result);
255 ++info.operator_applications;
256 op.matvec(work, z);
257 auto before = norm(work);
258 if (!before) return fail(GmresStatus::nonfinite_result);
259 auto orthogonalize = [&] {
260 for (std::size_t k = 0; k <= j; ++k)
261 {
262 S dot = scalar_at_precision<S>(ops.inner_product(basis[k], work), precision);
263 if (!uni20::isfinite(dot)) return false;
264 H(k, j) += dot;
265 ops.axpy(work, -dot, basis[k]);
266 }
267 return true;
268 };
269 if (!orthogonalize()) return fail(GmresStatus::nonfinite_result);
270 auto after = norm(work);
271 if (!after) return fail(GmresStatus::nonfinite_result);
272 if (*after < *before / dgks)
273 {
274 if (!orthogonalize()) return fail(GmresStatus::nonfinite_result);
275 after = norm(work);
276 if (!after) return fail(GmresStatus::nonfinite_result);
277 }
278 H(j + 1, j) = scalar_at_precision<S>(*after, precision);
279 breakdown = *after == 0 || (*before > 0 && *after / *before <= epsilon);
280 if (!breakdown && j + 1 < m && info.iterations < options.max_iterations)
281 {
282 basis.push_back(ops.allocate_like(b));
283 ops.copy(basis.back(), work);
284 gmres_detail::normalize<S>(ops, basis.back(), *after, precision);
285 }
286 for (std::size_t k = 0; k < j; ++k)
287 gmres_detail::rotate(rotations[k], H(k, j), H(k + 1, j));
288 auto rotation = gmres_detail::rotation(H(j, j), H(j + 1, j), precision);
289 if (!uni20::isfinite(rotation.c) || !uni20::isfinite(rotation.s) || !uni20::isfinite(rotation.r))
291 if (rotation.r == zero)
292 {
293 breakdown = true;
294 break; // Keep the preceding full-rank projected prefix, if any.
295 }
296 H(j, j) = rotation.r;
297 H(j + 1, j) = zero;
298 rotations.push_back(rotation);
299 gmres_detail::rotate(rotation, g[j], g[j + 1]);
300 columns = j + 1;
301 if (breakdown || reached(math::abs(g[j + 1]))) break;
302 }
303 if (columns == 0) return fail(GmresStatus::breakdown);
304 std::vector<S> y(g.begin(), g.begin() + columns);
305 for (std::size_t row = columns; row-- > 0;)
306 {
307 for (std::size_t col = row + 1; col < columns; ++col)
308 y[row] -= H(row, col) * y[col];
309 y[row] /= H(row, row);
311 }
312 ops.set_zero(delta);
313 for (std::size_t j = 0; j < columns; ++j)
314 ops.axpy(delta, y[j], basis[j]);
315 ++info.preconditioner_applications;
316 preconditioner.apply(z, delta);
317 if (!norm(z)) return fail(GmresStatus::nonfinite_result);
318 ops.copy(candidate, x);
319 ops.axpy(candidate, one, z);
320 auto candidate_norm = norm(candidate);
321 if (!candidate_norm) return fail(GmresStatus::nonfinite_result);
322 ops.copy(delta, candidate);
323 ops.axpy(delta, -one, x);
324 auto change = norm(delta);
325 if (!change) return fail(GmresStatus::nonfinite_result);
326 beta = evaluate(candidate);
327 if (!beta) return fail(GmresStatus::nonfinite_result);
328 ops.copy(x, candidate);
329 diagnose(*beta, *candidate_norm);
330 if (reached(*beta)) return fail(GmresStatus::converged);
331 if (breakdown) return fail(GmresStatus::breakdown);
332 if (*change == 0) return fail(GmresStatus::stagnated);
333 }
334 return info;
335}
336
338template <RealOrComplex S, class Ops, class Op, class V>
339 requires KrylovVectorOps<Ops, V, S> && KrylovOperator<Op, V>
340auto gmres_with_info(Ops& ops, Op& op, V& x, V const& b, GmresOptions<make_real_t<S>> const& options, Precision p)
341{
342 IdentityPreconditioner<Ops> identity{ops};
343 return gmres_with_info<S>(ops, op, identity, x, b, options, p);
344}
345
347template <RealOrComplex S, class Ops, class Op, class P, class V>
348 requires(!has_runtime_precision_v<S>) && KrylovVectorOps<Ops, V, S> && KrylovOperator<Op, V> &&
349 KrylovPreconditioner<P, V>
350auto gmres_with_info(Ops& ops, Op& op, P& preconditioner, V& x, V const& b,
351 GmresOptions<make_real_t<S>> const& options = {})
352{
353 return gmres_with_info<S>(ops, op, preconditioner, x, b, options, native_precision<S>());
354}
355
357template <RealOrComplex S, class Ops, class Op, class V>
358 requires(!has_runtime_precision_v<S>) && KrylovVectorOps<Ops, V, S> && KrylovOperator<Op, V>
359auto gmres_with_info(Ops& ops, Op& op, V& x, V const& b, GmresOptions<make_real_t<S>> const& options = {})
360{
361 return gmres_with_info<S>(ops, op, x, b, options, native_precision<S>());
362}
363} // namespace uni20::krylov
AsyncTask square(ReadBuffer< int > in, WriteBuffer< int > out)
Definition async_tbb_reduction_example.cpp:39
Exact arithmetic or an explicit binary working precision.
Definition core/precision.hpp:13
constexpr bit_count_type bit_count() const
Number of binary significand bits.
Definition core/precision.hpp:43
Optional customization point for direct vector norms.
Definition matrix_free.hpp:437
Fixed linear inverse-preconditioner application, output <- M^{-1} input.
Definition gmres.hpp:58
locale_t value
Definition data_table.cpp:49
Optional Euclidean-norm error diagnostics for matrix-free solves.
uni20::complex< T > conj(detail::standard_complex< T > x)
Returns the complex conjugate for complex-valued scalars.
Definition math.hpp:70
typename make_real< T >::type make_real_t
Alias for the underlying real-valued type of T.
Definition scalar_traits.hpp:168
constexpr bool isfinite(I const &x) noexcept
Returns whether an integer scalar is finite.
Definition math.hpp:121
bool reached(R const &residual, R const &rhs, R const &relative, R const &absolute)
Definition gmres.hpp:156
std::optional< GmresErrorEstimates< R > > error_estimates(GmresResidual< R > const &residual, GmresOperatorEstimates< R > const &properties)
Definition gmres_diagnostics.hpp:132
void rotate(Rotation< S > const &rotation, S &x, S &y)
Definition gmres.hpp:115
Rotation< S > rotation(S const &f, S const &g, Precision p)
Definition gmres.hpp:101
void normalize(Ops &ops, V &v, make_real_t< S > const &length, Precision p)
Definition gmres.hpp:139
Tolerances< R > tolerances(GmresOptions< R > const &options, Precision precision)
Definition gmres.hpp:77
GmresOperatorEstimates< R > operator_estimates_at_precision(GmresOperatorEstimates< R > const &input, Precision p)
Definition gmres_diagnostics.hpp:73
std::optional< make_real_t< S > > norm(Ops &ops, V const &v, Precision p)
Definition gmres.hpp:122
DiagnosticMagnitude< R > magnitude(R const &value)
Definition gmres_diagnostics.hpp:98
Definition block_tensor_vector.hpp:23
GmresStatus
Numerical termination of a matrix-free linear solve.
Definition gmres.hpp:16
Scalar dot(std::span< Scalar const > x, std::span< Scalar const > y)
Compute a BLAS-style real dot product.
Definition dense_linalg.hpp:240
GmresInfo< make_real_t< S > > gmres_with_info(Ops &ops, Op &op, P &preconditioner, V &x, V const &b, GmresOptions< make_real_t< S > > const &options, Precision precision)
Solve A*x=b with restarted GMRES and a fixed right preconditioner.
Definition gmres.hpp:180
constexpr detail::scalar_math::sqrt_fn sqrt
Square root; exact inputs requiring approximation need an explicit finite precision.
Definition math.hpp:461
constexpr detail::scalar_math::abs_fn abs
Absolute value or complex magnitude, preserving the selected scalar implementation.
Definition math.hpp:459
constexpr detail::scalar_math::real_fn real
Read the real component by value; real and integral scalars retain their type.
Definition math.hpp:671
constexpr detail::scalar_math::hypot_fn hypot
Euclidean length with scalar-specific overflow and underflow handling.
Definition math.hpp:507
Definition data_table.cpp:8
mpreal norm(mpcomplex const &z)
Definition mpcomplex.hpp:494
mpreal epsilon(Precision precision)
Spacing above one for a specified binary working precision.
Definition mpreal.hpp:805
mpreal beta(mpreal const &a, mpreal const &x)
Real Beta function; positive rational/integer pairs retain exact values.
Definition mpreal_special.hpp:164
Recoverable solve outcome; counters include attempted operator calls.
Definition gmres.hpp:42
std::optional< GmresResidual< R > > residual
Definition gmres.hpp:48
GmresStatus status
Definition gmres.hpp:43
std::size_t iterations
Definition gmres.hpp:44
std::size_t operator_applications
Definition gmres.hpp:46
std::size_t preconditioner_applications
Definition gmres.hpp:47
std::size_t cycles
Definition gmres.hpp:45
std::optional< GmresErrorEstimates< R > > errors
Definition gmres.hpp:49
bool succeeded() const noexcept
Definition gmres.hpp:50
Optional properties of the original operator in the vector space's 2-norm.
Definition gmres_diagnostics.hpp:38
Residual stopping policy and storage/iteration limits.
Definition gmres.hpp:27
std::size_t max_iterations
Definition gmres.hpp:32
std::optional< R > relative_tolerance
Absent means sqrt(epsilon) at the GMRES working precision.
Definition gmres.hpp:29
std::size_t restart
Definition gmres.hpp:31
GmresOperatorEstimates< R > operator_estimates
Definition gmres.hpp:33
R absolute_tolerance
Definition gmres.hpp:30
Recomputed Euclidean norms for the published iterate.
Definition gmres_diagnostics.hpp:46
Identity preconditioner using the supplied vector copy operation.
Definition gmres.hpp:64
void apply(V &output, V const &input)
Definition gmres.hpp:66
Ops & ops
Definition gmres.hpp:65
make_real_t< S > c
Definition gmres.hpp:94
R absolute
Definition gmres.hpp:74
R relative
Definition gmres.hpp:73
Project-level numeric limits customization point.
Definition numeric_limits.hpp:16