diff --git a/dune/xt/la/eigen-solver/internal/base.hh b/dune/xt/la/eigen-solver/internal/base.hh index e55b8fca9c080ffdc827175aea7d6c05bfb499c2..59a15978745300cb985a0feaea0a0283c999c0ce 100644 --- a/dune/xt/la/eigen-solver/internal/base.hh +++ b/dune/xt/la/eigen-solver/internal/base.hh @@ -50,7 +50,7 @@ static inline void ensure_eigen_solver_type(const std::string& type, const std:: if (!contained) DUNE_THROW(Exceptions::eigen_solver_failed_bc_it_was_not_set_up_correctly, "Given type '" << type << "' is not one of the available types: " << available_types); -} // ... ensure_type(...) +} // ... ensure_eigen_solver_type(...) static inline Common::Configuration default_eigen_solver_options() @@ -245,7 +245,7 @@ public: *this, check_eigendecomposition > 0 ? check_eigendecomposition : options_->get<double>("real_tolerance")); } return *eigenvectors_inverse_; - } // ... eigenvectors(...) + } // ... eigenvectors_inverse(...) const RealMatrixType& real_eigenvectors() const { @@ -289,7 +289,7 @@ public: assert_real_eigendecomposition); } return *real_eigenvectors_inverse_; - } // ... eigenvectors(...) + } // ... real_eigenvectors_inverse(...) protected: void compute_and_check() const diff --git a/dune/xt/la/exceptions.hh b/dune/xt/la/exceptions.hh index 7a9d2d5c7cfec50dda2ab8cb68752048b1f9afc2..368c63d249b165b38e12cbbc8cb0b60b0a9178e8 100644 --- a/dune/xt/la/exceptions.hh +++ b/dune/xt/la/exceptions.hh @@ -67,6 +67,36 @@ class eigen_solver_failed_bc_result_is_not_an_eigendecomposition : public eigen_ {}; +class generalized_eigen_solver_failed : public Dune::Exception +{}; + +class generalized_eigen_solver_failed_bc_data_did_not_fulfill_requirements : public generalized_eigen_solver_failed +{}; + +class generalized_eigen_solver_failed_bc_it_was_not_set_up_correctly : public generalized_eigen_solver_failed +{}; + +class generalized_eigen_solver_failed_bc_result_contained_inf_or_nan : public generalized_eigen_solver_failed +{}; + +class generalized_eigen_solver_failed_bc_eigenvalues_are_not_real_as_requested : public generalized_eigen_solver_failed +{}; + +class generalized_eigen_solver_failed_bc_eigenvalues_are_not_positive_as_requested + : public generalized_eigen_solver_failed +{}; + +class generalized_eigen_solver_failed_bc_eigenvalues_are_not_negative_as_requested + : public generalized_eigen_solver_failed +{}; + +class generalized_eigen_solver_failed_bc_eigenvectors_are_not_real_as_requested : public generalized_eigen_solver_failed +{}; + +// class generalized_eigen_solver_failed_bc_result_is_not_an_eigendecomposition : public generalized_eigen_solver_failed +//{}; + + class matrix_invert_failed : public Dune::Exception {}; diff --git a/dune/xt/la/generalized-eigen-solver.hh b/dune/xt/la/generalized-eigen-solver.hh new file mode 100644 index 0000000000000000000000000000000000000000..8e049388ba8117edba54574e2c7171c062ec3f45 --- /dev/null +++ b/dune/xt/la/generalized-eigen-solver.hh @@ -0,0 +1,125 @@ +// This file is part of the dune-xt-la project: +// https://github.com/dune-community/dune-xt-la +// Copyright 2009-2018 dune-xt-la developers and contributors. All rights reserved. +// License: Dual licensed as BSD 2-Clause License (http://opensource.org/licenses/BSD-2-Clause) +// or GPL-2.0+ (http://opensource.org/licenses/gpl-license) +// with "runtime exception" (http://www.dune-project.org/license.html) +// Authors: +// Felix Schindler (2019) + +#ifndef DUNE_XT_LA_GENERALIZED_EIGEN_SOLVER_HH +#define DUNE_XT_LA_GENERALIZED_EIGEN_SOLVER_HH + +#include <string> +#include <vector> + +#include <dune/common/typetraits.hh> + +#include <dune/xt/common/configuration.hh> +#include <dune/xt/common/type_traits.hh> + +namespace Dune { +namespace XT { +namespace LA { + + +/** + * \brief A means to obtain available options at compile time. + * \note This class needs to be specialized for each MatrixType, the purpose of this variant is merely to document the + * expected functionality. + */ +template <class MatrixType, bool is_matrix = XT::Common::is_matrix<MatrixType>::value> +class GeneralizedEigenSolverOptions +{ + static_assert(AlwaysFalse<MatrixType>::value, + "Please implement for given MatrixType and add the respective include below!"); + + static std::vector<std::string> types(); + + static Common::Configuration options(const std::string /*type*/ = ""); +}; // class GeneralizedEigenSolverOptions + + +template <class MatrixType> +std::vector<std::string> generalized_eigen_solver_types(const MatrixType& /*matrix*/) +{ + return GeneralizedEigenSolverOptions<MatrixType>::types(); +} + + +template <class MatrixType> +Common::Configuration generalized_eigen_solver_options(const MatrixType& /*matrix*/, const std::string type = "") +{ + return GeneralizedEigenSolverOptions<MatrixType>::options(type); +} + + +template <class MatrixImp, bool is_matrix = XT::Common::is_matrix<MatrixImp>::value> +class GeneralizedEigenSolver +{ + static_assert(AlwaysFalse<MatrixImp>::value, + "Please implement for given MatrixType and add the respective include below!"); + +public: + typedef MatrixImp MatrixType; + typedef double FieldType; + typedef int RealMatrixType; + typedef int ComplexMatrixType; + + GeneralizedEigenSolver(const MatrixType& /*matrix*/, const std::string& /*type*/ = "") + { + static_assert(AlwaysFalse<MatrixType>::value, + "Please implement for given MatrixType and add the respective include below!"); + } + + GeneralizedEigenSolver(const MatrixType& /*matrix*/, const Common::Configuration /*opts*/) + { + static_assert(AlwaysFalse<MatrixType>::value, + "Please implement for given MatrixType and add the respective include below!"); + } + + const Common::Configuration& options() const; + + const MatrixType& lhs_matrix() const; + + const MatrixType& rhs_matrix() const; + + const std::vector<Common::complex_t<FieldType>>& eigenvalues() const; + + const std::vector<Common::real_t<FieldType>>& real_eigenvalues() const; + + const std::vector<Common::real_t<FieldType>>& + min_eigenvalues(const size_t /*num_evs*/ = std::numeric_limits<size_t>::max()) const; + + const std::vector<Common::real_t<FieldType>>& + max_eigenvalues(const size_t /*num_evs*/ = std::numeric_limits<size_t>::max()) const; + + const ComplexMatrixType& eigenvectors() const; + + const RealMatrixType& real_eigenvectors() const; +}; // class GeneralizedEigenSolver + + +template <class M> +GeneralizedEigenSolver<M> +make_generalized_eigen_solver(const M& lhs_matrix, const M& rhs_matrix, const std::string& type = "") +{ + return GeneralizedEigenSolver<M>(lhs_matrix, rhs_matrix, type); +} + + +template <class M> +GeneralizedEigenSolver<M> +make_generalized_eigen_solver(const M& lhs_matrix, const M& rhs_matrix, const XT::Common::Configuration& options) +{ + return GeneralizedEigenSolver<M>(lhs_matrix, rhs_matrix, options); +} + + +} // namespace LA +} // namespace XT +} // namespace Dune + +#include "generalized-eigen-solver/default.hh" + +#endif // DUNE_XT_LA_GENERALIZED_EIGEN_SOLVER_HH diff --git a/dune/xt/la/generalized-eigen-solver/default.hh b/dune/xt/la/generalized-eigen-solver/default.hh new file mode 100644 index 0000000000000000000000000000000000000000..3f03043cf5b159f1943662a1a8c54f5331d90627 --- /dev/null +++ b/dune/xt/la/generalized-eigen-solver/default.hh @@ -0,0 +1,124 @@ +// This file is part of the dune-xt-la project: +// https://github.com/dune-community/dune-xt-la +// Copyright 2009-2018 dune-xt-la developers and contributors. All rights reserved. +// License: Dual licensed as BSD 2-Clause License (http://opensource.org/licenses/BSD-2-Clause) +// or GPL-2.0+ (http://opensource.org/licenses/gpl-license) +// with "runtime exception" (http://www.dune-project.org/license.html) +// Authors: +// Felix Schindler (2019) + +#ifndef DUNE_XT_LA_GENERALIZED_EIGEN_SOLVER_DEFAULT_HH +#define DUNE_XT_LA_GENERALIZED_EIGEN_SOLVER_DEFAULT_HH + +#include <vector> + +#include <dune/xt/common/matrix.hh> +#include <dune/xt/la/container/conversion.hh> +#include <dune/xt/la/exceptions.hh> +#include <dune/xt/la/generalized-eigen-solver.hh> + +#include "internal/base.hh" +#include "internal/lapacke.hh" + +namespace Dune { +namespace XT { +namespace LA { + + +template <class MatrixType> +class GeneralizedEigenSolverOptions<MatrixType, true> +{ +public: + static std::vector<std::string> types() + { + std::vector<std::string> tps; + if (Common::Lapacke::available()) + tps.push_back("lapack"); + DUNE_THROW_IF(tps.empty(), + Exceptions::generalized_eigen_solver_failed, + "No backend available for generalized eigenvalue problems!"); + return tps; + } + + static Common::Configuration options(const std::string type = "") + { + const std::string actual_type = type.empty() ? types()[0] : type; + internal::ensure_generalized_eigen_solver_type(actual_type, types()); + Common::Configuration opts = internal::default_generalized_eigen_solver_options(); + opts["type"] = actual_type; + return opts; + } +}; // class GeneralizedEigenSolverOptions<> + + +template <class MatrixImp> +class GeneralizedEigenSolver<MatrixImp, true> + : public internal::GeneralizedEigenSolverBase< + MatrixImp, + typename Common::MatrixAbstraction<MatrixImp>::ScalarType, + typename Common::MatrixAbstraction<MatrixImp>::template MatrixTypeTemplate< + Common::MatrixAbstraction<MatrixImp>::static_rows, + Common::MatrixAbstraction<MatrixImp>::static_cols, + typename Common::MatrixAbstraction<MatrixImp>::RealType>, + typename Common::MatrixAbstraction<MatrixImp>::template MatrixTypeTemplate< + Common::MatrixAbstraction<MatrixImp>::static_rows, + Common::MatrixAbstraction<MatrixImp>::static_cols, + std::complex<typename Common::MatrixAbstraction<MatrixImp>::RealType>>> +{ + using M = Common::MatrixAbstraction<MatrixImp>; + using BaseType = internal::GeneralizedEigenSolverBase< + MatrixImp, + typename M::ScalarType, + typename M::template MatrixTypeTemplate<M::static_rows, M::static_cols, typename M::RealType>, + typename M::template MatrixTypeTemplate<M::static_rows, M::static_cols, std::complex<typename M::RealType>>>; + +public: + using typename BaseType::ComplexMatrixType; + using typename BaseType::ComplexType; + using typename BaseType::MatrixType; + using typename BaseType::RealMatrixType; + using typename BaseType::RealType; + using RealM = Common::MatrixAbstraction<RealMatrixType>; + using ComplexM = Common::MatrixAbstraction<ComplexMatrixType>; + + template <class... Args> + explicit GeneralizedEigenSolver(Args&&... args) + : BaseType(std::forward<Args>(args)...) + {} + +protected: + void compute() const override final + { + const auto type = options_->template get<std::string>("type"); + if (type == "lapack") { + DUNE_THROW_IF(!Common::Lapacke::available(), + Exceptions::generalized_eigen_solver_failed_bc_it_was_not_set_up_correctly, + "Lapacke backend not available!"); + if (!options_->template get<bool>("compute_eigenvectors")) + eigenvalues_ = std::make_unique<std::vector<ComplexType>>( + internal::compute_generalized_eigenvalues_using_lapack(lhs_matrix_, rhs_matrix_)); + else { + DUNE_THROW(Exceptions::generalized_eigen_solver_failed, + "Eigenvectors of generalized eigenvalue problems are not yet implemented using lapacke!"); + } + } else + DUNE_THROW(Common::Exceptions::internal_error, + "Given type '" << type << "' is none of GeneralizedEigenSolverOptions<...>::types()," + << " and internal::GeneralizedEigenSolverBase promised to check this!" + << "\n\nThese are the available types:\n\n" + << GeneralizedEigenSolverOptions<MatrixType>::types()); + } //... compute(...) + + using BaseType::eigenvalues_; + using BaseType::eigenvectors_; + using BaseType::lhs_matrix_; + using BaseType::options_; + using BaseType::rhs_matrix_; +}; // class GeneralizedEigenSolver<MatrixType, true> + + +} // namespace LA +} // namespace XT +} // namespace Dune + +#endif // DUNE_XT_LA_GENERALIZED_EIGEN_SOLVER_DEFAULT_HH diff --git a/dune/xt/la/generalized-eigen-solver/internal/base.hh b/dune/xt/la/generalized-eigen-solver/internal/base.hh new file mode 100644 index 0000000000000000000000000000000000000000..fdd5e5c2fed027a0e5af509272f7e6581356f75b --- /dev/null +++ b/dune/xt/la/generalized-eigen-solver/internal/base.hh @@ -0,0 +1,819 @@ +// This file is part of the dune-xt-la project: +// https://github.com/dune-community/dune-xt-la +// Copyright 2009-2018 dune-xt-la developers and contributors. All rights reserved. +// License: Dual licensed as BSD 2-Clause License (http://opensource.org/licenses/BSD-2-Clause) +// or GPL-2.0+ (http://opensource.org/licenses/gpl-license) +// with "runtime exception" (http://www.dune-project.org/license.html) +// Authors: +// Felix Schindler (2019) + +#ifndef DUNE_XT_LA_GENERALIZED_EIGEN_SOLVER_INTERNAL_BASE_HH +#define DUNE_XT_LA_GENERALIZED_EIGEN_SOLVER_INTERNAL_BASE_HH + +#include <algorithm> +#include <functional> +#include <memory> +#include <numeric> + +#include <dune/xt/common/configuration.hh> +#include <dune/xt/common/type_traits.hh> +#include <dune/xt/common/vector.hh> +#include <dune/xt/common/matrix.hh> + +#include <dune/xt/la/container/common/vector/dense.hh> +#include <dune/xt/la/container/conversion.hh> +#include <dune/xt/la/container/matrix-interface.hh> +#include <dune/xt/la/exceptions.hh> +#include <dune/xt/la/matrix-inverter.hh> + +namespace Dune { +namespace XT { +namespace LA { + + +// forward +template <class MatrixType, bool is_matrix> +class GeneralizedEigenSolverOptions; + + +namespace internal { + + +static inline void ensure_generalized_eigen_solver_type(const std::string& type, + const std::vector<std::string>& available_types) +{ + bool contained = false; + for (const auto& tp : available_types) + if (type == tp) + contained = true; + if (!contained) + DUNE_THROW(Exceptions::generalized_eigen_solver_failed_bc_it_was_not_set_up_correctly, + "Given type '" << type << "' is not one of the available types: " << available_types); +} // ... ensure_generalized_eigen_solver_type(...) + + +static inline Common::Configuration default_generalized_eigen_solver_options() +{ + Common::Configuration opts; + opts["compute_eigenvalues"] = "true"; + opts["compute_eigenvectors"] = "false"; + opts["check_for_inf_nan"] = "true"; + opts["real_tolerance"] = "1e-15"; // is only used if the respective assert_... is negative + opts["assert_real_eigenvalues"] = "-1"; // if positive, this is the check tolerance + opts["assert_positive_eigenvalues"] = "-1"; // if positive, this is the check tolerance + opts["assert_negative_eigenvalues"] = "-1"; // if positive, this is the check tolerance + opts["assert_real_eigenvectors"] = "-1"; // if positive, this is the check tolerance + return opts; +} // ... default_generalized_eigen_solver_options(...) + + +/** + * \sa default_generalized_eigen_solver_options() + * \note If the provided options contain a subtree "matrix-inverter" that one is forwarded on eigenvector inversion. + */ +template <class MatrixImp, class FieldImp, class RealMatrixImp, class ComplexMatrixImp> +class GeneralizedEigenSolverBase +{ + static_assert(is_matrix<MatrixImp>::value || XT::Common::is_matrix<MatrixImp>::value, ""); + static_assert(is_matrix<RealMatrixImp>::value || XT::Common::is_matrix<RealMatrixImp>::value, ""); + static_assert(is_matrix<ComplexMatrixImp>::value || XT::Common::is_matrix<ComplexMatrixImp>::value, ""); + static_assert((is_matrix<MatrixImp>::value && is_matrix<RealMatrixImp>::value && is_matrix<ComplexMatrixImp>::value) + || (XT::Common::is_matrix<MatrixImp>::value && XT::Common::is_matrix<RealMatrixImp>::value + && XT::Common::is_matrix<ComplexMatrixImp>::value), + ""); + using ThisType = GeneralizedEigenSolverBase<MatrixImp, FieldImp, RealMatrixImp, ComplexMatrixImp>; + +public: + using MatrixType = MatrixImp; + using RealType = XT::Common::field_t<FieldImp>; + using ComplexType = XT::Common::complex_t<RealType>; + using RealMatrixType = RealMatrixImp; + using ComplexMatrixType = ComplexMatrixImp; + + GeneralizedEigenSolverBase(const MatrixType& lhs_matrix, const MatrixType& rhs_matrix, const std::string& type = "") + : GeneralizedEigenSolverBase(lhs_matrix, rhs_matrix, GeneralizedEigenSolverOptions<MatrixType, true>::options(type)) + {} + + GeneralizedEigenSolverBase(const MatrixType& lhs_matrix, + const MatrixType& rhs_matrix, + const Common::Configuration opts) + : lhs_matrix_(lhs_matrix) + , rhs_matrix_(rhs_matrix) + , stored_options_(opts) + , options_(&stored_options_) + , computed_(false) + , disable_checks_(options_->get<bool>("disable_checks", false)) + { + pre_checks(); + } + + GeneralizedEigenSolverBase(const MatrixType& lhs_matrix, const MatrixType& rhs_matrix, Common::Configuration* opts) + : lhs_matrix_(lhs_matrix) + , rhs_matrix_(rhs_matrix) + , options_(opts) + , computed_(false) + , disable_checks_(options_->get<bool>("disable_checks", false)) + { + pre_checks(); + } + + GeneralizedEigenSolverBase(const ThisType& other) = default; + + GeneralizedEigenSolverBase(ThisType&& source) = default; + + virtual ~GeneralizedEigenSolverBase() = default; + + const Common::Configuration& options() const + { + return *options_; + } + + const MatrixType& lhs_matrix() const + { + return lhs_matrix_; + } + + const MatrixType& rhs_matrix() const + { + return rhs_matrix_; + } + +protected: + /** + * \brief Does the actual computation. + * \attention The implementor has to fill the appropriate members! + * \note The implementor can assume that the given options_ contain a valid 'type' and all default keys. + * \nte The implementor does not need to guard against multiple calls of this method. + */ + virtual void compute() const = 0; + +public: + const std::vector<ComplexType>& eigenvalues() const + { + compute_and_check(); + if (eigenvalues_) + return *eigenvalues_; + else if (options_->get<bool>("compute_eigenvalues")) + DUNE_THROW(Common::Exceptions::internal_error, "The eigenvalues_ member is not filled after calling compute()!"); + else + DUNE_THROW(Common::Exceptions::you_are_using_this_wrong, + "Do not call eigenvalues() if 'compute_eigenvalues' is false!\n\nThese were the given options:\n\n" + << *options_); + } // ... eigenvalues(...) + + const std::vector<RealType>& real_eigenvalues() const + { + compute_and_check(); + if (eigenvalues_) { + if (!real_eigenvalues_) + compute_real_eigenvalues(); + } else if (options_->get<bool>("compute_eigenvalues")) + DUNE_THROW(Common::Exceptions::internal_error, "The eigenvalues_ member is not filled after calling compute()!"); + else + DUNE_THROW( + Common::Exceptions::you_are_using_this_wrong, + "Do not call real_eigenvalues() if 'compute_eigenvalues' is false!\n\nThese were the given options:\n\n" + << *options_); + assert(real_eigenvalues_ && "These have to exist after compute_real_eigenvalues()!"); + return *real_eigenvalues_; + } // ... real_eigenvalues(...) + + std::vector<RealType> min_eigenvalues(const size_t num_evs = std::numeric_limits<size_t>::max()) const + { + compute_and_check(); + if (eigenvalues_) { + if (!real_eigenvalues_) + compute_real_eigenvalues(); + } else if (options_->get<bool>("compute_eigenvalues")) + DUNE_THROW(Common::Exceptions::internal_error, "The eigenvalues_ member is not filled after calling compute()!"); + else + DUNE_THROW(Common::Exceptions::you_are_using_this_wrong, + "Do not call min_eigenvalues() if 'compute_eigenvalues' is false!\n\nThese were the given options:\n\n" + << *options_); + assert(real_eigenvalues_ && "These have to exist after compute_real_eigenvalues()!"); + std::vector<RealType> evs = *real_eigenvalues_; + std::sort(evs.begin(), evs.end(), [](const RealType& a, const RealType& b) { return a < b; }); + evs.resize(std::min(evs.size(), num_evs)); + return evs; + } // ... min_eigenvalues(...) + + std::vector<RealType> max_eigenvalues(const size_t num_evs = std::numeric_limits<size_t>::max()) const + { + compute_and_check(); + if (eigenvalues_) { + if (!real_eigenvalues_) + compute_real_eigenvalues(); + } else if (options_->get<bool>("compute_eigenvalues")) + DUNE_THROW(Common::Exceptions::internal_error, "The eigenvalues_ member is not filled after calling compute()!"); + else + DUNE_THROW(Common::Exceptions::you_are_using_this_wrong, + "Do not call max_eigenvalues() if 'compute_eigenvalues' is false!\n\nThese were the given options:\n\n" + << *options_); + assert(real_eigenvalues_ && "These have to exist after compute_real_eigenvalues()!"); + std::vector<RealType> evs = *real_eigenvalues_; + std::sort(evs.begin(), evs.end(), [](const RealType& a, const RealType& b) { return a > b; }); + evs.resize(std::min(evs.size(), num_evs)); + return evs; + } // ... max_eigenvalues(...) + + const ComplexMatrixType& eigenvectors() const + { + compute_and_check(); + if (eigenvectors_) + return *eigenvectors_; + else if (options_->get<bool>("compute_eigenvectors")) + DUNE_THROW(Common::Exceptions::internal_error, "The eigenvectors_ member is not filled after calling compute()!"); + else + DUNE_THROW(Common::Exceptions::you_are_using_this_wrong, + "Do not call eigenvectors() if 'compute_eigenvectors' is false!\n\nThese were the given options:\n\n" + << *options_); + } // ... eigenvectors(...) + + // const ComplexMatrixType& eigenvectors_inverse() const + // { + // compute_and_check(); + // if (!eigenvectors_) { + // if (options_->get<bool>("compute_eigenvectors")) + // DUNE_THROW(Common::Exceptions::internal_error, + // "The eigenvectors_ member is not filled after calling compute()!"); + // else + // DUNE_THROW(Common::Exceptions::you_are_using_this_wrong, + // "Do not call eigenvectors_inverse() if 'compute_eigenvectors' is false!\n\nThese were the given " + // "options:\n\n" + // << *options_); + // } + // invert_eigenvectors(); + // assert(eigenvectors_inverse_ && "This must not happen after calling invert_eigenvectors()!"); + // if (!disable_checks_) { + // const double check_eigendecomposition = options_->get<double>("assert_eigendecomposition"); + // if (check_eigendecomposition > 0) + // complex_eigendecomposition_helper<>::check( + // *this, check_eigendecomposition > 0 ? check_eigendecomposition : + // options_->get<double>("real_tolerance")); + // } + // return *eigenvectors_inverse_; + // } // ... eigenvectors(...) + + const RealMatrixType& real_eigenvectors() const + { + compute_and_check(); + if (eigenvectors_) { + if (!real_eigenvectors_) + compute_real_eigenvectors(); + } else if (options_->get<bool>("compute_eigenvectors")) + DUNE_THROW(Common::Exceptions::internal_error, "The eigenvectors_ member is not filled after calling compute()!"); + else + DUNE_THROW( + Common::Exceptions::you_are_using_this_wrong, + "Do not call real_eigenvectors() if 'compute_eigenvectors' is false!\n\nThese were the given options:\n\n" + << *options_); + assert(real_eigenvectors_ && "These have to exist after compute_real_eigenvectors()!"); + return *real_eigenvectors_; + } // ... real_eigenvectors(...) + + // const MatrixType& real_eigenvectors_inverse() const + // { + // compute_and_check(); + // if (!real_eigenvectors_) { + // if (options_->get<double>("assert_real_eigendecomposition") > 0 + // || options_->get<double>("assert_real_eigenvectors") > 0) + // DUNE_THROW(Common::Exceptions::internal_error, + // "The real_eigenvectors_ member is not filled after calling compute()!"); + // else + // DUNE_THROW(Common::Exceptions::you_are_using_this_wrong, + // "Do not call real_eigenvectors_inverse() after providing these options:\n\n" + // << *options_); + // } + // invert_real_eigenvectors(); + // assert(real_eigenvectors_inverse_ && "This must not happen after calling invert_real_eigenvectors()!"); + // if (!disable_checks_) { + // const double assert_real_eigendecomposition = options_->get<double>("assert_real_eigendecomposition"); + // if (assert_real_eigendecomposition > 0.) + // assert_eigendecomposition(matrix_, + // *real_eigenvalues_, + // *real_eigenvectors_, + // *real_eigenvectors_inverse_, + // assert_real_eigendecomposition); + // } + // return *real_eigenvectors_inverse_; + // } // ... eigenvectors(...) + +protected: + void compute_and_check() const + { + if (!computed_) { + compute(); + post_checks(); + } + computed_ = true; + } + + void pre_checks() + { + if (!disable_checks_) { + // check options + if (!options_->has_key("type")) + DUNE_THROW(Exceptions::generalized_eigen_solver_failed_bc_it_was_not_set_up_correctly, + "Missing 'type' in given options:\n\n" + << *options_); + internal::ensure_generalized_eigen_solver_type(options_->get<std::string>("type"), + GeneralizedEigenSolverOptions<MatrixType, true>::types()); + const Common::Configuration default_opts = + GeneralizedEigenSolverOptions<MatrixType, true>::options(options_->get<std::string>("type")); + for (const std::string& default_key : default_opts.getValueKeys()) { + if (!options_->has_key(default_key)) + (*options_)[default_key] = default_opts.get<std::string>(default_key); + } + if (options_->get<double>("real_tolerance") <= 0) + DUNE_THROW(Exceptions::generalized_eigen_solver_failed_bc_it_was_not_set_up_correctly, + "It does not make sense to enforce a non-positive tolerance!"); + if (options_->get<double>("assert_positive_eigenvalues") > 0 + && options_->get<double>("assert_negative_eigenvalues") > 0) + DUNE_THROW(Exceptions::generalized_eigen_solver_failed_bc_it_was_not_set_up_correctly, + "It does not make sense to assert positive and negative eigenvalues!"); + if (!options_->get<bool>("compute_eigenvalues") + && (options_->get<double>("assert_real_eigenvalues") > 0 + || options_->get<double>("assert_positive_eigenvalues") > 0 + || options_->get<double>("assert_negative_eigenvalues") > 0 + /*|| options_->get<double>("assert_eigendecomposition") > 0 + || options_->get<double>("assert_real_eigendecomposition") > 0*/)) + (*options_)["compute_eigenvalues"] = "true"; + if (options_->get<double>("assert_real_eigenvalues") <= 0 + && (options_->get<double>("assert_positive_eigenvalues") > 0 + || options_->get<double>("assert_negative_eigenvalues") > 0 + /*|| options_->get<double>("assert_real_eigendecomposition") > 0*/)) + (*options_)["assert_real_eigenvalues"] = (*options_)["real_tolerance"]; + // check matrix + check_size(lhs_matrix_, rhs_matrix_); + if (options_->get<bool>("check_for_inf_nan") && contains_inf_or_nan(lhs_matrix_)) { + DUNE_THROW(Exceptions::generalized_eigen_solver_failed_bc_data_did_not_fulfill_requirements, + "Given LHS matrix contains inf or nan and you requested checking. To disable this check set " + "'check_for_inf_nan' to false in the options." + << "\n\nThese were the given options:\n\n" + << *options_ << "\nThis was the given LHS matrix:\n\n" + << lhs_matrix_); + } + if (options_->get<bool>("check_for_inf_nan") && contains_inf_or_nan(rhs_matrix_)) { + DUNE_THROW(Exceptions::generalized_eigen_solver_failed_bc_data_did_not_fulfill_requirements, + "Given RHS matrix contains inf or nan and you requested checking. To disable this check set " + "'check_for_inf_nan' to false in the options." + << "\n\nThese were the given options:\n\n" + << *options_ << "\nThis was the given RHS matrix:\n\n" + << rhs_matrix_); + } + } + } // ... pre_checks(...) + + void post_checks() const + { + if (!disable_checks_) { + if (options_->get<bool>("compute_eigenvalues") && !eigenvalues_) + DUNE_THROW(Common::Exceptions::internal_error, + "The eigenvalues_ member is not filled after calling compute()!"); + if (options_->get<bool>("compute_eigenvectors") && !eigenvectors_) + DUNE_THROW(Common::Exceptions::internal_error, + "The eigenvectors_ member is not filled after calling compute()!"); + if (options_->get<bool>("check_for_inf_nan")) { + if (eigenvalues_ && contains_inf_or_nan(*eigenvalues_)) + DUNE_THROW(Exceptions::generalized_eigen_solver_failed_bc_result_contained_inf_or_nan, + "Computed eigenvalues contain inf or nan and you requested checking. To disable this check set " + "'check_for_inf_nan' to false in the options." + << "\n\nThese were the given options:\n\n" + << *options_ << "\nThese are the computed eigenvalues:\n\n" + << *eigenvalues_); + if (eigenvectors_ && contains_inf_or_nan(*eigenvectors_)) + DUNE_THROW(Exceptions::generalized_eigen_solver_failed_bc_result_contained_inf_or_nan, + "Computed eigenvectors contain inf or nan and you requested checking. To disable this check set " + "'check_for_inf_nan' to false in the options." + << "\n\nThese were the given options:\n\n" + << *options_ << "\nThese are the computed eigenvectors:\n\n" + << *eigenvectors_); + } + const double assert_real_eigenvalues = options_->get<double>("assert_real_eigenvalues"); + const double assert_positive_eigenvalues = options_->get<double>("assert_positive_eigenvalues"); + const double assert_negative_eigenvalues = options_->get<double>("assert_negative_eigenvalues"); + // const double check_real_eigendecomposition = options_->get<double>("assert_real_eigendecomposition"); + if (assert_real_eigenvalues > 0 || assert_positive_eigenvalues > 0 || assert_negative_eigenvalues > 0 + /*|| check_real_eigendecomposition > 0*/) + compute_real_eigenvalues(); + if (assert_positive_eigenvalues > 0) { + assert(real_eigenvalues_ && "This must not happen after compute_real_eigenvalues()!"); + for (const auto& ev : *real_eigenvalues_) { + if (ev < assert_positive_eigenvalues) + DUNE_THROW(Exceptions::generalized_eigen_solver_failed_bc_eigenvalues_are_not_positive_as_requested, + "These were the given options:\n\n" + << *options_ << "\nThis were the given matrices:\n\nLHS\n" + << lhs_matrix_ << "\n\nRHS\n" + << rhs_matrix_ << "\nThese are the computed eigenvalues:\n\n" + << *real_eigenvalues_); + } + } + if (assert_negative_eigenvalues > 0) { + assert(real_eigenvalues_ && "This must not happen after compute_real_eigenvalues()!"); + for (const auto& ev : *real_eigenvalues_) { + if (ev > -1 * assert_negative_eigenvalues) + DUNE_THROW(Exceptions::generalized_eigen_solver_failed_bc_eigenvalues_are_not_negative_as_requested, + "These were the given options:\n\n" + << *options_ << "\nThis were the given matrices:\n\nLHS\n" + << lhs_matrix_ << "\n\nRHS\n" + << rhs_matrix_ << "\nThese are the computed eigenvalues:\n\n" + << *real_eigenvalues_); + } + } + if (options_->get<double>("assert_real_eigenvectors") > 0 /*|| check_real_eigendecomposition > 0*/) + compute_real_eigenvectors(); + // const double check_eigendecomposition = options_->get<double>("assert_eigendecomposition"); + // if (check_eigendecomposition > 0) + // complex_eigendecomposition_helper<>::check(*this, check_eigendecomposition); + // if (check_real_eigendecomposition > 0) { + // invert_real_eigenvectors(); + // assert_eigendecomposition(matrix_, + // *real_eigenvalues_, + // *real_eigenvectors_, + // *real_eigenvectors_inverse_, + // check_real_eigendecomposition); + // } + } + } // ... post_checks(...) + + void compute_real_eigenvalues() const + { + assert(eigenvalues_ && "This should not happen!"); + if (!real_eigenvalues_) { + real_eigenvalues_ = std::make_unique<std::vector<RealType>>(eigenvalues_->size()); + for (size_t ii = 0; ii < eigenvalues_->size(); ++ii) + (*real_eigenvalues_)[ii] = (*eigenvalues_)[ii].real(); + + if (!disable_checks_) { + const double assert_real_eigenvalues = options_->get<double>("assert_real_eigenvalues"); + const double tolerance = + (assert_real_eigenvalues > 0) ? assert_real_eigenvalues : options_->get<double>("real_tolerance"); + for (size_t ii = 0; ii < eigenvalues_->size(); ++ii) { + if (std::abs((*eigenvalues_)[ii].imag()) > tolerance) + DUNE_THROW(Exceptions::generalized_eigen_solver_failed_bc_eigenvalues_are_not_real_as_requested, + "These were the given options:\n\n" + << *options_ << "\nThese are the computed eigenvalues:\n\n" + << *eigenvalues_); + } // ii + } // if (!disable_checks_) + } // if (!real_eigenvalues_) + } // ... compute_real_eigenvalues(...) + + template <bool is_common_matrix = XT::Common::is_matrix<MatrixType>::value, class T = MatrixType> + struct real_eigenvectors_helper + {}; + + template <class T> + struct real_eigenvectors_helper<true, T> + { + static void compute(const ThisType& self, const double& tolerance) + { + using RM = XT::Common::MatrixAbstraction<RealMatrixType>; + using CM = XT::Common::MatrixAbstraction<ComplexMatrixType>; + const size_t rows = CM::rows(*self.eigenvectors_); + const size_t cols = CM::cols(*self.eigenvectors_); + self.real_eigenvectors_ = std::make_unique<RealMatrixType>(RM::create(rows, cols)); + bool is_complex = false; + for (size_t ii = 0; ii < rows; ++ii) { + for (size_t jj = 0; jj < cols; ++jj) { + const auto complex_value = CM::get_entry(*self.eigenvectors_, ii, jj); + if (std::abs(complex_value.imag()) > tolerance) { + is_complex = true; + ii = rows; + break; + } + RM::set_entry(*self.real_eigenvectors_, ii, jj, complex_value.real()); + } // jj + } // ii + + if (is_complex) { + // try to get real eigenvectors from the complex ones. If both the matrix and the eigenvalues are real, the + // eigenvectors can also be chosen real. If there is a imaginary eigenvector, both real and imaginary part are + // eigenvectors (if non-zero) to the same eigenvalue. So to get real eigenvectors, sort the eigenvectors by + // eigenvalues and, separately for each eigenvalue, perform a Gram-Schmidt process with all real and imaginary + // parts of the eigenvectors + self.compute_real_eigenvalues(); + + // form groups of equal eigenvalues + struct Cmp + { + bool operator()(const RealType& a, const RealType& b) const + { + return XT::Common::FloatCmp::lt(a, b); + } + }; + std::vector<std::vector<size_t>> eigenvalue_groups; + std::vector<size_t> eigenvalue_multiplicity; + std::set<RealType, Cmp> eigenvalues_done; + for (size_t jj = 0; jj < rows; ++jj) { + const auto curr_eigenvalue = (*self.real_eigenvalues_)[jj]; + if (!eigenvalues_done.count(curr_eigenvalue)) { + std::vector<size_t> curr_group; + curr_group.push_back(jj); + eigenvalue_multiplicity.push_back(1); + for (size_t kk = jj + 1; kk < rows; ++kk) { + if (XT::Common::FloatCmp::eq(curr_eigenvalue, (*self.real_eigenvalues_)[kk])) { + curr_group.push_back(kk); + ++(eigenvalue_multiplicity.back()); + } + } // kk + eigenvalue_groups.push_back(curr_group); + eigenvalues_done.insert(curr_eigenvalue); + } + } // jj + + // For each eigenvalue, calculate a orthogonal basis of the n-dim real eigenspace from the 2n real + // and imaginary parts of the complex eigenvectors + for (size_t kk = 0; kk < eigenvalue_groups.size(); ++kk) { + const auto& group = eigenvalue_groups[kk]; + typedef typename XT::LA::CommonDenseVector<RealType> RealVectorType; + std::vector<RealVectorType> input_vectors(2 * eigenvalue_multiplicity[kk], RealVectorType(rows, 0.)); + size_t index = 0; + for (const auto& jj : group) { + for (size_t ll = 0; ll < cols; ++ll) { + input_vectors[index][ll] = CM::get_entry(*self.eigenvectors_, ll, jj).real(); + input_vectors[index + 1][ll] = CM::get_entry(*self.eigenvectors_, ll, jj).imag(); + } + index += 2; + } // jj + + // orthonormalize + for (size_t ii = 0; ii < input_vectors.size(); ++ii) { + auto& v_i = input_vectors[ii]; + for (size_t jj = 0; jj < ii; ++jj) { + const auto& v_j = input_vectors[jj]; + const auto vj_vj = v_j.dot(v_j); + if (XT::Common::FloatCmp::eq(vj_vj, 0.)) + continue; + const auto vj_vi = v_j.dot(v_i); + for (size_t rr = 0; rr < rows; ++rr) + v_i[rr] -= vj_vi / vj_vj * v_j[rr]; + } // jj + RealType l2_norm = std::sqrt(std::accumulate( + v_i.begin(), v_i.end(), 0., [](const RealType& a, const RealType& b) { return a + b * b; })); + if (XT::Common::FloatCmp::ne(l2_norm, 0.)) + v_i *= 1. / l2_norm; + } // ii + // copy eigenvectors back to eigenvectors matrix + index = 0; + for (size_t ii = 0; ii < input_vectors.size(); ++ii) { + if (XT::Common::FloatCmp::ne(input_vectors[ii], RealVectorType(rows, 0.))) { + if (index >= eigenvalue_multiplicity[kk]) { + DUNE_THROW(Exceptions::generalized_eigen_solver_failed_bc_eigenvectors_are_not_real_as_requested, + "Eigenvectors are complex and calculating real eigenvectors failed!" + << "These were the given options:\n\n" + << *self.options_ << "\n\nThis were the given matrices:\n\nLHS\n" + << self.lhs_matrix_ << "\n\nRHS\n" + << self.rhs_matrix_ << "\nThese are the computed eigenvectors:\n\n" + << std::setprecision(17) << *self.eigenvectors_); + } + for (size_t rr = 0; rr < rows; ++rr) + RM::set_entry(*self.real_eigenvectors_, rr, group[index], input_vectors[ii].get_entry(rr)); + index++; + } // if (input_vectors[ii] != 0) + } // ii + if (index < eigenvalue_multiplicity[kk]) { + DUNE_THROW(Exceptions::generalized_eigen_solver_failed_bc_eigenvectors_are_not_real_as_requested, + "Eigenvectors are complex and calculating real eigenvectors failed!" + << "These were the given options:\n\n" + << *self.options_ << "\n\nThis were the given matrices:\n\nLHS\n" + << self.lhs_matrix_ << "\n\nRHS\n" + << self.rhs_matrix_ << "\nThese are the computed eigenvectors:\n\n" + << std::setprecision(17) << *self.eigenvectors_); + } + } // kk + } // if(is_complex) + } // static void compute(...) + }; // real_eigenvectors_helper<true, ...> + + template <class T> + struct real_eigenvectors_helper<false, T> + { + static void compute(ThisType& self, const double& tolerance) + { + if (RealMatrixType::sparse) { + const size_t rows = self.eigenvectors_->rows(); + const size_t cols = self.eigenvectors_->cols(); + const auto pattern = self.eigenvectors_->pattern(); + self.real_eigenvectors_ = std::make_unique<RealMatrixType>(rows, cols, pattern); + for (size_t ii = 0; ii < rows; ++ii) + for (size_t jj : pattern.inner(ii)) { + const auto complex_value = self.eigenvectors_->get_entry(ii, jj); + if (std::abs(complex_value.imag()) > tolerance) + DUNE_THROW(Exceptions::generalized_eigen_solver_failed_bc_eigenvectors_are_not_real_as_requested, + "These were the given options:\n\n" + << *self.options_ << "\nThese are the computed eigenvectors:\n\n" + << std::setprecision(17) << *self.eigenvectors_); + self.real_eigenvectors_->set_entry(ii, jj, complex_value.real()); + } + } else { + const size_t rows = self.eigenvectors_->rows(); + const size_t cols = self.eigenvectors_->cols(); + self.real_eigenvectors_ = std::make_unique<RealMatrixType>(rows, cols); + for (size_t ii = 0; ii < rows; ++ii) + for (size_t jj = 0; jj < cols; ++jj) { + const auto complex_value = self.eigenvectors_->get_entry(ii, jj); + if (std::abs(complex_value.imag()) > tolerance) + DUNE_THROW(Exceptions::generalized_eigen_solver_failed_bc_eigenvectors_are_not_real_as_requested, + "These were the given options:\n\n" + << *self.options_ << "\nThese are the computed eigenvectors:\n\n" + << std::setprecision(17) << *self.eigenvectors_); + self.real_eigenvectors_->set_entry(ii, jj, complex_value.real()); + } + } + } + }; // real_eigenvectors_helper<false, ...> + + void compute_real_eigenvectors() const + { + assert(eigenvectors_ && "This should not happen!"); + if (!real_eigenvectors_) { + const double assert_real_eigenvectors = options_->get<double>("assert_real_eigenvectors"); + const double tolerance = + (assert_real_eigenvectors > 0) ? assert_real_eigenvectors : options_->get<double>("real_tolerance"); + real_eigenvectors_helper<>::compute(*this, tolerance); + } + } + + // void invert_eigenvectors() const + // { + // assert(eigenvectors_ && "This must not happen when you call this function!"); + // try { + // if (options_->has_sub("matrix-inverter")) { + // eigenvectors_inverse_ = + // std::make_unique<ComplexMatrixType>(invert_matrix(*eigenvectors_, options_->sub("matrix-inverter"))); + // } else + // eigenvectors_inverse_ = std::make_unique<ComplexMatrixType>(invert_matrix(*eigenvectors_)); + // } catch (const Exceptions::matrix_invert_failed& ee) { + // DUNE_THROW(Exceptions::generalized_eigen_solver_failed, + // "The computed matrix of eigenvectors is not invertible!" + // << "\n\nmatrix = " << std::setprecision(17) << matrix_ << "\n\noptions: " << *options_ + // << "\n\neigenvectors = " << std::setprecision(17) << *eigenvectors_ + // << "\n\nThis was the original error: " << ee.what()); + // } + // } // ... invert_eigenvectors(...) + + // void invert_real_eigenvectors() const + // { + // assert(real_eigenvectors_ && "This must not happen when you call this function!"); + // try { + // if (options_->has_sub("matrix-inverter")) { + // real_eigenvectors_inverse_ = + // std::make_unique<MatrixType>(invert_matrix(*real_eigenvectors_, options_->sub("matrix-inverter"))); + // } else + // real_eigenvectors_inverse_ = std::make_unique<MatrixType>(invert_matrix(*real_eigenvectors_)); + // } catch (const Exceptions::matrix_invert_failed& ee) { + // DUNE_THROW(Exceptions::generalized_eigen_solver_failed, + // "The computed matrix of real eigenvectors is not invertible!" + // << "\n\nmatrix = " << std::setprecision(17) << matrix_ << "\n\noptions: " << *options_ + // << "\n\nreal_eigenvectors = " << std::setprecision(17) << *real_eigenvectors_ + // << "\n\nThis was the original error: " << ee.what()); + // } + // } // ... invert_real_eigenvectors(...) + + // template <class A, class B, class C, class D> + // void assert_eigendecomposition(const std::unique_ptr<A>& mat, + // const B& eigenvalues, + // const C& eigenvectors, + // const D& eigenvectors_inverse, + // const double& tolerance) const + // { + // assert_eigendecomposition(*mat, eigenvalues, eigenvectors, eigenvectors_inverse, tolerance); + // } + + // template <class A, class B, class C, class D> + // void assert_eigendecomposition(const A& mat, + // const B& eigenvalues, + // const C& eigenvectors, + // const D& eigenvectors_inverse, + // const double& tolerance) const + // { + // using M = Common::MatrixAbstraction<C>; + // const size_t rows = Common::get_matrix_rows(mat); + // const size_t cols = Common::get_matrix_cols(mat); + // auto eigenvalue_matrix = M::create(rows, cols, typename M::ScalarType(0.)); + // for (size_t ii = 0; ii < rows; ++ii) + // Common::set_matrix_entry(eigenvalue_matrix, ii, ii, eigenvalues[ii]); + // const auto decomposition_error = eigenvectors * eigenvalue_matrix * eigenvectors_inverse - mat; + // for (size_t ii = 0; ii < rows; ++ii) + // for (size_t jj = 0; jj < cols; ++jj) + // if (std::abs(Common::get_matrix_entry(decomposition_error, ii, jj)) > tolerance) + // DUNE_THROW(Exceptions::generalized_eigen_solver_failed_bc_result_is_not_an_eigendecomposition, + // "\n\nmatrix = " << std::setprecision(17) << matrix_ << "\n\noptions: " << *options_ + // << "\n\neigenvalues (lambda)= " << std::setprecision(17) << eigenvalues + // << "\n\neigenvectors (T) = " << std::setprecision(17) << eigenvectors + // << "\n\n(T * (lambda * T^-1)) - matrix = " << decomposition_error); + // } // ... assert_eigendecomposition(...) + + // template <bool upcast_required = !std::is_same<MatrixType, ComplexMatrixType>::value, bool anything = true> + // struct complex_eigendecomposition_helper; + + // template <bool anything> + // struct complex_eigendecomposition_helper<true, anything> + // { + // static void check(const ThisType& self, const double& tolerance) + // { + // self.invert_eigenvectors(); + // self.assert_eigendecomposition(Dune::XT::LA::convert_to<ComplexMatrixType>(self.matrix_), + // *self.eigenvalues_, + // *self.eigenvectors_, + // *self.eigenvectors_inverse_, + // tolerance); + // } + // }; + + // template <bool anything> + // struct complex_eigendecomposition_helper<false, anything> + // { + // static void check(const ThisType& self, const double& tolerance) + // { + // self.invert_eigenvectors(); + // self.assert_eigendecomposition( + // self.matrix_, *self.eigenvalues_, *self.eigenvectors_, *self.eigenvectors_inverse_, tolerance); + // } + // }; + + template <class M> + void check_size(const MatrixInterface<M>& lhs_mat, const MatrixInterface<M>& rhs_mat) const + { + const auto sz = lhs_mat.rows(); + if (lhs_mat.cols() != sz) + DUNE_THROW(Exceptions::generalized_eigen_solver_failed_bc_data_did_not_fulfill_requirements, + "LHS Matrix has to be square, is " << lhs_mat.rows() << "x" << lhs_mat.cols() << "!"); + if (rhs_mat.rows() != sz) + DUNE_THROW(Exceptions::generalized_eigen_solver_failed_bc_data_did_not_fulfill_requirements, + "Matrices have to be of same size, are " << lhs_mat.rows() << "x" << lhs_mat.cols() << " and " + << rhs_mat.rows() << "x" << rhs_mat.cols() << "!"); + if (rhs_mat.cols() != sz) + DUNE_THROW(Exceptions::generalized_eigen_solver_failed_bc_data_did_not_fulfill_requirements, + "RHS Matrix has to be square, is " << rhs_mat.rows() << "x" << rhs_mat.cols() << "!"); + } // ... check_size(...) + + template <class M> + typename std::enable_if<XT::Common::is_matrix<M>::value && !is_matrix<M>::value, void>::type + check_size(const M& lhs_mat, const M& rhs_mat) const + { + using Mat = XT::Common::MatrixAbstraction<M>; + const auto sz = Mat::rows(lhs_mat); + if (Mat::cols(lhs_mat) != sz) + DUNE_THROW(Exceptions::generalized_eigen_solver_failed_bc_data_did_not_fulfill_requirements, + "LHS Matrix has to be square, is " << Mat::rows(lhs_mat) << "x" << Mat::cols(lhs_mat) << "!"); + if (Mat::rows(rhs_mat) != sz) + DUNE_THROW(Exceptions::generalized_eigen_solver_failed_bc_data_did_not_fulfill_requirements, + "Matrices have to be of same size, are " << Mat::rows(lhs_mat) << "x" << Mat::cols(lhs_mat) << " and " + << Mat::rows(rhs_mat) << "x" << Mat::cols(rhs_mat) << "!"); + if (Mat::cols(rhs_mat) != sz) + DUNE_THROW(Exceptions::generalized_eigen_solver_failed_bc_data_did_not_fulfill_requirements, + "RHS Matrix has to be square, is " << Mat::rows(rhs_mat) << "x" << Mat::cols(rhs_mat) << "!"); + } // ... check_size(...) + + template <class T> + bool contains_inf_or_nan(const std::vector<T>& vec) const + { + for (const auto& element : vec) + if (XT::Common::isinf(element) || XT::Common::isnan(element)) + return true; + return false; + } + + template <class M> + bool contains_inf_or_nan(const MatrixInterface<M>& mat) const + { + return !mat.valid(); + } + + template <class M> + typename std::enable_if<XT::Common::is_matrix<M>::value && !is_matrix<M>::value, bool>::type + contains_inf_or_nan(const M& mat) const + { + using Mat = XT::Common::MatrixAbstraction<M>; + for (size_t ii = 0; ii < Mat::rows(mat); ++ii) + for (size_t jj = 0; jj < Mat::cols(mat); ++jj) { + const auto value = Mat::get_entry(mat, ii, jj); + if (XT::Common::isinf(value) || XT::Common::isnan(value)) + return true; + } + return false; + } // ... contains_inf_or_nan(...) + + const MatrixType& lhs_matrix_; + const MatrixType& rhs_matrix_; + mutable Common::Configuration stored_options_; + mutable Common::Configuration* options_; + mutable bool computed_; + mutable std::unique_ptr<std::vector<ComplexType>> eigenvalues_; + mutable std::unique_ptr<std::vector<RealType>> real_eigenvalues_; + mutable std::unique_ptr<ComplexMatrixType> eigenvectors_; + mutable std::unique_ptr<RealMatrixType> real_eigenvectors_; + // mutable std::unique_ptr<ComplexMatrixType> eigenvectors_inverse_; + // mutable std::unique_ptr<RealMatrixType> real_eigenvectors_inverse_; + const bool disable_checks_; +}; // class GeneralizedEigenSolverBase + + +} // namespace internal +} // namespace LA +} // namespace XT +} // namespace Dune + +#endif // DUNE_XT_LA_GENERALIZED_EIGEN_SOLVER_INTERNAL_BASE_HH diff --git a/dune/xt/la/generalized-eigen-solver/internal/lapacke.hh b/dune/xt/la/generalized-eigen-solver/internal/lapacke.hh new file mode 100644 index 0000000000000000000000000000000000000000..36bf88463a26c89e9ae29993d01775b56c23d12a --- /dev/null +++ b/dune/xt/la/generalized-eigen-solver/internal/lapacke.hh @@ -0,0 +1,170 @@ +// This file is part of the dune-xt-la project: +// https://github.com/dune-community/dune-xt-la +// Copyright 2009-2018 dune-xt-la developers and contributors. All rights reserved. +// License: Dual licensed as BSD 2-Clause License (http://opensource.org/licenses/BSD-2-Clause) +// or GPL-2.0+ (http://opensource.org/licenses/gpl-license) +// with "runtime exception" (http://www.dune-project.org/license.html) +// Authors: +// Felix Schindler (2019) + +#ifndef DUNE_XT_LA_GENERALIZED_EIGEN_SOLVER_INTERNAL_LAPACKE_HH +#define DUNE_XT_LA_GENERALIZED_EIGEN_SOLVER_INTERNAL_LAPACKE_HH + +#include <complex> +#include <vector> +#include <string> +#include <numeric> +#include <type_traits> + +#include <dune/common/typetraits.hh> + +#include <dune/xt/common/matrix.hh> +#include <dune/xt/common/type_traits.hh> +#include <dune/xt/common/lapacke.hh> + +#include <dune/xt/la/container/common/matrix/dense.hh> +#include <dune/xt/la/exceptions.hh> +#include <dune/xt/la/type_traits.hh> + +#include <dune/xt/la/eigen-solver/internal/lapacke.hh> + +namespace Dune { +namespace XT { +namespace LA { +namespace internal { + + +/** + * \sa https://software.intel.com/en-us/mkl-developer-reference-c-sygv + * \note Most likely, you do not want to use this function directly, but compute_generalized_eigenvalues_using_lapack. + */ +template <class RealMatrixType> +typename std::enable_if<Common::is_matrix<std::decay_t<RealMatrixType>>::value, std::vector<std::complex<double>>>::type +compute_generalized_eigenvalues_of_real_matrices_using_lapack(RealMatrixType&& lhs_matrix, RealMatrixType&& rhs_matrix) +{ + MatrixDataProvider<std::decay_t<RealMatrixType>, is_contiguous_and_mutable<RealMatrixType>::value> + lhs_matrix_data_provider(lhs_matrix); + MatrixDataProvider<std::decay_t<RealMatrixType>, is_contiguous_and_mutable<RealMatrixType>::value> + rhs_matrix_data_provider(rhs_matrix); + return compute_generalized_eigenvalues_of_real_matrices_using_lapack_impl( + lhs_matrix, lhs_matrix_data_provider, rhs_matrix, rhs_matrix_data_provider); +} + + +template <class RealMatrixType, bool contiguous_and_mutable> +typename std::enable_if<Common::is_matrix<RealMatrixType>::value, std::vector<std::complex<double>>>::type +compute_generalized_eigenvalues_of_real_matrices_using_lapack_impl( + const RealMatrixType& lhs_matrix, + MatrixDataProvider<RealMatrixType, contiguous_and_mutable>& lhs_matrix_data_provider, + const RealMatrixType& rhs_matrix, + MatrixDataProvider<RealMatrixType, contiguous_and_mutable>& rhs_matrix_data_provider) +{ + if (!Common::Lapacke::available()) + DUNE_THROW(Exceptions::generalized_eigen_solver_failed_bc_it_was_not_set_up_correctly, + "Do not call any lapack related method if Common::Lapacke::available() is false!"); + using real_type = typename Dune::XT::Common::MatrixAbstraction<RealMatrixType>::S; + static_assert(Dune::XT::Common::is_arithmetic<real_type>::value && !Dune::XT::Common::is_complex<real_type>::value, + "Not implemented for complex matrices (yet)!"); + const size_t size = Dune::XT::Common::get_matrix_rows(lhs_matrix); + const auto sz = XT::Common::numeric_cast<int>(size); +#ifdef DUNE_XT_LA_DISABLE_ALL_CHECKS + assert(Dune::XT::Common::get_matrix_cols(lhs_matrix) == size); + assert(Dune::XT::Common::get_matrix_rows(rhs_matrix) == size); + assert(Dune::XT::Common::get_matrix_cols(rhs_matrix) == size); +#else + if (Dune::XT::Common::get_matrix_cols(lhs_matrix) != size) + DUNE_THROW(Exceptions::generalized_eigen_solver_failed_bc_data_did_not_fulfill_requirements, + "Given LHS matrix has to be square, is " << size << "x" << Dune::XT::Common::get_matrix_cols(lhs_matrix) + << "!"); + if (Dune::XT::Common::get_matrix_rows(rhs_matrix) != size) + DUNE_THROW(Exceptions::generalized_eigen_solver_failed_bc_data_did_not_fulfill_requirements, + "Given matrices have to be of same size, are " << size << "x" + << Dune::XT::Common::get_matrix_cols(lhs_matrix) << "and " + << Dune::XT::Common::get_matrix_rows(rhs_matrix) << "x" + << Dune::XT::Common::get_matrix_cols(rhs_matrix) << "!"); + if (Dune::XT::Common::get_matrix_cols(rhs_matrix) != size) + DUNE_THROW(Exceptions::generalized_eigen_solver_failed_bc_data_did_not_fulfill_requirements, + "Given RHS matrix has to be square, is " << Dune::XT::Common::get_matrix_rows(rhs_matrix) << "x" + << Dune::XT::Common::get_matrix_cols(rhs_matrix) << "!"); +#endif // DUNE_XT_LA_DISABLE_ALL_CHECKS + thread_local std::vector<double> real_part_of_eigenvalues(size, 0.); + int storage_layout = MatrixDataProvider<RealMatrixType, contiguous_and_mutable>::storage_layout + == Common::StorageLayout::dense_row_major + ? Common::Lapacke::row_major() + : Common::Lapacke::col_major(); + const int info = XT::Common::Lapacke::dsygv(storage_layout, + /*problem type=*/1, + /*only eigenvalues*/ 'N', + /*upper triangles*/ 'U', + sz, + lhs_matrix_data_provider.data(), + sz, + rhs_matrix_data_provider.data(), + sz, + real_part_of_eigenvalues.data()); + if (info != 0) + DUNE_THROW(Dune::XT::LA::Exceptions::generalized_eigen_solver_failed, + "The lapack backend reported '" + << info << "', see https://software.intel.com/en-us/mkl-developer-reference-c-sygv!"); + std::vector<std::complex<double>> eigenvalues(size); + for (size_t ii = 0; ii < size; ++ii) + eigenvalues[ii] = {real_part_of_eigenvalues[ii], 0.}; + return eigenvalues; +} // ... compute_generalized_eigenvalues_of_real_matrices_using_lapack_impl(...) + + +/** + * \note Most likely, you do not want to use this function directly, but compute_generalized_eigenvalues_using_lapack. + */ +template <class MatrixType> +struct generalized_eigenvalues_lapack_helper +{ + static_assert(Common::is_matrix<MatrixType>::value, ""); + + template <bool is_complex = Common::is_complex<typename Common::MatrixAbstraction<MatrixType>::S>::value, + bool anything = true> + struct dtype_switch; + + template <bool anything> + struct dtype_switch<true, anything> + { + template <class MatrixImp> + static inline std::vector<std::complex<double>> eigenvalues(MatrixImp&& /*lhs_matrix*/, MatrixImp&& /*rhs_matrix*/) + { + static_assert(AlwaysFalse<MatrixImp>::value, + "Not yet implemented for complex matrices, take a look at " + "https://software.intel.com/en-us/mkl-developer-reference-c-sygv " + "and add a corresponding free function like " + "compute_generalized_eigenvalues_of_real_matrices_using_lapack(...)!"); + return std::vector<std::complex<double>>(); + } + }; + + template <bool anything> + struct dtype_switch<false, anything> + { + template <class MatrixImp> + static inline std::vector<std::complex<double>> eigenvalues(MatrixImp&& lhs_matrix, MatrixImp&& rhs_matrix) + { + return compute_generalized_eigenvalues_of_real_matrices_using_lapack(std::forward<MatrixImp>(lhs_matrix), + std::forward<MatrixImp>(rhs_matrix)); + } + }; +}; // class generalized_eigenvalues_lapack_helper + + +template <class MatrixType> +typename std::enable_if<Common::is_matrix<std::decay_t<MatrixType>>::value, std::vector<std::complex<double>>>::type +compute_generalized_eigenvalues_using_lapack(MatrixType&& lhs_matrix, MatrixType&& rhs_matrix) +{ + return generalized_eigenvalues_lapack_helper<std::decay_t<MatrixType>>::template dtype_switch<>::eigenvalues( + std::forward<MatrixType>(lhs_matrix), std::forward<MatrixType>(rhs_matrix)); +} + + +} // namespace internal +} // namespace LA +} // namespace XT +} // namespace Dune + +#endif // DUNE_XT_LA_GENERALIZED_EIGEN_SOLVER_INTERNAL_LAPACKE_HH diff --git a/dune/xt/la/test/generalized-eigensolver.hh b/dune/xt/la/test/generalized-eigensolver.hh new file mode 100644 index 0000000000000000000000000000000000000000..d25eaed99724aecf619debc76b7c46cc93d1f6a7 --- /dev/null +++ b/dune/xt/la/test/generalized-eigensolver.hh @@ -0,0 +1,331 @@ +// This file is part of the dune-xt-la project: +// https://github.com/dune-community/dune-xt-la +// Copyright 2009-2018 dune-xt-la developers and contributors. All rights reserved. +// License: Dual licensed as BSD 2-Clause License (http://opensource.org/licenses/BSD-2-Clause) +// or GPL-2.0+ (http://opensource.org/licenses/gpl-license) +// with "runtime exception" (http://www.dune-project.org/license.html) +// Authors: +// Felix Schindler (2019) + +#ifndef DUNE_XT_LA_TEST_GENERALIZED_EIGENSOLVER_HH +#define DUNE_XT_LA_TEST_GENERALIZED_EIGENSOLVER_HH + +#include <dune/xt/common/exceptions.hh> +#include <dune/xt/common/unused.hh> +#include <dune/xt/common/logging.hh> +#include <dune/xt/common/test/gtest/gtest.h> + +#include <dune/xt/la/container.hh> +#include <dune/xt/la/container/conversion.hh> +#include <dune/xt/la/container/eye-matrix.hh> +#include <dune/xt/la/generalized-eigen-solver.hh> + +using namespace Dune; +using namespace Dune::XT; +using namespace Dune::XT::LA; + + +template <class MatrixImp, class F, class C, class R> +struct GeneralizedEigenSolverTest : public ::testing::Test +{ + using MatrixType = MatrixImp; + using FieldType = Common::real_t<F>; + using ComplexMatrixType = C; + using RealMatrixType = R; + using RealType = Common::real_t<FieldType>; + using EigenValuesType = std::vector<Common::complex_t<FieldType>>; + using RealEigenValuesType = std::vector<Common::real_t<FieldType>>; + typedef GeneralizedEigenSolver<MatrixType> GeneralizedEigenSolverType; + typedef GeneralizedEigenSolverOptions<MatrixType> GeneralizedEigenSolverOpts; + + using M = Common::MatrixAbstraction<MatrixType>; + + GeneralizedEigenSolverTest() + : all_matrices_and_expected_eigenvalues_and_vectors_are_computed_(false) + , broken_matrix_(M::create(M::has_static_size ? M::static_rows : 1, M::has_static_size ? M::static_cols : 1)) + , unit_matrix_( + eye_matrix<MatrixType>(M::has_static_size ? M::static_rows : 1, M::has_static_size ? M::static_cols : 1)) + { + M::set_entry(broken_matrix_, 0, 0, std::numeric_limits<typename M::S>::infinity()); + } + + void make_unit_matrix() + { + ASSERT_TRUE(all_matrices_and_expected_eigenvalues_and_vectors_are_computed_); + unit_matrix_ = eye_matrix<MatrixType>(M::has_static_size ? M::static_rows : M::rows(matrix_), + M::has_static_size ? M::static_cols : M::cols(matrix_)); + } + + static void exports_correct_types() + { + const bool MatrixType_is_correct = std::is_same<typename GeneralizedEigenSolverType::MatrixType, MatrixType>::value; + EXPECT_TRUE(MatrixType_is_correct); + const bool RealType_is_correct = std::is_same<typename GeneralizedEigenSolverType::RealType, RealType>::value; + EXPECT_TRUE(RealType_is_correct); + const bool RealMatrixType_is_correct = + std::is_same<typename GeneralizedEigenSolverType::RealMatrixType, RealMatrixType>::value; + EXPECT_TRUE(RealMatrixType_is_correct); + const bool ComplexMatrixType_is_correct = + std::is_same<typename GeneralizedEigenSolverType::ComplexMatrixType, ComplexMatrixType>::value; + EXPECT_TRUE(ComplexMatrixType_is_correct); + } // ... exports_correct_types() + + static void has_types_and_options() + { + std::vector<std::string> types = GeneralizedEigenSolverOpts::types(); + EXPECT_GT(types.size(), 0); + Common::Configuration opts = GeneralizedEigenSolverOpts::options(); + EXPECT_TRUE(opts.has_key("type")); + EXPECT_EQ(types[0], opts.get<std::string>("type")); + for (const auto& tp : types) { + Common::Configuration tp_opts = GeneralizedEigenSolverOpts::options(tp); + EXPECT_TRUE(tp_opts.has_key("type")); + EXPECT_EQ(tp, tp_opts.get<std::string>("type")); + } + } // ... has_types_and_options(...) + + void throws_on_broken_matrix_construction() + { + try { + GeneralizedEigenSolverType DXTC_UNUSED(default_solver){broken_matrix_, unit_matrix_}; + FAIL() << "Expected LA::Exceptions::generalized_eigen_solver_failed_bc_data_did_not_fulfill_requirements"; + } catch (const LA::Exceptions::generalized_eigen_solver_failed_bc_data_did_not_fulfill_requirements& /*ee*/) { + } catch (...) { + FAIL() << "Expected LA::Exceptions::generalized_eigen_solver_failed_bc_data_did_not_fulfill_requirements"; + } + for (const auto& tp : GeneralizedEigenSolverOpts::types()) { + try { + GeneralizedEigenSolverType DXTC_UNUSED(default_opts_solver)(broken_matrix_, unit_matrix_, tp); + FAIL() << "Expected LA::Exceptions::generalized_eigen_solver_failed_bc_data_did_not_fulfill_requirements"; + } catch (const LA::Exceptions::generalized_eigen_solver_failed_bc_data_did_not_fulfill_requirements& /*ee*/) { + } catch (...) { + FAIL() << "Expected LA::Exceptions::generalized_eigen_solver_failed_bc_data_did_not_fulfill_requirements"; + } + try { + GeneralizedEigenSolverType DXTC_UNUSED(solver)( + broken_matrix_, unit_matrix_, GeneralizedEigenSolverOpts::options(tp)); + FAIL() << "Expected LA::Exceptions::generalized_eigen_solver_failed_bc_data_did_not_fulfill_requirements"; + } catch (const LA::Exceptions::generalized_eigen_solver_failed_bc_data_did_not_fulfill_requirements& /*ee*/) { + } catch (...) { + FAIL() << "Expected LA::Exceptions::generalized_eigen_solver_failed_bc_data_did_not_fulfill_requirements"; + } + } + } // ... throws_on_broken_matrix_construction(...) + + void allows_broken_matrix_construction_when_checks_disabled() + { + for (const auto& tp : GeneralizedEigenSolverOpts::types()) { + Common::Configuration opts_with_disabled_check = GeneralizedEigenSolverOpts::options(tp); + opts_with_disabled_check["check_for_inf_nan"] = "false"; + GeneralizedEigenSolverType DXTC_UNUSED(solver)(broken_matrix_, unit_matrix_, opts_with_disabled_check); + } + } + + void throws_on_inconsistent_given_options() + { + for (const auto& tp : GeneralizedEigenSolverOpts::types()) { + Common::Configuration inconsistent_opts = GeneralizedEigenSolverOpts::options(tp); + inconsistent_opts["assert_positive_eigenvalues"] = "1e-15"; + inconsistent_opts["assert_negative_eigenvalues"] = "1e-15"; + try { + GeneralizedEigenSolverType DXTC_UNUSED(solver)(unit_matrix_, unit_matrix_, inconsistent_opts); + FAIL() << "Expected LA::Exceptions::generalized_eigen_solver_failed_bc_it_was_not_set_up_correctly"; + } catch (const LA::Exceptions::generalized_eigen_solver_failed_bc_it_was_not_set_up_correctly& /*ee*/) { + } catch (...) { + FAIL() << "Expected LA::Exceptions::generalized_eigen_solver_failed_bc_it_was_not_set_up_correctly"; + } + } + } // ... throws_on_inconsistent_given_options(...) + + void is_constructible() + { + ASSERT_TRUE(all_matrices_and_expected_eigenvalues_and_vectors_are_computed_); + this->make_unit_matrix(); + GeneralizedEigenSolverType DXTC_UNUSED(default_solver){matrix_, unit_matrix_}; + for (const auto& tp : GeneralizedEigenSolverOpts::types()) { + GeneralizedEigenSolverType DXTC_UNUSED(default_opts_solver)(matrix_, unit_matrix_, tp); + GeneralizedEigenSolverType DXTC_UNUSED(solver)(matrix_, unit_matrix_, GeneralizedEigenSolverOpts::options(tp)); + } + } // ... is_constructible(...) + + static bool find_ev(const EigenValuesType& expected_evs, + const XT::Common::complex_t<FieldType>& actual_ev, + const double& tolerance) + { + for (const auto& expected_ev : expected_evs) { + if (Common::FloatCmp::eq(actual_ev, expected_ev, {tolerance, tolerance})) + return true; + } + return false; + } + + static bool find_ev(const RealEigenValuesType& expected_evs, const RealType& actual_ev, const double& tolerance) + { + for (const auto& expected_ev : expected_evs) { + if (Common::FloatCmp::eq(actual_ev, expected_ev, tolerance)) + return true; + } + return false; + } + + void gives_correct_eigenvalues(const Common::Configuration& tolerances = {}) + { + ASSERT_TRUE(all_matrices_and_expected_eigenvalues_and_vectors_are_computed_); + this->make_unit_matrix(); + for (const auto& tp : GeneralizedEigenSolverOpts::types()) { + const double tolerance = tolerances.get(tp, 1e-15); + GeneralizedEigenSolverType solver(matrix_, unit_matrix_, tp); + try { + const auto& eigenvalues = solver.eigenvalues(); + EXPECT_EQ(Common::get_matrix_rows(matrix_), eigenvalues.size()); + for (const auto& ev : eigenvalues) { + EXPECT_TRUE(find_ev(expected_eigenvalues_, ev, tolerance)) + << "\n\nactual eigenvalue: " << ev << "\n\nexpected eigenvalues: " << expected_eigenvalues_ + << "\n\ntype: " << tp << "\n\ntolerance: " << tolerance; + } + } catch (const Dune::MathError&) { + if (tolerance > 0) { + FAIL() << "Dune::MathError thrown when trying to get eigenvalues!" + << "\n\ntype: " << tp << "\n\ntolerance: " << tolerance; + } + } + } + } // ... gives_correct_eigenvalues(...) + + bool all_matrices_and_expected_eigenvalues_and_vectors_are_computed_; + MatrixType broken_matrix_; + MatrixType unit_matrix_; + MatrixType matrix_; + EigenValuesType expected_eigenvalues_; +}; // ... struct GeneralizedEigenSolverTest + + +template <class M, class F, class C, class R> +struct GeneralizedEigenSolverTestForMatricesWithRealEigenvaluesAndVectors + : public GeneralizedEigenSolverTest<M, F, C, R> +{ + using BaseType = GeneralizedEigenSolverTest<M, F, C, R>; + using BaseType::find_ev; + using typename BaseType::ComplexMatrixType; + using typename BaseType::EigenValuesType; + using typename BaseType::FieldType; + using typename BaseType::GeneralizedEigenSolverOpts; + using typename BaseType::GeneralizedEigenSolverType; + using typename BaseType::MatrixType; + using typename BaseType::RealEigenValuesType; + using typename BaseType::RealMatrixType; + using typename BaseType::RealType; + + void gives_correct_real_eigenvalues(const Common::Configuration& tolerances = {}) + { + ASSERT_TRUE(all_matrices_and_expected_eigenvalues_and_vectors_are_computed_); + this->make_unit_matrix(); + for (const auto& tp : GeneralizedEigenSolverOpts::types()) { + const double tolerance = tolerances.get(tp, 1e-15); + GeneralizedEigenSolverType solver(matrix_, unit_matrix_, tp); + const auto& eigenvalues = solver.eigenvalues(); + EXPECT_EQ(Common::get_matrix_rows(matrix_), eigenvalues.size()); + for (const auto& complex_ev : eigenvalues) { + if (tolerance > 0) + EXPECT_TRUE(Common::FloatCmp::eq(0., complex_ev.imag(), tolerance)) + << "\n type: " << tp << "\n tolerance: " << tolerance; + else { + // negative tolerance: we expect a failure + EXPECT_FALSE(Common::FloatCmp::eq(0., complex_ev.imag())) + << "\n\nTHIS IS A GOOD THING! UPDATE THE EXPECTATIONS IN tolerances!\n\n" + << "\n type: " << tp; + } + const auto real_ev = complex_ev.real(); + if (tolerance > 0) + EXPECT_TRUE(find_ev(expected_real_eigenvalues_, real_ev, tolerance)) + << "\n\nactual eigenvalue: " << real_ev << "\n\nexpected eigenvalues: " << expected_real_eigenvalues_ + << "\n\ntype: " << tp << "\n\ntolerance: " << tolerance; + else { + // negative tolerance: we expect a failure + EXPECT_FALSE(find_ev(expected_real_eigenvalues_, real_ev, 1e-15)) + << "\n\nTHIS IS A GOOD THING! UPDATE THE EXPECTATIONS IN tolerances!\n\n" + << "\n\nactual eigenvalue: " << real_ev << "\n\nexpected eigenvalues: " << expected_real_eigenvalues_ + << "\n\ntype: " << tp; + } + } + if (tolerance > 0) { + const auto& real_eigenvalues = solver.real_eigenvalues(); + EXPECT_EQ(Common::get_matrix_rows(matrix_), real_eigenvalues.size()); + for (const auto& real_ev : real_eigenvalues) { + EXPECT_TRUE(find_ev(expected_real_eigenvalues_, real_ev, tolerance)) + << "\n\nactual eigenvalue: " << real_ev << "\n\nexpected eigenvalues: " << expected_real_eigenvalues_ + << "\n\ntype: " << tp << "\n\ntolerance: " << tolerance; + } + } else { + // negative tolerance: we expect a failure + const auto& real_eigenvalues = solver.real_eigenvalues(); /// \todo: Add try/catch around this, too! + EXPECT_EQ(Common::get_matrix_rows(matrix_), real_eigenvalues.size()); + for (const auto& real_ev : real_eigenvalues) { + EXPECT_FALSE(find_ev(expected_real_eigenvalues_, real_ev, 1e-15)) + << "\n\nTHIS IS A GOOD THING! UPDATE THE EXPECTATIONS IN tolerances!\n\n" + << "\n\nactual eigenvalue: " << real_ev << "\n\nexpected eigenvalues: " << expected_real_eigenvalues_ + << "\n\ntype: " << tp; + } + } + } + } // ... gives_correct_real_eigenvalues(...) + + void gives_correct_max_eigenvalue(const Common::Configuration& tolerances = {}) + { + ASSERT_TRUE(all_matrices_and_expected_eigenvalues_and_vectors_are_computed_); + this->make_unit_matrix(); + for (const auto& tp : GeneralizedEigenSolverOpts::types()) { + const double tolerance = tolerances.get(tp, 1e-15); + GeneralizedEigenSolverType solver(matrix_, unit_matrix_, tp); + const auto actual_max_eigenvalues = solver.max_eigenvalues(1); /// \todo: Add try/catch around this, too! + ASSERT_GE(1, actual_max_eigenvalues.size()); + if (tolerance > 0) + EXPECT_TRUE(Common::FloatCmp::eq(expected_max_ev_, actual_max_eigenvalues[0], tolerance)) + << "\n\nactual max eigenvalue: " << actual_max_eigenvalues[0] + << "\n\nexpected max eigenvalue: " << expected_max_ev_ << "\n\ntolerance: " << tolerance + << "\n\ntype: " << tp; + else { + // negative tolerance: we expect a failure + EXPECT_NE(expected_max_ev_, actual_max_eigenvalues[0]) + << "\n\nTHIS IS A GOOD THING! UPDATE THE EXPECTATIONS IN tolerances!\n\n" + << "\n\nactual max eigenvalue: " << actual_max_eigenvalues[0] + << "\n\nexpected max eigenvalue: " << expected_max_ev_ << "\n\ntype: " << tp; + } + } + } + + void gives_correct_min_eigenvalue(const Common::Configuration& tolerances = {}) + { + ASSERT_TRUE(all_matrices_and_expected_eigenvalues_and_vectors_are_computed_); + this->make_unit_matrix(); + for (const auto& tp : GeneralizedEigenSolverOpts::types()) { + const double tolerance = tolerances.get(tp, 1e-15); + GeneralizedEigenSolverType solver(matrix_, unit_matrix_, tp); + const auto actual_min_eigenvalues = solver.min_eigenvalues(1); /// \todo: Add try/catch around this, too! + ASSERT_GE(1, actual_min_eigenvalues.size()); + if (tolerance > 0) + EXPECT_TRUE(Common::FloatCmp::eq(expected_min_ev_, actual_min_eigenvalues[0], tolerance)) + << "\n\nactual min eigenvalue: " << actual_min_eigenvalues[0] + << "\n\nexpected min eigenvalue: " << expected_min_ev_ << "\n\ntolerance: " << tolerance + << "\n\ntype: " << tp; + else { + // negative tolerance: we expect a failure + EXPECT_NE(expected_min_ev_, actual_min_eigenvalues[0]) + << "\n\nTHIS IS A GOOD THING! UPDATE THE EXPECTATIONS IN tolerances!\n\n" + << "\n\nactual min eigenvalue: " << actual_min_eigenvalues[0] + << "\n\nexpected min eigenvalue: " << expected_min_ev_ << "\n\ntype: " << tp; + } + } + } + + using BaseType::all_matrices_and_expected_eigenvalues_and_vectors_are_computed_; + using BaseType::expected_eigenvalues_; + using BaseType::matrix_; + using BaseType::unit_matrix_; + RealEigenValuesType expected_real_eigenvalues_; + RealType expected_max_ev_; + RealType expected_min_ev_; +}; // struct GeneralizedEigenSolverTestForMatricesWithRealEigenvaluesAndVectors + + +#endif // DUNE_XT_LA_TEST_GENERALIZED_EIGENSOLVER_HH diff --git a/dune/xt/la/test/generalized-eigensolver_for_real_matrix_with_real_evs.py b/dune/xt/la/test/generalized-eigensolver_for_real_matrix_with_real_evs.py new file mode 100644 index 0000000000000000000000000000000000000000..eeba057a279a3a08d5ea5cbfa89cc810857e5900 --- /dev/null +++ b/dune/xt/la/test/generalized-eigensolver_for_real_matrix_with_real_evs.py @@ -0,0 +1,35 @@ +# ~~~ +# This file is part of the dune-xt-la project: +# https://github.com/dune-community/dune-xt-la +# Copyright 2009-2018 dune-xt-la developers and contributors. All rights reserved. +# License: Dual licensed as BSD 2-Clause License (http://opensource.org/licenses/BSD-2-Clause) +# or GPL-2.0+ (http://opensource.org/licenses/gpl-license) +# with "runtime exception" (http://www.dune-project.org/license.html) +# Authors: +# Felix Schindler (2017) +# René Fritze (2017 - 2019) +# Tobias Leibner (2017 - 2018) +# ~~~ + +from dune.xt.codegen import typeid_to_typedef_name as safe_name, have_eigen + +matrix = [ + 'EigenDenseMatrix<double>', 'FieldMatrix<double, 2, 2>', 'CommonDenseMatrix<double>', 'CommonSparseMatrix<double>' +] +field = ['double', 'double', 'double', 'double'] +complex_matrix = [ + 'EigenDenseMatrix<std::complex<double>>', 'FieldMatrix<std::complex<double>, 2, 2>', + 'CommonDenseMatrix<std::complex<double>>', 'CommonSparseMatrix<std::complex<double>>' +] +real_matrix = [ + 'EigenDenseMatrix<double>', 'FieldMatrix<double, 2, 2>', 'CommonDenseMatrix<double>', 'CommonSparseMatrix<double>' +] + + +def _ok(ft): + if 'Eigen' in ft[0]: + return have_eigen(cache) + return True + + +testtypes = [(safe_name('_'.join(ft)), *ft) for ft in zip(matrix, field, complex_matrix, real_matrix) if _ok(ft)] diff --git a/dune/xt/la/test/generalized-eigensolver_for_real_matrix_with_real_evs.tpl b/dune/xt/la/test/generalized-eigensolver_for_real_matrix_with_real_evs.tpl new file mode 100644 index 0000000000000000000000000000000000000000..6b3acd02c12a6bc5fe9f171d3f8a983ef509a797 --- /dev/null +++ b/dune/xt/la/test/generalized-eigensolver_for_real_matrix_with_real_evs.tpl @@ -0,0 +1,97 @@ +// This file is part of the dune-xt-la project: +// https://github.com/dune-community/dune-xt-la +// Copyright 2009-2017 dune-xt-la developers and contributors. All rights reserved. +// License: Dual licensed as BSD 2-Clause License (http://opensource.org/licenses/BSD-2-Clause) +// or GPL-2.0+ (http://opensource.org/licenses/gpl-license) +// with "runtime exception" (http://www.dune-project.org/license.html) +// Authors: +// Felix Schindler (2019) + +#include <dune/xt/common/test/main.hxx> // <- has to come first (includes the config.h)! + +#include <dune/xt/la/test/generalized-eigensolver.hh> + +{% for T_NAME, TESTMATRIXTYPE, TESTFIELDTYPE, TESTCOMPLEXMATRIXTYPE, TESTREALMATRIXTYPE in config.testtypes %} +struct GeneralizedEigenSolverForMatrixFullOfOnes_{{T_NAME}} + : public GeneralizedEigenSolverTestForMatricesWithRealEigenvaluesAndVectors<{{TESTMATRIXTYPE}}, + {{TESTFIELDTYPE}}, + {{TESTCOMPLEXMATRIXTYPE}}, + {{TESTREALMATRIXTYPE}}> +{ + using BaseType = GeneralizedEigenSolverTestForMatricesWithRealEigenvaluesAndVectors; + using typename BaseType::MatrixType; + using typename BaseType::ComplexMatrixType; + using typename BaseType::RealMatrixType; + using typename BaseType::EigenValuesType; + using typename BaseType::RealEigenValuesType; + + GeneralizedEigenSolverForMatrixFullOfOnes_{{T_NAME}}() + { + matrix_ = XT::Common::from_string<MatrixType>("[1 1; 1 1]"); + expected_eigenvalues_ = XT::Common::from_string<EigenValuesType>("[2 0]"); + expected_real_eigenvalues_ = XT::Common::from_string<RealEigenValuesType>("[2 0]"); + expected_max_ev_ = 2; + expected_min_ev_ = 0; + all_matrices_and_expected_eigenvalues_and_vectors_are_computed_ = true; + } + + using BaseType::all_matrices_and_expected_eigenvalues_and_vectors_are_computed_; + using BaseType::matrix_; + using BaseType::expected_eigenvalues_; + using BaseType::expected_real_eigenvalues_; + using BaseType::expected_max_ev_; + using BaseType::expected_min_ev_; +}; // struct GeneralizedEigenSolverForMatrixFullOfOnes_{{T_NAME}} + + +TEST_F(GeneralizedEigenSolverForMatrixFullOfOnes_{{T_NAME}}, exports_correct_types) +{ + exports_correct_types(); +} + +TEST_F(GeneralizedEigenSolverForMatrixFullOfOnes_{{T_NAME}}, has_types_and_options) +{ + has_types_and_options(); +} + +TEST_F(GeneralizedEigenSolverForMatrixFullOfOnes_{{T_NAME}}, throws_on_broken_matrix_construction) +{ + throws_on_broken_matrix_construction(); +} + +TEST_F(GeneralizedEigenSolverForMatrixFullOfOnes_{{T_NAME}}, allows_broken_matrix_construction_when_checks_disabled) +{ + allows_broken_matrix_construction_when_checks_disabled(); +} + +TEST_F(GeneralizedEigenSolverForMatrixFullOfOnes_{{T_NAME}}, throws_on_inconsistent_given_options) +{ + throws_on_inconsistent_given_options(); +} + +TEST_F(GeneralizedEigenSolverForMatrixFullOfOnes_{{T_NAME}}, is_constructible) +{ + is_constructible(); +} + +TEST_F(GeneralizedEigenSolverForMatrixFullOfOnes_{{T_NAME}}, gives_correct_eigenvalues) +{ + gives_correct_eigenvalues(); +} + +TEST_F(GeneralizedEigenSolverForMatrixFullOfOnes_{{T_NAME}}, gives_correct_real_eigenvalues) +{ + gives_correct_real_eigenvalues(); +} + +TEST_F(GeneralizedEigenSolverForMatrixFullOfOnes_{{T_NAME}}, gives_correct_max_eigenvalue) +{ + gives_correct_max_eigenvalue(); +} + +TEST_F(GeneralizedEigenSolverForMatrixFullOfOnes_{{T_NAME}}, gives_correct_min_eigenvalue) +{ + gives_correct_min_eigenvalue(); +} + +{% endfor %}