69template <uni20::Real Scalar>
struct LanczosOrthogonalizationDiagnostics
72 Scalar max_reorthogonalization_correction_ratio = Scalar{};
73 int max_reorthogonalization_passes = 0;
76template <uni20::Real Scalar>
77void record_lanczos_orthogonalization_pass(LanczosOrthogonalizationDiagnostics<Scalar>* diagnostics,
78 Scalar max_correction, Scalar reference_norm,
int pass_count)
80 if (diagnostics ==
nullptr)
85 diagnostics->max_reorthogonalization_correction =
86 std::max(diagnostics->max_reorthogonalization_correction, max_correction);
87 if (reference_norm > Scalar{})
89 diagnostics->max_reorthogonalization_correction_ratio =
90 std::max(diagnostics->max_reorthogonalization_correction_ratio, max_correction / reference_norm);
92 diagnostics->max_reorthogonalization_passes = std::max(diagnostics->max_reorthogonalization_passes, pass_count);
95template <uni20::RealOrComplex Scalar,
typename Vector,
typename OpOps,
typename BOps,
typename MetricOps>
96class SymmetricBMetricOps {
98 SymmetricBMetricOps(OpOps& op_ops, BOps& b_ops, MetricOps& metric_ops)
99 : op_ops_(op_ops), b_ops_(b_ops), metric_ops_(metric_ops)
102 [[nodiscard]] std::size_t problem_dimension()
const {
return op_ops_.problem_dimension(); }
104 [[nodiscard]] std::size_t vector_dimension(Vector
const& x)
const {
return op_ops_.vector_dimension(x); }
106 [[nodiscard]] Vector allocate_like(Vector
const& x) {
return op_ops_.allocate_like(x); }
108 void copy(Vector& dst, Vector
const& src) { op_ops_.copy(dst, src); }
110 void axpy(Vector& y, Scalar alpha, Vector
const& x) { op_ops_.axpy(y, alpha, x); }
112 void scal(Vector& x, Scalar alpha) { op_ops_.scal(x, alpha); }
114 void set_zero(Vector& x) { op_ops_.set_zero(x); }
116 [[nodiscard]] Scalar
inner_product(Vector
const& x, Vector
const& y)
118 if (!scratch_.has_value())
120 scratch_.emplace(b_ops_.allocate_like(y));
122 return metric_inner_product_or_apply<Scalar>(metric_ops_, b_ops_, op_ops_, x, y, *scratch_);
125 void matvec(Vector& y, Vector
const& x) { op_ops_.matvec(y, x); }
130 MetricOps& metric_ops_;
131 std::optional<Vector> scratch_;
134template <uni20::Real Scalar>
struct GivensRotation
136 Scalar
c = Scalar{1};
141template <uni20::Real Scalar> GivensRotation<Scalar> lartg_like(Scalar f, Scalar g)
145 return GivensRotation<Scalar>{f < Scalar{} ? Scalar{-1} : Scalar{1}, Scalar{},
uni20::math::abs(f)};
149 return GivensRotation<Scalar>{Scalar{}, g < Scalar{} ? Scalar{-1} : Scalar{1},
uni20::math::abs(g)};
155 return GivensRotation<Scalar>{f / r, g / r, r};
158template <uni20::Real Scalar>
163 Scalar
const q_lhs = q[
row, lhs];
164 Scalar
const q_rhs = q[
row, rhs];
165 q[
row, lhs] =
c * q_lhs + s * q_rhs;
166 q[
row, rhs] = -s * q_lhs +
c * q_rhs;
170#if defined(__cpp_lib_constexpr_cmath)
171template <uni20::Real Scalar>
constexpr Scalar ritz_convergence_floor()
173template <uni20::Real Scalar> Scalar ritz_convergence_floor()
179template <uni20::Real Scalar>
bool symmetric_ritz_converged(Scalar
value, Scalar residual_bound, Scalar tolerance)
181 Scalar
const eps23 = ritz_convergence_floor<Scalar>();
185template <uni20::Real Scalar>
186Scalar symmetric_breakdown_threshold(SymmetricEigenParams<Scalar>
const& params, Scalar local_scale)
188 Scalar
const tolerance = params.breakdown_tolerance > Scalar{}
189 ? params.breakdown_tolerance
191 return tolerance * std::max(Scalar{1}, local_scale);
194template <uni20::Real Scalar>
195bool symmetric_happy_breakdown(Scalar residual_norm, SymmetricEigenParams<Scalar>
const& params, Scalar local_scale)
197 return residual_norm <= symmetric_breakdown_threshold(params, local_scale);
200template <uni20::RealOrComplex Scalar,
typename Vector,
typename Ops>
203 return norm_or_inner_product<Scalar>(ops, action);
206template <uni20::RealOrComplex Scalar>
218 throw std::runtime_error(
"Hermitian Lanczos received a projected diagonal with non-negligible imaginary part");
224template <uni20::RealOrComplex Scalar,
typename Vector,
typename Ops>
226orthogonalize_lanczos_residual(Ops& ops, std::vector<Vector>
const& basis, Vector& residual,
230 auto norm = [&]() {
return norm_or_inner_product<Scalar>(ops, residual); };
231 auto orthogonalize_once = [&]() {
232 Real max_correction{};
233 for (Vector
const& basis_vector : basis)
235 Scalar
const correction = ops.inner_product(basis_vector, residual);
237 ops.axpy(residual, -correction, basis_vector);
239 return max_correction;
243 Real const first_max_correction = orthogonalize_once();
244 record_lanczos_orthogonalization_pass(diagnostics, first_max_correction, original_norm, 1);
246 if (original_norm ==
Real{} || residual_norm >
Real{0.717} * original_norm)
248 return residual_norm;
251 Real const first_refined_norm = residual_norm;
252 Real const second_max_correction = orthogonalize_once();
253 record_lanczos_orthogonalization_pass(diagnostics, second_max_correction, first_refined_norm, 2);
254 residual_norm =
norm();
255 if (first_refined_norm ==
Real{} || residual_norm <=
Real{0.717} * first_refined_norm)
257 ops.set_zero(residual);
260 return residual_norm;
263enum class SymmetricResultPolicy
269template <uni20::Real Scalar>
270std::size_t effective_retained_ritz_count(SymmetricEigenParams<Scalar>
const& params, std::size_t available_count)
272 if (params.eigenvalue_count <= 0)
274 throw std::invalid_argument(
"symmetric restart selection requires a positive eigenvalue count");
278 throw std::invalid_argument(
"both-end symmetric selection requires eigenvalue_count > 1");
281 std::size_t
const requested_count =
static_cast<std::size_t
>(params.eigenvalue_count);
282 std::size_t
const retained_count = params.retained_ritz_count > 0
283 ?
static_cast<std::size_t
>(params.retained_ritz_count)
285 params.eigenvalue_count, static_cast<int>(available_count)));
286 if (retained_count < requested_count)
288 throw std::invalid_argument(
"retained Ritz count must be at least the requested eigenvalue count");
290 if (retained_count > available_count)
292 throw std::invalid_argument(
"retained Ritz count cannot exceed the projected subspace size");
294 return retained_count;
297template <uni20::Real Scalar>
298std::vector<std::size_t> ordered_symmetric_ritz_indices(std::vector<Scalar>
const& values,
SpectrumPart spectrum)
300 std::vector<std::size_t> indices(
values.size());
301 std::iota(indices.begin(), indices.end(), std::size_t{0});
306 std::ranges::sort(indices,
310 std::ranges::sort(indices,
315 std::ranges::sort(indices, [&](std::size_t lhs, std::size_t rhs) {
return values[lhs] <
values[rhs]; });
319 std::ranges::sort(indices, [&](std::size_t lhs, std::size_t rhs) {
return values[lhs] >
values[rhs]; });
322 std::ranges::sort(indices, [&](std::size_t lhs, std::size_t rhs) {
return values[lhs] <
values[rhs]; });
326 throw std::invalid_argument(
"imaginary spectrum selectors are not valid for symmetric/Hermitian Lanczos");
331template <uni20::Real Scalar>
332std::vector<std::size_t> select_symmetric_ritz_indices(std::vector<Scalar>
const& values,
333 SymmetricEigenParams<Scalar>
const& params)
337 throw std::invalid_argument(
"both-end symmetric selection requires eigenvalue_count > 1");
340 std::vector<std::size_t> indices(
values.size());
341 std::iota(indices.begin(), indices.end(), std::size_t{0});
343 switch (params.spectrum)
346 std::ranges::sort(indices,
350 std::ranges::sort(indices,
355 std::ranges::sort(indices, [&](std::size_t lhs, std::size_t rhs) {
return values[lhs] >
values[rhs]; });
359 std::ranges::sort(indices, [&](std::size_t lhs, std::size_t rhs) {
return values[lhs] <
values[rhs]; });
362 std::ranges::sort(indices, [&](std::size_t lhs, std::size_t rhs) {
return values[lhs] <
values[rhs]; });
364 std::size_t
const requested_count =
365 std::min<std::size_t>(
static_cast<std::size_t
>(params.eigenvalue_count), indices.size());
366 std::size_t
const low_count = requested_count / 2;
367 std::size_t
const high_count = requested_count - low_count;
368 std::vector<std::size_t> both_end_indices;
369 both_end_indices.reserve(requested_count);
370 both_end_indices.insert(both_end_indices.end(), indices.begin(),
371 indices.begin() +
static_cast<std::ptrdiff_t
>(low_count));
372 both_end_indices.insert(both_end_indices.end(), indices.end() -
static_cast<std::ptrdiff_t
>(high_count),
374 return both_end_indices;
379 throw std::invalid_argument(
"imaginary spectrum selectors are not valid for symmetric/Hermitian Lanczos");
382 std::size_t
const count = std::min<std::size_t>(
static_cast<std::size_t
>(params.eigenvalue_count), indices.size());
383 indices.resize(count);
387template <uni20::Real Scalar>
388std::vector<Scalar> symmetric_ritz_residual_bounds(Scalar residual_norm,
389 TridiagonalEigensystem<Scalar>
const& projected)
391 std::vector<Scalar> residual_bounds(projected.eigenvalues.size(), Scalar{});
392 for (std::size_t i = 0; i < residual_bounds.size(); ++i)
394 residual_bounds[i] = residual_norm *
uni20::math::abs(projected.eigenvectors[projected.eigenvectors.rows() - 1, i]);
396 return residual_bounds;
399template <uni20::Real Scalar>
400void record_symmetric_final_diagnostics(SymmetricLanczosDiagnostics<Scalar>& diagnostics,
401 TridiagonalEigensystem<Scalar>
const& projected,
402 std::vector<Scalar>
const& residual_bounds,
403 SymmetricEigenParams<Scalar>
const& params, Scalar residual_norm)
405 diagnostics.final_projected_dimension =
static_cast<int>(projected.eigenvalues.size());
406 diagnostics.final_residual_norm = residual_norm;
407 diagnostics.final_ritz_values = projected.eigenvalues;
408 diagnostics.final_ritz_bounds = residual_bounds;
409 diagnostics.final_selected_indices = select_symmetric_ritz_indices(projected.eigenvalues, params);
412template <uni20::Real Scalar>
413void record_symmetric_restart_cycle(SymmetricLanczosDiagnostics<Scalar>& diagnostics,
int cycle,
414 TridiagonalEigensystem<Scalar>
const& projected,
415 std::vector<Scalar>
const& residual_bounds,
416 SymmetricRestartPlan<Scalar>
const& restart_plan,
417 SymmetricRestartSelection<Scalar>
const& selection, Scalar residual_norm)
419 diagnostics.restart_count =
420 std::max(diagnostics.restart_count,
static_cast<int>(diagnostics.restart_cycles.size()) + 1);
421 diagnostics.restart_cycles.push_back(SymmetricRestartCycleDiagnostics<Scalar>{
423 .converged_count = selection.converged_count,
424 .projected_dimension = projected.eigenvalues.size(),
425 .retained_count = restart_plan.retained_count,
426 .shift_count = restart_plan.shift_count,
427 .protected_zero_bound_count = restart_plan.protected_zero_bound_count,
428 .enlarged_for_partial_convergence = restart_plan.enlarged_for_partial_convergence,
429 .residual_norm = residual_norm,
430 .ritz_values = projected.eigenvalues,
431 .ritz_bounds = residual_bounds,
432 .shifts = selection.shifts,
433 .wanted_indices = selection.wanted_indices,
434 .shift_indices = selection.shift_indices,
435 .wanted_converged = selection.wanted_converged,
439template <uni20::LapackRealOrComplex Scalar,
typename Vector, KrylovMatrixFreeOperator<Vector, Scalar> Ops>
440EigenResult<uni20::make_real_t<Scalar>, Vector>
444 int matvec_count, SymmetricResultPolicy policy = SymmetricResultPolicy::Requested)
447 Real const tolerance =
450 EigenResult<Real, Vector> result;
451 result.iteration_count = iteration_count;
452 result.matvec_count = matvec_count;
454 std::vector<std::size_t> selected = select_symmetric_ritz_indices(projected.eigenvalues, params);
455 if (policy == SymmetricResultPolicy::ConvergedWanted)
457 std::erase_if(selected, [&](std::size_t ritz_index) {
458 Real const residual_bound =
459 residual_norm *
uni20::math::abs(projected.eigenvectors[projected.eigenvectors.rows() - 1, ritz_index]);
460 return !symmetric_ritz_converged(projected.eigenvalues[ritz_index], residual_bound, tolerance);
464 result.eigenvalues.reserve(selected.size());
465 result.residual_bounds.reserve(selected.size());
466 result.eigenvectors.reserve(params.compute_eigenvectors ? selected.size() : 0);
467 for (std::size_t
const ritz_index : selected)
469 result.eigenvalues.push_back(projected.eigenvalues[ritz_index]);
470 Real const residual_bound =
471 residual_norm *
uni20::math::abs(projected.eigenvectors[projected.eigenvectors.rows() - 1, ritz_index]);
472 result.residual_bounds.push_back(residual_bound);
473 if (symmetric_ritz_converged(projected.eigenvalues[ritz_index], residual_bound, tolerance))
475 ++result.converged_count;
477 if (params.compute_eigenvectors)
479 Vector vector = ops.allocate_like(basis.front());
480 ops.set_zero(vector);
481 for (std::size_t
row = 0;
row < basis.size(); ++
row)
483 ops.axpy(vector,
static_cast<Scalar
>(projected.eigenvectors[
row, ritz_index]), basis[
row]);
485 result.eigenvectors.push_back(std::move(vector));
489 bool const requested_count_available = result.eigenvalues.size() >=
static_cast<std::size_t
>(params.eigenvalue_count);
490 bool const requested_count_converged = result.converged_count >= params.eigenvalue_count;
491 result.status = requested_count_available && requested_count_converged ? 0 : 1;
510template <uni20::Real Scalar>
511SymmetricRestartSelection<Scalar>
514 std::span<std::size_t const> protected_shift_indices = {})
516 if (values.size() != residual_bounds.size())
518 throw std::invalid_argument(
"symmetric restart selection received inconsistent Ritz arrays");
520 std::size_t
const wanted_count = detail::effective_retained_ritz_count(params, values.size());
521 std::vector<std::size_t> ordered = detail::ordered_symmetric_ritz_indices(values, params.
spectrum);
522 std::vector<std::size_t> wanted_indices;
523 std::vector<std::size_t> unwanted_indices;
526 std::size_t
const low_count = wanted_count / 2;
527 std::size_t
const high_count = wanted_count - low_count;
528 wanted_indices.reserve(wanted_count);
529 wanted_indices.insert(wanted_indices.end(), ordered.begin(),
530 ordered.begin() +
static_cast<std::ptrdiff_t
>(low_count));
531 wanted_indices.insert(wanted_indices.end(), ordered.end() -
static_cast<std::ptrdiff_t
>(high_count), ordered.end());
532 unwanted_indices.assign(ordered.begin() +
static_cast<std::ptrdiff_t
>(low_count),
533 ordered.end() -
static_cast<std::ptrdiff_t
>(high_count));
537 wanted_indices.assign(ordered.end() -
static_cast<std::ptrdiff_t
>(wanted_count), ordered.end());
538 unwanted_indices.assign(ordered.begin(), ordered.end() -
static_cast<std::ptrdiff_t
>(wanted_count));
541 std::vector<bool> protected_index(
values.size(),
false);
542 for (std::size_t
const index : protected_shift_indices)
544 if (index >=
values.size())
546 throw std::invalid_argument(
"symmetric restart selection received an invalid protected Ritz index");
548 protected_index[index] =
true;
549 if (std::ranges::find(wanted_indices, index) == wanted_indices.end())
551 wanted_indices.push_back(index);
554 std::erase_if(unwanted_indices, [&](std::size_t index) {
return protected_index[index]; });
555 shift_count = std::min(shift_count, unwanted_indices.size());
557 Scalar
const tolerance =
560 SymmetricRestartSelection<Scalar> selection;
561 selection.wanted_indices = std::move(wanted_indices);
562 selection.wanted_converged.reserve(selection.wanted_indices.size());
563 for (std::size_t
const index : selection.wanted_indices)
565 bool const converged = detail::symmetric_ritz_converged(values[index], residual_bounds[index], tolerance);
566 selection.wanted_converged.push_back(
converged);
569 ++selection.converged_count;
573 selection.shift_indices.assign(unwanted_indices.begin(),
574 unwanted_indices.begin() +
static_cast<std::ptrdiff_t
>(shift_count));
575 std::ranges::sort(selection.shift_indices, [&](std::size_t lhs, std::size_t rhs) {
576 return uni20::math::abs(residual_bounds[lhs]) > uni20::math::abs(residual_bounds[rhs]);
578 selection.shifts.reserve(selection.shift_indices.size());
579 for (std::size_t
const index : selection.shift_indices)
581 selection.shifts.push_back(values[index]);
600template <uni20::Real Scalar>
602 std::vector<Scalar>
const& values,
603 std::vector<Scalar>
const& residual_bounds,
int converged_count)
605 if (values.size() != residual_bounds.size())
607 throw std::invalid_argument(
"symmetric restart planning received inconsistent Ritz arrays");
610 std::size_t
const order = values.size();
611 std::size_t retained_count = detail::effective_retained_ritz_count(params, order);
612 if (retained_count >= order)
614 throw std::invalid_argument(
"symmetric restart planning requires retained_count < projected size");
617 std::size_t shift_count = order - retained_count;
623 if (residual_bounds[index] ==
Scalar{})
633 shift_count = order - retained_count;
636 if (converged_count < params.eigenvalue_count && shift_count > 0)
638 std::size_t
const old_retained_count = retained_count;
639 retained_count += std::min<std::size_t>(
static_cast<std::size_t
>(converged_count), shift_count / 2);
640 if (retained_count == 1 && order >= 6)
642 retained_count = order / 2;
644 else if (retained_count == 1 && order > 2)
648 retained_count = std::min(retained_count, order);
649 shift_count = order - retained_count;
669template <uni20::Real Scalar>
671 std::vector<Scalar>
const& shifts)
673 std::size_t
const order = diagonal.size();
674 if (subdiagonal.size() + 1 != order && !(order == 0 && subdiagonal.empty()))
676 throw std::invalid_argument(
"symmetric QR shifts received inconsistent tridiagonal sizes");
682 if (order == 0 || shifts.empty())
684 result.
diagonal = std::move(diagonal);
690 std::size_t itop = 0;
691 for (
Scalar const shift : shifts)
693 std::size_t istart = itop;
694 while (istart + 1 < order)
696 std::size_t iend = order - 1;
697 for (std::size_t i = istart; i + 1 < order; ++i)
702 subdiagonal[i] =
Scalar{};
710 Scalar f = diagonal[istart] - shift;
711 Scalar g = subdiagonal[istart];
712 auto rotation = detail::lartg_like(f, g);
716 Scalar a1 =
c * diagonal[istart] + s * subdiagonal[istart];
717 Scalar a2 =
c * subdiagonal[istart] + s * diagonal[istart + 1];
718 Scalar a4 =
c * diagonal[istart + 1] - s * subdiagonal[istart];
719 Scalar a3 =
c * subdiagonal[istart] - s * diagonal[istart];
720 diagonal[istart] =
c * a1 + s * a2;
721 diagonal[istart + 1] =
c * a4 - s * a3;
722 subdiagonal[istart] =
c * a3 + s * a4;
723 detail::apply_givens_to_transform(result.
transform, istart, istart + 1,
c, s);
725 for (std::size_t i = istart + 1; i < iend; ++i)
727 f = subdiagonal[i - 1];
728 g = s * subdiagonal[i];
730 rotation = detail::lartg_like(f, g);
741 subdiagonal[i - 1] = r;
742 a1 =
c * diagonal[i] + s * subdiagonal[i];
743 a2 =
c * subdiagonal[i] + s * diagonal[i + 1];
744 a3 =
c * subdiagonal[i] - s * diagonal[i];
745 a4 =
c * diagonal[i + 1] - s * subdiagonal[i];
746 diagonal[i] =
c * a1 + s * a2;
747 diagonal[i + 1] =
c * a4 - s * a3;
748 subdiagonal[i] =
c * a3 + s * a4;
749 detail::apply_givens_to_transform(result.
transform, i, i + 1,
c, s);
753 if (iend > 0 && subdiagonal[iend - 1] <
Scalar{})
755 subdiagonal[iend - 1] = -subdiagonal[iend - 1];
765 for (std::size_t i = itop; i + 1 < order; ++i)
767 if (subdiagonal[i] >
Scalar{})
775 for (std::size_t i = itop; i + 1 < order; ++i)
780 subdiagonal[i] =
Scalar{};
784 result.
diagonal = std::move(diagonal);
807template <uni20::LapackRealOrComplex Scalar,
typename Vector, KrylovMatrixFreeOperator<Vector, Scalar> Ops>
808SymmetricCompressedLanczosFactorization<uni20::make_real_t<Scalar>, Vector>
811 std::size_t retained_count)
814 std::size_t
const order = basis.size();
817 throw std::invalid_argument(
"symmetric restart compression requires a nonempty basis");
819 if (shifted.diagonal.size() != order || shifted.subdiagonal.size() + 1 != order)
821 throw std::invalid_argument(
"symmetric restart compression received inconsistent tridiagonal sizes");
824 if (shifted.transform.rows() != order_extent || shifted.transform.cols() != order_extent)
826 throw std::invalid_argument(
"symmetric restart compression received an inconsistent transform size");
828 if (retained_count == 0 || retained_count > order)
830 throw std::invalid_argument(
"symmetric restart compression received an invalid retained count");
833 auto transformed_column = [&](std::size_t column) {
834 Vector vector = ops.allocate_like(basis.front());
835 ops.set_zero(vector);
836 for (std::size_t
row = 0;
row < order; ++
row)
838 Real const coefficient = shifted.transform[
row, column];
839 if (coefficient !=
Real{})
841 ops.axpy(vector,
static_cast<Scalar>(coefficient), basis[
row]);
849 .residual = ops.allocate_like(residual),
852 .residual_norm =
Real{},
853 .residual_scale = shifted.transform[order - 1, retained_count - 1],
854 .trailing_coupling = retained_count < order ? shifted.subdiagonal[retained_count - 1] :
Real{}};
856 result.basis.reserve(retained_count);
857 for (std::size_t column = 0; column < retained_count; ++column)
859 result.basis.push_back(transformed_column(column));
862 result.diagonal.assign(shifted.diagonal.begin(),
863 shifted.diagonal.begin() +
static_cast<std::ptrdiff_t
>(retained_count));
864 if (retained_count > 1)
866 result.subdiagonal.assign(shifted.subdiagonal.begin(),
867 shifted.subdiagonal.begin() +
static_cast<std::ptrdiff_t
>(retained_count - 1));
870 ops.copy(result.residual, residual);
871 ops.scal(result.residual,
static_cast<Scalar>(result.residual_scale));
872 if (result.trailing_coupling !=
Real{})
874 Vector next_vector = transformed_column(retained_count);
875 ops.axpy(result.residual,
static_cast<Scalar>(result.trailing_coupling), next_vector);
878 result.residual_norm = norm_or_inner_product<Scalar>(ops, result.residual);
895template <uni20::LapackRealOrComplex Scalar,
typename Vector, KrylovMatrixFreeOperator<Vector, Scalar> Ops>
896EigenResult<uni20::make_real_t<Scalar>, Vector>
903 throw std::invalid_argument(
"symmetric Lanczos requires a positive eigenvalue count");
908 if (basis_limit <= 0)
910 throw std::invalid_argument(
"symmetric Lanczos requires a positive Krylov dimension");
912 if (basis_limit >
static_cast<int>(problem_dimension))
914 throw std::invalid_argument(
"symmetric Lanczos requires krylov_dimension <= problem dimension");
918 throw std::invalid_argument(
"symmetric Lanczos requires eigenvalue_count <= krylov_dimension");
922 std::vector<Vector> basis;
923 std::vector<Real> diagonal;
924 std::vector<Real> subdiagonal;
925 Real final_residual_norm{};
926 basis.reserve(
static_cast<std::size_t
>(basis_limit));
927 diagonal.reserve(
static_cast<std::size_t
>(basis_limit));
928 subdiagonal.reserve(
static_cast<std::size_t
>(basis_limit > 0 ? basis_limit - 1 : 0));
930 Vector start = ops.allocate_like(initial);
931 ops.copy(start, initial);
932 Real const start_norm = norm_or_inner_product<Scalar>(ops, start);
933 if (start_norm <=
Real{})
935 throw std::invalid_argument(
"symmetric Lanczos initial vector has zero norm");
937 ops.scal(start,
Scalar{1} /
static_cast<Scalar>(start_norm));
938 basis.push_back(std::move(start));
940 for (
int step = 0; step < basis_limit; ++step)
942 Vector w = ops.allocate_like(basis[
static_cast<std::size_t
>(step)]);
943 ops.matvec(w, basis[
static_cast<std::size_t
>(step)]);
948 ops.axpy(w, -
static_cast<Scalar>(subdiagonal[
static_cast<std::size_t
>(step - 1)]),
949 basis[
static_cast<std::size_t
>(step - 1)]);
952 Real const projection_scale = detail::hermitian_projection_scale<Scalar>(ops, w);
953 Real const alpha = detail::hermitian_projection_scalar(ops.inner_product(basis[
static_cast<std::size_t
>(step)], w),
955 diagonal.push_back(alpha);
956 ops.axpy(w, -
static_cast<Scalar>(alpha), basis[
static_cast<std::size_t
>(step)]);
958 Real const beta = detail::orthogonalize_lanczos_residual<Scalar>(ops, basis, w);
959 Real const previous_beta = step > 0 ? subdiagonal[
static_cast<std::size_t
>(step - 1)] :
Real{};
960 Real const breakdown_scale =
962 if (step + 1 == basis_limit || detail::symmetric_happy_breakdown(beta, params, breakdown_scale))
964 final_residual_norm = beta;
969 subdiagonal.push_back(beta);
970 basis.push_back(std::move(w));
975 auto final_result = detail::make_symmetric_lanczos_result<Scalar>(ops, basis, final_residual_norm, projected, params,
977 if (params.
diagnostics != KrylovDiagnosticsLevel::None)
981 std::vector<Real>
const residual_bounds = detail::symmetric_ritz_residual_bounds(final_residual_norm, projected);
982 detail::record_symmetric_final_diagnostics(diagnostics, projected, residual_bounds, params, final_residual_norm);
983 final_result.diagnostics = std::move(diagnostics);
1001template <uni20::LapackRealOrComplex Scalar,
typename Vector, KrylovMatrixFreeOperator<Vector, Scalar> Ops>
1002EigenResult<uni20::make_real_t<Scalar>, Vector>
1009 throw std::invalid_argument(
"restarted symmetric Lanczos requires a positive eigenvalue count");
1013 throw std::invalid_argument(
"restarted symmetric Lanczos requires a positive iteration limit");
1020 throw std::invalid_argument(
"restarted symmetric Lanczos requires eigenvalue_count < krylov_dimension");
1022 if (basis_limit >
static_cast<int>(problem_dimension))
1024 throw std::invalid_argument(
"restarted symmetric Lanczos requires krylov_dimension <= problem dimension");
1027 std::size_t
const ncv =
static_cast<std::size_t
>(basis_limit);
1028 std::size_t
const retained_count =
1030 if (retained_count >= ncv)
1032 throw std::invalid_argument(
"restarted symmetric Lanczos requires retained_ritz_count < krylov_dimension");
1035 std::vector<Vector> basis;
1036 std::vector<Real> diagonal;
1037 std::vector<Real> subdiagonal;
1039 diagonal.reserve(ncv);
1040 subdiagonal.reserve(ncv - 1);
1042 Vector start = ops.allocate_like(initial);
1043 ops.copy(start, initial);
1044 Real const start_norm = norm_or_inner_product<Scalar>(ops, start);
1045 if (start_norm <=
Real{})
1047 throw std::invalid_argument(
"restarted symmetric Lanczos initial vector has zero norm");
1049 ops.scal(start,
Scalar{1} /
static_cast<Scalar>(start_norm));
1050 basis.push_back(std::move(start));
1052 int matvec_count = 0;
1053 int restart_cycle_count = 0;
1054 int restart_count = 0;
1057 std::optional<SymmetricLanczosDiagnostics<Real>> diagnostics;
1058 if (params.
diagnostics != KrylovDiagnosticsLevel::None)
1060 diagnostics.emplace();
1064 if (diagnostics.has_value())
1066 diagnostics->restart_count = restart_count;
1067 diagnostics->op_count = matvec_count;
1068 result.diagnostics = *diagnostics;
1075 restart_cycle_count = cycle + 1;
1076 Real residual_norm{};
1077 bool cycle_happy_breakdown =
false;
1078 Vector residual = ops.allocate_like(basis.front());
1079 ops.set_zero(residual);
1081 while (diagonal.size() < ncv)
1083 std::size_t
const step = diagonal.size();
1084 if (basis.size() != step + 1)
1086 throw std::logic_error(
"restarted symmetric Lanczos basis/projection state is inconsistent");
1089 Vector w = ops.allocate_like(basis[step]);
1090 ops.matvec(w, basis[step]);
1095 ops.axpy(w, -
static_cast<Scalar>(subdiagonal[step - 1]), basis[step - 1]);
1098 Real const projection_scale = detail::hermitian_projection_scale<Scalar>(ops, w);
1099 Real const alpha = detail::hermitian_projection_scalar(ops.inner_product(basis[step], w), projection_scale);
1100 diagonal.push_back(alpha);
1101 ops.axpy(w, -
static_cast<Scalar>(alpha), basis[step]);
1103 Real const beta = detail::orthogonalize_lanczos_residual<Scalar>(ops, basis, w);
1104 Real const previous_beta = step > 0 ? subdiagonal[step - 1] :
Real{};
1105 Real const breakdown_scale =
1107 cycle_happy_breakdown = detail::symmetric_happy_breakdown(beta, params, breakdown_scale);
1108 residual = std::move(w);
1109 residual_norm = beta;
1110 if (diagonal.size() == ncv || cycle_happy_breakdown)
1115 ops.scal(residual,
Scalar{1} /
static_cast<Scalar>(beta));
1116 subdiagonal.push_back(beta);
1117 basis.push_back(std::move(residual));
1121 std::vector<Real> residual_bounds = detail::symmetric_ritz_residual_bounds(residual_norm, projected_for_result);
1122 if (diagnostics.has_value())
1124 detail::record_symmetric_final_diagnostics(*diagnostics, projected_for_result, residual_bounds, params,
1132 last_result = detail::make_symmetric_lanczos_result<Scalar>(ops, basis, residual_norm, projected_for_result,
1133 cycle_result_params, restart_cycle_count, matvec_count);
1134 last_partial_exit_result = detail::make_symmetric_lanczos_result<Scalar>(
1135 ops, basis, residual_norm, projected_for_result, cycle_result_params, restart_cycle_count, matvec_count,
1136 detail::SymmetricResultPolicy::ConvergedWanted);
1137 if (last_result.
status == 0 || cycle_happy_breakdown || diagonal.size() < ncv)
1141 return finish(detail::make_symmetric_lanczos_result<Scalar>(ops, basis, residual_norm, projected_for_result,
1142 params, restart_cycle_count, matvec_count));
1144 return finish(std::move(last_result));
1156 if (selection.
shifts.empty())
1160 ? detail::make_symmetric_lanczos_result<Scalar>(ops, basis, residual_norm, projected_for_result, params,
1161 restart_cycle_count, matvec_count,
1162 detail::SymmetricResultPolicy::ConvergedWanted)
1163 : std::move(last_partial_exit_result);
1165 return finish(std::move(exit_result));
1169 if (diagnostics.has_value() && params.
diagnostics == KrylovDiagnosticsLevel::Full)
1171 detail::record_symmetric_restart_cycle(*diagnostics, cycle, projected_for_result, residual_bounds, restart_plan,
1172 selection, residual_norm);
1177 compress_symmetric_lanczos_restart<Scalar>(ops, basis, residual, shifted, restart_plan.
retained_count);
1179 basis = std::move(compressed.basis);
1180 diagonal = std::move(compressed.diagonal);
1181 subdiagonal = std::move(compressed.subdiagonal);
1182 residual = std::move(compressed.residual);
1183 residual_norm = compressed.residual_norm;
1184 Real const compressed_breakdown_scale =
1187 if (detail::symmetric_happy_breakdown(residual_norm, params, compressed_breakdown_scale))
1190 std::vector<Real>
const after_restart_bounds =
1191 detail::symmetric_ritz_residual_bounds(residual_norm, projected_after_restart);
1192 if (diagnostics.has_value())
1194 detail::record_symmetric_final_diagnostics(*diagnostics, projected_after_restart, after_restart_bounds, params,
1197 return finish(detail::make_symmetric_lanczos_result<Scalar>(ops, basis, residual_norm, projected_after_restart,
1198 params, restart_cycle_count, matvec_count));
1201 ops.scal(residual,
Scalar{1} /
static_cast<Scalar>(residual_norm));
1202 subdiagonal.push_back(residual_norm);
1203 basis.push_back(std::move(residual));
1206 if (last_result.
status != 0)
1208 last_partial_exit_result.
status = 1;
1209 return finish(std::move(last_partial_exit_result));
1211 return finish(std::move(last_result));
1219template <uni20::Real Scalar>
1225 case SymmetricSpectralTransform::Regular:
1226 case SymmetricSpectralTransform::GeneralizedInverse:
1227 return transformed_value;
1228 case SymmetricSpectralTransform::ShiftInvert:
1229 if (transformed_value ==
Scalar{})
1231 throw std::domain_error(
"shift-invert eigenvalue map received a zero transformed Ritz value");
1233 return options.
sigma +
Scalar{1} / transformed_value;
1234 case SymmetricSpectralTransform::Buckling:
1235 if (transformed_value ==
Scalar{1})
1237 throw std::domain_error(
"buckling eigenvalue map received a unit transformed Ritz value");
1239 return options.
sigma * transformed_value / (transformed_value -
Scalar{1});
1240 case SymmetricSpectralTransform::Cayley:
1241 if (transformed_value ==
Scalar{1})
1243 throw std::domain_error(
"Cayley eigenvalue map received a unit transformed Ritz value");
1245 return options.
sigma * (transformed_value +
Scalar{1}) / (transformed_value -
Scalar{1});
1247 throw std::invalid_argument(
"unknown symmetric spectral transform");
1267template <uni20::LapackRealOrComplex Scalar,
typename Vector, KrylovMatrixFreeOperator<Vector, Scalar> Ops>
1268EigenResult<uni20::make_real_t<Scalar>, Vector>
1273 auto result = symmetric_lanczos_restarted_standard<Scalar>(op_ops, initial, params);
1306template <uni20::LapackRealOrComplex Scalar,
typename Vector, KrylovMatrixFreeOperator<Vector, Scalar> OpOps,
1307 KrylovMatrixFreeOperator<Vector, Scalar> BOps,
typename MetricOps>
1309 OpOps& op_ops, BOps& b_ops, MetricOps& metric_ops, Vector
const& initial,
1313 std::size_t
const problem_dimension =
1315 if (b_ops.problem_dimension() != problem_dimension || b_ops.vector_dimension(initial) != problem_dimension)
1317 throw std::invalid_argument(
"generalized restarted symmetric Lanczos B operation dimension does not match");
1320 detail::SymmetricBMetricOps<Scalar, Vector, OpOps, BOps, MetricOps> wrapped_ops(op_ops, b_ops, metric_ops);
1321 return symmetric_lanczos_restarted_transformed<Scalar>(wrapped_ops, initial, params, options);
1341template <uni20::LapackRealOrComplex Scalar,
typename Vector, KrylovMatrixFreeOperator<Vector, Scalar> OpOps,
1342 KrylovMatrixFreeOperator<Vector, Scalar> BOps>
1347 return symmetric_lanczos_restarted_generalized_transformed<Scalar>(op_ops, b_ops, b_ops, initial, params, options);
General-purpose owning tensor with immediate or deferred mdspan metadata.
Definition basic_tensor.hpp:109
auto rows() const noexcept
Return the matrix row count for rank-two tensors.
Definition basic_tensor.hpp:768
Concept for complex scalar types.
Definition scalar_concepts.hpp:75
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
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
Definition block_tensor_vector.hpp:23
EigenResult< uni20::make_real_t< Scalar >, Vector > symmetric_lanczos_standard(Ops &ops, Vector const &initial, SymmetricEigenParams< uni20::make_real_t< Scalar > > const ¶ms)
Run the native symmetric/Hermitian Lanczos full-projection solver.
Definition symmetric_lanczos.hpp:897
EigenResult< uni20::make_real_t< Scalar >, Vector > symmetric_lanczos_restarted_transformed(Ops &op_ops, Vector const &initial, SymmetricEigenParams< uni20::make_real_t< Scalar > > const ¶ms, SymmetricSpectralTransformOptions< uni20::make_real_t< Scalar > > const &options)
Run restarted symmetric Lanczos on a caller-supplied transformed operator.
Definition symmetric_lanczos.hpp:1269
void scal(Scalar const &alpha, std::span< Scalar > vector)
Scale a local dense vector in place.
Definition dense_linalg.hpp:193
int effective_symmetric_krylov_dimension(SymmetricEigenParams< Scalar > const ¶ms, std::size_t problem_dimension)
Return the effective symmetric/Hermitian Krylov subspace dimension.
Definition matrix_free.hpp:225
SymmetricRestartPlan< Scalar > plan_symmetric_restart(SymmetricEigenParams< Scalar > const ¶ms, std::vector< Scalar > const &values, std::vector< Scalar > const &residual_bounds, int converged_count)
Adjust restart dimensions for implicit-restart edge cases.
Definition symmetric_lanczos.hpp:601
void laset(uni20::DenseMatrix< Scalar > &matrix, Scalar const &diagonal, Scalar const &off_diagonal, MatrixFill fill)
Set a selected region of a local dense matrix.
Definition dense_linalg.hpp:294
Scalar map_symmetric_transformed_eigenvalue(Scalar transformed_value, SymmetricSpectralTransformOptions< Scalar > const &options)
Map a native symmetric transformed Ritz value back to the original eigenvalue scale.
Definition symmetric_lanczos.hpp:1220
int default_symmetric_retained_ritz_count(int eigenvalue_count, int krylov_dimension)
Return the default symmetric/Hermitian retained Ritz count.
Definition matrix_free.hpp:248
SpectrumPart
Spectrum selector for matrix-free eigenvalue solvers.
Definition matrix_free.hpp:23
void axpy(std::span< Scalar > y, Scalar const &alpha, std::span< Scalar const > x)
Add a scaled local dense vector to another vector.
Definition dense_linalg.hpp:206
int effective_symmetric_retained_ritz_count(SymmetricEigenParams< Scalar > const ¶ms, int krylov_dimension)
Return the effective symmetric/Hermitian retained Ritz count.
Definition matrix_free.hpp:283
SymmetricQrShiftResult< Scalar > apply_symmetric_qr_shifts(std::vector< Scalar > diagonal, std::vector< Scalar > subdiagonal, std::vector< Scalar > const &shifts)
Apply implicit shifted QR sweeps to a symmetric tridiagonal matrix.
Definition symmetric_lanczos.hpp:670
SymmetricRestartSelection< Scalar > select_symmetric_restart(std::vector< Scalar > const &values, std::vector< Scalar > const &residual_bounds, SymmetricEigenParams< Scalar > const ¶ms, std::size_t shift_count, std::span< std::size_t const > protected_shift_indices={})
Select wanted Ritz values and exact-shift candidates for symmetric restart.
Definition symmetric_lanczos.hpp:512
EigenResult< uni20::make_real_t< Scalar >, Vector > symmetric_lanczos_restarted_generalized_transformed(OpOps &op_ops, BOps &b_ops, MetricOps &metric_ops, Vector const &initial, SymmetricEigenParams< uni20::make_real_t< Scalar > > const ¶ms, SymmetricSpectralTransformOptions< uni20::make_real_t< Scalar > > const &options)
Run restarted symmetric Lanczos on a transformed generalized operator with a B metric.
Definition symmetric_lanczos.hpp:1308
TridiagonalEigensystem< Scalar > symmetric_tridiagonal_eigensystem(std::vector< Scalar > diagonal, std::vector< Scalar > subdiagonal, bool compute_vectors)
Solve a real symmetric tridiagonal eigensystem through LAPACK sterf or steqr.
Definition dense_subspace.hpp:288
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
SymmetricCompressedLanczosFactorization< uni20::make_real_t< Scalar >, Vector > compress_symmetric_lanczos_restart(Ops &ops, std::vector< Vector > const &basis, Vector const &residual, SymmetricQrShiftResult< uni20::make_real_t< Scalar > > const &shifted, std::size_t retained_count)
Compress a Lanczos factorization with the accumulated restart transform.
Definition symmetric_lanczos.hpp:809
void copy(std::span< Scalar > destination, std::span< Scalar const > source)
Copy a local dense vector.
Definition dense_linalg.hpp:183
EigenResult< uni20::make_real_t< Scalar >, Vector > symmetric_lanczos_restarted_standard(Ops &ops, Vector const &initial, SymmetricEigenParams< uni20::make_real_t< Scalar > > const ¶ms)
Run a native symmetric/Hermitian Lanczos solve with implicit restarts.
Definition symmetric_lanczos.hpp:1003
auto values(Tensor &tensor)
Definition packed_matrix.hpp:29
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
constexpr detail::scalar_math::pow_fn pow
Power with the selected scalar's domain, exactness and precision rules.
Definition math.hpp:463
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::imag_fn imag
Read the imaginary component by value; real zero retains the input's type and precision.
Definition math.hpp:673
void inner_product(BackendSelector selector, async::Async< OutputTensor > &output, async::Async< LhsTensor > const &lhs, async::Async< RhsTensor > const &rhs)
Schedule an inner product into an explicit async scalar Tensor.
Definition linalg/async/reductions.hpp:455
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 hypot(mpreal const &x, mpreal const &y)
Definition mpreal.hpp:660
void scale(Tensor &tensor, Scalar factor)
Multiply every stored numerical block by a scalar in place.
Definition block_tensor_linear.hpp:400
std::ptrdiff_t index_type
Definition types.hpp:19
mpreal copysign(mpreal const &magnitude, mpreal const &sign)
Transfer the sign while preserving the magnitude's precision and exactness.
Definition mpreal_functions.hpp:224
void set_zero(Tensor &tensor)
Set every stored numerical block to zero without changing structure.
Definition block_tensor_linear.hpp:369
Matrix-free eigenvalue result with solver-owned vector objects.
Definition matrix_free.hpp:355
int status
Definition matrix_free.hpp:362
int matvec_count
Definition matrix_free.hpp:361
int converged_count
Definition matrix_free.hpp:359
int iteration_count
Definition matrix_free.hpp:360
Compressed symmetric Lanczos factorization after implicit restart.
Definition symmetric_lanczos.hpp:57
std::vector< Scalar > subdiagonal
Definition symmetric_lanczos.hpp:61
std::vector< Scalar > diagonal
Definition symmetric_lanczos.hpp:60
Scalar trailing_coupling
Definition symmetric_lanczos.hpp:64
std::vector< Vector > basis
Definition symmetric_lanczos.hpp:58
Vector residual
Definition symmetric_lanczos.hpp:59
Scalar residual_scale
Definition symmetric_lanczos.hpp:63
Scalar residual_norm
Definition symmetric_lanczos.hpp:62
Common parameters for a symmetric or Hermitian eigenvalue solve.
Definition matrix_free.hpp:157
KrylovDiagnosticsLevel diagnostics
Definition matrix_free.hpp:178
int eigenvalue_count
Number of converged eigenpairs requested by the caller.
Definition matrix_free.hpp:159
bool compute_eigenvectors
Definition matrix_free.hpp:177
SpectrumPart spectrum
Definition matrix_free.hpp:176
Scalar tolerance
Definition matrix_free.hpp:170
int max_iterations
Definition matrix_free.hpp:169
int retained_ritz_count
Optional number of Ritz vectors retained internally after restart.
Definition matrix_free.hpp:166
Optional diagnostic state for symmetric Lanczos solves.
Definition matrix_free.hpp:342
int op_count
Definition matrix_free.hpp:344
Result of applying implicit QR shifts to a symmetric tridiagonal projection.
Definition symmetric_lanczos.hpp:47
uni20::DenseMatrix< Scalar > transform
Definition symmetric_lanczos.hpp:50
std::vector< Scalar > subdiagonal
Definition symmetric_lanczos.hpp:49
std::vector< Scalar > diagonal
Definition symmetric_lanczos.hpp:48
Restart dimensions after implicit-restart edge-case adjustment.
Definition symmetric_lanczos.hpp:36
bool enlarged_for_partial_convergence
Definition symmetric_lanczos.hpp:41
std::size_t shift_count
Definition symmetric_lanczos.hpp:38
std::vector< std::size_t > protected_zero_bound_indices
Definition symmetric_lanczos.hpp:40
std::size_t protected_zero_bound_count
Definition symmetric_lanczos.hpp:39
std::size_t retained_count
Definition symmetric_lanczos.hpp:37
Ritz-value selection state used by symmetric implicit restart logic.
Definition symmetric_lanczos.hpp:25
std::vector< std::size_t > wanted_indices
Definition symmetric_lanczos.hpp:26
std::vector< Scalar > shifts
Definition symmetric_lanczos.hpp:28
int converged_count
Definition symmetric_lanczos.hpp:30
std::vector< std::size_t > shift_indices
Definition symmetric_lanczos.hpp:27
std::vector< bool > wanted_converged
Definition symmetric_lanczos.hpp:29
Project-level numeric limits customization point.
Definition numeric_limits.hpp:16