Metis 2.0.0
High-performance C++20 dual-mode numerical framework
Loading...
Searching...
No Matches
Linalg.hpp
Go to the documentation of this file.
1#pragma once
7
12#include "metis/math/Logic.hpp"
13#include "metis/math/Trig.hpp"
14#include <Eigen/Dense>
15#include <Eigen/IterativeLinearSolvers>
16#include <Eigen/SparseCholesky>
17#include <Eigen/SparseLU>
18#include <Eigen/SparseQR>
19#include <algorithm>
20#include <array>
21#include <casadi/casadi.hpp>
22#include <cmath>
23#include <complex>
24#include <functional>
25#include <limits>
26#include <numeric>
27#include <string>
28#include <unsupported/Eigen/IterativeSolvers>
29#include <vector>
30
31namespace metis {
32
33// --- Conversion Helpers ---
34// (Moved to MetisTypes.hpp: to_mx, to_eigen, as_vector)
35
44
55
65
73
81
92 double tolerance = 1e-10;
93 int max_iterations = 500;
94 int gmres_restart = 30;
97 casadi::Dict symbolic_options;
98
101 LinearSolvePolicy policy;
103 policy.dense_solver = solver;
104 return policy;
105 }
106
107 static LinearSolvePolicy
114
115 static LinearSolvePolicy
124
126 tolerance = value;
127 return *this;
128 }
129
131 max_iterations = value;
132 return *this;
133 }
134
136 gmres_restart = value;
137 return *this;
138 }
139
142 preconditioner_hook = std::move(hook);
143 return *this;
144 }
145
146 LinearSolvePolicy &set_symbolic_solver(const std::string &solver,
147 const casadi::Dict &opts = casadi::Dict()) {
148 symbolic_linear_solver = solver;
149 symbolic_options = opts;
150 return *this;
151 }
152};
153
154namespace detail {
155
157 public:
158 using Scalar = double;
159 using RealScalar = double;
160 using StorageIndex = int;
161 enum { ColsAtCompileTime = Eigen::Dynamic, MaxColsAtCompileTime = Eigen::Dynamic };
162
164
165 void set_apply(std::function<NumericVector(const NumericVector &)> apply) {
166 apply_ = std::move(apply);
167 }
168
169 Eigen::Index rows() const noexcept { return size_; }
170 Eigen::Index cols() const noexcept { return size_; }
171
172 template <typename MatType> FunctionalPreconditioner &analyzePattern(const MatType &) {
173 return *this;
174 }
175
176 template <typename MatType> FunctionalPreconditioner &factorize(const MatType &mat) {
177 size_ = mat.cols();
178 initialized_ = true;
179 return *this;
180 }
181
182 template <typename MatType> FunctionalPreconditioner &compute(const MatType &mat) {
183 return factorize(mat);
184 }
185
186 template <typename Rhs, typename Dest> void _solve_impl(const Rhs &b, Dest &x) const {
187 NumericVector rhs = b;
188 x = apply_ ? apply_(rhs) : rhs;
189 }
190
191 template <typename Rhs>
192 inline const Eigen::Solve<FunctionalPreconditioner, Rhs>
193 solve(const Eigen::MatrixBase<Rhs> &b) const {
194 eigen_assert(initialized_ && "FunctionalPreconditioner is not initialized.");
195 eigen_assert(size_ == b.rows() && "FunctionalPreconditioner::solve(): invalid rhs size");
196 return Eigen::Solve<FunctionalPreconditioner, Rhs>(*this, b.derived());
197 }
198
199 Eigen::ComputationInfo info() const { return Eigen::Success; }
200
201 private:
202 std::function<NumericVector(const NumericVector &)> apply_;
203 Eigen::Index size_ = 0;
204 bool initialized_ = false;
205};
206
207inline void validate_linear_solve_dims(Eigen::Index a_rows, Eigen::Index a_cols,
208 Eigen::Index b_rows, const std::string &context) {
209 if (a_rows == 0 || a_cols == 0) {
210 throw InvalidArgument(context + ": coefficient matrix must be non-empty");
211 }
212 if (b_rows != a_rows) {
213 throw InvalidArgument(context + ": rhs row count must match coefficient matrix rows");
214 }
215}
216
217inline void validate_square_required(Eigen::Index rows, Eigen::Index cols,
218 const std::string &context, const std::string &solver_name) {
219 if (rows != cols) {
220 throw InvalidArgument(context + ": " + solver_name + " requires a square matrix");
221 }
222}
223
224inline void validate_iterative_policy(const LinearSolvePolicy &policy, const std::string &context) {
225 if (policy.tolerance <= 0.0) {
226 throw InvalidArgument(context + ": tolerance must be positive");
227 }
228 if (policy.max_iterations <= 0) {
229 throw InvalidArgument(context + ": max_iterations must be positive");
230 }
231 if (policy.gmres_restart <= 0) {
232 throw InvalidArgument(context + ": gmres_restart must be positive");
233 }
234}
235
236inline std::function<NumericVector(const NumericVector &)>
238 if (policy.preconditioner_hook) {
239 return policy.preconditioner_hook;
240 }
241
242 switch (policy.iterative_preconditioner) {
244 return [](const NumericVector &rhs) { return rhs; };
246 NumericVector inv_diag(A.rows());
247 const double eps = std::numeric_limits<double>::epsilon();
248 for (int i = 0; i < A.rows(); ++i) {
249 const double diag = A.coeff(i, i);
250 inv_diag(i) = std::abs(diag) > eps ? 1.0 / diag : 1.0;
251 }
252 return [inv_diag](const NumericVector &rhs) { return inv_diag.array() * rhs.array(); };
253 }
254 }
255 throw InvalidArgument("solve: unsupported iterative preconditioner");
256}
257
258template <typename MatrixLike> SparseMatrix dense_to_sparse(const MatrixLike &A) {
259 SparseMatrix sparse = A.sparseView();
260 sparse.makeCompressed();
261 return sparse;
262}
263
264template <typename DerivedB>
265auto solve_sparse_direct_numeric(const SparseMatrix &A, const Eigen::MatrixBase<DerivedB> &b,
266 const LinearSolvePolicy &policy) {
267 using Result = Eigen::Matrix<double, Eigen::Dynamic, DerivedB::ColsAtCompileTime>;
268
269 SparseMatrix matrix = A;
270 matrix.makeCompressed();
271
272 switch (policy.sparse_direct_solver) {
274 Eigen::SparseLU<SparseMatrix> solver;
275 solver.compute(matrix);
276 if (solver.info() != Eigen::Success) {
277 throw InvalidArgument("solve: SparseLU factorization failed");
278 }
279 return solver.solve(b).eval();
280 }
283 solver.compute(matrix);
284 if (solver.info() != Eigen::Success) {
285 throw InvalidArgument("solve: SparseQR factorization failed");
286 }
287 return solver.solve(b).eval();
288 }
290 validate_square_required(matrix.rows(), matrix.cols(), "solve", "SimplicialLLT");
292 solver.compute(matrix);
293 if (solver.info() != Eigen::Success) {
294 throw InvalidArgument("solve: SimplicialLLT factorization failed");
295 }
296 return solver.solve(b).eval();
297 }
299 validate_square_required(matrix.rows(), matrix.cols(), "solve", "SimplicialLDLT");
301 solver.compute(matrix);
302 if (solver.info() != Eigen::Success) {
303 throw InvalidArgument("solve: SimplicialLDLT factorization failed");
304 }
305 return solver.solve(b).eval();
306 }
307 }
308
309 return Result();
310}
311
312template <typename Solver, typename DerivedB>
313auto solve_iterative_with_solver(Solver &solver, const Eigen::MatrixBase<DerivedB> &b) {
314 using Result = Eigen::Matrix<double, Eigen::Dynamic, DerivedB::ColsAtCompileTime>;
315
316 Result x(b.rows(), b.cols());
317 for (int col = 0; col < b.cols(); ++col) {
318 NumericVector rhs = b.col(col);
319 NumericVector sol = solver.solve(rhs);
320 if (solver.info() != Eigen::Success) {
321 throw InvalidArgument("solve: iterative solver failed to converge");
322 }
323 x.col(col) = sol;
324 }
325 return x;
326}
327
328template <typename DerivedB>
329auto solve_iterative_numeric(const SparseMatrix &A, const Eigen::MatrixBase<DerivedB> &b,
330 const LinearSolvePolicy &policy) {
331 validate_square_required(A.rows(), A.cols(), "solve", "iterative Krylov backends");
332 validate_iterative_policy(policy, "solve");
333
334 FunctionalPreconditioner preconditioner;
335 preconditioner.set_apply(make_preconditioner(A, policy));
336
337 switch (policy.iterative_solver) {
339 Eigen::BiCGSTAB<SparseMatrix, FunctionalPreconditioner> solver;
340 solver.setTolerance(policy.tolerance);
341 solver.setMaxIterations(policy.max_iterations);
342 solver.preconditioner() = preconditioner;
343 solver.compute(A);
344 return solve_iterative_with_solver(solver, b);
345 }
347 Eigen::GMRES<SparseMatrix, FunctionalPreconditioner> solver;
348 solver.setTolerance(policy.tolerance);
349 solver.setMaxIterations(policy.max_iterations);
350 solver.set_restart(policy.gmres_restart);
351 solver.preconditioner() = preconditioner;
352 solver.compute(A);
353 return solve_iterative_with_solver(solver, b);
354 }
355 }
356
357 using Result = Eigen::Matrix<double, Eigen::Dynamic, DerivedB::ColsAtCompileTime>;
358 return Result();
359}
360
361// True when A's compile-time shape does not forbid squareness (i.e. could be
362// square at runtime). False only when both dims are fixed and unequal.
363template <typename DerivedA>
364inline constexpr bool dense_solver_square_compatible =
365 DerivedA::RowsAtCompileTime == Eigen::Dynamic ||
366 DerivedA::ColsAtCompileTime == Eigen::Dynamic ||
367 DerivedA::RowsAtCompileTime == DerivedA::ColsAtCompileTime;
368
369template <typename DerivedA, typename DerivedB>
370auto solve_dense_numeric(const Eigen::MatrixBase<DerivedA> &A, const Eigen::MatrixBase<DerivedB> &b,
371 const LinearSolvePolicy &policy) {
372 switch (policy.dense_solver) {
374 return A.colPivHouseholderQr().solve(b).eval();
376 validate_square_required(A.rows(), A.cols(), "solve", "PartialPivLU");
378 return A.partialPivLu().solve(b).eval();
379 }
380 break;
382 validate_square_required(A.rows(), A.cols(), "solve", "FullPivLU");
384 return A.fullPivLu().solve(b).eval();
385 }
386 break;
388 validate_square_required(A.rows(), A.cols(), "solve", "LLT");
391 if (solver.info() != Eigen::Success) {
392 throw InvalidArgument("solve: LLT factorization failed");
393 }
394 return solver.solve(b).eval();
395 }
396 break;
398 validate_square_required(A.rows(), A.cols(), "solve", "LDLT");
401 if (solver.info() != Eigen::Success) {
402 throw InvalidArgument("solve: LDLT factorization failed");
403 }
404 return solver.solve(b).eval();
405 }
406 break;
407 }
408
409 return A.colPivHouseholderQr().solve(b).eval();
410}
411
412} // namespace detail
413
414// --- solve(A, b) ---
425template <typename DerivedA, typename DerivedB>
426auto solve(const Eigen::MatrixBase<DerivedA> &A, const Eigen::MatrixBase<DerivedB> &b) {
427 return solve(A, b, LinearSolvePolicy());
428}
429
433template <typename DerivedA, typename DerivedB>
434auto solve(const Eigen::MatrixBase<DerivedA> &A, const Eigen::MatrixBase<DerivedB> &b,
435 const LinearSolvePolicy &policy) {
436 using Scalar = typename DerivedA::Scalar;
437 detail::validate_linear_solve_dims(A.rows(), A.cols(), b.rows(), "solve");
438
439 if constexpr (std::is_floating_point_v<Scalar>) {
440 // Unify the return type across dense/sparse/iterative backends so `auto`
441 // deduction succeeds. For Ax = b with A: M×N and b: M×K, the solution x
442 // is N×K — rows come from A's *columns*, cols come from b. Dynamic
443 // compile-time dims fall through to runtime sizes via the constructor.
444 using Result =
445 Eigen::Matrix<Scalar, DerivedA::ColsAtCompileTime, DerivedB::ColsAtCompileTime>;
446 switch (policy.backend) {
448 return Result(detail::solve_dense_numeric(A, b, policy));
450 return Result(
453 return Result(
455 }
456 return Result(detail::solve_dense_numeric(A, b, policy));
457 } else {
458 SymbolicScalar A_mx = to_mx(A);
459 SymbolicScalar b_mx = to_mx(b);
460 if (policy.symbolic_linear_solver.empty()) {
461 if (!policy.symbolic_options.empty()) {
462 throw InvalidArgument(
463 "solve: symbolic_options require a non-empty symbolic_linear_solver");
464 }
465 SymbolicScalar x_mx = SymbolicScalar::solve(A_mx, b_mx);
466 return to_eigen(x_mx);
467 }
468 SymbolicScalar x_mx = SymbolicScalar::solve(A_mx, b_mx, policy.symbolic_linear_solver,
469 policy.symbolic_options);
470 return to_eigen(x_mx);
471 }
472}
473
479template <typename DerivedB>
480auto solve(const SparseMatrix &A, const Eigen::MatrixBase<DerivedB> &b) {
482}
483
487template <typename DerivedB>
488auto solve(const SparseMatrix &A, const Eigen::MatrixBase<DerivedB> &b,
489 const LinearSolvePolicy &policy) {
490 detail::validate_linear_solve_dims(A.rows(), A.cols(), b.rows(), "solve");
491
492 switch (policy.backend) {
494 return detail::solve_dense_numeric(NumericMatrix(A), b, policy);
496 return detail::solve_sparse_direct_numeric(A, b, policy);
498 return detail::solve_iterative_numeric(A, b, policy);
499 }
500
501 return detail::solve_sparse_direct_numeric(A, b, policy);
502}
503
504// --- outer(x, y) ---
511template <typename DerivedX, typename DerivedY>
512auto outer(const Eigen::MatrixBase<DerivedX> &x, const Eigen::MatrixBase<DerivedY> &y) {
513 // Eigen's outer product works efficiently for both numeric and symbolic scalars
514 // because MX * MX (scalar mult) creates a standard multiplication node.
515 return x * y.transpose();
516}
517
518// --- Dot Product ---
525template <typename DerivedA, typename DerivedB>
526auto dot(const Eigen::MatrixBase<DerivedA> &a, const Eigen::MatrixBase<DerivedB> &b) {
527 return a.dot(b);
528}
529
530// --- Cross Product ---
538template <typename DerivedA, typename DerivedB>
539auto cross(const Eigen::MatrixBase<DerivedA> &a, const Eigen::MatrixBase<DerivedB> &b) {
540 if (a.size() != 3 || b.size() != 3) {
541 throw InvalidArgument("cross: both vectors must have exactly 3 elements");
542 }
543 // Manual implementation to support Dynamic vectors (Eigen::cross requires fixed size 3)
544 using Scalar = typename DerivedA::Scalar;
545 Eigen::Matrix<Scalar, Eigen::Dynamic, 1> res(3);
546 res(0) = a(1) * b(2) - a(2) * b(1);
547 res(1) = a(2) * b(0) - a(0) * b(2);
548 res(2) = a(0) * b(1) - a(1) * b(0);
549 return res;
550}
551
552// --- Inverse ---
558template <typename Derived> auto inv(const Eigen::MatrixBase<Derived> &A) {
559 using Scalar = typename Derived::Scalar;
560 if constexpr (std::is_floating_point_v<Scalar>) {
561 return A.inverse().eval();
562 } else {
563 SymbolicScalar A_mx = to_mx(A);
564 SymbolicScalar inv_mx = inv(A_mx);
565 return to_eigen(inv_mx);
566 }
567}
568
569// --- Determinant ---
575template <typename Derived> auto det(const Eigen::MatrixBase<Derived> &A) {
576 using Scalar = typename Derived::Scalar;
577 if constexpr (std::is_floating_point_v<Scalar>) {
578 return A.determinant();
579 } else {
580 SymbolicScalar A_mx = to_mx(A);
581 return det(A_mx);
582 }
583}
584
585// --- Inner Product ---
592template <typename DerivedA, typename DerivedB>
593auto inner(const Eigen::MatrixBase<DerivedA> &a, const Eigen::MatrixBase<DerivedB> &b) {
594 return metis::dot(a, b);
595}
596
597// --- Pseudo-Inverse ---
603template <typename Derived> auto pinv(const Eigen::MatrixBase<Derived> &A) {
604 using Scalar = typename Derived::Scalar;
605 if constexpr (std::is_floating_point_v<Scalar>) {
606 return A.completeOrthogonalDecomposition().pseudoInverse();
607 } else {
608 SymbolicScalar A_mx = to_mx(A);
609 SymbolicScalar pinv_mx = SymbolicScalar::pinv(A_mx);
610 return to_eigen(pinv_mx);
611 }
612}
613
614// --- Norm Extensions ---
615
625
632template <typename Derived>
633auto norm(const Eigen::MatrixBase<Derived> &x, NormType type = NormType::L2) {
634 using Scalar = typename Derived::Scalar;
635
636 if constexpr (std::is_floating_point_v<Scalar>) {
637 switch (type) {
638 case NormType::L1:
639 return x.template lpNorm<1>();
640 case NormType::L2:
641 return x.norm();
642 case NormType::Inf:
643 return x.template lpNorm<Eigen::Infinity>();
645 return x.norm(); // Frobenius equals L2 for vectors
646 default:
647 return x.norm();
648 }
649 } else {
650 SymbolicScalar x_mx = to_mx(x);
651 switch (type) {
652 case NormType::L1:
653 return SymbolicScalar::norm_1(x_mx);
654 case NormType::L2:
655 return SymbolicScalar::norm_2(x_mx);
656 case NormType::Inf:
657 return SymbolicScalar::norm_inf(x_mx);
659 return SymbolicScalar::norm_fro(x_mx);
660 default:
661 return SymbolicScalar::norm_2(x_mx);
662 }
663 }
664}
665
666// Backwards compatibility overload for just L2 norm (default handled above really, but if called
667// without args, it works)
668
677
678namespace detail {
679
680template <typename Scalar> MetisVector<Scalar> normalize_vector(const MetisVector<Scalar> &v) {
681 const auto v_norm = metis::norm(v);
682 if constexpr (std::is_floating_point_v<Scalar>) {
683 if (v_norm <= std::numeric_limits<Scalar>::epsilon()) {
684 throw InvalidArgument("eigendecomposition: eigenvector construction failed");
685 }
686 }
687 return v / v_norm;
688}
689
690template <typename Scalar>
692 const auto n01 = metis::dot(cands[0], cands[0]);
693 const auto n02 = metis::dot(cands[1], cands[1]);
694 const auto n12 = metis::dot(cands[2], cands[2]);
695
696 if constexpr (std::is_floating_point_v<Scalar>) {
697 const auto *best = &cands[0];
698 Scalar best_norm = n01;
699 if (n02 > best_norm) {
700 best = &cands[1];
701 best_norm = n02;
702 }
703 if (n12 > best_norm) {
704 best = &cands[2];
705 }
706 return normalize_vector(*best);
707 } else {
708 auto best = metis::detail::select(n02 > n01, cands[1], cands[0]);
709 auto best_norm = metis::where(n02 > n01, n02, n01);
710 best = metis::detail::select(n12 > best_norm, cands[2], best);
711 return normalize_vector(best);
712 }
713}
714
715template <typename Scalar>
717 MetisVector<Scalar> first(2);
718 first << A(0, 1), lambda - A(0, 0);
719
720 MetisVector<Scalar> second(2);
721 second << lambda - A(1, 1), A(1, 0);
722
723 if constexpr (std::is_floating_point_v<Scalar>) {
724 return normalize_vector(metis::dot(first, first) >= metis::dot(second, second) ? first
725 : second);
726 } else {
728 metis::dot(first, first) >= metis::dot(second, second), first, second));
729 }
730}
731
732template <typename Scalar>
734 MetisMatrix<Scalar> shifted = A;
735 shifted(0, 0) = shifted(0, 0) - lambda;
736 shifted(1, 1) = shifted(1, 1) - lambda;
737 shifted(2, 2) = shifted(2, 2) - lambda;
738
739 const MetisVector<Scalar> r0 = shifted.row(0).transpose();
740 const MetisVector<Scalar> r1 = shifted.row(1).transpose();
741 const MetisVector<Scalar> r2 = shifted.row(2).transpose();
742
744 {metis::cross(r0, r1), metis::cross(r0, r2), metis::cross(r1, r2)});
745}
746
747template <typename Scalar> Scalar determinant_3x3(const MetisMatrix<Scalar> &A) {
748 return A(0, 0) * (A(1, 1) * A(2, 2) - A(1, 2) * A(2, 1)) -
749 A(0, 1) * (A(1, 0) * A(2, 2) - A(1, 2) * A(2, 0)) +
750 A(0, 2) * (A(1, 0) * A(2, 1) - A(1, 1) * A(2, 0));
751}
752
753template <typename Scalar>
754void sort_eigenpairs(MetisVector<Scalar> &eigenvalues, MetisMatrix<Scalar> &eigenvectors) {
755 std::vector<Eigen::Index> order(static_cast<size_t>(eigenvalues.size()));
756 std::iota(order.begin(), order.end(), Eigen::Index{0});
757 std::sort(order.begin(), order.end(), [&](Eigen::Index lhs, Eigen::Index rhs) {
758 return eigenvalues(lhs) < eigenvalues(rhs);
759 });
760
761 MetisVector<Scalar> sorted_values(eigenvalues.size());
762 MetisMatrix<Scalar> sorted_vectors(eigenvectors.rows(), eigenvectors.cols());
763 for (Eigen::Index i = 0; i < eigenvalues.size(); ++i) {
764 sorted_values(i) = eigenvalues(order[static_cast<size_t>(i)]);
765 sorted_vectors.col(i) = eigenvectors.col(order[static_cast<size_t>(i)]);
766 }
767
768 eigenvalues = std::move(sorted_values);
769 eigenvectors = std::move(sorted_vectors);
770}
771
772template <typename Scalar>
774 if (A.rows() != A.cols()) {
775 throw InvalidArgument("eig_symmetric: input must be square");
776 }
777
778 if (A.rows() == 1) {
780 result.eigenvalues.resize(1);
781 result.eigenvalues(0) = A(0, 0);
783 return result;
784 }
785
786 if (A.rows() == 2) {
787 const Scalar trace = A(0, 0) + A(1, 1);
788 const Scalar disc = metis::sqrt((A(0, 0) - A(1, 1)) * (A(0, 0) - A(1, 1)) +
789 Scalar(4.0) * A(0, 1) * A(0, 1));
790
792 result.eigenvalues.resize(2);
793 result.eigenvalues(0) = Scalar(0.5) * (trace - disc);
794 result.eigenvalues(1) = Scalar(0.5) * (trace + disc);
795
796 result.eigenvectors.resize(2, 2);
797 result.eigenvectors.col(0) = symmetric_eigenvector_2x2(A, result.eigenvalues(0));
798 result.eigenvectors.col(1) = symmetric_eigenvector_2x2(A, result.eigenvalues(1));
799 return result;
800 }
801
802 if (A.rows() == 3) {
803 const Scalar q = (A(0, 0) + A(1, 1) + A(2, 2)) / Scalar(3.0);
804 const Scalar p1 = A(0, 1) * A(0, 1) + A(0, 2) * A(0, 2) + A(1, 2) * A(1, 2);
805 const Scalar a00 = A(0, 0) - q;
806 const Scalar a11 = A(1, 1) - q;
807 const Scalar a22 = A(2, 2) - q;
808 const Scalar p2 = a00 * a00 + a11 * a11 + a22 * a22 + Scalar(2.0) * p1;
809
810 if constexpr (std::is_floating_point_v<Scalar>) {
811 if (p2 <= std::numeric_limits<Scalar>::epsilon()) {
815 return result;
816 }
817 }
818
819 const auto has_spread = p2 > Scalar(0.0);
820 const Scalar p = metis::where(has_spread, metis::sqrt(p2 / Scalar(6.0)), Scalar(1.0));
821
822 MetisMatrix<Scalar> centered = A;
823 centered(0, 0) = centered(0, 0) - q;
824 centered(1, 1) = centered(1, 1) - q;
825 centered(2, 2) = centered(2, 2) - q;
826 const MetisMatrix<Scalar> B = centered / p;
827
828 const Scalar r = metis::clamp(determinant_3x3(B) / Scalar(2.0), -1.0, 1.0);
829 const Scalar phi = metis::where(has_spread, metis::acos(r) / Scalar(3.0), Scalar(0.0));
830
831 constexpr double kTwoPiOverThree = 2.0943951023931954923;
832 Scalar largest = q + Scalar(2.0) * p * metis::cos(phi);
833 Scalar smallest = q + Scalar(2.0) * p * metis::cos(phi + Scalar(kTwoPiOverThree));
834 Scalar middle = Scalar(3.0) * q - largest - smallest;
835
836 largest = metis::where(has_spread, largest, q);
837 middle = metis::where(has_spread, middle, q);
838 smallest = metis::where(has_spread, smallest, q);
839
841 result.eigenvalues.resize(3);
842 result.eigenvalues << smallest, middle, largest;
843
844 MetisMatrix<Scalar> vectors(3, 3);
845 vectors.col(0) = symmetric_eigenvector_3x3(A, smallest);
846 vectors.col(1) = symmetric_eigenvector_3x3(A, middle);
847 vectors.col(2) = symmetric_eigenvector_3x3(A, largest);
848
850 result.eigenvectors = metis::detail::select(has_spread, vectors, identity);
851 return result;
852 }
853
854 throw InvalidArgument(
855 "eig_symmetric: symbolic support is limited to 1x1, 2x2, and 3x3 matrices");
856}
857
858} // namespace detail
859
860// --- Eigendecomposition ---
868template <typename Derived> auto eig(const Eigen::MatrixBase<Derived> &A) {
869 using Scalar = typename Derived::Scalar;
870
871 if (A.rows() != A.cols()) {
872 throw InvalidArgument("eig: input must be square");
873 }
874
875 if constexpr (std::is_floating_point_v<Scalar>) {
876 using Matrix = MetisMatrix<Scalar>;
877 using Complex = std::complex<Scalar>;
878 using ComplexMatrix = Eigen::Matrix<Complex, Eigen::Dynamic, Eigen::Dynamic>;
879 using ComplexVector = Eigen::Matrix<Complex, Eigen::Dynamic, 1>;
880
881 if (A.rows() == 1) {
883 result.eigenvalues.resize(1);
884 result.eigenvalues(0) = A(0, 0);
885 result.eigenvectors = Matrix::Identity(1, 1);
886 return result;
887 }
888
889 Eigen::EigenSolver<Matrix> solver(A.eval());
890 if (solver.info() != Eigen::Success) {
891 throw InvalidArgument("eig: EigenSolver failed");
892 }
893
894 const ComplexVector values_complex = solver.eigenvalues();
895 const ComplexMatrix vectors_complex = solver.eigenvectors();
896 constexpr Scalar kImagTol = Scalar(1e-10);
897
898 for (Eigen::Index i = 0; i < values_complex.size(); ++i) {
899 if (std::abs(values_complex(i).imag()) > kImagTol) {
900 throw InvalidArgument("eig: complex eigenvalues are not supported");
901 }
902 }
903
904 for (Eigen::Index i = 0; i < vectors_complex.rows(); ++i) {
905 for (Eigen::Index j = 0; j < vectors_complex.cols(); ++j) {
906 if (std::abs(vectors_complex(i, j).imag()) > kImagTol) {
907 throw InvalidArgument("eig: complex eigenvectors are not supported");
908 }
909 }
910 }
911
913 result.eigenvalues = values_complex.real();
914 result.eigenvectors = vectors_complex.real();
916 return result;
917 } else {
918 throw InvalidArgument(
919 "eig: symbolic general eigendecomposition is not supported for CasADi MX; use "
920 "eig_symmetric() for 1x1, 2x2, or 3x3 symmetric matrices");
921 }
922}
923
931template <typename Derived> auto eig_symmetric(const Eigen::MatrixBase<Derived> &A) {
932 using Scalar = typename Derived::Scalar;
933
934 if (A.rows() != A.cols()) {
935 throw InvalidArgument("eig_symmetric: input must be square");
936 }
937
938 if constexpr (std::is_floating_point_v<Scalar>) {
939 if (!A.isApprox(A.transpose(), 1e-12)) {
940 throw InvalidArgument("eig_symmetric: numeric input must be symmetric");
941 }
942
943 using Matrix = MetisMatrix<Scalar>;
944 Eigen::SelfAdjointEigenSolver<Matrix> solver(A.eval());
945 if (solver.info() != Eigen::Success) {
946 throw InvalidArgument("eig_symmetric: SelfAdjointEigenSolver failed");
947 }
948
950 result.eigenvalues = solver.eigenvalues();
951 result.eigenvectors = solver.eigenvectors();
953 return result;
954 } else {
955 return detail::eig_symmetric_symbolic(A.eval());
956 }
957}
958
959// --- Explicit 3x3 Symmetric Inverse (AeroSandbox Helper) ---
976template <typename T>
977std::tuple<T, T, T, T, T, T> inv_symmetric_3x3_explicit(const T &m11, const T &m22, const T &m33,
978 const T &m12, const T &m23, const T &m13) {
979
980 T det = m11 * (m33 * m22 - m23 * m23) - m12 * (m33 * m12 - m23 * m13) +
981 m13 * (m23 * m12 - m22 * m13);
982
983 T inv_det = 1.0 / det;
984
985 T a11 = (m33 * m22 - m23 * m23) * inv_det;
986 T a12 = (m13 * m23 - m33 * m12) * inv_det;
987 T a13 = (m12 * m23 - m13 * m22) * inv_det;
988 T a22 = (m33 * m11 - m13 * m13) * inv_det;
989 T a23 = (m12 * m13 - m11 * m23) * inv_det;
990 T a33 = (m11 * m22 - m12 * m12) * inv_det;
991
992 return {a11, a12, a13, a22, a23, a33};
993}
994
995// --- Sparse Matrix Utilities ---
996// NOTE: Sparse matrices are for NUMERIC data only. CasADi MX handles sparsity internally.
997// For symbolic sparsity analysis, use metis::SparsityPattern.
998
1005template <typename Scalar>
1006constexpr bool is_numeric_scalar_v = std::is_floating_point_v<Scalar> || std::is_integral_v<Scalar>;
1007
1025inline SparseMatrix sparse_from_triplets(int rows, int cols,
1026 const std::vector<SparseTriplet> &triplets) {
1027 SparseMatrix m(rows, cols);
1028 m.setFromTriplets(triplets.begin(), triplets.end());
1029 return m;
1030}
1031
1041inline SparseMatrix to_sparse(const NumericMatrix &dense, double tol = 0.0) {
1042 SparseMatrix sparse = dense.sparseView(1.0, tol);
1043 sparse.makeCompressed();
1044 return sparse;
1045}
1046
1053inline NumericMatrix to_dense(const SparseMatrix &sparse) { return NumericMatrix(sparse); }
1054
1062 SparseMatrix I(n, n);
1063 I.setIdentity();
1064 return I;
1065}
1066
1067} // namespace metis
Scalar and element-wise arithmetic functions (abs, sqrt, pow, exp, log, etc.).
Conditional selection, comparison, and logical operations.
C++20 concepts constraining valid Metis scalar types.
Custom exception hierarchy for Metis framework.
Core type aliases for numeric and symbolic Eigen/CasADi interop.
Trigonometric and inverse trigonometric functions.
Input validation failed (e.g., mismatched sizes, invalid parameters).
Definition MetisError.hpp:31
const Eigen::Solve< FunctionalPreconditioner, Rhs > solve(const Eigen::MatrixBase< Rhs > &b) const
Definition Linalg.hpp:193
Eigen::ComputationInfo info() const
Definition Linalg.hpp:199
FunctionalPreconditioner & compute(const MatType &mat)
Definition Linalg.hpp:182
@ MaxColsAtCompileTime
Definition Linalg.hpp:161
@ ColsAtCompileTime
Definition Linalg.hpp:161
double Scalar
Definition Linalg.hpp:158
FunctionalPreconditioner & analyzePattern(const MatType &)
Definition Linalg.hpp:172
int StorageIndex
Definition Linalg.hpp:160
Eigen::Index cols() const noexcept
Definition Linalg.hpp:170
FunctionalPreconditioner & factorize(const MatType &mat)
Definition Linalg.hpp:176
void _solve_impl(const Rhs &b, Dest &x) const
Definition Linalg.hpp:186
Eigen::Index rows() const noexcept
Definition Linalg.hpp:169
void set_apply(std::function< NumericVector(const NumericVector &)> apply)
Definition Linalg.hpp:165
double RealScalar
Definition Linalg.hpp:159
Smooth approximation of ReLU function: softplus(x) = (1/beta) * log(1 + exp(beta * x)).
Definition Diagnostics.hpp:131
void sort_eigenpairs(MetisVector< Scalar > &eigenvalues, MetisMatrix< Scalar > &eigenvectors)
Definition Linalg.hpp:754
auto select(const Cond &cond, const Eigen::MatrixBase< DerivedTrue > &if_true, const Eigen::MatrixBase< DerivedFalse > &if_false)
Definition Logic.hpp:55
void validate_linear_solve_dims(Eigen::Index a_rows, Eigen::Index a_cols, Eigen::Index b_rows, const std::string &context)
Definition Linalg.hpp:207
void validate_square_required(Eigen::Index rows, Eigen::Index cols, const std::string &context, const std::string &solver_name)
Definition Linalg.hpp:217
MetisVector< Scalar > symmetric_eigenvector_2x2(const MetisMatrix< Scalar > &A, const Scalar &lambda)
Definition Linalg.hpp:716
MetisVector< Scalar > best_eigenvector_candidate(const std::array< MetisVector< Scalar >, 3 > &cands)
Definition Linalg.hpp:691
MetisVector< Scalar > symmetric_eigenvector_3x3(const MetisMatrix< Scalar > &A, const Scalar &lambda)
Definition Linalg.hpp:733
auto solve_sparse_direct_numeric(const SparseMatrix &A, const Eigen::MatrixBase< DerivedB > &b, const LinearSolvePolicy &policy)
Definition Linalg.hpp:265
void validate_iterative_policy(const LinearSolvePolicy &policy, const std::string &context)
Definition Linalg.hpp:224
auto solve_iterative_with_solver(Solver &solver, const Eigen::MatrixBase< DerivedB > &b)
Definition Linalg.hpp:313
Scalar determinant_3x3(const MetisMatrix< Scalar > &A)
Definition Linalg.hpp:747
std::function< NumericVector(const NumericVector &)> make_preconditioner(const SparseMatrix &A, const LinearSolvePolicy &policy)
Definition Linalg.hpp:237
constexpr bool dense_solver_square_compatible
Definition Linalg.hpp:364
SparseMatrix dense_to_sparse(const MatrixLike &A)
Definition Linalg.hpp:258
auto solve_iterative_numeric(const SparseMatrix &A, const Eigen::MatrixBase< DerivedB > &b, const LinearSolvePolicy &policy)
Definition Linalg.hpp:329
EigenDecomposition< Scalar > eig_symmetric_symbolic(const MetisMatrix< Scalar > &A)
Definition Linalg.hpp:773
MetisVector< Scalar > normalize_vector(const MetisVector< Scalar > &v)
Definition Linalg.hpp:680
auto solve_dense_numeric(const Eigen::MatrixBase< DerivedA > &A, const Eigen::MatrixBase< DerivedB > &b, const LinearSolvePolicy &policy)
Definition Linalg.hpp:370
Definition Diagnostics.hpp:19
Eigen::Matrix< Scalar, Eigen::Dynamic, 1 > MetisVector
Dynamic-size column vector for both numeric and symbolic backends.
Definition MetisTypes.hpp:49
IterativeKrylovSolver
Iterative Krylov solver algorithm.
Definition Linalg.hpp:69
@ BiCGSTAB
Biconjugate gradient stabilized.
Definition Linalg.hpp:70
@ GMRES
Generalized minimal residual.
Definition Linalg.hpp:71
auto inner(const Eigen::MatrixBase< DerivedA > &a, const Eigen::MatrixBase< DerivedB > &b)
Computes inner product of two vectors (dot product).
Definition Linalg.hpp:593
SparseDirectLinearSolver
Sparse direct solver algorithm.
Definition Linalg.hpp:59
@ SimplicialLDLT
Simplicial LDLT (symmetric only).
Definition Linalg.hpp:63
@ SimplicialLLT
Simplicial Cholesky (SPD only).
Definition Linalg.hpp:62
@ SparseLU
Sparse LU factorization.
Definition Linalg.hpp:60
@ SparseQR
Sparse QR factorization.
Definition Linalg.hpp:61
SparseMatrix to_sparse(const NumericMatrix &dense, double tol=0.0)
Convert dense matrix to sparse.
Definition Linalg.hpp:1041
auto outer(const Eigen::MatrixBase< DerivedX > &x, const Eigen::MatrixBase< DerivedY > &y)
Computes outer product x * y^T.
Definition Linalg.hpp:512
NumericMatrix to_dense(const SparseMatrix &sparse)
Convert sparse matrix to dense.
Definition Linalg.hpp:1053
@ None
Definition AutoDiff.hpp:30
Eigen::LDLT< MatrixType > LDLT
Definition MetisTypes.hpp:89
IterativePreconditioner
Preconditioner for iterative solvers.
Definition Linalg.hpp:77
@ None
No preconditioning.
Definition Linalg.hpp:78
@ Diagonal
Jacobi (diagonal) preconditioner.
Definition Linalg.hpp:79
auto dot(const Eigen::MatrixBase< DerivedA > &a, const Eigen::MatrixBase< DerivedB > &b)
Computes dot product of two vectors.
Definition Linalg.hpp:526
Eigen::SparseQR< MatrixType, Ord > SparseQR
Definition MetisTypes.hpp:96
Solver
Available NLP solvers.
Definition OptiOptions.hpp:21
Eigen::FullPivLU< MatrixType > FullPivLU
Definition MetisTypes.hpp:91
MetisVector< NumericScalar > NumericVector
Eigen::VectorXd equivalent.
Definition MetisTypes.hpp:67
casadi::MX SymbolicScalar
CasADi MX symbolic scalar.
Definition MetisTypes.hpp:70
Eigen::ColPivHouseholderQR< MatrixType > ColPivHouseholderQR
Definition MetisTypes.hpp:92
auto cross(const Eigen::MatrixBase< DerivedA > &a, const Eigen::MatrixBase< DerivedB > &b)
Computes 3D cross product.
Definition Linalg.hpp:539
T sqrt(const T &x)
Computes the square root of a scalar.
Definition Arithmetic.hpp:46
auto where(const Cond &cond, const T1 &if_true, const T2 &if_false)
Select values based on condition (ternary operator) Returns: cond ? if_true : if_false Supports mixed...
Definition Logic.hpp:43
Eigen::Matrix< casadi::MX, Eigen::Dynamic, Eigen::Dynamic > to_eigen(const casadi::MX &m)
Convert CasADi MX to Eigen matrix of MX.
Definition MetisTypes.hpp:230
std::tuple< T, T, T, T, T, T > inv_symmetric_3x3_explicit(const T &m11, const T &m22, const T &m33, const T &m12, const T &m23, const T &m13)
Explicit inverse of a symmetric 3x3 matrix.
Definition Linalg.hpp:977
SparseMatrix sparse_from_triplets(int rows, int cols, const std::vector< SparseTriplet > &triplets)
Create sparse matrix from triplets.
Definition Linalg.hpp:1025
NormType
Norm type selection.
Definition Linalg.hpp:619
@ Inf
Infinity (max absolute) norm.
Definition Linalg.hpp:622
@ L2
L2 (Euclidean) norm.
Definition Linalg.hpp:621
@ L1
L1 (Manhattan) norm.
Definition Linalg.hpp:620
@ Frobenius
Frobenius norm.
Definition Linalg.hpp:623
auto solve(const Eigen::MatrixBase< DerivedA > &A, const Eigen::MatrixBase< DerivedB > &b)
Solves linear system Ax = b using the default backend policy.
Definition Linalg.hpp:426
auto inv(const Eigen::MatrixBase< Derived > &A)
Computes matrix inverse.
Definition Linalg.hpp:558
Eigen::SparseMatrix< double > SparseMatrix
Sparse matrix types for efficient storage of large, sparse numeric data.
Definition MetisTypes.hpp:80
auto eig_symmetric(const Eigen::MatrixBase< Derived > &A)
Computes the eigendecomposition of a symmetric matrix.
Definition Linalg.hpp:931
constexpr bool is_numeric_scalar_v
Compile-time check for numeric scalar types.
Definition Linalg.hpp:1006
Eigen::PartialPivLU< MatrixType > PartialPivLU
Definition MetisTypes.hpp:90
Eigen::Matrix< Scalar, Eigen::Dynamic, Eigen::Dynamic > MetisMatrix
Dynamic-size matrix for both numeric and symbolic backends.
Definition MetisTypes.hpp:43
DenseLinearSolver
Dense linear solver algorithm.
Definition Linalg.hpp:48
@ FullPivLU
Full-pivot LU (square only).
Definition Linalg.hpp:51
@ LLT
Cholesky (SPD only).
Definition Linalg.hpp:52
@ LDLT
LDLT (symmetric only).
Definition Linalg.hpp:53
@ PartialPivLU
Partial-pivot LU (square only).
Definition Linalg.hpp:50
@ ColPivHouseholderQR
Column-pivoted Householder QR (default, general).
Definition Linalg.hpp:49
Eigen::LLT< MatrixType > LLT
Dense decomposition type aliases matching Eigen's decomposition templates. Use these in metis interna...
Definition MetisTypes.hpp:88
T cos(const T &x)
Computes cosine of x.
Definition Trig.hpp:46
casadi::MX to_mx(const Eigen::MatrixBase< Derived > &e)
Convert Eigen matrix of MX (or numeric) to CasADi MX.
Definition MetisTypes.hpp:206
auto pinv(const Eigen::MatrixBase< Derived > &A)
Computes Moore-Penrose pseudo-inverse.
Definition Linalg.hpp:603
auto norm(const Eigen::MatrixBase< Derived > &x, NormType type=NormType::L2)
Computes vector/matrix norm.
Definition Linalg.hpp:633
auto det(const Eigen::MatrixBase< Derived > &A)
Computes matrix determinant.
Definition Linalg.hpp:575
auto clamp(const T &val, const TLow &low, const THigh &high)
Clamps value between low and high.
Definition Logic.hpp:236
SparseMatrix sparse_identity(int n)
Create identity sparse matrix.
Definition Linalg.hpp:1061
Eigen::SimplicialLLT< MatrixType > SimplicialLLT
Definition MetisTypes.hpp:97
Eigen::SimplicialLDLT< MatrixType > SimplicialLDLT
Definition MetisTypes.hpp:98
const char * solver_name(Solver solver)
Get the CasADi solver name string.
Definition OptiOptions.hpp:38
T acos(const T &x)
Computes arc cosine of x.
Definition Trig.hpp:121
LinearSolveBackend
Backend selection for linear system solves.
Definition Linalg.hpp:39
@ IterativeKrylov
Iterative Krylov subspace method.
Definition Linalg.hpp:42
@ Dense
Dense matrix factorization.
Definition Linalg.hpp:40
@ SparseDirect
Sparse direct factorization.
Definition Linalg.hpp:41
MetisMatrix< NumericScalar > NumericMatrix
Eigen::MatrixXd equivalent.
Definition MetisTypes.hpp:66
auto eig(const Eigen::MatrixBase< Derived > &A)
Computes the eigendecomposition of a square matrix with a real spectrum.
Definition Linalg.hpp:868
Result of eigendecomposition: eigenvalues and eigenvectors.
Definition Linalg.hpp:673
MetisVector< Scalar > eigenvalues
Eigenvalues in ascending order.
Definition Linalg.hpp:674
MetisMatrix< Scalar > eigenvectors
Eigenvectors as columns.
Definition Linalg.hpp:675
Configuration for linear system solve backend and algorithm.
Definition Linalg.hpp:86
int gmres_restart
Definition Linalg.hpp:94
LinearSolvePolicy & set_gmres_restart(int value)
Definition Linalg.hpp:135
int max_iterations
Definition Linalg.hpp:93
casadi::Dict symbolic_options
Definition Linalg.hpp:97
SparseDirectLinearSolver sparse_direct_solver
Definition Linalg.hpp:89
IterativeKrylovSolver iterative_solver
Definition Linalg.hpp:90
LinearSolvePolicy & set_preconditioner_hook(std::function< NumericVector(const NumericVector &)> hook)
Definition Linalg.hpp:141
LinearSolvePolicy & set_max_iterations(int value)
Definition Linalg.hpp:130
double tolerance
Definition Linalg.hpp:92
std::function< NumericVector(const NumericVector &)> preconditioner_hook
Definition Linalg.hpp:95
static LinearSolvePolicy iterative(IterativeKrylovSolver solver=IterativeKrylovSolver::BiCGSTAB, IterativePreconditioner preconditioner=IterativePreconditioner::Diagonal)
Definition Linalg.hpp:116
LinearSolvePolicy & set_symbolic_solver(const std::string &solver, const casadi::Dict &opts=casadi::Dict())
Definition Linalg.hpp:146
static LinearSolvePolicy sparse_direct(SparseDirectLinearSolver solver=SparseDirectLinearSolver::SparseLU)
Definition Linalg.hpp:108
std::string symbolic_linear_solver
Definition Linalg.hpp:96
IterativePreconditioner iterative_preconditioner
Definition Linalg.hpp:91
LinearSolveBackend backend
Definition Linalg.hpp:87
DenseLinearSolver dense_solver
Definition Linalg.hpp:88
LinearSolvePolicy & set_tolerance(double value)
Definition Linalg.hpp:125
static LinearSolvePolicy dense(DenseLinearSolver solver=DenseLinearSolver::ColPivHouseholderQR)
Definition Linalg.hpp:100