Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
lu_refine.hpp
Go to the documentation of this file.
1#pragma once
2
10
11namespace uni20::linalg
12{
18template <RealOrComplex W, RealOrComplex R> struct LuRefinementResult
19{
21 std::optional<DenseMatrix<W>> solution = {};
22 std::vector<RefinementColumn<make_real_t<R>>> columns = {};
23};
24
25namespace refinement_detail
26{
27template <class F, class W, class R>
29{
30 require_precision<F>(p.factor);
31 require_precision<W>(p.working);
32 require_precision<R>(p.residual);
33 (void)p.factor.bit_count();
34 (void)p.working.bit_count();
35 (void)p.residual.bit_count();
36 using RR = make_real_t<R>;
37 if (options.backward_error_tolerance)
38 ERROR_IF(!uni20::isfinite(*options.backward_error_tolerance) || *options.backward_error_tolerance < 0,
39 "refinement requires a finite nonnegative backward-error tolerance");
40 RR tolerance = options.backward_error_tolerance
41 ? convert<RR>(*options.backward_error_tolerance, p.residual)
42 : convert<RR>(numeric_limits<make_real_t<W>>::epsilon(p.working), p.residual);
43 ERROR_IF(!uni20::isfinite(tolerance), "refinement tolerance is not representable at residual precision");
44 return tolerance;
45}
46
52
53template <class W, class R, class A, class B>
54LuRefinementResult<W, R> empty_result(A const& a, B const& b, Precision working, Precision residual)
55{
57 result.solution.emplace(a.extent(1), b.extent(1), working);
58 auto zero = scalar_at_precision<make_real_t<R>>(0, residual);
59 result.columns.assign(b.extent(1), {.error = {zero, zero, zero}, .converged = true});
60 return result;
61}
62
63template <RealOrComplex S> auto component_magnitude(S const& value)
64{
65 if constexpr (Complex<S>)
66 return std::max(math::abs(value.real()), math::abs(value.imag()));
67 else
68 return math::abs(value);
69}
70
71// Transfer the bounded significand and combine exponents before applying the
72// scale. Neither scaling in From nor converting to To first is safe when their
73// exponent ranges differ, even if the final value is representable.
74template <RealOrComplex To, RealOrComplex From>
75 requires(Complex<To> == Complex<From>)
76To convert_scaled(From const& value, std::int64_t exponent, Precision p)
77{
78 if (exponent == 0) return convert<To>(value, p);
79 if constexpr (Complex<To>)
80 return To(convert_scaled<make_real_t<To>>(value.real(), exponent, p),
81 convert_scaled<make_real_t<To>>(value.imag(), exponent, p));
82 else
83 {
84 auto parts = math::frexp(value);
85 return math::ldexp(convert<To>(parts.fraction, p), parts.exponent + exponent);
86 }
87}
88
89// Normalize toward the factors' scale with headroom for the smallest pivot.
90// Normalizing every RHS to unity can overflow a triangular solve even for tiny,
91// well-conditioned matrices. Binary shifts avoid rounding the scale itself.
92template <class Backend, class F, class W, class R>
93std::optional<DenseMatrix<W>> correction(Backend& backend, LuFactorization<F> const& factor, DenseMatrix<R> const& rhs,
94 LuRefinementPrecision<F, W, R> p, std::vector<bool> const& active,
95 std::int64_t target_exponent, SolveInfo& info)
96{
97 using RR = make_real_t<R>;
98 auto zero = scalar_at_precision<RR>(0, p.residual);
99 std::size_t n = rhs.extent(0), count = rhs.extent(1);
100 DenseMatrix<F> work(n, count, p.factor);
101 std::vector<std::int64_t> shifts(count, 0);
102 for (std::size_t j = 0; j < count; ++j)
103 {
104 if (!active[j]) continue;
105 RR magnitude = zero;
106 for (std::size_t i = 0; i < n; ++i)
107 magnitude = std::max(magnitude, component_magnitude(R(rhs[i, j])));
108 if (magnitude == 0) continue;
109 shifts[j] = target_exponent - math::frexp(magnitude).exponent;
110 for (std::size_t i = 0; i < n; ++i)
111 work[i, j] = convert_scaled<F>(R(rhs[i, j]), shifts[j], p.factor);
112 }
113 info = lu_solve_inplace_with_info(backend, factor, work);
114 if (!info.succeeded()) return {};
115 DenseMatrix<W> result(n, count, p.working);
116 for (std::size_t j = 0; j < count; ++j)
117 {
118 if (!active[j]) continue;
119 for (std::size_t i = 0; i < n; ++i)
120 {
121 R scaled = convert_scaled<R>(F(work[i, j]), -shifts[j], p.residual);
122 W value = convert<W>(scaled, p.working);
124 {
125 info.status = SolveStatus::nonfinite_result;
126 return {};
127 }
128 result[i, j] = std::move(value);
129 }
130 }
131 return result;
132}
133
134template <class Backend, class F, class W, class R>
135LuRefinementResult<W, R> run(Backend& backend, LuFactorization<F> const& factor, DenseMatrix<R> const& a,
137 RefinementOptions<make_real_t<R>> const& options, make_real_t<R> const& tolerance)
138{
139 std::size_t n = a.extent(0), count = b.extent(1);
141 SolveInfo solve_info;
142 std::vector<bool> active(count, true);
143 auto upper_scale = scalar_at_precision<make_real_t<F>>(0, p.factor);
144 auto pivot_scale = component_magnitude(F(factor.packed()[0, 0]));
145 for (std::size_t j = 0; j < n; ++j)
146 for (std::size_t i = 0; i <= j; ++i)
147 {
148 auto magnitude = component_magnitude(F(factor.packed()[i, j]));
149 upper_scale = std::max(upper_scale, magnitude);
150 if (i == j) pivot_scale = std::min(pivot_scale, magnitude);
151 }
152 auto target_exponent = std::min(std::int64_t{0}, math::frexp(upper_scale).exponent);
153 if constexpr (numeric_limits<make_real_t<F>>::is_bounded)
154 {
155 // Leave significand-sized exponent headroom when dividing by the smallest
156 // diagonal. This limits inflation for disparate pivots without sacrificing
157 // normalization of tiny RHS values against uniformly large factors.
158 using FR = make_real_t<F>;
159 auto pivot_exponent = math::frexp(pivot_scale).exponent;
160 std::int64_t headroom = numeric_limits<FR>::max_exponent - numeric_limits<FR>::digits(p.factor);
161 target_exponent = std::min(target_exponent, pivot_exponent + headroom);
162 }
163 auto initial = correction(backend, factor, b, p, active, target_exponent, solve_info);
164 if (!initial)
165 {
166 result.info = {.status = RefinementStatus::correction_failed, .solve_info = solve_info};
167 return result;
168 }
169 auto rx = pack<R>(*initial, p.residual, solve_info);
170 if (!rx)
171 {
172 result.info = conversion_failure(solve_info);
173 return result;
174 }
175 auto evaluated = evaluate(a, b, *rx, p.residual);
176 if (!evaluated.info.succeeded())
177 {
178 result.info = conversion_failure(evaluated.info);
179 return result;
180 }
181 result.solution.emplace(std::move(*initial));
182 for (auto const& error : evaluated.columns)
183 result.columns.push_back({.error = error, .converged = error.componentwise_backward_error <= tolerance});
184
185 for (std::size_t step = 0;; ++step)
186 {
187 bool all_converged = true, any_active = false;
188 for (std::size_t j = 0; j < count; ++j)
189 {
190 all_converged = all_converged && result.columns[j].converged;
191 active[j] = !result.columns[j].converged && !result.columns[j].stagnated;
192 any_active = any_active || active[j];
193 }
194 if (all_converged) return result;
195 if (!any_active || step == options.max_iterations)
196 {
197 result.info.status = !any_active ? RefinementStatus::stagnated : RefinementStatus::iteration_limit;
198 return result;
199 }
200 auto delta = correction(backend, factor, *evaluated.residual, p, active, target_exponent, solve_info);
201 if (!delta)
202 {
203 result.info = {.status = RefinementStatus::correction_failed, .solve_info = solve_info};
204 return result;
205 }
206 // Publish only after every new value and the recomputed diagnostics pass.
207 // This owns a finite W iterate already at p.working, so copying it at the
208 // same precision cannot return a numerical conversion failure.
209 auto next = pack<W>(*result.solution, p.working, solve_info);
210 std::vector<bool> changed(count, false);
211 for (std::size_t j = 0; j < count; ++j)
212 if (active[j])
213 for (std::size_t i = 0; i < n; ++i)
214 {
215 W value = (*next)[i, j] + (*delta)[i, j];
217 {
218 result.info.status = RefinementStatus::nonfinite_result;
219 return result;
220 }
221 changed[j] = changed[j] || value != (*next)[i, j];
222 (*next)[i, j] = std::move(value);
223 }
224 auto next_r = pack<R>(*next, p.residual, solve_info);
225 if (!next_r)
226 {
227 result.info = conversion_failure(solve_info);
228 return result;
229 }
230 auto next_evaluated = evaluate(a, b, *next_r, p.residual);
231 if (!next_evaluated.info.succeeded())
232 {
233 result.info = conversion_failure(next_evaluated.info);
234 return result;
235 }
236 result.solution.emplace(std::move(*next));
237 evaluated = std::move(next_evaluated);
238 for (std::size_t j = 0; j < count; ++j)
239 {
240 auto& column = result.columns[j];
241 column.error = evaluated.columns[j];
242 column.converged = column.error.componentwise_backward_error <= tolerance;
243 if (active[j])
244 {
245 ++column.corrections;
246 column.stagnated = !column.converged && !changed[j];
247 }
248 }
249 }
250}
251
252template <class A, class B> void require_shape(A const& a, B const& b)
253{
254 ERROR_IF(a.extent(0) != a.extent(1) || a.extent(0) != b.extent(0),
255 "LU refinement requires square A and matching RHS rows");
256}
257} // namespace refinement_detail
258
270template <KernelBackendSelector Backend, RealOrComplex F, RealOrComplex W, RealOrComplex R,
271 refinement_detail::HostMatrix A, refinement_detail::HostMatrix B>
273[[nodiscard]] auto lu_refine_with_info(Backend&& backend, LuFactorization<F> const& factor, A const& a, B const& b,
275 RefinementOptions<make_real_t<R>> const& options = {})
276{
277 auto tolerance = refinement_detail::validate(p, options);
278 refinement_detail::require_shape(a, b);
279 ERROR_IF(factor.order() != std::size_t(a.extent(0)) || factor.precision() != p.factor,
280 "refinement factors must match the matrix order and factor precision");
281 if (a.extent(0) == 0 || b.extent(1) == 0) return refinement_detail::empty_result<W, R>(a, b, p.working, p.residual);
282 LuRefinementResult<W, R> result;
283 SolveInfo info;
284 auto av = refinement_detail::pack<R>(a, p.residual, info);
285 auto bv = av ? refinement_detail::pack<R>(b, p.residual, info) : std::nullopt;
286 if (!av || !bv)
287 {
288 result.info = refinement_detail::conversion_failure(info);
289 return result;
290 }
291 return refinement_detail::run(backend, factor, *av, *bv, p, options, tolerance);
292}
293
301template <KernelBackendSelector Backend, RealOrComplex F, RealOrComplex W, RealOrComplex R,
302 refinement_detail::HostMatrix A, refinement_detail::HostMatrix B>
303 requires(Complex<F> == Complex<tensor_element_t<A>> && Complex<F> == Complex<tensor_element_t<B>>)
304[[nodiscard]] auto lu_refine_with_info(Backend&& backend, A const& a, B const& b, LuRefinementPrecision<F, W, R> p,
305 RefinementOptions<make_real_t<R>> const& options = {},
306 LuOptions<make_real_t<F>> const& factor_options = {.retain_norms = false})
307{
308 auto tolerance = refinement_detail::validate(p, options);
309 refinement_detail::require_shape(a, b);
310 detail::require_solve_options(SolveOptions<make_real_t<F>>{factor_options.relative_pivot_tolerance});
311 if (a.extent(0) == 0 || b.extent(1) == 0) return refinement_detail::empty_result<W, R>(a, b, p.working, p.residual);
312 LuRefinementResult<W, R> result;
313 SolveInfo info;
314 auto av = refinement_detail::pack<R>(a, p.residual, info);
315 auto bv = av ? refinement_detail::pack<R>(b, p.residual, info) : std::nullopt;
316 auto af = bv ? refinement_detail::pack<F>(a, p.factor, info) : std::nullopt;
317 if (!av || !bv || !af)
318 {
319 result.info = refinement_detail::conversion_failure(info);
320 return result;
321 }
322 auto factor = lu_factor_with_info(backend, *af, p.factor, factor_options);
323 if (!factor.info.succeeded())
324 {
325 result.info = {.status = RefinementStatus::factorization_failed, .solve_info = factor.info};
326 return result;
327 }
328 return refinement_detail::run(backend, *factor.factor, *av, *bv, p, options, tolerance);
329}
330
332template <RealOrComplex F, RealOrComplex W, RealOrComplex R, refinement_detail::HostMatrix A,
333 refinement_detail::HostMatrix B>
334 requires(Complex<F> == Complex<tensor_element_t<A>> && Complex<F> == Complex<tensor_element_t<B>>)
335[[nodiscard]] auto lu_refine_with_info(A const& a, B const& b, LuRefinementPrecision<F, W, R> p,
336 RefinementOptions<make_real_t<R>> const& options = {},
337 LuOptions<make_real_t<F>> const& factor_options = {.retain_norms = false})
338{
339 return lu_refine_with_info(select_backend_for<DenseMatrix<F>>(lu_factor_op{}), a, b, p, options, factor_options);
340}
341
343template <RealOrComplex F, RealOrComplex W, RealOrComplex R, refinement_detail::HostMatrix A,
344 refinement_detail::HostMatrix B>
345 requires(Complex<F> == Complex<tensor_element_t<A>> && Complex<F> == Complex<tensor_element_t<B>>)
346[[nodiscard]] auto lu_refine_with_info(LuFactorization<F> const& factor, A const& a, B const& b,
348 RefinementOptions<make_real_t<R>> const& options = {})
349{
350 return lu_refine_with_info(select_backend_for<DenseMatrix<F>>(lu_solve_op{}), factor, a, b, p, options);
351}
352
354template <class... Args>
355 requires requires(Args&&... args) { lu_refine_with_info(std::forward<Args>(args)...); }
356[[nodiscard]] auto lu_refine(Args&&... args)
357{
358 auto result = lu_refine_with_info(std::forward<Args>(args)...);
359 ERROR_IF(!result.info.succeeded(), "LU refinement failed", static_cast<int>(result.info.status));
360 return std::move(*result.solution);
361}
362} // namespace uni20::linalg
Exact arithmetic or an explicit binary working precision.
Definition core/precision.hpp:13
General-purpose owning tensor with immediate or deferred mdspan metadata.
Definition basic_tensor.hpp:109
auto extent(size_type axis) const noexcept
Return one tensor extent.
Definition basic_tensor.hpp:720
Immutable owning packed factors satisfying P A = L U.
Definition ops/lu.hpp:34
Precision precision() const
Finite working precision used for these factors and subsequent solves.
Definition ops/lu.hpp:44
DenseMatrix< S > const & packed() const &noexcept
Read-only packed L/U matrix, valid while this object retains its storage.
Definition ops/lu.hpp:40
std::size_t order() const noexcept
Matrix order, including zero for a successfully factored empty matrix.
Definition ops/lu.hpp:38
Concept for complex scalar types.
Definition scalar_concepts.hpp:75
Concept for types that are either real or complex.
Definition scalar_concepts.hpp:87
locale_t value
Definition data_table.cpp:49
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
Definition qnum.hpp:403
void info(checked_format_string< Args... > format, Args &&... args)
Emit an informational display message.
Definition display.hpp:393
auto validate(LuRefinementPrecision< F, W, R > p, RefinementOptions< make_real_t< R > > const &options)
Definition lu_refine.hpp:28
SolveDiagnosticsResult< S > evaluate(DenseMatrix< S > const &a, DenseMatrix< S > const &b, DenseMatrix< S > const &x, Precision p)
Definition solve_diagnostics.hpp:81
void require_shape(A const &a, B const &b)
Definition lu_refine.hpp:252
LuRefinementResult< W, R > run(Backend &backend, LuFactorization< F > const &factor, DenseMatrix< R > const &a, DenseMatrix< R > const &b, LuRefinementPrecision< F, W, R > p, RefinementOptions< make_real_t< R > > const &options, make_real_t< R > const &tolerance)
Definition lu_refine.hpp:135
std::optional< DenseMatrix< W > > correction(Backend &backend, LuFactorization< F > const &factor, DenseMatrix< R > const &rhs, LuRefinementPrecision< F, W, R > p, std::vector< bool > const &active, std::int64_t target_exponent, SolveInfo &info)
Definition lu_refine.hpp:93
LuRefinementResult< W, R > empty_result(A const &a, B const &b, Precision working, Precision residual)
Definition lu_refine.hpp:54
auto component_magnitude(S const &value)
Definition lu_refine.hpp:63
To convert_scaled(From const &value, std::int64_t exponent, Precision p)
Definition lu_refine.hpp:76
RefinementInfo conversion_failure(SolveInfo const &info)
Definition lu_refine.hpp:47
Definition linalg/async.hpp:28
SolveInfo lu_solve_inplace_with_info(Backend &&backend, LuFactorization< S > const &factor, B &&b)
Solve in an existing RHS workspace, preserving the factors on every outcome.
Definition ops/lu.hpp:169
auto lu_refine(Args &&... args)
Return the refined solution only on convergence; other outcomes use the terminal error policy.
Definition lu_refine.hpp:356
@ nonfinite_input
An input coefficient or RHS component was nonfinite; both workspaces are preserved.
@ nonfinite_result
A factor, solution, reduction, or required magnitude became nonfinite.
auto lu_refine_with_info(Backend &&backend, LuFactorization< F > const &factor, A const &a, B const &b, LuRefinementPrecision< F, W, R > p, RefinementOptions< make_real_t< R > > const &options={})
Refine a preserving solve using existing LU factors and the original A and B.
Definition lu_refine.hpp:273
auto lu_factor_with_info(Backend &&backend, A const &a, Precision p, LuOptions< make_real_t< tensor_element_t< A > > > const &options={})
Factor at explicit finite precision, preserving input values and defaults.
Definition ops/lu.hpp:114
constexpr detail::scalar_math::ldexp_fn ldexp
Multiply by a power of two using the scalar's exponent interface.
Definition math.hpp:511
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::frexp_fn frexp
Binary fraction and exponent as an owning result.
Definition math.hpp:535
Definition data_table.cpp:8
mpreal epsilon(Precision precision)
Spacing above one for a specified binary working precision.
Definition mpreal.hpp:805
Owning reusable host LU factors and solves through operation dispatch.
Preserving host residual and backward-error evaluation at explicit precision.
Scalar families and explicit precisions for three-precision LU refinement.
Definition refinement_info.hpp:15
Refinement outcome, retaining the last finite, diagnosed iterate on later failure.
Definition lu_refine.hpp:19
RefinementInfo info
Definition lu_refine.hpp:20
std::optional< DenseMatrix< W > > solution
Definition lu_refine.hpp:21
std::vector< RefinementColumn< make_real_t< R > > > columns
Definition lu_refine.hpp:22
Outer-loop outcome and, on a factor/solve failure, the provider's numerical diagnostics.
Definition refinement_info.hpp:53
RefinementStatus status
Definition refinement_info.hpp:54
Stopping policy; tolerance is expressed in the residual real scalar.
Definition refinement_info.hpp:61
Diagnostics written by a completed numerical attempt, independent of dispatch.
Definition solve_info.hpp:41
Project-level numeric limits customization point.
Definition numeric_limits.hpp:16
#define ERROR_IF(cond,...)
Definition trace.hpp:389