68 std::vector<RealSchurBlock<Scalar>>
blocks;
97#if defined(__cpp_lib_constexpr_cmath)
98template <uni20::Real Scalar>
constexpr Scalar nonsymmetric_convergence_floor()
100template <uni20::Real Scalar>
Scalar nonsymmetric_convergence_floor()
106template <uni20::Real Scalar>
109 Scalar const eps23 = nonsymmetric_convergence_floor<Scalar>();
113template <uni20::Real Scalar> Scalar arnoldi_breakdown_threshold(Scalar tolerance, Scalar local_scale)
115 Scalar
const base_tolerance =
117 return base_tolerance * std::max(Scalar{1}, local_scale);
120template <uni20::RealOrComplex Scalar>
122 std::size_t column, std::size_t coefficient_count,
127 for (std::size_t
row = 0;
row < coefficient_count; ++
row)
134template <uni20::RealOrComplex Scalar>
136 std::vector<Scalar>
const& residual_coupling,
148 for (Scalar
const& coupling : residual_coupling)
155template <uni20::RealOrComplex Scalar,
typename Vector,
typename Ops>
161 auto norm = [&]() {
return norm_or_inner_product<Scalar>(ops, residual); };
162 auto orthogonalize_once = [&]() {
163 Real max_correction{};
164 for (std::size_t
row = 0;
row < basis.size(); ++
row)
166 Scalar
const coefficient = ops.inner_product(basis[
row], residual);
167 hessenberg[
row, column] += coefficient;
169 ops.axpy(residual, -coefficient, basis[
row]);
171 return max_correction;
175 (void)orthogonalize_once();
177 if (original_norm ==
Real{} || residual_norm >
Real{0.717} * original_norm)
179 return residual_norm;
182 Real const first_refined_norm = residual_norm;
183 (void)orthogonalize_once();
184 residual_norm =
norm();
185 if (first_refined_norm ==
Real{} || residual_norm <=
Real{0.717} * first_refined_norm)
187 ops.set_zero(residual);
190 return residual_norm;
193template <
typename Compare>
void sort_indices(std::vector<std::size_t>& indices, Compare&& compare)
195 std::ranges::sort(indices, [&](std::size_t lhs, std::size_t rhs) {
196 if (compare(lhs, rhs))
200 if (compare(rhs, lhs))
208template <uni20::LapackReal Scalar> Scalar schur_block_score(RealSchurBlock<Scalar>
const& block,
SpectrumPart spectrum)
210 auto const first = block.first_eigenvalue;
211 auto const second = block.size == 2 ? block.second_eigenvalue : block.first_eigenvalue;
219 return std::max(first.real(), second.real());
221 return std::min(first.real(), second.real());
223 return std::max(first.imag(), second.imag());
225 return std::min(first.imag(), second.imag());
229 throw std::invalid_argument(
"algebraic and both-end selectors are not valid for nonsymmetric Schur restart");
234template <uni20::LapackReal Scalar>
bool larger_is_better(
SpectrumPart spectrum)
249 throw std::invalid_argument(
"algebraic and both-end selectors are not valid for nonsymmetric Schur restart");
254template <uni20::Real Scalar,
typename Vector, KrylovMatrixFreeOperator<Vector, Scalar> Ops>
255NonsymmetricEigenResult<Scalar, Vector> make_real_nonsymmetric_arnoldi_result(
256 Ops& ops, ArnoldiFactorization<Scalar, Vector>
const& factorization, ArnoldiRitzExtraction<Scalar>
const& ritz,
257 std::vector<std::size_t>
const& selected, NonsymmetricEigenParams<Scalar>
const& params,
int iteration_count,
258 int matvec_count,
int restart_count, Scalar projected_departure)
260 Scalar
const tolerance =
263 NonsymmetricEigenResult<Scalar, Vector> result;
264 result.iteration_count = iteration_count;
265 result.matvec_count = matvec_count;
266 result.eigenvalues.reserve(selected.size());
267 result.residual_bounds.reserve(selected.size());
268 result.reality.reserve(selected.size());
270 bool const can_compute_right_eigenvectors =
271 params.compute_eigenvectors &&
272 std::ranges::all_of(selected, [&](std::size_t index) {
return ritz.reality[index] ==
RitzReality::Real; });
273 result.right_eigenvectors.reserve(can_compute_right_eigenvectors ? selected.size() : 0);
275 bool saw_complex =
false;
276 bool saw_ambiguous =
false;
277 for (std::size_t
const index : selected)
280 Scalar
const residual_bound = ritz.residual_bounds[index];
283 result.eigenvalues.push_back(
value);
284 result.residual_bounds.push_back(residual_bound);
285 result.reality.push_back(reality);
286 if (nonsymmetric_ritz_converged(
value, residual_bound, tolerance))
288 ++result.converged_count;
293 if (can_compute_right_eigenvectors)
295 Vector vector = ops.allocate_like(factorization.basis.front());
296 ops.set_zero(vector);
297 for (std::size_t
row = 0; row < static_cast<std::size_t>(factorization.step_count); ++
row)
299 ops.axpy(vector, ritz.projected_right_eigenvectors[
row, index].real(), factorization.basis[
row]);
301 result.right_eigenvectors.push_back(std::move(vector));
307 NonsymmetricArnoldiDiagnostics<Scalar> diagnostics;
308 diagnostics.restart_count = restart_count;
309 diagnostics.op_count = matvec_count;
310 diagnostics.final_projected_dimension = factorization.step_count;
311 diagnostics.final_residual_norm = factorization.residual_norm;
312 diagnostics.projected_departure_from_normality = projected_departure;
313 diagnostics.final_ritz_values = ritz.ritz_values;
314 diagnostics.final_ritz_bounds = ritz.residual_bounds;
315 diagnostics.final_ritz_reality = ritz.reality;
316 result.diagnostics = std::move(diagnostics);
323 else if (saw_complex)
325 switch (params.real_policy)
343 if (result.eigenvalues.size() <
static_cast<std::size_t
>(params.eigenvalue_count))
351template <uni20::LapackComplexReal Real,
typename Vector, KrylovMatrixFreeOperator<Vector, uni20::complex<Real>> Ops>
352NonsymmetricEigenResult<Real, Vector> make_complex_nonsymmetric_arnoldi_result(
354 ArnoldiRitzExtraction<Real>
const& ritz, std::vector<std::size_t>
const& selected,
355 NonsymmetricEigenParams<Real>
const& params,
int iteration_count,
int matvec_count,
int restart_count,
356 Real projected_departure)
360 Real const tolerance =
363 NonsymmetricEigenResult<Real, Vector> result;
364 result.iteration_count = iteration_count;
365 result.matvec_count = matvec_count;
366 result.eigenvalues.reserve(selected.size());
367 result.residual_bounds.reserve(selected.size());
368 result.reality.reserve(selected.size());
369 result.right_eigenvectors.reserve(params.compute_eigenvectors ? selected.size() : 0);
371 for (std::size_t
const index : selected)
374 Real const residual_bound = ritz.residual_bounds[index];
376 result.eigenvalues.push_back(
value);
377 result.residual_bounds.push_back(residual_bound);
379 if (nonsymmetric_ritz_converged(
value, residual_bound, tolerance))
381 ++result.converged_count;
384 if (params.compute_eigenvectors)
386 Vector vector = ops.allocate_like(factorization.basis.front());
387 ops.set_zero(vector);
388 for (std::size_t
row = 0; row < static_cast<std::size_t>(factorization.step_count); ++
row)
390 ops.axpy(vector, ritz.projected_right_eigenvectors[
row, index], factorization.basis[
row]);
392 result.right_eigenvectors.push_back(std::move(vector));
398 NonsymmetricArnoldiDiagnostics<Real> diagnostics;
399 diagnostics.restart_count = restart_count;
400 diagnostics.op_count = matvec_count;
401 diagnostics.final_projected_dimension = factorization.step_count;
402 diagnostics.final_residual_norm = factorization.residual_norm;
403 diagnostics.projected_departure_from_normality = projected_departure;
404 diagnostics.final_ritz_values = ritz.ritz_values;
405 diagnostics.final_ritz_bounds = ritz.residual_bounds;
406 diagnostics.final_ritz_reality = ritz.reality;
407 result.diagnostics = std::move(diagnostics);
412 if (result.eigenvalues.size() <
static_cast<std::size_t
>(params.eigenvalue_count))
430template <uni20::RealOrComplex Scalar>
435 throw std::invalid_argument(
"projected_departure_from_normality requires a square matrix");
440 std::size_t
const order =
static_cast<std::size_t
>(matrix.
rows());
442 for (std::size_t col = 0; col < order; ++col)
444 for (std::size_t
row = 0;
row < order; ++
row)
451 Real commutator_norm_squared{};
452 for (std::size_t col = 0; col < order; ++col)
454 for (std::size_t
row = 0;
row < order; ++
row)
458 for (std::size_t inner = 0; inner < order; ++inner)
460 hth += detail::conjugate_if_complex(matrix[inner,
row]) * matrix[inner, col];
461 hht += matrix[
row, inner] * detail::conjugate_if_complex(matrix[col, inner]);
463 Scalar const difference = hth - hht;
487template <uni20::RealOrComplex Scalar,
typename Vector, KrylovMatrixFreeOperator<Vector, Scalar> Ops>
488ArnoldiFactorization<Scalar, Vector>
495 throw std::invalid_argument(
"arnoldi_factorize requires a positive step count");
500 Vector q0 = ops.allocate_like(initial);
501 ops.copy(q0, initial);
502 Real const initial_norm = norm_or_inner_product<Scalar>(ops, q0);
503 if (initial_norm ==
Real{})
505 throw std::invalid_argument(
"arnoldi_factorize received a zero initial vector");
507 ops.scal(q0, Scalar{1} /
static_cast<Scalar
>(initial_norm));
509 ArnoldiFactorization<Scalar, Vector> factorization;
510 factorization.hessenberg =
512 laset(factorization.hessenberg, Scalar{}, Scalar{}, MatrixFill::All);
513 factorization.basis.push_back(std::move(q0));
515 for (
int step = 0; step < max_steps; ++step)
517 Vector residual = ops.allocate_like(initial);
518 ops.matvec(residual, factorization.basis[
static_cast<std::size_t
>(step)]);
519 ++factorization.op_count;
521 Real const beta = detail::orthogonalize_arnoldi_residual<Scalar>(ops, factorization.basis, factorization.hessenberg,
522 static_cast<std::size_t
>(step), residual);
523 factorization.residual_norm =
beta;
524 factorization.step_count = step + 1;
525 Real const breakdown_scale = detail::arnoldi_hessenberg_column_scale(
526 factorization.hessenberg,
static_cast<std::size_t
>(step),
static_cast<std::size_t
>(step + 1),
beta);
527 if (
beta <= detail::arnoldi_breakdown_threshold(breakdown_tolerance, breakdown_scale))
529 factorization.happy_breakdown =
true;
533 factorization.hessenberg[
static_cast<std::size_t
>(step + 1),
static_cast<std::size_t
>(step)] =
534 static_cast<Scalar
>(
beta);
535 ops.scal(residual, Scalar{1} /
static_cast<Scalar
>(
beta));
536 factorization.basis.push_back(std::move(residual));
539 return factorization;
547template <uni20::RealOrComplex Scalar,
typename Vector>
552 throw std::invalid_argument(
"arnoldi_projected_hessenberg requires a non-empty Arnoldi factorization");
555 std::size_t
const projected_dimension =
static_cast<std::size_t
>(factorization.
step_count);
556 auto const projected_extent =
static_cast<uni20::index_type>(projected_dimension);
557 if (factorization.
hessenberg.rows() < projected_extent || factorization.
hessenberg.cols() < projected_extent)
559 throw std::invalid_argument(
"arnoldi_projected_hessenberg received an inconsistent Hessenberg projection");
563 for (std::size_t col = 0; col < projected_dimension; ++col)
565 for (std::size_t
row = 0;
row < projected_dimension; ++
row)
586template <uni20::LapackReal Scalar,
typename Vector>
592 throw std::invalid_argument(
"extract_arnoldi_ritz requires a non-empty Arnoldi factorization");
595 std::size_t
const projected_dimension =
static_cast<std::size_t
>(factorization.
step_count);
596 auto const projected_extent =
static_cast<uni20::index_type>(projected_dimension);
597 if (factorization.
hessenberg.rows() < projected_extent || factorization.
hessenberg.cols() < projected_extent)
599 throw std::invalid_argument(
"extract_arnoldi_ritz received an inconsistent Hessenberg projection");
603 Scalar hessenberg_scale = Scalar{1};
604 for (std::size_t col = 0; col < projected_dimension; ++col)
606 for (std::size_t
row = 0;
row < projected_dimension; ++
row)
615 ArnoldiRitzExtraction<Scalar> result;
616 result.ritz_values = std::move(eigensystem.eigenvalues);
617 result.projected_right_eigenvectors = std::move(eigensystem.right_eigenvectors);
618 result.residual_bounds.resize(projected_dimension, Scalar{});
624 Scalar
const classification_scale = std::max(
scale, hessenberg_scale);
625 for (std::size_t col = 0; col < projected_dimension; ++col)
627 Scalar norm_squared = Scalar{};
628 for (std::size_t
row = 0;
row < projected_dimension; ++
row)
633 if (vector_norm == Scalar{})
635 throw std::runtime_error(
"extract_arnoldi_ritz received a zero projected eigenvector from LAPACK");
637 Scalar
const last_component =
uni20::math::abs(result.projected_right_eigenvectors[projected_dimension - 1, col]);
638 result.residual_bounds[col] =
beta * last_component / vector_norm;
639 result.reality[col] =
classify_ritz_reality(result.ritz_values[col], complex_pair_tolerance, classification_scale);
657template <uni20::LapackComplex Complex,
typename Vector>
658ArnoldiRitzExtraction<uni20::make_real_t<Complex>>
665 throw std::invalid_argument(
"extract_complex_arnoldi_ritz requires a non-empty Arnoldi factorization");
670 std::size_t
const projected_dimension =
static_cast<std::size_t
>(factorization.
step_count);
671 auto const projected_extent =
static_cast<uni20::index_type>(projected_dimension);
672 if (factorization.
hessenberg.rows() < projected_extent || factorization.
hessenberg.cols() < projected_extent)
674 throw std::invalid_argument(
"extract_complex_arnoldi_ritz received an inconsistent Hessenberg projection");
679 for (std::size_t col = 0; col < projected_dimension; ++col)
681 for (std::size_t
row = 0;
row < projected_dimension; ++
row)
687 auto eigensystem = complex_nonsymmetric_eigensystem<Real>(std::move(projected),
true);
689 ArnoldiRitzExtraction<Real> result;
690 result.ritz_values = std::move(eigensystem.eigenvalues);
691 result.projected_right_eigenvectors = std::move(eigensystem.right_eigenvectors);
692 result.residual_bounds.resize(projected_dimension,
Real{});
698 Real const classification_scale = std::max(
scale, hessenberg_scale);
699 for (std::size_t col = 0; col < projected_dimension; ++col)
702 for (std::size_t
row = 0;
row < projected_dimension; ++
row)
707 if (vector_norm ==
Real{})
709 throw std::runtime_error(
"extract_complex_arnoldi_ritz received a zero projected eigenvector from LAPACK");
711 Real const last_component =
uni20::math::abs(result.projected_right_eigenvectors[projected_dimension - 1, col]);
712 result.residual_bounds[col] =
beta * last_component / vector_norm;
713 result.reality[col] =
classify_ritz_reality(result.ritz_values[col], complex_pair_tolerance, classification_scale);
730template <uni20::Real Scalar>
736 throw std::invalid_argument(
"nonsymmetric Ritz selection requires a positive eigenvalue count");
740 throw std::invalid_argument(
"nonsymmetric Ritz selection requires eigenvalue_count <= projected size");
743 std::vector<std::size_t> indices(values.size());
744 std::iota(indices.begin(), indices.end(), std::size_t{0});
749 detail::sort_indices(indices, [&](std::size_t lhs, std::size_t rhs) {
754 detail::sort_indices(indices, [&](std::size_t lhs, std::size_t rhs) {
759 detail::sort_indices(indices,
760 [&](std::size_t lhs, std::size_t rhs) {
return values[lhs].real() > values[rhs].real(); });
763 detail::sort_indices(indices,
764 [&](std::size_t lhs, std::size_t rhs) {
return values[lhs].real() < values[rhs].real(); });
767 detail::sort_indices(indices,
768 [&](std::size_t lhs, std::size_t rhs) {
return values[lhs].imag() > values[rhs].imag(); });
771 detail::sort_indices(indices,
772 [&](std::size_t lhs, std::size_t rhs) {
return values[lhs].imag() < values[rhs].imag(); });
777 throw std::invalid_argument(
"algebraic and both-end selectors are not valid for nonsymmetric Arnoldi");
786template <uni20::Complex Scalar> std::vector<std::size_t> all_ritz_indices(std::vector<Scalar>
const& values)
788 std::vector<std::size_t> indices(values.size());
789 std::iota(indices.begin(), indices.end(), std::size_t{0});
799template <uni20::Real Scalar>
819template <uni20::LapackReal Scalar>
825 throw std::invalid_argument(
"nonsymmetric Schur restart requires a positive eigenvalue count");
827 std::size_t total_scalar_count = 0;
828 for (
auto const& block : blocks)
830 if (block.size != 1 && block.size != 2)
832 throw std::invalid_argument(
"nonsymmetric Schur restart received an invalid block size");
834 total_scalar_count += block.size;
838 std::vector<std::size_t> indices(blocks.size());
839 std::iota(indices.begin(), indices.end(), std::size_t{0});
840 bool const descending = detail::larger_is_better<Scalar>(params.
spectrum);
841 detail::sort_indices(indices, [&](std::size_t lhs, std::size_t rhs) {
842 Scalar const lhs_score = detail::schur_block_score(blocks[lhs], params.
spectrum);
843 Scalar const rhs_score = detail::schur_block_score(blocks[rhs], params.
spectrum);
844 return descending ? lhs_score > rhs_score : lhs_score < rhs_score;
848 for (std::size_t
const index : indices)
850 if (selection.scalar_count >=
static_cast<std::size_t
>(requested_count))
855 selection.scalar_count += blocks[index].size;
857 selection.enlarged_to_preserve_block = selection.scalar_count >
static_cast<std::size_t
>(requested_count);
874template <uni20::LapackReal Scalar,
typename Vector, KrylovMatrixFreeOperator<Vector, Scalar> Ops>
875RealSchurCompressedArnoldiFactorization<Scalar, Vector>
881 throw std::invalid_argument(
"real Schur Arnoldi compression requires a non-empty factorization");
884 std::size_t
const order =
static_cast<std::size_t
>(factorization.
step_count);
887 throw std::invalid_argument(
"real Schur Arnoldi compression received an inconsistent basis");
891 auto schur =
real_schur(std::move(projected),
true);
895 throw std::invalid_argument(
"real Schur Arnoldi compression selected an invalid retained dimension");
899 std::size_t
const retained_count = selection.
scalar_count;
900 auto transformed_column = [&](std::size_t column) {
901 Vector vector = ops.allocate_like(factorization.
basis.front());
902 ops.set_zero(vector);
903 for (std::size_t
row = 0;
row < order; ++
row)
905 Scalar const coefficient = schur.schur_vectors[
row, column];
906 if (coefficient !=
Scalar{})
908 ops.axpy(vector, coefficient, factorization.
basis[
row]);
916 .residual = ops.allocate_like(factorization.
basis.front()),
918 .residual_coupling = std::vector<Scalar>(retained_count,
Scalar{}),
921 .residual_norm =
Scalar{},
925 result.basis.reserve(retained_count);
926 for (std::size_t column = 0; column < retained_count; ++column)
928 result.basis.push_back(transformed_column(column));
931 for (std::size_t col = 0; col < retained_count; ++col)
933 for (std::size_t
row = 0;
row < retained_count; ++
row)
935 result.schur_form[
row, col] = schur.schur_form[
row, col];
939 result.eigenvalues.assign(schur.eigenvalues.begin(),
940 schur.eigenvalues.begin() +
static_cast<std::ptrdiff_t
>(retained_count));
941 for (
auto const& block : schur.blocks)
943 if (block.begin >= retained_count)
947 if (block.begin + block.size > retained_count)
949 throw std::logic_error(
"real Schur Arnoldi compression split a Schur block");
951 result.blocks.push_back(block);
954 ops.set_zero(result.residual);
958 ops.copy(result.residual, factorization.
basis[order]);
959 ops.scal(result.residual,
beta);
960 for (std::size_t col = 0; col < retained_count; ++col)
962 result.residual_coupling[col] = schur.schur_vectors[order - 1, col];
965 result.residual_norm = norm_or_inner_product<Scalar>(ops, result.residual);
986template <uni20::LapackReal Scalar,
typename Vector, KrylovMatrixFreeOperator<Vector, Scalar> Ops>
987ArnoldiFactorization<Scalar, Vector>
989 std::size_t target_step_count,
Scalar breakdown_tolerance =
Scalar{})
991 std::size_t
const retained_count = compressed.
basis.size();
992 if (retained_count == 0)
994 throw std::invalid_argument(
"real Schur Arnoldi expansion requires a nonempty retained basis");
996 if (target_step_count < retained_count)
998 throw std::invalid_argument(
"real Schur Arnoldi expansion target is smaller than retained basis");
1001 if (compressed.
schur_form.rows() != retained_extent || compressed.
schur_form.cols() != retained_extent ||
1004 throw std::invalid_argument(
"real Schur Arnoldi expansion received inconsistent compressed dimensions");
1006 std::size_t
const problem_dimension =
1008 if (target_step_count > problem_dimension)
1010 throw std::invalid_argument(
"real Schur Arnoldi expansion target exceeds problem dimension");
1013 ArnoldiFactorization<Scalar, Vector> factorization;
1015 laset(factorization.hessenberg, Scalar{}, Scalar{}, MatrixFill::All);
1016 factorization.basis.reserve(target_step_count + 1);
1017 for (
auto const& vector : compressed.basis)
1019 Vector
copy = ops.allocate_like(vector);
1020 ops.copy(
copy, vector);
1021 factorization.basis.push_back(std::move(
copy));
1024 for (std::size_t col = 0; col < retained_count; ++col)
1026 for (std::size_t
row = 0;
row < retained_count; ++
row)
1032 factorization.step_count =
static_cast<int>(retained_count);
1034 Scalar
const compressed_breakdown_scale = detail::arnoldi_compressed_relation_scale(
1036 if (compressed.
residual_norm <= detail::arnoldi_breakdown_threshold(breakdown_tolerance, compressed_breakdown_scale))
1038 factorization.happy_breakdown =
1040 breakdown_tolerance, compressed_breakdown_scale);
1041 return factorization;
1044 Vector next_vector = ops.allocate_like(compressed.
residual);
1045 ops.copy(next_vector, compressed.
residual);
1046 ops.scal(next_vector, Scalar{1} / compressed.
residual_norm);
1047 factorization.basis.push_back(std::move(next_vector));
1048 for (std::size_t col = 0; col < retained_count; ++col)
1052 if (target_step_count == retained_count)
1054 return factorization;
1057 for (std::size_t step = retained_count; step < target_step_count; ++step)
1059 Vector residual = ops.allocate_like(factorization.basis[step]);
1060 ops.matvec(residual, factorization.basis[step]);
1061 ++factorization.op_count;
1063 Scalar
const beta = detail::orthogonalize_arnoldi_residual<Scalar>(ops, factorization.basis,
1064 factorization.hessenberg, step, residual);
1065 factorization.residual_norm =
beta;
1066 factorization.step_count =
static_cast<int>(step + 1);
1067 Scalar
const breakdown_scale =
1068 detail::arnoldi_hessenberg_column_scale(factorization.hessenberg, step, step + 1,
beta);
1069 if (
beta <= detail::arnoldi_breakdown_threshold(breakdown_tolerance, breakdown_scale))
1071 factorization.happy_breakdown =
true;
1075 factorization.hessenberg[step + 1, step] =
beta;
1076 ops.scal(residual, Scalar{1} /
beta);
1077 factorization.basis.push_back(std::move(residual));
1080 return factorization;
1097template <uni20::LapackComplexReal Real,
typename Vector, KrylovMatrixFreeOperator<Vector, uni20::complex<Real>> Ops>
1098ComplexSchurCompressedArnoldiFactorization<Real, Vector>
1103 if (factorization.step_count <= 0)
1105 throw std::invalid_argument(
"complex Schur Arnoldi compression requires a non-empty factorization");
1110 std::size_t
const order =
static_cast<std::size_t
>(factorization.step_count);
1111 if (factorization.basis.size() < order || (!factorization.happy_breakdown && factorization.basis.size() <= order))
1113 throw std::invalid_argument(
"complex Schur Arnoldi compression received an inconsistent basis");
1117 auto schur = complex_schur<Real>(std::move(projected),
true);
1119 if (retained_indices.empty() || retained_indices.size() > order)
1121 throw std::invalid_argument(
"complex Schur Arnoldi compression selected an invalid retained dimension");
1123 schur = reorder_complex_schur<Real>(std::move(schur), retained_indices);
1125 std::size_t
const retained_count = retained_indices.size();
1126 auto transformed_column = [&](std::size_t column) {
1127 Vector vector = ops.allocate_like(factorization.basis.front());
1128 ops.set_zero(vector);
1129 for (std::size_t
row = 0;
row < order; ++
row)
1131 Complex const coefficient = schur.schur_vectors[
row, column];
1134 ops.axpy(vector, coefficient, factorization.basis[
row]);
1142 .residual = ops.allocate_like(factorization.basis.front()),
1144 .residual_coupling = std::vector<Complex>(retained_count,
Complex{}),
1146 .residual_norm =
Real{},
1147 .happy_breakdown = factorization.happy_breakdown};
1149 result.basis.reserve(retained_count);
1150 for (std::size_t column = 0; column < retained_count; ++column)
1152 result.basis.push_back(transformed_column(column));
1155 for (std::size_t col = 0; col < retained_count; ++col)
1157 for (std::size_t
row = 0;
row < retained_count; ++
row)
1159 result.schur_form[
row, col] = schur.schur_form[
row, col];
1163 result.eigenvalues.assign(schur.eigenvalues.begin(),
1164 schur.eigenvalues.begin() +
static_cast<std::ptrdiff_t
>(retained_count));
1166 ops.set_zero(result.residual);
1167 if (!factorization.happy_breakdown)
1169 Complex const beta = factorization.hessenberg[order, order - 1];
1170 ops.copy(result.residual, factorization.basis[order]);
1171 ops.scal(result.residual,
beta);
1172 for (std::size_t col = 0; col < retained_count; ++col)
1174 result.residual_coupling[col] = schur.schur_vectors[order - 1, col];
1177 result.residual_norm = norm_or_inner_product<Complex>(ops, result.residual);
1197template <uni20::LapackComplexReal Real,
typename Vector, KrylovMatrixFreeOperator<Vector, uni20::complex<Real>> Ops>
1198ArnoldiFactorization<uni20::complex<Real>, Vector>
1201 std::size_t target_step_count,
Real breakdown_tolerance =
Real{})
1205 std::size_t
const retained_count = compressed.
basis.size();
1206 if (retained_count == 0)
1208 throw std::invalid_argument(
"complex Schur Arnoldi expansion requires a nonempty retained basis");
1210 if (target_step_count < retained_count)
1212 throw std::invalid_argument(
"complex Schur Arnoldi expansion target is smaller than retained basis");
1215 if (compressed.
schur_form.rows() != retained_extent || compressed.
schur_form.cols() != retained_extent ||
1218 throw std::invalid_argument(
"complex Schur Arnoldi expansion received inconsistent compressed dimensions");
1220 std::size_t
const problem_dimension =
1222 if (target_step_count > problem_dimension)
1224 throw std::invalid_argument(
"complex Schur Arnoldi expansion target exceeds problem dimension");
1227 ArnoldiFactorization<Complex, Vector> factorization;
1230 factorization.basis.reserve(target_step_count + 1);
1231 for (
auto const& vector : compressed.basis)
1233 Vector
copy = ops.allocate_like(vector);
1234 ops.copy(
copy, vector);
1235 factorization.basis.push_back(std::move(
copy));
1238 for (std::size_t col = 0; col < retained_count; ++col)
1240 for (std::size_t
row = 0;
row < retained_count; ++
row)
1246 factorization.step_count =
static_cast<int>(retained_count);
1248 Real const compressed_breakdown_scale = detail::arnoldi_compressed_relation_scale(
1250 if (compressed.
residual_norm <= detail::arnoldi_breakdown_threshold(breakdown_tolerance, compressed_breakdown_scale))
1252 factorization.happy_breakdown =
1254 breakdown_tolerance, compressed_breakdown_scale);
1255 return factorization;
1258 Vector next_vector = ops.allocate_like(compressed.
residual);
1259 ops.copy(next_vector, compressed.
residual);
1261 factorization.basis.push_back(std::move(next_vector));
1262 for (std::size_t col = 0; col < retained_count; ++col)
1266 if (target_step_count == retained_count)
1268 return factorization;
1271 for (std::size_t step = retained_count; step < target_step_count; ++step)
1273 Vector residual = ops.allocate_like(factorization.basis[step]);
1274 ops.matvec(residual, factorization.basis[step]);
1275 ++factorization.op_count;
1277 Real const beta = detail::orthogonalize_arnoldi_residual<Complex>(ops, factorization.basis,
1278 factorization.hessenberg, step, residual);
1279 factorization.residual_norm =
beta;
1280 factorization.step_count =
static_cast<int>(step + 1);
1281 Real const breakdown_scale =
1282 detail::arnoldi_hessenberg_column_scale(factorization.hessenberg, step, step + 1,
beta);
1283 if (
beta <= detail::arnoldi_breakdown_threshold(breakdown_tolerance, breakdown_scale))
1285 factorization.happy_breakdown =
true;
1289 factorization.hessenberg[step + 1, step] =
beta;
1291 factorization.basis.push_back(std::move(residual));
1294 return factorization;
1309template <uni20::LapackReal Scalar,
typename Vector, KrylovMatrixFreeOperator<Vector, Scalar> Ops>
1310NonsymmetricEigenResult<Scalar, Vector>
1315 throw std::invalid_argument(
"real nonsymmetric Arnoldi requires a positive eigenvalue count");
1320 if (basis_limit <= 0)
1322 throw std::invalid_argument(
"real nonsymmetric Arnoldi requires a positive Krylov dimension");
1324 if (basis_limit >
static_cast<int>(problem_dimension))
1326 throw std::invalid_argument(
"real nonsymmetric Arnoldi requires krylov_dimension <= problem dimension");
1330 throw std::invalid_argument(
"real nonsymmetric Arnoldi requires eigenvalue_count <= krylov_dimension");
1336 std::vector<std::size_t>
const selected = undersized_projection
1340 return detail::make_real_nonsymmetric_arnoldi_result(ops, factorization, ritz, selected, params,
1342 projected_departure);
1358template <uni20::LapackComplexReal Real,
typename Vector, KrylovMatrixFreeOperator<Vector, uni20::complex<Real>> Ops>
1364 throw std::invalid_argument(
"complex nonsymmetric Arnoldi requires a positive eigenvalue count");
1371 if (basis_limit <= 0)
1373 throw std::invalid_argument(
"complex nonsymmetric Arnoldi requires a positive Krylov dimension");
1375 if (basis_limit >
static_cast<int>(problem_dimension))
1377 throw std::invalid_argument(
"complex nonsymmetric Arnoldi requires krylov_dimension <= problem dimension");
1381 throw std::invalid_argument(
"complex nonsymmetric Arnoldi requires eigenvalue_count <= krylov_dimension");
1388 throw std::invalid_argument(
"complex nonsymmetric Arnoldi iteration limit is too small for nev");
1391 int const initial_steps = std::min(basis_limit, params.
max_iterations);
1393 int matvec_count = factorization.
op_count;
1394 int restart_count = 0;
1400 std::vector<std::size_t>
const selected = undersized_projection
1405 return detail::make_complex_nonsymmetric_arnoldi_result(ops, factorization, ritz, selected, result_params,
1406 matvec_count, matvec_count, restart_count,
1407 projected_departure);
1412 auto make_final_result = [&]() {
1415 return make_cycle_result(params);
1417 return std::move(result);
1420 bool const all_selected_converged = result.
converged_count ==
static_cast<int>(selected.size());
1423 return make_final_result();
1427 static_cast<std::size_t
>(factorization.
step_count) <=
static_cast<std::size_t
>(requested_retained_count))
1429 return make_final_result();
1432 auto compressed = compress_complex_schur_arnoldi_restart<Real>(ops, factorization, params);
1434 if (compressed.
basis.size() >=
static_cast<std::size_t
>(basis_limit))
1436 return make_final_result();
1439 int const remaining_matvecs = params.
max_iterations - matvec_count;
1440 if (remaining_matvecs <= 0)
1442 return make_final_result();
1444 std::size_t
const expansion_count = std::min<std::size_t>(
1445 static_cast<std::size_t
>(basis_limit) - compressed.
basis.size(),
static_cast<std::size_t
>(remaining_matvecs));
1446 if (expansion_count == 0)
1448 return make_final_result();
1450 std::size_t
const target_step_count = compressed.
basis.size() + expansion_count;
1451 factorization = expand_complex_schur_arnoldi_restart<Real>(ops, compressed, target_step_count);
1452 matvec_count += factorization.
op_count;
1471template <uni20::LapackReal Scalar,
typename Vector, KrylovMatrixFreeOperator<Vector, Scalar> Ops>
1472NonsymmetricEigenResult<Scalar, Vector>
1478 throw std::invalid_argument(
"restarted real nonsymmetric Arnoldi requires a positive eigenvalue count");
1482 throw std::invalid_argument(
"restarted real nonsymmetric Arnoldi requires a positive iteration limit");
1485 std::size_t
const problem_dimension =
1488 if (basis_limit <= 0)
1490 throw std::invalid_argument(
"restarted real nonsymmetric Arnoldi requires a positive Krylov dimension");
1492 if (basis_limit >
static_cast<int>(problem_dimension))
1494 throw std::invalid_argument(
"restarted real nonsymmetric Arnoldi requires krylov_dimension <= problem dimension");
1498 throw std::invalid_argument(
"restarted real nonsymmetric Arnoldi requires eigenvalue_count <= krylov_dimension");
1506 throw std::invalid_argument(
"restarted real nonsymmetric Arnoldi iteration limit is too small for nev");
1509 int const initial_steps = std::min(basis_limit, params.
max_iterations);
1511 int matvec_count = factorization.
op_count;
1512 int restart_count = 0;
1518 std::vector<std::size_t>
const selected = undersized_projection
1523 return detail::make_real_nonsymmetric_arnoldi_result(ops, factorization, ritz, selected, result_params,
1524 matvec_count, matvec_count, restart_count,
1525 projected_departure);
1530 auto make_final_result = [&]() {
1533 return make_cycle_result(params);
1535 return std::move(result);
1538 bool const all_selected_converged = result.
converged_count ==
static_cast<int>(selected.size());
1541 return make_final_result();
1545 static_cast<std::size_t
>(factorization.
step_count) <=
static_cast<std::size_t
>(requested_retained_count))
1547 return make_final_result();
1550 auto compressed = compress_real_schur_arnoldi_restart<Scalar>(ops, factorization, params);
1552 if (compressed.
basis.size() >=
static_cast<std::size_t
>(basis_limit))
1554 return make_final_result();
1557 int const remaining_matvecs = params.
max_iterations - matvec_count;
1558 if (remaining_matvecs <= 0)
1560 return make_final_result();
1562 std::size_t
const expansion_count = std::min<std::size_t>(
1563 static_cast<std::size_t
>(basis_limit) - compressed.
basis.size(),
static_cast<std::size_t
>(remaining_matvecs));
1564 if (expansion_count == 0)
1566 return make_final_result();
1568 std::size_t
const target_step_count = compressed.
basis.size() + expansion_count;
1569 factorization = expand_real_schur_arnoldi_restart<Scalar>(ops, compressed, target_step_count);
1570 matvec_count += factorization.
op_count;
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
auto cols() const noexcept
Return the matrix column count for rank-two tensors.
Definition basic_tensor.hpp:776
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 detail::complex_type< Real >::type complex
Project-level complex scalar spelling.
Definition types.hpp:66
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
NonsymmetricEigenResult< Scalar, Vector > real_nonsymmetric_arnoldi_standard(Ops &ops, Vector const &initial, NonsymmetricEigenParams< Scalar > const ¶ms)
Run the first native non-restarted real nonsymmetric Arnoldi solver.
Definition nonsymmetric_arnoldi.hpp:1311
int effective_nonsymmetric_retained_ritz_count(NonsymmetricEigenParams< Scalar > const ¶ms, int krylov_dimension)
Return the effective nonsymmetric retained Ritz or Schur count.
Definition matrix_free.hpp:302
ArnoldiFactorization< Scalar, Vector > expand_real_schur_arnoldi_restart(Ops &ops, RealSchurCompressedArnoldiFactorization< Scalar, Vector > const &compressed, std::size_t target_step_count, Scalar breakdown_tolerance=Scalar{})
Expand a compressed real Schur Arnoldi relation back to a requested dimension.
Definition nonsymmetric_arnoldi.hpp:988
NonsymmetricEigenResult< Scalar, Vector > real_nonsymmetric_arnoldi_restarted_standard(Ops &ops, Vector const &initial, NonsymmetricEigenParams< Scalar > const ¶ms)
Run a restarted native real nonsymmetric Arnoldi solver.
Definition nonsymmetric_arnoldi.hpp:1473
ComplexSchurCompressedArnoldiFactorization< Real, Vector > compress_complex_schur_arnoldi_restart(Ops &ops, ArnoldiFactorization< uni20::complex< Real >, Vector > const &factorization, NonsymmetricEigenParams< Real > const ¶ms)
Compress a complex Arnoldi factorization through complex Schur reordering.
Definition nonsymmetric_arnoldi.hpp:1099
std::vector< std::size_t > select_nonsymmetric_restart_indices(std::vector< uni20::complex< Scalar > > const &values, NonsymmetricEigenParams< Scalar > const ¶ms)
Select retained Ritz or Schur indices for a nonsymmetric thick restart.
Definition nonsymmetric_arnoldi.hpp:800
RealSchurRestartSelection< Scalar > select_real_schur_restart_blocks(std::vector< RealSchurBlock< Scalar > > const &blocks, NonsymmetricEigenParams< Scalar > const ¶ms)
Select real Schur blocks for a nonsymmetric thick restart.
Definition nonsymmetric_arnoldi.hpp:820
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
RitzReality
Numerical reality classification for a complex Ritz value from a real Arnoldi solve.
Definition matrix_free.hpp:69
std::vector< std::size_t > select_nonsymmetric_ritz_indices(std::vector< uni20::complex< Scalar > > const &values, NonsymmetricEigenParams< Scalar > const ¶ms)
Select wanted Ritz indices for a nonsymmetric projected spectrum.
Definition nonsymmetric_arnoldi.hpp:731
SpectrumPart
Spectrum selector for matrix-free eigenvalue solvers.
Definition matrix_free.hpp:23
RealNonsymmetricEigensystem< Real > real_nonsymmetric_eigensystem(uni20::DenseMatrix< Real > matrix, bool compute_right_vectors)
Definition dense_subspace.hpp:695
NonsymmetricEigenResult< Real, Vector > complex_nonsymmetric_arnoldi_standard(Ops &ops, Vector const &initial, NonsymmetricEigenParams< Real > const ¶ms)
Run a restarted complex nonsymmetric Arnoldi solver.
Definition nonsymmetric_arnoldi.hpp:1359
RealSchurCompressedArnoldiFactorization< Scalar, Vector > compress_real_schur_arnoldi_restart(Ops &ops, ArnoldiFactorization< Scalar, Vector > const &factorization, NonsymmetricEigenParams< Scalar > const ¶ms)
Compress an Arnoldi factorization through real Schur block reordering.
Definition nonsymmetric_arnoldi.hpp:876
RealSchurDecomposition< Scalar > reorder_real_schur(RealSchurDecomposition< Scalar > decomposition, std::vector< std::size_t > const &leading_block_order)
Reorder a column-major real Schur decomposition by moving selected blocks to the front.
Definition dense_subspace.hpp:574
ArnoldiFactorization< uni20::complex< Real >, Vector > expand_complex_schur_arnoldi_restart(Ops &ops, ComplexSchurCompressedArnoldiFactorization< Real, Vector > const &compressed, std::size_t target_step_count, Real breakdown_tolerance=Real{})
Expand a compressed complex Schur Arnoldi relation back to a requested dimension.
Definition nonsymmetric_arnoldi.hpp:1199
@ RequireRealEigenpairs
Accept only Ritz values that are numerically real; report genuinely complex wanted pairs.
@ AllowRealSchurPairs
Allow real Schur two-plane output for complex conjugate pairs once that output is implemented.
@ PromoteToComplexSuggested
Stop with a promotion recommendation when wanted Ritz values are genuinely complex.
ArnoldiRitzExtraction< Scalar > extract_arnoldi_ritz(ArnoldiFactorization< Scalar, Vector > const &factorization, Scalar complex_pair_tolerance=Scalar{}, Scalar scale=Scalar{1})
Extract projected Ritz values and residual estimates from a real Arnoldi factorization.
Definition nonsymmetric_arnoldi.hpp:587
int effective_nonsymmetric_krylov_dimension(NonsymmetricEigenParams< Scalar > const ¶ms, std::size_t problem_dimension)
Return the effective nonsymmetric Arnoldi subspace dimension.
Definition matrix_free.hpp:234
RitzReality classify_ritz_reality(C theta, uni20::make_real_t< C > tolerance=uni20::make_real_t< C >{}, uni20::make_real_t< C > scale=uni20::make_real_t< C >{1}, uni20::make_real_t< C > ambiguity_factor=uni20::make_real_t< C >{10})
Classify whether a complex Ritz value is numerically real.
Definition matrix_free.hpp:499
ArnoldiRitzExtraction< uni20::make_real_t< Complex > > extract_complex_arnoldi_ritz(ArnoldiFactorization< Complex, Vector > const &factorization, uni20::make_real_t< Complex > complex_pair_tolerance=uni20::make_real_t< Complex >{}, uni20::make_real_t< Complex > scale=uni20::make_real_t< Complex >{1})
Extract projected Ritz values from a complex Arnoldi factorization.
Definition nonsymmetric_arnoldi.hpp:659
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
uni20::DenseMatrix< Scalar > arnoldi_projected_hessenberg(ArnoldiFactorization< Scalar, Vector > const &factorization)
Copy the square projected Hessenberg block from an Arnoldi factorization.
Definition nonsymmetric_arnoldi.hpp:548
@ ComplexPromotionRecommended
RealSchurDecomposition< Scalar > real_schur(uni20::DenseMatrix< Scalar > matrix, bool compute_vectors)
Compute the real Schur decomposition of a column-major DenseMatrix.
Definition dense_subspace.hpp:368
uni20::make_real_t< Scalar > projected_departure_from_normality(uni20::DenseMatrix< Scalar > const &matrix)
Measure departure from normality of a projected matrix.
Definition nonsymmetric_arnoldi.hpp:431
ArnoldiFactorization< Scalar, Vector > arnoldi_factorize(Ops &ops, Vector const &initial, int max_steps, uni20::make_real_t< Scalar > breakdown_tolerance=uni20::make_real_t< Scalar >{})
Build a full-reorthogonalized Arnoldi factorization.
Definition nonsymmetric_arnoldi.hpp:489
void copy(std::span< Scalar > destination, std::span< Scalar const > source)
Copy a local dense vector.
Definition dense_linalg.hpp:183
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::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
auto abs_squared(T const &value)
Square the magnitude, evaluating the scalar absolute value once.
Definition math.hpp:681
mpreal norm(mpcomplex const &z)
Definition mpcomplex.hpp:494
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 beta(mpreal const &a, mpreal const &x)
Real Beta function; positive rational/integer pairs retain exact values.
Definition mpreal_special.hpp:164
Arnoldi factorization for a nonsymmetric matrix-free operator.
Definition nonsymmetric_arnoldi.hpp:24
std::vector< Vector > basis
Definition nonsymmetric_arnoldi.hpp:25
int op_count
Definition nonsymmetric_arnoldi.hpp:28
uni20::make_real_t< Scalar > residual_norm
Definition nonsymmetric_arnoldi.hpp:29
uni20::DenseMatrix< Scalar > hessenberg
Definition nonsymmetric_arnoldi.hpp:26
bool happy_breakdown
Definition nonsymmetric_arnoldi.hpp:30
int step_count
Definition nonsymmetric_arnoldi.hpp:27
Compressed complex Arnoldi relation after a Schur restart.
Definition nonsymmetric_arnoldi.hpp:83
std::vector< Complex > residual_coupling
Definition nonsymmetric_arnoldi.hpp:89
std::vector< Complex > eigenvalues
Definition nonsymmetric_arnoldi.hpp:90
std::vector< Vector > basis
Definition nonsymmetric_arnoldi.hpp:86
uni20::DenseMatrix< Complex > schur_form
Definition nonsymmetric_arnoldi.hpp:88
Real residual_norm
Definition nonsymmetric_arnoldi.hpp:91
bool happy_breakdown
Definition nonsymmetric_arnoldi.hpp:92
Vector residual
Definition nonsymmetric_arnoldi.hpp:87
Parameters for a native nonsymmetric eigenvalue solve.
Definition matrix_free.hpp:106
Scalar complex_pair_tolerance
Definition matrix_free.hpp:112
int max_iterations
Definition matrix_free.hpp:110
SpectrumPart spectrum
Definition matrix_free.hpp:113
bool compute_eigenvectors
Definition matrix_free.hpp:114
int eigenvalue_count
Definition matrix_free.hpp:107
Matrix-free nonsymmetric eigenvalue result.
Definition matrix_free.hpp:143
int converged_count
Definition matrix_free.hpp:148
Expert eigensystem diagnostics for a dense real nonsymmetric matrix.
Definition dense_subspace.hpp:39
Compressed real Arnoldi relation after a Schur restart.
Definition nonsymmetric_arnoldi.hpp:62
Vector residual
Definition nonsymmetric_arnoldi.hpp:64
bool happy_breakdown
Definition nonsymmetric_arnoldi.hpp:70
uni20::DenseMatrix< Scalar > schur_form
Definition nonsymmetric_arnoldi.hpp:65
std::vector< uni20::complex< Scalar > > eigenvalues
Definition nonsymmetric_arnoldi.hpp:67
std::vector< Vector > basis
Definition nonsymmetric_arnoldi.hpp:63
std::vector< RealSchurBlock< Scalar > > blocks
Definition nonsymmetric_arnoldi.hpp:68
Scalar residual_norm
Definition nonsymmetric_arnoldi.hpp:69
bool enlarged_to_preserve_block
Definition nonsymmetric_arnoldi.hpp:71
std::vector< Scalar > residual_coupling
Definition nonsymmetric_arnoldi.hpp:66
Block-aware selection result for real Schur restart.
Definition nonsymmetric_arnoldi.hpp:46
std::size_t scalar_count
Definition nonsymmetric_arnoldi.hpp:48
std::vector< std::size_t > block_indices
Definition nonsymmetric_arnoldi.hpp:47
bool enlarged_to_preserve_block
Definition nonsymmetric_arnoldi.hpp:49
Project-level numeric limits customization point.
Definition numeric_limits.hpp:16