Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
taylor_exponential.hpp
Go to the documentation of this file.
1
6#pragma once
7
9
10#include <algorithm>
11#include <cmath>
12#include <concepts>
13#include <limits>
14#include <optional>
15#include <stdexcept>
16#include <type_traits>
17#include <utility>
18
19namespace uni20::krylov
20{
21
39
56
60template <uni20::RealOrComplex Scalar, typename Vector> struct TaylorExponentialResult
61{
62 Vector action;
64 int matvec_count = 0;
67 bool converged = true;
68 std::optional<TaylorExponentialDiagnostics<uni20::make_real_t<Scalar>>> diagnostics;
69};
70
71namespace detail
72{
73
74template <uni20::Real Scalar> Scalar default_taylor_exponential_tolerance()
75{
77}
78
79template <uni20::Real Scalar>
80Scalar effective_taylor_exponential_tolerance(TaylorExponentialParams<Scalar> const& params)
81{
82 return params.tolerance > Scalar{} ? params.tolerance : default_taylor_exponential_tolerance<Scalar>();
83}
84
85template <uni20::Real Scalar>
86void validate_taylor_exponential_params(Scalar operator_norm_bound, TaylorExponentialParams<Scalar> const& params)
87{
88 if (!uni20::isfinite(operator_norm_bound) || operator_norm_bound < Scalar{})
89 {
90 throw std::invalid_argument("Taylor exponential requires a finite non-negative operator norm bound");
91 }
92 if (params.tolerance < Scalar{} || !uni20::isfinite(params.tolerance))
93 {
94 throw std::invalid_argument("Taylor exponential requires a finite non-negative tolerance");
95 }
96 if (params.step_norm_limit <= Scalar{} || !uni20::isfinite(params.step_norm_limit))
97 {
98 throw std::invalid_argument("Taylor exponential requires a positive finite step norm limit");
99 }
100 if (params.max_taylor_degree <= 0)
101 {
102 throw std::invalid_argument("Taylor exponential requires a positive maximum Taylor degree");
103 }
104 if (params.max_scaling_steps <= 0)
105 {
106 throw std::invalid_argument("Taylor exponential requires a positive maximum scaling step count");
107 }
108}
109
110template <uni20::Real Scalar>
111int taylor_scaling_steps(Scalar scaled_operator_norm, TaylorExponentialParams<Scalar> const& params)
112{
113 if (scaled_operator_norm == Scalar{})
114 {
115 return 1;
116 }
117
118 using std::ceil;
119
120 Scalar const ratio = scaled_operator_norm / params.step_norm_limit;
121 Scalar const steps = ceil(std::max(Scalar{1}, ratio));
122 if (steps > static_cast<Scalar>(params.max_scaling_steps))
123 {
124 throw std::runtime_error("Taylor exponential requires more scaling steps than allowed");
125 }
126 if (steps > static_cast<Scalar>(uni20::numeric_limits<int>::max()))
127 {
128 throw std::overflow_error("Taylor exponential scaling step count exceeds int range");
129 }
130 return static_cast<int>(steps);
131}
132
133template <uni20::Real Scalar> Scalar taylor_tail_bound(Scalar term_norm, Scalar step_norm_bound, int degree)
134{
135 if (term_norm == Scalar{} || step_norm_bound == Scalar{})
136 {
137 return Scalar{};
138 }
139
140 Scalar const ratio = step_norm_bound / static_cast<Scalar>(degree + 1);
141 if (ratio >= Scalar{1})
142 {
144 }
145 return term_norm * ratio / (Scalar{1} - ratio);
146}
147
148template <uni20::Real Scalar>
149Scalar amplified_tail_bound(Scalar local_tail_bound, Scalar step_norm_bound, int remaining_steps)
150{
151 if (local_tail_bound == Scalar{} || remaining_steps <= 0)
152 {
153 return local_tail_bound;
154 }
155
156 using std::exp;
157 using std::log;
158
159 Scalar const exponent = static_cast<Scalar>(remaining_steps) * step_norm_bound;
160 Scalar const max_value = uni20::numeric_limits<Scalar>::max();
161 if (exponent >= log(max_value) - log(local_tail_bound))
162 {
164 }
165 return local_tail_bound * exp(exponent);
166}
167
168template <uni20::RealOrComplex Scalar, typename Vector>
169void attach_taylor_exponential_diagnostics(TaylorExponentialResult<Scalar, Vector>& result,
170 TaylorExponentialDiagnostics<uni20::make_real_t<Scalar>> const& diagnostics,
172{
173 if (level == KrylovDiagnosticsLevel::None)
174 {
175 return;
176 }
177 result.diagnostics = diagnostics;
178}
179
180} // namespace detail
181
203template <uni20::RealOrComplex Scalar, typename TimeScalar, typename Vector,
204 KrylovMatrixFreeOperator<Vector, Scalar> Ops>
205 requires(std::constructible_from<uni20::make_real_t<Scalar>, TimeScalar> ||
206 (uni20::Complex<Scalar> && std::constructible_from<Scalar, TimeScalar>))
207TaylorExponentialResult<Scalar, Vector>
208taylor_exponential_action(Ops& ops, Vector const& initial, TimeScalar time,
209 uni20::make_real_t<Scalar> operator_norm_bound,
210 TaylorExponentialParams<uni20::make_real_t<Scalar>> const& params = {})
211{
213
214 detail::validate_taylor_exponential_params(operator_norm_bound, params);
215 validate_matrix_free_dimensions(ops, initial, "Taylor exponential");
216
217 Scalar const time_scalar = static_cast<Scalar>(time);
218 Real const time_abs = static_cast<Real>(uni20::math::abs(time_scalar));
219 if (!uni20::isfinite(time_abs))
220 {
221 throw std::invalid_argument("Taylor exponential requires a finite time coefficient");
222 }
223
224 Real const tolerance = detail::effective_taylor_exponential_tolerance(params);
225 Real const scaled_operator_norm = time_abs * operator_norm_bound;
226 if (!uni20::isfinite(scaled_operator_norm))
227 {
228 throw std::overflow_error("Taylor exponential scaled operator norm overflowed");
229 }
230 int const scaling_steps = detail::taylor_scaling_steps(scaled_operator_norm, params);
231 Real const step_norm_bound = scaled_operator_norm / static_cast<Real>(scaling_steps);
232 Real const step_tolerance = tolerance / static_cast<Real>(scaling_steps);
233 Scalar const step_time = time_scalar / Scalar{static_cast<Real>(scaling_steps)};
234
235 Vector state = ops.allocate_like(initial);
236 ops.copy(state, initial);
237
238 TaylorExponentialDiagnostics<Real> diagnostics;
239 diagnostics.scaling_steps = scaling_steps;
240 diagnostics.tolerance = tolerance;
241 diagnostics.operator_norm_bound = operator_norm_bound;
242 diagnostics.step_norm_bound = step_norm_bound;
243
244 if (time_abs == Real{} || operator_norm_bound == Real{} || norm_or_inner_product<Scalar>(ops, state) == Real{})
245 {
246 TaylorExponentialResult<Scalar, Vector> result{.action = std::move(state),
247 .estimated_error = Real{},
248 .matvec_count = 0,
249 .scaling_steps = scaling_steps,
250 .max_degree_used = 0,
251 .converged = true,
252 .diagnostics = std::nullopt};
253 detail::attach_taylor_exponential_diagnostics(result, diagnostics, params.diagnostics);
254 return result;
255 }
256
257 Vector step_result = ops.allocate_like(initial);
258 Vector term = ops.allocate_like(initial);
259 Vector scratch = ops.allocate_like(initial);
260
261 bool all_steps_converged = true;
262 Real estimated_error = Real{};
263 int matvec_count = 0;
264 int max_degree_used = 0;
265
266 for (int step = 0; step < scaling_steps; ++step)
267 {
268 ops.copy(step_result, state);
269 ops.copy(term, state);
270
271 bool step_converged = false;
272 Real final_tail_bound = uni20::numeric_limits<Real>::infinity();
274 int degree_used = 0;
275
276 for (int degree = 1; degree <= params.max_taylor_degree; ++degree)
277 {
278 ops.matvec(scratch, term);
279 ++matvec_count;
280 ops.scal(scratch, step_time / static_cast<Real>(degree));
281 ops.copy(term, scratch);
282 ops.axpy(step_result, Scalar{1}, term);
283
284 final_term_norm = norm_or_inner_product<Scalar>(ops, term);
285 final_tail_bound = detail::taylor_tail_bound(final_term_norm, step_norm_bound, degree);
286 degree_used = degree;
287 if (final_tail_bound <= step_tolerance)
288 {
289 step_converged = true;
290 break;
291 }
292 }
293
294 max_degree_used = std::max(max_degree_used, degree_used);
295 diagnostics.final_step_degree = degree_used;
296 diagnostics.final_tail_bound = final_tail_bound;
297 diagnostics.final_term_norm = final_term_norm;
298 estimated_error += detail::amplified_tail_bound(final_tail_bound, step_norm_bound, scaling_steps - step - 1);
299
300 ops.copy(state, step_result);
301 if (!step_converged)
302 {
303 all_steps_converged = false;
304 if (params.throw_on_nonconvergence)
305 {
306 throw std::runtime_error("Taylor exponential did not satisfy the requested tolerance");
307 }
308 estimated_error = uni20::numeric_limits<Real>::infinity();
309 break;
310 }
311 }
312
313 diagnostics.matvec_count = matvec_count;
314 diagnostics.max_degree_used = max_degree_used;
315 diagnostics.estimated_error = estimated_error;
316 diagnostics.converged = all_steps_converged;
317
318 TaylorExponentialResult<Scalar, Vector> result{.action = std::move(state),
319 .estimated_error = estimated_error,
320 .matvec_count = matvec_count,
321 .scaling_steps = scaling_steps,
322 .max_degree_used = max_degree_used,
323 .converged = all_steps_converged,
324 .diagnostics = std::nullopt};
325 detail::attach_taylor_exponential_diagnostics(result, diagnostics, params.diagnostics);
326 return result;
327}
328
329} // namespace uni20::krylov
Concept for complex scalar types.
Definition scalar_concepts.hpp:75
Concept for types that are either real or complex.
Definition scalar_concepts.hpp:87
Concept for real scalar types (float, double, etc.).
Definition scalar_concepts.hpp:69
Concept for numeric scalar types (integer, real, or complex).
Definition scalar_concepts.hpp:81
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
R ratio(DiagnosticMagnitude< R > const &a, DiagnosticMagnitude< R > const &b)
Definition gmres_diagnostics.hpp:125
Definition block_tensor_vector.hpp:23
TaylorExponentialResult< Scalar, Vector > taylor_exponential_action(Ops &ops, Vector const &initial, TimeScalar time, uni20::make_real_t< Scalar > operator_norm_bound, TaylorExponentialParams< uni20::make_real_t< Scalar > > const &params={})
Compute exp(t A) v using conservative scaled Taylor series.
Definition taylor_exponential.hpp:208
KrylovDiagnosticsLevel
Amount of solver-internal diagnostic state to retain.
Definition matrix_free.hpp:37
std::size_t validate_matrix_free_dimensions(Ops const &ops, Vector const &vector, char const *context)
Validate that an initial/prototype vector matches the matrix-free problem dimension.
Definition matrix_free.hpp:549
constexpr detail::scalar_math::abs_fn abs
Absolute value or complex magnitude, preserving the selected scalar implementation.
Definition math.hpp:459
Definition async/async.hpp:26
mpcomplex log(mpcomplex const &z)
Principal logarithm, with the sign of imaginary zero selecting the cut side.
Definition mpcomplex.hpp:505
auto degree(QNum const &q) -> int
Return the integral degree of a quantum number.
Definition qnum.hpp:270
mpreal ceil(mpreal const &x)
Round toward positive infinity; exact inputs produce exact integers.
Definition mpreal_functions.hpp:72
mpcomplex exp(mpcomplex const &z)
Definition mpcomplex.hpp:503
Diagnostic state for Taylor exponential actions.
Definition taylor_exponential.hpp:43
int final_step_degree
Definition taylor_exponential.hpp:47
Scalar operator_norm_bound
Definition taylor_exponential.hpp:49
int max_degree_used
Definition taylor_exponential.hpp:46
int scaling_steps
Definition taylor_exponential.hpp:45
Scalar tolerance
Definition taylor_exponential.hpp:48
int matvec_count
Definition taylor_exponential.hpp:44
Scalar final_tail_bound
Definition taylor_exponential.hpp:52
Scalar final_term_norm
Definition taylor_exponential.hpp:53
bool converged
Definition taylor_exponential.hpp:54
Scalar estimated_error
Definition taylor_exponential.hpp:51
Scalar step_norm_bound
Definition taylor_exponential.hpp:50
Parameters for conservative Taylor scaling exponential actions.
Definition taylor_exponential.hpp:25
KrylovDiagnosticsLevel diagnostics
Optional diagnostic retention level.
Definition taylor_exponential.hpp:37
Scalar step_norm_limit
Target upper bound for ||h A|| in one scaled Taylor step.
Definition taylor_exponential.hpp:29
int max_scaling_steps
Maximum number of scaled Taylor steps allowed.
Definition taylor_exponential.hpp:33
Scalar tolerance
Absolute action tolerance; zero selects 100 * epsilon.
Definition taylor_exponential.hpp:27
int max_taylor_degree
Maximum Taylor degree allowed within one scaled step.
Definition taylor_exponential.hpp:31
bool throw_on_nonconvergence
Throw if any scaled step fails to satisfy the tail bound.
Definition taylor_exponential.hpp:35
Result for validation-oriented Taylor exponential actions.
Definition taylor_exponential.hpp:61
std::optional< TaylorExponentialDiagnostics< uni20::make_real_t< Scalar > > > diagnostics
Definition taylor_exponential.hpp:68
uni20::make_real_t< Scalar > estimated_error
Definition taylor_exponential.hpp:63
int max_degree_used
Definition taylor_exponential.hpp:66
int scaling_steps
Definition taylor_exponential.hpp:65
int matvec_count
Definition taylor_exponential.hpp:64
bool converged
Definition taylor_exponential.hpp:67
Vector action
Definition taylor_exponential.hpp:62
Project-level numeric limits customization point.
Definition numeric_limits.hpp:16