diff --git a/.gitignore b/.gitignore index d256352..4347258 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Executables and builds build/ +benchmark/ *.exe # VS Code files diff --git a/CMakeLists.txt b/CMakeLists.txt index 153b493..b1ecf0d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) ######## Build examples ######## -set(EXAMPLE_LIST "quick_start" "simple_dae" "perovskite_model" "flame_propagation") +set(EXAMPLE_LIST "quick_start" "simple_dae" "perovskite_model" "flame_propagation" "jacobian_shape" "jacobian_compare") include_directories(${PROJECT_SOURCE_DIR}) @@ -28,7 +28,7 @@ set(PROJECT_TEST "dae-cpp-test") include(CTest) enable_testing() -FILE(GLOB SOURCES_TEST ${PROJECT_SOURCE_DIR}/tests/*.cpp) +FILE(GLOB SOURCES_TEST ${PROJECT_SOURCE_DIR}/tests/test_*.cpp) add_executable(${PROJECT_TEST} ${SOURCES_TEST}) diff --git a/Eigen/CholmodSupport b/Eigen/CholmodSupport index 2961863..adc5f8d 100644 --- a/Eigen/CholmodSupport +++ b/Eigen/CholmodSupport @@ -12,9 +12,7 @@ #include "src/Core/util/DisableStupidWarnings.h" -extern "C" { #include -} /** \ingroup Support_modules * \defgroup CholmodSupport_Module CholmodSupport module diff --git a/Eigen/commit-d3cd3126520f1e81aeb2abb5e5ae77bd322f8193 b/Eigen/commit-d26e19714fca2faf544619c3604f88d980e5a207 similarity index 100% rename from Eigen/commit-d3cd3126520f1e81aeb2abb5e5ae77bd322f8193 rename to Eigen/commit-d26e19714fca2faf544619c3604f88d980e5a207 diff --git a/Eigen/src/Core/Matrix.h b/Eigen/src/Core/Matrix.h index c11a994..af6afaf 100644 --- a/Eigen/src/Core/Matrix.h +++ b/Eigen/src/Core/Matrix.h @@ -207,7 +207,7 @@ class Matrix : public PlainObjectBase::value) : Base(std::move(other)) {} - EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE Matrix& operator=(Matrix&& other) + EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE constexpr Matrix& operator=(Matrix&& other) EIGEN_NOEXCEPT_IF(std::is_nothrow_move_assignable::value) { Base::operator=(std::move(other)); return *this; diff --git a/Eigen/src/Core/PlainObjectBase.h b/Eigen/src/Core/PlainObjectBase.h index 5878385..5f846a0 100644 --- a/Eigen/src/Core/PlainObjectBase.h +++ b/Eigen/src/Core/PlainObjectBase.h @@ -452,7 +452,9 @@ class PlainObjectBase : public internal::dense_xpr_base::type /** This is a special case of the templated operator=. Its purpose is to * prevent a default operator= from hiding the templated operator=. */ - EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE Derived& operator=(const PlainObjectBase& other) { return _set(other); } + EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE constexpr Derived& operator=(const PlainObjectBase& other) { + return _set(other); + } /** \sa MatrixBase::lazyAssign() */ template @@ -470,28 +472,29 @@ class PlainObjectBase : public internal::dense_xpr_base::type // Prevent user from trying to instantiate PlainObjectBase objects // by making all its constructor protected. See bug 1074. protected: - EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE PlainObjectBase() : m_storage() { + EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE constexpr PlainObjectBase() : m_storage() { // EIGEN_INITIALIZE_COEFFS_IF_THAT_OPTION_IS_ENABLED } #ifndef EIGEN_PARSED_BY_DOXYGEN // FIXME is it still needed ? /** \internal */ - EIGEN_DEVICE_FUNC explicit PlainObjectBase(internal::constructor_without_unaligned_array_assert) + EIGEN_DEVICE_FUNC constexpr explicit PlainObjectBase(internal::constructor_without_unaligned_array_assert) : m_storage(internal::constructor_without_unaligned_array_assert()) { // EIGEN_INITIALIZE_COEFFS_IF_THAT_OPTION_IS_ENABLED } #endif - EIGEN_DEVICE_FUNC PlainObjectBase(PlainObjectBase&& other) EIGEN_NOEXCEPT : m_storage(std::move(other.m_storage)) {} + EIGEN_DEVICE_FUNC constexpr PlainObjectBase(PlainObjectBase&& other) EIGEN_NOEXCEPT + : m_storage(std::move(other.m_storage)) {} - EIGEN_DEVICE_FUNC PlainObjectBase& operator=(PlainObjectBase&& other) EIGEN_NOEXCEPT { + EIGEN_DEVICE_FUNC constexpr PlainObjectBase& operator=(PlainObjectBase&& other) EIGEN_NOEXCEPT { m_storage = std::move(other.m_storage); return *this; } /** Copy constructor */ - EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE PlainObjectBase(const PlainObjectBase& other) + EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE constexpr PlainObjectBase(const PlainObjectBase& other) : Base(), m_storage(other.m_storage) {} EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE PlainObjectBase(Index size, Index rows, Index cols) : m_storage(size, rows, cols) { @@ -749,7 +752,7 @@ class PlainObjectBase : public internal::dense_xpr_base::type // aliasing is dealt once in internal::call_assignment // so at this stage we have to assume aliasing... and resising has to be done later. template - EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE Derived& _set(const DenseBase& other) { + EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE constexpr Derived& _set(const DenseBase& other) { internal::call_assignment(this->derived(), other.derived()); return this->derived(); } @@ -760,7 +763,7 @@ class PlainObjectBase : public internal::dense_xpr_base::type * \sa operator=(const MatrixBase&), _set() */ template - EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE Derived& _set_noalias(const DenseBase& other) { + EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE constexpr Derived& _set_noalias(const DenseBase& other) { // I don't think we need this resize call since the lazyAssign will anyways resize // and lazyAssign will be called by the assign selector. //_resize_to_match(other); diff --git a/Eigen/src/Core/arch/AVX/PacketMath.h b/Eigen/src/Core/arch/AVX/PacketMath.h index d752f06..3b94af5 100644 --- a/Eigen/src/Core/arch/AVX/PacketMath.h +++ b/Eigen/src/Core/arch/AVX/PacketMath.h @@ -270,9 +270,7 @@ struct packet_traits : default_packet_traits { template <> struct packet_traits : default_packet_traits { typedef Packet4l type; - // There is no half-size packet for current Packet4l. - // TODO: support as SSE path. - typedef Packet4l half; + typedef Packet2l half; enum { Vectorizable = 1, AlignedOnScalar = 1, HasCmp = 1, size = 4 }; }; template <> @@ -332,6 +330,7 @@ template <> struct unpacket_traits { typedef double type; typedef Packet2d half; + typedef Packet4l integer_packet; enum { size = 4, alignment = Aligned32, @@ -368,7 +367,7 @@ struct unpacket_traits { template <> struct unpacket_traits { typedef int64_t type; - typedef Packet4l half; + typedef Packet2l half; enum { size = 4, alignment = Aligned32, @@ -623,22 +622,22 @@ EIGEN_DEVICE_FUNC inline Packet4ul pgather(const uint64_t* template <> EIGEN_DEVICE_FUNC inline void pscatter(int64_t* to, const Packet4l& from, Index stride) { __m128i low = _mm256_extractf128_si256(from, 0); - to[stride * 0] = _mm_extract_epi64(low, 0); - to[stride * 1] = _mm_extract_epi64(low, 1); + to[stride * 0] = _mm_extract_epi64_0(low); + to[stride * 1] = _mm_extract_epi64_1(low); __m128i high = _mm256_extractf128_si256(from, 1); - to[stride * 2] = _mm_extract_epi64(high, 0); - to[stride * 3] = _mm_extract_epi64(high, 1); + to[stride * 2] = _mm_extract_epi64_0(high); + to[stride * 3] = _mm_extract_epi64_1(high); } template <> EIGEN_DEVICE_FUNC inline void pscatter(uint64_t* to, const Packet4ul& from, Index stride) { __m128i low = _mm256_extractf128_si256(from, 0); - to[stride * 0] = _mm_extract_epi64(low, 0); - to[stride * 1] = _mm_extract_epi64(low, 1); + to[stride * 0] = _mm_extract_epi64_0(low); + to[stride * 1] = _mm_extract_epi64_1(low); __m128i high = _mm256_extractf128_si256(from, 1); - to[stride * 2] = _mm_extract_epi64(high, 0); - to[stride * 3] = _mm_extract_epi64(high, 1); + to[stride * 2] = _mm_extract_epi64_0(high); + to[stride * 3] = _mm_extract_epi64_1(high); } template <> EIGEN_STRONG_INLINE void pstore1(int64_t* to, const int64_t& a) { @@ -652,21 +651,21 @@ EIGEN_STRONG_INLINE void pstore1(uint64_t* to, const uint64_t& a) { } template <> EIGEN_STRONG_INLINE int64_t pfirst(const Packet4l& a) { - return _mm_cvtsi128_si64(_mm256_castsi256_si128(a)); + return _mm_extract_epi64_0(_mm256_castsi256_si128(a)); } template <> EIGEN_STRONG_INLINE uint64_t pfirst(const Packet4ul& a) { - return _mm_cvtsi128_si64(_mm256_castsi256_si128(a)); + return _mm_extract_epi64_0(_mm256_castsi256_si128(a)); } template <> EIGEN_STRONG_INLINE int64_t predux(const Packet4l& a) { __m128i r = _mm_add_epi64(_mm256_castsi256_si128(a), _mm256_extractf128_si256(a, 1)); - return _mm_extract_epi64(r, 0) + _mm_extract_epi64(r, 1); + return _mm_extract_epi64_0(r) + _mm_extract_epi64_1(r); } template <> EIGEN_STRONG_INLINE uint64_t predux(const Packet4ul& a) { __m128i r = _mm_add_epi64(_mm256_castsi256_si128(a), _mm256_extractf128_si256(a, 1)); - return numext::bit_cast(_mm_extract_epi64(r, 0) + _mm_extract_epi64(r, 1)); + return numext::bit_cast(_mm_extract_epi64_0(r) + _mm_extract_epi64_1(r)); } #define MM256_SHUFFLE_EPI64(A, B, M) _mm256_shuffle_pd(_mm256_castsi256_pd(A), _mm256_castsi256_pd(B), M) EIGEN_DEVICE_FUNC inline void ptranspose(PacketBlock& kernel) { diff --git a/Eigen/src/Core/arch/AVX/TypeCasting.h b/Eigen/src/Core/arch/AVX/TypeCasting.h index 3688f8d..2581eff 100644 --- a/Eigen/src/Core/arch/AVX/TypeCasting.h +++ b/Eigen/src/Core/arch/AVX/TypeCasting.h @@ -47,6 +47,13 @@ template <> struct type_casting_traits : vectorized_type_casting_traits {}; template <> struct type_casting_traits : vectorized_type_casting_traits {}; + +#ifdef EIGEN_VECTORIZE_AVX2 +template <> +struct type_casting_traits : vectorized_type_casting_traits {}; +template <> +struct type_casting_traits : vectorized_type_casting_traits {}; +#endif #endif template <> @@ -188,6 +195,35 @@ EIGEN_STRONG_INLINE Packet4ui preinterpret(const Packet8ui } #ifdef EIGEN_VECTORIZE_AVX2 +template <> +EIGEN_STRONG_INLINE Packet4l pcast(const Packet4d& a) { +#if defined(EIGEN_VECTORIZE_AVX512DQ) && defined(EIGEN_VECTORIZE_AVS512VL) + return _mm256_cvttpd_epi64(a); +#else + EIGEN_ALIGN16 double aux[4]; + pstore(aux, a); + return _mm256_set_epi64x(static_cast(aux[3]), static_cast(aux[2]), static_cast(aux[1]), + static_cast(aux[0])); +#endif +} + +template <> +EIGEN_STRONG_INLINE Packet4d pcast(const Packet4l& a) { +#if defined(EIGEN_VECTORIZE_AVX512DQ) && defined(EIGEN_VECTORIZE_AVS512VL) + return _mm256_cvtepi64_pd(a); +#else + EIGEN_ALIGN16 int64_t aux[4]; + pstore(aux, a); + return _mm256_set_pd(static_cast(aux[3]), static_cast(aux[2]), static_cast(aux[1]), + static_cast(aux[0])); +#endif +} + +template <> +EIGEN_STRONG_INLINE Packet4d pcast(const Packet2l& a, const Packet2l& b) { + return _mm256_set_m128d((pcast(b)), (pcast(a))); +} + template <> EIGEN_STRONG_INLINE Packet4ul preinterpret(const Packet4l& a) { return Packet4ul(a); @@ -198,6 +234,21 @@ EIGEN_STRONG_INLINE Packet4l preinterpret(const Packet4ul& return Packet4l(a); } +template <> +EIGEN_STRONG_INLINE Packet4l preinterpret(const Packet4d& a) { + return _mm256_castpd_si256(a); +} + +template <> +EIGEN_STRONG_INLINE Packet4d preinterpret(const Packet4l& a) { + return _mm256_castsi256_pd(a); +} + +// truncation operations +template <> +EIGEN_STRONG_INLINE Packet2l preinterpret(const Packet4l& a) { + return _mm256_castsi256_si128(a); +} #endif template <> diff --git a/Eigen/src/Core/arch/AVX512/PacketMath.h b/Eigen/src/Core/arch/AVX512/PacketMath.h index b6d2d98..ed2f189 100644 --- a/Eigen/src/Core/arch/AVX512/PacketMath.h +++ b/Eigen/src/Core/arch/AVX512/PacketMath.h @@ -34,6 +34,7 @@ namespace internal { typedef __m512 Packet16f; typedef __m512i Packet16i; typedef __m512d Packet8d; +// TODO(rmlarsen): Add support for Packet8l. #ifndef EIGEN_VECTORIZE_AVX512FP16 typedef eigen_packet_wrapper<__m256i, 1> Packet16h; #endif diff --git a/Eigen/src/Core/arch/SSE/PacketMath.h b/Eigen/src/Core/arch/SSE/PacketMath.h index bc4eb9a..008109a 100644 --- a/Eigen/src/Core/arch/SSE/PacketMath.h +++ b/Eigen/src/Core/arch/SSE/PacketMath.h @@ -145,6 +145,27 @@ EIGEN_STRONG_INLINE Packet2d vec2d_unpackhi(const Packet2d& a, const Packet2d& b #define EIGEN_DECLARE_CONST_Packet4ui(NAME, X) const Packet4ui p4ui_##NAME = pset1(X) +// Work around lack of extract/cvt for epi64 when compiling for 32-bit. +#if EIGEN_ARCH_x86_64 +EIGEN_ALWAYS_INLINE int64_t _mm_extract_epi64_0(const __m128i& a) { return _mm_cvtsi128_si64(a); } +#ifdef EIGEN_VECTORIZE_SSE4_1 +EIGEN_ALWAYS_INLINE int64_t _mm_extract_epi64_1(const __m128i& a) { return _mm_extract_epi64(a, 1); } +#else +EIGEN_ALWAYS_INLINE int64_t _mm_extract_epi64_1(const __m128i& a) { + return _mm_cvtsi128_si64(_mm_castpd_si128(_mm_shuffle_pd(_mm_castsi128_pd(a), _mm_castsi128_pd(a), 0x1))); +} +#endif +#else +// epi64 instructions are not available. The following seems to generate the same instructions +// with -O2 in GCC/Clang. +EIGEN_ALWAYS_INLINE int64_t _mm_extract_epi64_0(const __m128i& a) { + return numext::bit_cast(_mm_cvtsd_f64(_mm_castsi128_pd(a))); +} +EIGEN_ALWAYS_INLINE int64_t _mm_extract_epi64_1(const __m128i& a) { + return numext::bit_cast(_mm_cvtsd_f64(_mm_shuffle_pd(_mm_castsi128_pd(a), _mm_castsi128_pd(a), 0x1))); +} +#endif + // Use the packet_traits defined in AVX/PacketMath.h instead if we're going // to leverage AVX instructions. #ifndef EIGEN_VECTORIZE_AVX @@ -1610,7 +1631,7 @@ EIGEN_STRONG_INLINE double pfirst(const Packet2d& a) { } template <> EIGEN_STRONG_INLINE int64_t pfirst(const Packet2l& a) { - int64_t x = _mm_cvtsi128_si64(a); + int64_t x = _mm_extract_epi64_0(a); return x; } template <> @@ -1637,7 +1658,7 @@ EIGEN_STRONG_INLINE double pfirst(const Packet2d& a) { } template <> EIGEN_STRONG_INLINE int64_t pfirst(const Packet2l& a) { - int64_t x = _mm_cvtsi128_si64(a); + int64_t x = _mm_extract_epi64_0(a); return x; } template <> @@ -1661,7 +1682,7 @@ EIGEN_STRONG_INLINE double pfirst(const Packet2d& a) { } template <> EIGEN_STRONG_INLINE int64_t pfirst(const Packet2l& a) { - return _mm_cvtsi128_si64(a); + return _mm_extract_epi64_0(a); } template <> EIGEN_STRONG_INLINE int pfirst(const Packet4i& a) { diff --git a/Eigen/src/Core/arch/SSE/TypeCasting.h b/Eigen/src/Core/arch/SSE/TypeCasting.h index cbc6d47..9a7732a 100644 --- a/Eigen/src/Core/arch/SSE/TypeCasting.h +++ b/Eigen/src/Core/arch/SSE/TypeCasting.h @@ -37,6 +37,13 @@ template <> struct type_casting_traits : vectorized_type_casting_traits {}; template <> struct type_casting_traits : vectorized_type_casting_traits {}; + +#ifndef EIGEN_VECTORIZE_AVX2 +template <> +struct type_casting_traits : vectorized_type_casting_traits {}; +template <> +struct type_casting_traits : vectorized_type_casting_traits {}; +#endif #endif template <> @@ -79,6 +86,22 @@ EIGEN_STRONG_INLINE Packet4i pcast(const Packet2d& a, const (1 << 2) | (1 << 6))); } +template <> +EIGEN_STRONG_INLINE Packet2l pcast(const Packet2d& a) { +#if EIGEN_ARCH_x86_64 + return _mm_set_epi64x(_mm_cvttsd_si64(preverse(a)), _mm_cvttsd_si64(a)); +#else + return _mm_set_epi64x(static_cast(pfirst(preverse(a))), static_cast(pfirst(a))); +#endif +} + +template <> +EIGEN_STRONG_INLINE Packet2d pcast(const Packet2l& a) { + EIGEN_ALIGN16 int64_t aux[2]; + pstore(aux, a); + return _mm_set_pd(static_cast(aux[1]), static_cast(aux[0])); +} + template <> EIGEN_STRONG_INLINE Packet4f pcast(const Packet4i& a) { return _mm_cvtepi32_ps(a); @@ -126,6 +149,15 @@ EIGEN_STRONG_INLINE Packet2d preinterpret(const Packet4i& a) return _mm_castsi128_pd(a); } +template <> +EIGEN_STRONG_INLINE Packet2d preinterpret(const Packet2l& a) { + return _mm_castsi128_pd(a); +} +template <> +EIGEN_STRONG_INLINE Packet2l preinterpret(const Packet2d& a) { + return _mm_castpd_si128(a); +} + template <> EIGEN_STRONG_INLINE Packet4i preinterpret(const Packet2d& a) { return _mm_castpd_si128(a); @@ -140,6 +172,7 @@ template <> EIGEN_STRONG_INLINE Packet4i preinterpret(const Packet4ui& a) { return Packet4i(a); } + // Disable the following code since it's broken on too many platforms / compilers. // #elif defined(EIGEN_VECTORIZE_SSE) && (!EIGEN_ARCH_x86_64) && (!EIGEN_COMP_MSVC) #if 0 diff --git a/Eigen/src/Core/products/TriangularMatrixVector.h b/Eigen/src/Core/products/TriangularMatrixVector.h index 0e0e690..05a5827 100644 --- a/Eigen/src/Core/products/TriangularMatrixVector.h +++ b/Eigen/src/Core/products/TriangularMatrixVector.h @@ -301,9 +301,13 @@ struct trmv_selector { } else { // Allocate either with alloca or malloc. Eigen::internal::check_size_for_overflow(actualRhs.size()); +#ifdef EIGEN_ALLOCA buffer = static_cast((sizeof(RhsScalar) * actualRhs.size() <= EIGEN_STACK_ALLOCATION_LIMIT) ? EIGEN_ALIGNED_ALLOCA(sizeof(RhsScalar) * actualRhs.size()) : Eigen::internal::aligned_malloc(sizeof(RhsScalar) * actualRhs.size())); +#else + buffer = static_cast(Eigen::internal::aligned_malloc(sizeof(RhsScalar) * actualRhs.size())); +#endif } #ifdef EIGEN_DENSE_STORAGE_CTOR_PLUGIN Index size = actualRhs.size(); diff --git a/Eigen/src/Core/util/ConfigureVectorization.h b/Eigen/src/Core/util/ConfigureVectorization.h index e692438..1c72173 100644 --- a/Eigen/src/Core/util/ConfigureVectorization.h +++ b/Eigen/src/Core/util/ConfigureVectorization.h @@ -266,6 +266,9 @@ #ifdef __AVX512BF16__ #define EIGEN_VECTORIZE_AVX512BF16 #endif +#ifdef __AVX512VL__ +#define EIGEN_VECTORIZE_AVX512VL +#endif #ifdef __AVX512FP16__ #ifdef __AVX512VL__ #define EIGEN_VECTORIZE_AVX512FP16 diff --git a/Eigen/src/Geometry/OrthoMethods.h b/Eigen/src/Geometry/OrthoMethods.h index 34399a7..a8e0502 100644 --- a/Eigen/src/Geometry/OrthoMethods.h +++ b/Eigen/src/Geometry/OrthoMethods.h @@ -98,7 +98,7 @@ EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE namespace internal { template + bool Vectorizable = bool((evaluator::Flags & evaluator::Flags) & PacketAccessBit)> struct cross3_impl { EIGEN_DEVICE_FUNC static inline typename internal::plain_matrix_type::type run(const VectorLhs& lhs, const VectorRhs& rhs) { diff --git a/Eigen/src/SparseCholesky/SimplicialCholesky.h b/Eigen/src/SparseCholesky/SimplicialCholesky.h index 423287b..f3ce975 100644 --- a/Eigen/src/SparseCholesky/SimplicialCholesky.h +++ b/Eigen/src/SparseCholesky/SimplicialCholesky.h @@ -58,6 +58,7 @@ class SimplicialCholeskyBase : public SparseSolverBase { enum { UpLo = internal::traits::UpLo }; typedef typename MatrixType::Scalar Scalar; typedef typename MatrixType::RealScalar RealScalar; + typedef typename internal::traits::DiagonalScalar DiagonalScalar; typedef typename MatrixType::StorageIndex StorageIndex; typedef SparseMatrix CholMatrixType; typedef CholMatrixType const* ConstCholMatrixPtr; @@ -114,7 +115,7 @@ class SimplicialCholeskyBase : public SparseSolverBase { * * \returns a reference to \c *this. */ - Derived& setShift(const RealScalar& offset, const RealScalar& scale = 1) { + Derived& setShift(const DiagonalScalar& offset, const DiagonalScalar& scale = 1) { m_shiftOffset = offset; m_shiftScale = scale; return derived(); @@ -178,18 +179,18 @@ class SimplicialCholeskyBase : public SparseSolverBase { protected: /** Computes the sparse Cholesky decomposition of \a matrix */ - template + template void compute(const MatrixType& matrix) { eigen_assert(matrix.rows() == matrix.cols()); Index size = matrix.cols(); CholMatrixType tmp(size, size); ConstCholMatrixPtr pmat; - ordering(matrix, pmat, tmp); + ordering(matrix, pmat, tmp); analyzePattern_preordered(*pmat, DoLDLT); - factorize_preordered(*pmat); + factorize_preordered(*pmat); } - template + template void factorize(const MatrixType& a) { eigen_assert(a.rows() == a.cols()); Index size = a.cols(); @@ -200,28 +201,33 @@ class SimplicialCholeskyBase : public SparseSolverBase { // If there is no ordering, try to directly use the input matrix without any copy internal::simplicial_cholesky_grab_input::run(a, pmat, tmp); } else { - tmp.template selfadjointView() = a.template selfadjointView().twistedBy(m_P); + internal::permute_symm_to_symm(a, tmp, m_P.indices().data()); pmat = &tmp; } - factorize_preordered(*pmat); + factorize_preordered(*pmat); } - template + template void factorize_preordered(const CholMatrixType& a); - void analyzePattern(const MatrixType& a, bool doLDLT) { + template + void analyzePattern(const MatrixType& a) { eigen_assert(a.rows() == a.cols()); Index size = a.cols(); CholMatrixType tmp(size, size); ConstCholMatrixPtr pmat; - ordering(a, pmat, tmp); - analyzePattern_preordered(*pmat, doLDLT); + ordering(a, pmat, tmp); + analyzePattern_preordered(*pmat, DoLDLT); } void analyzePattern_preordered(const CholMatrixType& a, bool doLDLT); + template void ordering(const MatrixType& a, ConstCholMatrixPtr& pmat, CholMatrixType& ap); + inline DiagonalScalar getDiag(Scalar x) { return internal::traits::getDiag(x); } + inline Scalar getSymm(Scalar x) { return internal::traits::getSymm(x); } + /** keeps off-diagonal entries; drops diagonal entries */ struct keep_diag { inline bool operator()(const Index& row, const Index& col, const Scalar&) const { return row != col; } @@ -238,8 +244,8 @@ class SimplicialCholeskyBase : public SparseSolverBase { PermutationMatrix m_P; // the permutation PermutationMatrix m_Pinv; // the inverse permutation - RealScalar m_shiftOffset; - RealScalar m_shiftScale; + DiagonalScalar m_shiftOffset; + DiagonalScalar m_shiftScale; }; template > class SimplicialLDLT; +template > +class SimplicialNonHermitianLLT; +template > +class SimplicialNonHermitianLDLT; template > class SimplicialCholesky; @@ -260,12 +272,15 @@ struct traits > { typedef Ordering_ OrderingType; enum { UpLo = UpLo_ }; typedef typename MatrixType::Scalar Scalar; + typedef typename MatrixType::RealScalar DiagonalScalar; typedef typename MatrixType::StorageIndex StorageIndex; typedef SparseMatrix CholMatrixType; typedef TriangularView MatrixL; typedef TriangularView MatrixU; static inline MatrixL getL(const CholMatrixType& m) { return MatrixL(m); } static inline MatrixU getU(const CholMatrixType& m) { return MatrixU(m.adjoint()); } + static inline DiagonalScalar getDiag(Scalar x) { return numext::real(x); } + static inline Scalar getSymm(Scalar x) { return numext::conj(x); } }; template @@ -274,12 +289,49 @@ struct traits > { typedef Ordering_ OrderingType; enum { UpLo = UpLo_ }; typedef typename MatrixType::Scalar Scalar; + typedef typename MatrixType::RealScalar DiagonalScalar; typedef typename MatrixType::StorageIndex StorageIndex; typedef SparseMatrix CholMatrixType; typedef TriangularView MatrixL; typedef TriangularView MatrixU; static inline MatrixL getL(const CholMatrixType& m) { return MatrixL(m); } static inline MatrixU getU(const CholMatrixType& m) { return MatrixU(m.adjoint()); } + static inline DiagonalScalar getDiag(Scalar x) { return numext::real(x); } + static inline Scalar getSymm(Scalar x) { return numext::conj(x); } +}; + +template +struct traits > { + typedef MatrixType_ MatrixType; + typedef Ordering_ OrderingType; + enum { UpLo = UpLo_ }; + typedef typename MatrixType::Scalar Scalar; + typedef typename MatrixType::Scalar DiagonalScalar; + typedef typename MatrixType::StorageIndex StorageIndex; + typedef SparseMatrix CholMatrixType; + typedef TriangularView MatrixL; + typedef TriangularView MatrixU; + static inline MatrixL getL(const CholMatrixType& m) { return MatrixL(m); } + static inline MatrixU getU(const CholMatrixType& m) { return MatrixU(m.transpose()); } + static inline DiagonalScalar getDiag(Scalar x) { return x; } + static inline Scalar getSymm(Scalar x) { return x; } +}; + +template +struct traits > { + typedef MatrixType_ MatrixType; + typedef Ordering_ OrderingType; + enum { UpLo = UpLo_ }; + typedef typename MatrixType::Scalar Scalar; + typedef typename MatrixType::Scalar DiagonalScalar; + typedef typename MatrixType::StorageIndex StorageIndex; + typedef SparseMatrix CholMatrixType; + typedef TriangularView MatrixL; + typedef TriangularView MatrixU; + static inline MatrixL getL(const CholMatrixType& m) { return MatrixL(m); } + static inline MatrixU getU(const CholMatrixType& m) { return MatrixU(m.transpose()); } + static inline DiagonalScalar getDiag(Scalar x) { return x; } + static inline Scalar getSymm(Scalar x) { return x; } }; template @@ -287,6 +339,10 @@ struct traits > { typedef MatrixType_ MatrixType; typedef Ordering_ OrderingType; enum { UpLo = UpLo_ }; + typedef typename MatrixType::Scalar Scalar; + typedef typename MatrixType::RealScalar DiagonalScalar; + static inline DiagonalScalar getDiag(Scalar x) { return numext::real(x); } + static inline Scalar getSymm(Scalar x) { return numext::conj(x); } }; } // namespace internal @@ -346,7 +402,7 @@ class SimplicialLLT : public SimplicialCholeskyBase(matrix); + Base::template compute(matrix); return *this; } @@ -356,7 +412,7 @@ class SimplicialLLT : public SimplicialCholeskyBase(a); } /** Performs a numeric decomposition of \a matrix * @@ -364,7 +420,7 @@ class SimplicialLLT : public SimplicialCholeskyBase(a); } + void factorize(const MatrixType& a) { Base::template factorize(a); } /** \returns the determinant of the underlying matrix from the current factorization */ Scalar determinant() const { @@ -434,7 +490,7 @@ class SimplicialLDLT : public SimplicialCholeskyBase(matrix); + Base::template compute(matrix); return *this; } @@ -444,7 +500,7 @@ class SimplicialLDLT : public SimplicialCholeskyBase(a); } /** Performs a numeric decomposition of \a matrix * @@ -452,7 +508,177 @@ class SimplicialLDLT : public SimplicialCholeskyBase(a); } + void factorize(const MatrixType& a) { Base::template factorize(a); } + + /** \returns the determinant of the underlying matrix from the current factorization */ + Scalar determinant() const { return Base::m_diag.prod(); } +}; + +/** \ingroup SparseCholesky_Module + * \class SimplicialNonHermitianLLT + * \brief A direct sparse LLT Cholesky factorizations, for symmetric non-hermitian matrices. + * + * This class provides a LL^T Cholesky factorizations of sparse matrices that are + * symmetric but not hermitian. For real matrices, this is equivalent to the regular LLT factorization. + * The factorization allows for solving A.X = B where X and B can be either dense or sparse. + * + * In order to reduce the fill-in, a symmetric permutation P is applied prior to the factorization + * such that the factorized matrix is P A P^-1. + * + * \tparam MatrixType_ the type of the sparse matrix A, it must be a SparseMatrix<> + * \tparam UpLo_ the triangular part that will be used for the computations. It can be Lower + * or Upper. Default is Lower. + * \tparam Ordering_ The ordering method to use, either AMDOrdering<> or NaturalOrdering<>. Default is AMDOrdering<> + * + * \implsparsesolverconcept + * + * \sa class SimplicialNonHermitianLDLT, SimplicialLLT, class AMDOrdering, class NaturalOrdering + */ +template +class SimplicialNonHermitianLLT + : public SimplicialCholeskyBase > { + public: + typedef MatrixType_ MatrixType; + enum { UpLo = UpLo_ }; + typedef SimplicialCholeskyBase Base; + typedef typename MatrixType::Scalar Scalar; + typedef typename MatrixType::RealScalar RealScalar; + typedef typename MatrixType::StorageIndex StorageIndex; + typedef SparseMatrix CholMatrixType; + typedef Matrix VectorType; + typedef internal::traits Traits; + typedef typename Traits::MatrixL MatrixL; + typedef typename Traits::MatrixU MatrixU; + + public: + /** Default constructor */ + SimplicialNonHermitianLLT() : Base() {} + + /** Constructs and performs the LLT factorization of \a matrix */ + explicit SimplicialNonHermitianLLT(const MatrixType& matrix) : Base(matrix) {} + + /** \returns an expression of the factor L */ + inline const MatrixL matrixL() const { + eigen_assert(Base::m_factorizationIsOk && "Simplicial LLT not factorized"); + return Traits::getL(Base::m_matrix); + } + + /** \returns an expression of the factor U (= L^*) */ + inline const MatrixU matrixU() const { + eigen_assert(Base::m_factorizationIsOk && "Simplicial LLT not factorized"); + return Traits::getU(Base::m_matrix); + } + + /** Computes the sparse Cholesky decomposition of \a matrix */ + SimplicialNonHermitianLLT& compute(const MatrixType& matrix) { + Base::template compute(matrix); + return *this; + } + + /** Performs a symbolic decomposition on the sparcity of \a matrix. + * + * This function is particularly useful when solving for several problems having the same structure. + * + * \sa factorize() + */ + void analyzePattern(const MatrixType& a) { Base::template analyzePattern(a); } + + /** Performs a numeric decomposition of \a matrix + * + * The given matrix must has the same sparcity than the matrix on which the symbolic decomposition has been performed. + * + * \sa analyzePattern() + */ + void factorize(const MatrixType& a) { Base::template factorize(a); } + + /** \returns the determinant of the underlying matrix from the current factorization */ + Scalar determinant() const { + Scalar detL = Base::m_matrix.diagonal().prod(); + return detL * detL; + } +}; + +/** \ingroup SparseCholesky_Module + * \class SimplicialNonHermitianLDLT + * \brief A direct sparse LDLT Cholesky factorizations without square root, for symmetric non-hermitian matrices. + * + * This class provides a LDL^T Cholesky factorizations without square root of sparse matrices that are + * symmetric but not hermitian. For real matrices, this is equivalent to the regular LDLT factorization. + * The factorization allows for solving A.X = B where X and B can be either dense or sparse. + * + * In order to reduce the fill-in, a symmetric permutation P is applied prior to the factorization + * such that the factorized matrix is P A P^-1. + * + * \tparam MatrixType_ the type of the sparse matrix A, it must be a SparseMatrix<> + * \tparam UpLo_ the triangular part that will be used for the computations. It can be Lower + * or Upper. Default is Lower. + * \tparam Ordering_ The ordering method to use, either AMDOrdering<> or NaturalOrdering<>. Default is AMDOrdering<> + * + * \implsparsesolverconcept + * + * \sa class SimplicialNonHermitianLLT, SimplicialLDLT, class AMDOrdering, class NaturalOrdering + */ +template +class SimplicialNonHermitianLDLT + : public SimplicialCholeskyBase > { + public: + typedef MatrixType_ MatrixType; + enum { UpLo = UpLo_ }; + typedef SimplicialCholeskyBase Base; + typedef typename MatrixType::Scalar Scalar; + typedef typename MatrixType::RealScalar RealScalar; + typedef typename MatrixType::StorageIndex StorageIndex; + typedef SparseMatrix CholMatrixType; + typedef Matrix VectorType; + typedef internal::traits Traits; + typedef typename Traits::MatrixL MatrixL; + typedef typename Traits::MatrixU MatrixU; + + public: + /** Default constructor */ + SimplicialNonHermitianLDLT() : Base() {} + + /** Constructs and performs the LLT factorization of \a matrix */ + explicit SimplicialNonHermitianLDLT(const MatrixType& matrix) : Base(matrix) {} + + /** \returns a vector expression of the diagonal D */ + inline const VectorType vectorD() const { + eigen_assert(Base::m_factorizationIsOk && "Simplicial LDLT not factorized"); + return Base::m_diag; + } + /** \returns an expression of the factor L */ + inline const MatrixL matrixL() const { + eigen_assert(Base::m_factorizationIsOk && "Simplicial LDLT not factorized"); + return Traits::getL(Base::m_matrix); + } + + /** \returns an expression of the factor U (= L^*) */ + inline const MatrixU matrixU() const { + eigen_assert(Base::m_factorizationIsOk && "Simplicial LDLT not factorized"); + return Traits::getU(Base::m_matrix); + } + + /** Computes the sparse Cholesky decomposition of \a matrix */ + SimplicialNonHermitianLDLT& compute(const MatrixType& matrix) { + Base::template compute(matrix); + return *this; + } + + /** Performs a symbolic decomposition on the sparcity of \a matrix. + * + * This function is particularly useful when solving for several problems having the same structure. + * + * \sa factorize() + */ + void analyzePattern(const MatrixType& a) { Base::template analyzePattern(a); } + + /** Performs a numeric decomposition of \a matrix + * + * The given matrix must has the same sparcity than the matrix on which the symbolic decomposition has been performed. + * + * \sa analyzePattern() + */ + void factorize(const MatrixType& a) { Base::template factorize(a); } /** \returns the determinant of the underlying matrix from the current factorization */ Scalar determinant() const { return Base::m_diag.prod(); } @@ -475,7 +701,6 @@ class SimplicialCholesky : public SimplicialCholeskyBase CholMatrixType; typedef Matrix VectorType; - typedef internal::traits Traits; typedef internal::traits > LDLTTraits; typedef internal::traits > LLTTraits; @@ -511,9 +736,9 @@ class SimplicialCholesky : public SimplicialCholeskyBase(matrix); + Base::template compute(matrix); else - Base::template compute(matrix); + Base::template compute(matrix); return *this; } @@ -523,7 +748,12 @@ class SimplicialCholesky : public SimplicialCholeskyBase(a); + else + Base::template analyzePattern(a); + } /** Performs a numeric decomposition of \a matrix * @@ -533,9 +763,9 @@ class SimplicialCholesky : public SimplicialCholeskyBase(a); + Base::template factorize(a); else - Base::template factorize(a); + Base::template factorize(a); } /** \internal */ @@ -594,6 +824,7 @@ class SimplicialCholesky : public SimplicialCholeskyBase +template void SimplicialCholeskyBase::ordering(const MatrixType& a, ConstCholMatrixPtr& pmat, CholMatrixType& ap) { eigen_assert(a.rows() == a.cols()); const Index size = a.rows(); @@ -602,7 +833,7 @@ void SimplicialCholeskyBase::ordering(const MatrixType& a, ConstCholMat if (!internal::is_same >::value) { { CholMatrixType C; - C = a.template selfadjointView(); + internal::permute_symm_to_fullsymm(a, C, NULL); OrderingType ordering; ordering(C, m_Pinv); @@ -614,14 +845,14 @@ void SimplicialCholeskyBase::ordering(const MatrixType& a, ConstCholMat m_P.resize(0); ap.resize(size, size); - ap.template selfadjointView() = a.template selfadjointView().twistedBy(m_P); + internal::permute_symm_to_symm(a, ap, m_P.indices().data()); } else { m_Pinv.resize(0); m_P.resize(0); if (int(UpLo) == int(Lower) || MatrixType::IsRowMajor) { // we have to transpose the lower part to to the upper one ap.resize(size, size); - ap.template selfadjointView() = a.template selfadjointView(); + internal::permute_symm_to_symm(a, ap, NULL); } else internal::simplicial_cholesky_grab_input::run(a, pmat, ap); } diff --git a/Eigen/src/SparseCholesky/SimplicialCholesky_impl.h b/Eigen/src/SparseCholesky/SimplicialCholesky_impl.h index abfbbe6..0b13c56 100644 --- a/Eigen/src/SparseCholesky/SimplicialCholesky_impl.h +++ b/Eigen/src/SparseCholesky/SimplicialCholesky_impl.h @@ -67,7 +67,7 @@ void SimplicialCholeskyBase::analyzePattern_preordered(const CholMatrix } template -template +template void SimplicialCholeskyBase::factorize_preordered(const CholMatrixType& ap) { using std::sqrt; @@ -97,7 +97,7 @@ void SimplicialCholeskyBase::factorize_preordered(const CholMatrixType& for (typename CholMatrixType::InnerIterator it(ap, k); it; ++it) { StorageIndex i = it.index(); if (i <= k) { - y[i] += numext::conj(it.value()); /* scatter A(i,k) into Y (sum duplicates) */ + y[i] += getSymm(it.value()); /* scatter A(i,k) into Y (sum duplicates) */ Index len; for (len = 0; tags[i] != k; i = m_parent[i]) { pattern[len++] = i; /* L(k,i) is nonzero */ @@ -109,8 +109,8 @@ void SimplicialCholeskyBase::factorize_preordered(const CholMatrixType& /* compute numerical values kth row of L (a sparse triangular solve) */ - RealScalar d = - numext::real(y[k]) * m_shiftScale + m_shiftOffset; // get D(k,k), apply the shift function, and clear Y(k) + DiagonalScalar d = + getDiag(y[k]) * m_shiftScale + m_shiftOffset; // get D(k,k), apply the shift function, and clear Y(k) y[k] = Scalar(0); for (; top < size; ++top) { Index i = pattern[top]; /* pattern[top:n-1] is pattern of L(:,k) */ @@ -120,14 +120,14 @@ void SimplicialCholeskyBase::factorize_preordered(const CholMatrixType& /* the nonzero entry L(k,i) */ Scalar l_ki; if (DoLDLT) - l_ki = yi / numext::real(m_diag[i]); + l_ki = yi / getDiag(m_diag[i]); else yi = l_ki = yi / Lx[Lp[i]]; Index p2 = Lp[i] + m_nonZerosPerCol[i]; Index p; - for (p = Lp[i] + (DoLDLT ? 0 : 1); p < p2; ++p) y[Li[p]] -= numext::conj(Lx[p]) * yi; - d -= numext::real(l_ki * numext::conj(yi)); + for (p = Lp[i] + (DoLDLT ? 0 : 1); p < p2; ++p) y[Li[p]] -= getSymm(Lx[p]) * yi; + d -= getDiag(l_ki * getSymm(yi)); Li[p] = k; /* store L(k,i) in column form of L */ Lx[p] = l_ki; ++m_nonZerosPerCol[i]; /* increment count of nonzeros in col i */ @@ -141,7 +141,7 @@ void SimplicialCholeskyBase::factorize_preordered(const CholMatrixType& } else { Index p = Lp[k] + m_nonZerosPerCol[k]++; Li[p] = k; /* store L(k,k) = sqrt (d) in column k */ - if (d <= RealScalar(0)) { + if (NonHermitian ? d == RealScalar(0) : numext::real(d) <= RealScalar(0)) { ok = false; /* failure, matrix is not positive definite */ break; } diff --git a/Eigen/src/SparseCore/SparseMatrix.h b/Eigen/src/SparseCore/SparseMatrix.h index ecf406f..849970a 100644 --- a/Eigen/src/SparseCore/SparseMatrix.h +++ b/Eigen/src/SparseCore/SparseMatrix.h @@ -788,8 +788,11 @@ class SparseMatrix : public SparseCompressedBaseswap(other); } + + template + inline SparseMatrix(SparseCompressedBase&& other) : SparseMatrix() { *this = other.derived().markAsRValue(); } @@ -857,7 +860,10 @@ class SparseMatrix : public SparseCompressedBaseswap(other); + return *this; + } #ifndef EIGEN_PARSED_BY_DOXYGEN template @@ -872,6 +878,12 @@ class SparseMatrix : public SparseCompressedBase EIGEN_DONT_INLINE SparseMatrix& operator=(const SparseMatrixBase& other); + template + inline SparseMatrix& operator=(SparseCompressedBase&& other) { + *this = other.derived().markAsRValue(); + return *this; + } + #ifndef EIGEN_NO_IO friend std::ostream& operator<<(std::ostream& s, const SparseMatrix& m) { EIGEN_DBG_SPARSE( diff --git a/Eigen/src/SparseCore/SparseSelfAdjointView.h b/Eigen/src/SparseCore/SparseSelfAdjointView.h index 2f087c9..3402bae 100644 --- a/Eigen/src/SparseCore/SparseSelfAdjointView.h +++ b/Eigen/src/SparseCore/SparseSelfAdjointView.h @@ -34,13 +34,13 @@ namespace internal { template struct traits > : traits {}; -template +template void permute_symm_to_symm( const MatrixType& mat, SparseMatrix& _dest, const typename MatrixType::StorageIndex* perm = 0); -template +template void permute_symm_to_fullsymm( const MatrixType& mat, SparseMatrix& _dest, @@ -234,7 +234,7 @@ struct Assignment { template static void run(SparseMatrix& dst, const SrcXprType& src, const AssignOpType& /*func*/) { - internal::permute_symm_to_fullsymm(src.matrix(), dst); + internal::permute_symm_to_fullsymm(src.matrix(), dst); } // FIXME: the handling of += and -= in sparse matrices should be cleanup so that next two overloads could be reduced @@ -405,7 +405,7 @@ struct product_evaluator, ProductTag, Spar ***************************************************************************/ namespace internal { -template +template void permute_symm_to_fullsymm( const MatrixType& mat, SparseMatrix& _dest, @@ -476,13 +476,13 @@ void permute_symm_to_fullsymm( dest.valuePtr()[k] = it.value(); k = count[ip]++; dest.innerIndexPtr()[k] = jp; - dest.valuePtr()[k] = numext::conj(it.value()); + dest.valuePtr()[k] = (NonHermitian ? it.value() : numext::conj(it.value())); } } } } -template +template void permute_symm_to_symm(const MatrixType& mat, SparseMatrix& _dest, const typename MatrixType::StorageIndex* perm) { @@ -534,7 +534,7 @@ void permute_symm_to_symm(const MatrixType& mat, if (!StorageOrderMatch) std::swap(ip, jp); if (((int(DstMode) == int(Lower) && ip < jp) || (int(DstMode) == int(Upper) && ip > jp))) - dest.valuePtr()[k] = numext::conj(it.value()); + dest.valuePtr()[k] = (NonHermitian ? it.value() : numext::conj(it.value())); else dest.valuePtr()[k] = it.value(); } @@ -595,14 +595,14 @@ struct Assignment&) { // internal::permute_symm_to_fullsymm(m_matrix,_dest,m_perm.indices().data()); SparseMatrix tmp; - internal::permute_symm_to_fullsymm(src.matrix(), tmp, src.perm().indices().data()); + internal::permute_symm_to_fullsymm(src.matrix(), tmp, src.perm().indices().data()); dst = tmp; } template static void run(SparseSelfAdjointView& dst, const SrcXprType& src, const internal::assign_op&) { - internal::permute_symm_to_symm(src.matrix(), dst.matrix(), src.perm().indices().data()); + internal::permute_symm_to_symm(src.matrix(), dst.matrix(), src.perm().indices().data()); } }; diff --git a/Eigen/src/SparseCore/SparseVector.h b/Eigen/src/SparseCore/SparseVector.h index 0733718..fac162e 100644 --- a/Eigen/src/SparseCore/SparseVector.h +++ b/Eigen/src/SparseCore/SparseVector.h @@ -304,6 +304,24 @@ class SparseVector : public SparseCompressedBaseswap(other); } + + template + inline SparseVector(SparseCompressedBase&& other) : SparseVector() { + *this = other.derived().markAsRValue(); + } + + inline SparseVector& operator=(SparseVector&& other) { + this->swap(other); + return *this; + } + + template + inline SparseVector& operator=(SparseCompressedBase&& other) { + *this = other.derived().markAsRValue(); + return *this; + } + #ifndef EIGEN_PARSED_BY_DOXYGEN template inline SparseVector& operator=(const SparseSparseProduct& product) { diff --git a/Eigen/src/plugins/MatrixCwiseUnaryOps.inc b/Eigen/src/plugins/MatrixCwiseUnaryOps.inc index b23f4a5..325b0fb 100644 --- a/Eigen/src/plugins/MatrixCwiseUnaryOps.inc +++ b/Eigen/src/plugins/MatrixCwiseUnaryOps.inc @@ -17,6 +17,7 @@ typedef CwiseUnaryOp, const Derived> CwiseArgRet typedef CwiseUnaryOp, const Derived> CwiseCArgReturnType; typedef CwiseUnaryOp, const Derived> CwiseSqrtReturnType; typedef CwiseUnaryOp, const Derived> CwiseCbrtReturnType; +typedef CwiseUnaryOp, const Derived> CwiseSquareReturnType; typedef CwiseUnaryOp, const Derived> CwiseSignReturnType; typedef CwiseUnaryOp, const Derived> CwiseInverseReturnType; @@ -66,7 +67,15 @@ EIGEN_DOC_UNARY_ADDONS(cwiseCbrt, cube - root) /// /// \sa cwiseSqrt(), cwiseSquare(), cwisePow() /// -EIGEN_DEVICE_FUNC inline const CwiseCbrtReturnType cwiseCbrt() const { return CwiseSCbrtReturnType(derived()); } +EIGEN_DEVICE_FUNC inline const CwiseCbrtReturnType cwiseCbrt() const { return CwiseCbrtReturnType(derived()); } + +/// \returns an expression of the coefficient-wise square of *this. +/// +EIGEN_DOC_UNARY_ADDONS(cwiseSquare, square) +/// +/// \sa cwisePow(), cwiseSqrt(), cwiseCbrt() +/// +EIGEN_DEVICE_FUNC inline const CwiseSquareReturnType cwiseSquare() const { return CwiseSquareReturnType(derived()); } /// \returns an expression of the coefficient-wise signum of *this. /// diff --git a/README.md b/README.md index a1c0b74..2b1ca86 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # dae-cpp ![tests](https://github.com/dae-cpp/dae-cpp/actions/workflows/cmake-multi-platform.yml/badge.svg) -![version](https://img.shields.io/badge/version-2.0.1-blue) +![version](https://img.shields.io/badge/version-2.1.0-blue) [![Static Badge](https://img.shields.io/badge/Documentation-8A2BE2?logo=githubpages&logoColor=fff&style=flat)](https://dae-cpp.github.io/) **A simple but powerful header-only C++ solver for [systems of Differential-Algebraic Equations](https://en.wikipedia.org/wiki/Differential-algebraic_system_of_equations) (DAE).** @@ -22,13 +22,13 @@ to be solved in the interval $`t \in [0, t_\mathrm{end}]`$ with the initial cond - Uses [automatic](https://en.wikipedia.org/wiki/Automatic_differentiation) (algorithmic, exact) differentiation ([autodiff](https://autodiff.github.io/) package) to compute the Jacobian matrix, if it is not provided by the user. - Fourth-order variable-step implicit BDF time integrator that preserves accuracy even when the time step rapidly changes. - A very flexible and customizable variable time stepping algorithm based on the solution stability and variability. -- Mass matrix can be non-static (can depend on time) and it can be singular. +- Mass matrix can be non-static (can depend on time) and it can be singular (contain empty rows). - The library is extremely easy to use. A simple DAE can be set up using just a few lines of code (see [Quick Start](#quick-start) example below). ### How does it work The DAE solver uses implicit Backward Differentiation Formulae (BDF) of orders I-IV with adaptive time stepping. Every time step, the BDF integrator reduces the original DAE system to a system of nonlinear equations, which is solved using iterative [Quasi-Newton](https://en.wikipedia.org/wiki/Quasi-Newton_method) root-finding algorithm. The Quasi-Newton method reduces the problem further down to a system of linear equations, which is solved using [Eigen](https://eigen.tuxfamily.org/index.php?title=Main_Page), a versatile and fast C++ template library for linear algebra. -Eigen's sparse solver performs two steps: factorization (decomposition) of the Jacobian matrix and the linear system solving itself. This gives us the numerical solution of the entire DAE system at the current time step. Finally, depending on the convergence rate of the Quasi-Newton method, variability of the solution, and user-defined accuracy, the DAE solver adjusts the time step size and initiates a new iteration in time. +Eigen's sparse solver performs two main steps: factorization (decomposition) of the Jacobian matrix and the linear system solving itself. This gives us the numerical solution of the entire DAE system at the current time step. Finally, depending on the convergence rate of the Quasi-Newton method, variability of the solution, and user-defined accuracy, the DAE solver adjusts the time step size and initiates a new iteration in time. ## Installation @@ -57,6 +57,7 @@ ctest - For more information about the solver, please refer to the [Documentation](https://dae-cpp.github.io/) pages. - Ready to use examples are given in the [examples](https://github.com/dae-cpp/dae-cpp/tree/master/examples) directory of this repository. +- Examples can be built using CMake alongside with the tests (see [Testing](#testing)). - All notable user-facing changes to this project are documented in the [CHANGELOG](https://dae-cpp.github.io/CHANGELOG.html). ## Quick Start @@ -147,7 +148,7 @@ struct MyRHS ```cpp MyMassMatrix mass; // Mass matrix object -MyRHS rhs; // Vector-function object +MyRHS rhs; // Vector function object System my_system(mass, rhs); // Defines the DAE system object ``` @@ -205,7 +206,9 @@ Then add the user-defined Jacobian to the `solve()` method: my_system.solve(x0, t, MyJacobian()); ``` -Defining analytic Jacobian matrix can significantly speed up the computation (especially for big systems). +Defining analytic Jacobian matrix can significantly speed up the computation for big systems (with thousands of DAEs). + +If deriving the Jacobian matrix manually is not a feasible task (e.g., due to a very complex non-linear RHS), the solver allows the user to specify only the positions of non-zero elements of the Jacobian matrix (i.e., the Jacobian matrix shape). All the derivatives will be calculated automatically with a very small computation time penalty (compared to the manually derived analytic Jacobian). For more details, see the [Jacobian shape example](https://github.com/dae-cpp/dae-cpp/blob/master/examples/jacobian_shape/jacobian_shape.cpp). ### (Optional) Step 6. Tweak the [solver options](https://dae-cpp.github.io/solver-options.html) diff --git a/autodiff/commit-d66d8f4852146cc41168a31f750f3d09ef8f2e70 b/autodiff/commit-2e2f3a2b16afcd9c04e76c8a689e9fd23ff78679 similarity index 100% rename from autodiff/commit-d66d8f4852146cc41168a31f750f3d09ef8f2e70 rename to autodiff/commit-2e2f3a2b16afcd9c04e76c8a689e9fd23ff78679 diff --git a/autodiff/common/meta.hpp b/autodiff/common/meta.hpp index 7fe7d26..65da700 100644 --- a/autodiff/common/meta.hpp +++ b/autodiff/common/meta.hpp @@ -35,20 +35,12 @@ #include #ifndef AUTODIFF_DEVICE_FUNC -#ifdef EIGEN_CORE_MODULE_H -#include +#ifdef AUTODIFF_EIGEN_FOUND + #include + #define AUTODIFF_DEVICE_FUNC EIGEN_DEVICE_FUNC #else -#define EIGEN_CORE_MODULE_H -#include -#undef EIGEN_CORE_MODULE_H + #define AUTODIFF_DEVICE_FUNC #endif - -#ifdef EIGEN_DEVICE_FUNC -#define AUTODIFF_DEVICE_FUNC EIGEN_DEVICE_FUNC -#else -#define AUTODIFF_DEVICE_FUNC -#endif - #endif namespace autodiff { diff --git a/autodiff/forward/dual/dual.hpp b/autodiff/forward/dual/dual.hpp index 2177507..5e904e5 100644 --- a/autodiff/forward/dual/dual.hpp +++ b/autodiff/forward/dual/dual.hpp @@ -512,7 +512,7 @@ struct Dual G grad = {}; - AUTODIFF_DEVICE_FUNC Dual() + AUTODIFF_DEVICE_FUNC constexpr Dual() {} template || isArithmetic> = true> diff --git a/dae-cpp/assert-custom.hpp b/dae-cpp/assert-custom.hpp index 318b42e..0c5fce8 100644 --- a/dae-cpp/assert-custom.hpp +++ b/dae-cpp/assert-custom.hpp @@ -62,7 +62,7 @@ namespace daecpp_namespace_name /* * PRINT(condition, message) * - * Prints a message (or not), depending on the condition (e.g., verbosity level). + * Prints a message if condition (e.g., verbosity level) is true. * * Example: * NOTE(verbosity > 1, "v = " << v); diff --git a/dae-cpp/jacobian-matrix.hpp b/dae-cpp/jacobian-matrix.hpp index fd121f7..81e813a 100644 --- a/dae-cpp/jacobian-matrix.hpp +++ b/dae-cpp/jacobian-matrix.hpp @@ -14,6 +14,9 @@ #ifndef DAECPP_JACOBIAN_MATRIX_H #define DAECPP_JACOBIAN_MATRIX_H +#include // Formatted table printing +#include // std::pair + #include "sparse-matrix.hpp" #include "vector-function.hpp" @@ -39,7 +42,93 @@ class JacobianMatrix }; /* - * Automatic (algorithmic) Jacobian class. + * Helper Jacobian shape class. + * Computes Jacobian matrix from the given shape (a list of non-zero elements) provided by the user. + */ +template +class JacobianMatrixShape +{ + // Array of non-zero elements + std::vector> m_Jn; + + // The RHS for differentiation (a copy) + RHS m_rhs; + +public: + explicit JacobianMatrixShape(RHS rhs) : m_rhs(rhs) {} + + virtual ~JacobianMatrixShape() {} + + /* + * Defines the Jacobian matrix (matrix of the RHS derivatives) for the DAE system `M dx/dt = f`. + * Loops through all non-zero elements and performs automatic differentiation for each element. + */ + void operator()(sparse_matrix &J, const state_vector &x, const double t) + { + const int_type size = static_cast(x.size()); // System size + + state_type x_(size); // Vectors of `dual` numbers are defined with `_` suffix + + // Conversion to dual numbers for automatic differentiation + for (int_type k = 0; k < size; ++k) + { + x_[k] = x[k]; + } + + // Lambda-function with parameters for which the Jacobian is needed + auto f = [&rhs = m_rhs](const state_type &x_, const double t, const int_type row) + { + return rhs.equations(x_, t, row); + }; + + // Reserve memory + J.reserve(static_cast(m_Jn.size())); + + // Automatic differentiation of each element marked as non-zero by the user + for (const std::pair &Jn : m_Jn) + { + J(Jn.first, Jn.second, autodiff::derivative(f, wrt(x_[Jn.second]), at(x_, t, Jn.first))); + } + } + + /* + * Adds next non-zero element (i, j), where `i` is the row, and `j` is the column in the Jacobian matrix. + */ + void add_element(const int_type ind_i, const int_type ind_j) + { + m_Jn.emplace_back(std::make_pair(ind_i, ind_j)); + } + + /* + * Adds a row of non-zero elements (i, j_k), where `i` is the row index, and `j_k` is the array of columns. + */ + void add_element(const int_type ind_i, const std::vector &ind_j) + { + for (const int_type &j : ind_j) + { + m_Jn.emplace_back(std::make_pair(ind_i, j)); + } + } + + /* + * Clear arrays of non-zero elements + */ + void clear() + { + m_Jn.clear(); + } + + /* + * Reserve memory for the array of non-zero elements + */ + void reserve(const int_type N_elements) + { + m_Jn.reserve(N_elements); + } +}; + +/* + * Helper automatic (algorithmic) Jacobian class. * Performs algorithmic differentiation of the RHS using `autodiff` package. */ template @@ -66,7 +155,7 @@ class JacobianAutomatic x_[k] = x[k]; } - // The vector lambda-function with parameters for which the Jacobian is needed + // Vector lambda-function with parameters for which the Jacobian is needed auto f = [&rhs = m_rhs, size](const state_type &x_, const double t) { state_type f_(size); @@ -86,10 +175,170 @@ class JacobianAutomatic if (std::abs(val) > DAECPP_SPARSE_MATRIX_ELEMENT_TOLERANCE) { - J(i, j, val); + J(i, j, val); // Jacobian + } + } + } + } +}; + +namespace core +{ + +/* + * A helper structure to store the differences between two matrices + */ +struct MatrixDiff +{ + int_type i; // Row + int_type j; // Column + float_type M_ref; // Reference value + float_type M_compare; // Value to compare + float_type abs_err; // Absolute error + + MatrixDiff(const int_type i, const int_type j, const float_type M_ref, const float_type M_compare) + : i(i), j(j), M_ref(M_ref), M_compare(M_compare) + { + abs_err = M_compare - M_ref; + } +}; + +} // namespace core + +/* + * A helper class that compares the user-defined Jacobian (defined either explicitly or from the Jacobian matrix shape) + * with the automatic Jacobian computed from the vector function. + */ +template +class JacobianCompare +{ + RHS m_rhs; // The RHS for differentiation (a copy) + Jacobian m_jac_user; // User-defined Jacobian + + // Table formatting settings + const int m_column_width_ind{12}; // Width for the columns with indices (integers) + const int m_column_width_val{20}; // Width for the columns with values (doubles) + + bool m_is_first_call = true; // True if comparison is called for the first time + + /* + * Prints a horizontal line with the given delimiter for the table header/footer + */ + void m_table_line(const char delimiter) const + { + std::cout << std::string(m_column_width_ind, '-') << delimiter + << std::string(m_column_width_ind, '-') << delimiter + << std::string(m_column_width_val, '-') << delimiter + << std::string(m_column_width_val, '-') << delimiter + << std::string(m_column_width_val, '-') << "\n"; + } + + /* + * Prints formatted table with the comparison results + */ + void m_print_table(const std::vector &M_diff) const + { + // Print a horizontal line + m_table_line('-'); + + // Print the table header + std::cout << std::left << std::setw(m_column_width_ind) << " Row" << "|" + << std::left << std::setw(m_column_width_ind) << " Column" << "|" + << std::left << std::setw(m_column_width_val) << " Reference value" << "|" + << std::left << std::setw(m_column_width_val) << " User-defined value" << "|" + << std::left << std::setw(m_column_width_val) << " Absolute error" << "\n"; + + // Print a horizontal line + m_table_line('+'); + + // Print table rows + for (const auto &diff : M_diff) + { + std::cout << std::left << " " << std::setw(m_column_width_ind - 1) << diff.i << "|" + << std::left << " " << std::setw(m_column_width_ind - 1) << diff.j << "|" + << std::left << " " << std::setw(m_column_width_val - 1) << diff.M_ref << "|" + << std::left << " " << std::setw(m_column_width_val - 1) << diff.M_compare << "|" + << std::left << " " << std::setw(m_column_width_val - 1) << diff.abs_err << "\n"; + } + + // Print a horizontal line + m_table_line('-'); + + // New line and flush + std::cout << std::endl; + } + +public: + /* + * `JacobianCompare` class constructor. + * + * Parameters: + * jac - user-defined Jacobian (defined either explicitly or from the Jacobian matrix shape) + * rhs - the vector function (RHS) of the system + */ + JacobianCompare(Jacobian jac, RHS rhs) : m_jac_user(jac), m_rhs(rhs) {} + + /* + * Compares the user-defined Jacobian with automatic and prints the differences. + * Returns the number of differences. + * + * Parameters: + * x - the state vector (e.g., the initial condition) + * t - time (0 by default) + */ + auto operator()(const state_vector &x, const double t = 0.0) + { + if (m_is_first_call) + { + NOTE("Comparing the Jacobian matrices..."); + m_is_first_call = false; + } + + JacobianAutomatic jac_auto = JacobianAutomatic(m_rhs); + + sparse_matrix J_auto; // Automatic (reference) Jacobian + sparse_matrix J_user; // User-defined Jacobian + + const int_type size = static_cast(x.size()); // System size + + // Compute Jacobians + jac_auto(J_auto, x, t); + m_jac_user(J_user, x, t); + + // Convert to the dense form + auto Jd_auto = J_auto.dense(size); + auto Jd_user = J_user.dense(size); + + // Vector of differences between the matrices + std::vector J_diff; + + // Perform comparison element-by-element + for (int_type j = 0; j < size; ++j) + { + for (int_type i = 0; i < size; ++i) + { + if (std::abs(Jd_auto(i, j) - Jd_user(i, j)) > DAECPP_SPARSE_MATRIX_ELEMENT_TOLERANCE) + { + J_diff.emplace_back(core::MatrixDiff(i, j, Jd_auto(i, j), Jd_user(i, j))); } } } + + auto N_diff = J_diff.size(); // Number of differences found + + std::cout << "Jacobian matrix comparison summary at time t = " << t << ":\n"; + + if (N_diff == 0) + { + std::cout << "-- No differences found.\n"; + } + else + { + std::cout << "-- Found " << N_diff << " difference(s) compared to the automatic (reference) Jacobian:\n"; + m_print_table(J_diff); + } + + return N_diff; } }; diff --git a/dae-cpp/solver.hpp b/dae-cpp/solver.hpp index 4b14ed9..c75edcf 100644 --- a/dae-cpp/solver.hpp +++ b/dae-cpp/solver.hpp @@ -118,7 +118,7 @@ inline void finalize(const timer::Time &time, const int v, const Counters c) auto &t = time.timers; PRINT(v >= 1, "\nComputation time:" << std::right); - PRINT(v >= 1, "------------------------------------------------------------"); + PRINT(v >= 1, std::string(64, '-')); PRINT(v >= 1, " Initialization: " << print_time(t[timer::init], time.total)); PRINT(v >= 1, " Time derivative: " << print_time(t[timer::time_derivative], time.total)); PRINT(v >= 1, " RHS: " << print_time(t[timer::rhs], time.total)); @@ -130,9 +130,9 @@ inline void finalize(const timer::Time &time, const int v, const Counters c) PRINT(v >= 1, " Error control: " << print_time(t[timer::error_check], time.total)); PRINT(v >= 1, " Solution Manager: " << print_time(t[timer::manager], time.total)); PRINT(v >= 1, " Other calculations: " << print_time(time.other(), time.total)); - PRINT(v >= 1, "------------------------------------------------------------"); + PRINT(v >= 1, std::string(64, '-')); PRINT(v >= 1, "Total time: " << print_time(time.total, time.total) << std::left); - PRINT(v >= 1, "------------------------------------------------------------\n"); + PRINT(v >= 1, std::string(64, '-') << '\n'); } /* @@ -186,20 +186,28 @@ inline double time_derivative_approx(eivec &dxdt, const rvec &xk, const SolverSt break; case 4: + { alpha = (4.0 * h0 * h0 * h0 + h1 * h12 * h123 + 3.0 * h0 * h0 * (3.0 * h1 + 2.0 * h2 + h3) + 2.0 * h0 * (3.0 * h1 * h1 + h2 * h23 + 2.0 * h1 * (2.0 * h2 + h3))) / (h0 * h01 * h012 * h0123); + + const double term0 = h01 * h012 * h0123 / (h0 * h1 * h12 * h123); + const double term1 = h0 * h012 * h0123 / (h1 * h01 * h2 * h23); + const double term2 = h0 * h01 * h0123 / (h2 * h12 * h012 * h3); + const double term3 = h0 * h01 * h012 / (h3 * h23 * h123 * h0123); + for (std::size_t i = 0; i < size; ++i) { dxdt[i] = alpha * xk[i] - - h01 * h012 * h0123 / (h0 * h1 * h12 * h123) * state.x[0][i] + - h0 * h012 * h0123 / (h1 * h01 * h2 * h23) * state.x[1][i] - - h0 * h01 * h0123 / (h2 * h12 * h012 * h3) * state.x[2][i] + - h0 * h01 * h012 / (h3 * h23 * h123 * h0123) * state.x[3][i]; + term0 * state.x[0][i] + + term1 * state.x[1][i] - + term2 * state.x[2][i] + + term3 * state.x[3][i]; } - break; + } + break; default: ERROR("Unsupported time integration order."); @@ -227,7 +235,7 @@ inline double time_derivative_approx(eivec &dxdt, const rvec &xk, const SolverSt * `daecpp::exit_code::success` if integration is successful or error code if integration is failed (`int`) */ template -exit_code::status solve(Mass mass, RHS rhs, Jacobian jac, Manager mgr, const state_vector &x0, const double t_end, const std::vector &t_output, const SolverOptions &opt, bool is_jac_auto) +inline exit_code::status solve(Mass mass, RHS rhs, Jacobian jac, Manager mgr, const state_vector &x0, const double t_end, const std::vector &t_output, const SolverOptions &opt, bool is_jac_auto) { // Specific counters Counters c; @@ -791,7 +799,7 @@ exit_code::status solve(Mass mass, RHS rhs, Jacobian jac, Manager mgr, const sta * `daecpp::exit_code::success` (0) if integration is successful or error code if integration is failed (`int`) */ template -exit_code::status solve(Mass mass, RHS rhs, Jacobian jac, const state_vector &x0, const double t_end, Manager mgr = SolutionManager(), const SolverOptions &opt = SolverOptions()) +inline exit_code::status solve(Mass mass, RHS rhs, Jacobian jac, const state_vector &x0, const double t_end, Manager mgr = SolutionManager(), const SolverOptions &opt = SolverOptions()) { return core::detail::solve(mass, rhs, jac, mgr, x0, t_end, {}, opt, false); } @@ -812,7 +820,7 @@ exit_code::status solve(Mass mass, RHS rhs, Jacobian jac, const state_vector &x0 * `daecpp::exit_code::success` (0) if integration is successful or error code if integration is failed (`int`) */ template -exit_code::status solve(Mass mass, RHS rhs, const state_vector &x0, const double t_end, Manager mgr = SolutionManager(), const SolverOptions &opt = SolverOptions()) +inline exit_code::status solve(Mass mass, RHS rhs, const state_vector &x0, const double t_end, Manager mgr = SolutionManager(), const SolverOptions &opt = SolverOptions()) { return core::detail::solve(mass, rhs, JacobianAutomatic(rhs), mgr, x0, t_end, {}, opt, true); } @@ -833,7 +841,7 @@ exit_code::status solve(Mass mass, RHS rhs, const state_vector &x0, const double * `daecpp::exit_code::success` (0) if integration is successful or error code if integration is failed (`int`) */ template -exit_code::status solve(Mass mass, RHS rhs, Jacobian jac, const state_vector &x0, const std::vector &t_output, Manager mgr = SolutionManager(), const SolverOptions &opt = SolverOptions()) +inline exit_code::status solve(Mass mass, RHS rhs, Jacobian jac, const state_vector &x0, const std::vector &t_output, Manager mgr = SolutionManager(), const SolverOptions &opt = SolverOptions()) { return core::detail::solve(mass, rhs, jac, mgr, x0, 0.0, t_output, opt, false); } @@ -854,7 +862,7 @@ exit_code::status solve(Mass mass, RHS rhs, Jacobian jac, const state_vector &x0 * `daecpp::exit_code::success` (0) if integration is successful or error code if integration is failed (`int`) */ template -exit_code::status solve(Mass mass, RHS rhs, const state_vector &x0, const std::vector &t_output, Manager mgr = SolutionManager(), const SolverOptions &opt = SolverOptions()) +inline exit_code::status solve(Mass mass, RHS rhs, const state_vector &x0, const std::vector &t_output, Manager mgr = SolutionManager(), const SolverOptions &opt = SolverOptions()) { return core::detail::solve(mass, rhs, JacobianAutomatic(rhs), mgr, x0, 0.0, t_output, opt, true); } diff --git a/dae-cpp/sparse-matrix.hpp b/dae-cpp/sparse-matrix.hpp index 949f748..3691b29 100644 --- a/dae-cpp/sparse-matrix.hpp +++ b/dae-cpp/sparse-matrix.hpp @@ -146,6 +146,8 @@ struct sparse_matrix M.coeffRef(i[k], j[k]) += A[k]; } + // M.makeCompressed(); // It is already compressed + return M; } diff --git a/dae-cpp/typedefs.hpp b/dae-cpp/typedefs.hpp index 198c25d..69e9d16 100644 --- a/dae-cpp/typedefs.hpp +++ b/dae-cpp/typedefs.hpp @@ -23,8 +23,8 @@ // dae-cpp version #define DAECPP_VERSION_MAJOR 2 -#define DAECPP_VERSION_MINOR 0 -#define DAECPP_VERSION_PATCH 1 +#define DAECPP_VERSION_MINOR 1 +#define DAECPP_VERSION_PATCH 0 // Internal constants #define DAECPP_MAX_ORDER 4 @@ -63,7 +63,7 @@ typedef double float_type; #endif // Floating point dual number for automatic differentiation -typedef autodiff::real dual_type; +typedef autodiff::real1st dual_type; namespace core { diff --git a/dae-cpp/vector-function.hpp b/dae-cpp/vector-function.hpp index ea7acf6..11c0819 100644 --- a/dae-cpp/vector-function.hpp +++ b/dae-cpp/vector-function.hpp @@ -1,6 +1,6 @@ /* - * Vector function class. - * Defines the RHS `f` of the DAE system `M dx/dt = f`. + * Vector function classes. + * Define the RHS `f` of the DAE system `M dx/dt = f`. * This class is abstract and must be inherited. * * This file is part of dae-cpp. @@ -28,8 +28,8 @@ class VectorFunction public: /* * Defines the RHS (vector function) `f` of the DAE system `M dx/dt = f`. - * Takes vector x and time t and returns the RHS vector f. - * Vector f is already pre-allocated with f.size() == x.size(). + * Takes vector `x` and time `t` and returns the RHS vector `f`. + * Vector `f` is already pre-allocated with f.size() == x.size(). * This function is pure virtual and must be overriden. */ virtual void operator()(state_type &f, const state_type &x, const double t) const = 0; @@ -37,6 +37,40 @@ class VectorFunction virtual ~VectorFunction() {} }; +/* + * Vector function class used for automatic Jacobian computed from the user-defined Jacobian shape. + * Used to define the vector function (the RHS) element-by-element (equation-by-equation). + * Should be used together with `JacobianMatrixShape` class for the Jacobian matrix. + * This class is abstract and must be inherited. + */ +class VectorFunctionElements +{ +public: + /* + * Defines the RHS (vector function) `f` of the DAE system `M dx/dt = f`. + * Vector `f` is already pre-allocated with f.size() == x.size(). + */ + void operator()(state_type &f, const state_type &x, const double t) const + { + const int_type size = static_cast(x.size()); // System size + + for (int_type i = 0; i < size; ++i) + { + f[i] = equations(x, t, i); + } + } + + /* + * All RHS functions `f_i` for each equation `i` in the system. + * Takes vector `x`, time `t`, index `i`, and returns the RHS value `f_i` for the given equation `i`. + * I.e., it returns the i-th element of the vector function. + * This function is pure virtual and must be overriden. + */ + virtual dual_type equations(const state_type &x, const double t, const int_type i) const = 0; + + virtual ~VectorFunctionElements() {} +}; + } // namespace daecpp_namespace_name #endif // DAECPP_VECTOR_FUNCTION_H diff --git a/examples/jacobian_compare/jacobian_compare.cpp b/examples/jacobian_compare/jacobian_compare.cpp new file mode 100644 index 0000000..cafc319 --- /dev/null +++ b/examples/jacobian_compare/jacobian_compare.cpp @@ -0,0 +1,169 @@ +/* + * In this example, we do not solve any DAEs. Instead, we define a simple vector function + * (for demonstration purposes), the corresponding Jacobian matrix to boost the calculation performance, + * and then we use a built-in helper class `JacobianCompare` to compare our manually derived Jacobian + * with the one computed algorithmically from the vector function. + * + * We will make a few mistakes in the analytic Jacobian on purpose to see what information + * `JacobianCompare` can provide. + * + * Note that `JacobianCompare` can work with Jacobians derived from the given Jacobian shapes as well. + * + * This file is part of dae-cpp. + * + * dae-cpp is licensed under the MIT license. + * A copy of the license can be found in the LICENSE file. + * + * Copyright (c) 2024 Ivan Korotkin + */ + +// Main dae-cpp header +#include + +// dae-cpp namespace +using namespace daecpp; + +/* + * The vector function (RHS) of the problem. + */ +struct MyRHS +{ + /* + * Defines the RHS (vector function) `f` of the DAE system `M dx/dt = f`. + * Takes vector `x` and time `t` and returns the RHS vector `f`. + * Vector `f` is already pre-allocated with `f.size() == x.size()`. + */ + void operator()(state_type &f, const state_type &x, const double t) + { + // `dual_type` for automatic differentiation + dual_type u = x[0]; + dual_type v = x[1]; + dual_type w = x[2]; + dual_type z = x[3]; + + f[0] = z * 0.5; + f[1] = u * u + v * v + w * w; + f[2] = sin(u); + f[3] = exp(-v) + t * z; + } +}; + +/* + * Analytic Jacobian in sparse format. + * Obtained by differentiation the RHS w.r.t. vector `x`. + * Contains a few errors: + * - one missing element + * - one element computed incorrectly + * - one element is in wrong place (incorrect `i` and `j` indices) + */ +struct MyJacobianBad +{ + /* + * Defines the Jacobian matrix (matrix of the RHS derivatives) for the DAE system `M dx/dt = f`. + * Takes vector `x` and time `t` and returns the Jacobian matrix `J`. + * Matrix `J` is empty and should be filled with non-zero elements. + */ + void operator()(sparse_matrix &J, const state_vector &x, const double t) + { + float_type u = x[0]; + float_type v = x[1]; + float_type w = x[2]; + // float_type z = x[3]; // Unused + + J.reserve(6); // Pre-allocates memory for 6 non-zero elements + + J(0, 3, 1.0); // Row 0, column 3 - error, should be `0.5` + J(1, 0, 2.0 * u); // Row 1, column 0 + J(1, 1, 2.0 * v); // Row 1, column 1 + J(1, 2, 2.0 * w); // Row 1, column 2 + J(2, 1, cos(u)); // Row 2, column 1 - error, should be column 0 + J(3, 1, -exp(-v)); // Row 3, column 1 + // Let's "forget" to differentiate f[3] w.r.t. `z` - missing element (3, 3) + } +}; + +/* + * Correct analytic Jacobian in sparse format. + * Obtained by differentiation the RHS w.r.t. vector `x`. + */ +struct MyJacobianGood +{ + /* + * Defines the Jacobian matrix (matrix of the RHS derivatives) for the DAE system `M dx/dt = f`. + * Takes vector `x` and time `t` and returns the Jacobian matrix `J`. + * Matrix `J` is empty and should be filled with non-zero elements. + */ + void operator()(sparse_matrix &J, const state_vector &x, const double t) + { + float_type u = x[0]; + float_type v = x[1]; + float_type w = x[2]; + // float_type z = x[3]; // Unused + + J.reserve(7); // Pre-allocates memory for 7 non-zero elements + + J(0, 3, 0.5); // Row 0, column 3 + J(1, 0, 2.0 * u); // Row 1, column 0 + J(1, 1, 2.0 * v); // Row 1, column 1 + J(1, 2, 2.0 * w); // Row 1, column 2 + J(2, 0, cos(u)); // Row 2, column 1 + J(3, 1, -exp(-v)); // Row 3, column 1 + J(3, 3, t); // Row 3, column 3 + } +}; + +/* + * MAIN FUNCTION + * ============================================================================= + */ +int main() +{ + // Fill vectors x at which the Jacobian matrix will be tested + state_vector x0 = {1.0, 2.0, 3.0, 4.0}; + state_vector x1 = {-0.5, -0.1, 0.5, 0.1}; + + // Check "bad" Jacobian at `x0` and `x1` and times t = 1 and 2 + { + auto jac_comparison = JacobianCompare(MyJacobianBad(), MyRHS()); + + auto N_errors_1 = jac_comparison(x0, 1.0); + auto N_errors_2 = jac_comparison(x1, 2.0); + + std::cout << "Tested \"bad\" Jacobian at x0, found " << N_errors_1 << " errors.\n"; + std::cout << "Tested \"bad\" Jacobian at x1, found " << N_errors_2 << " errors.\n\n"; + } + + // This will show the following summary: + // + // Jacobian matrix comparison summary at time t = 1: + // -- Found 4 difference(s) compared to the automatic (reference) Jacobian: + // ---------------------------------------------------------------------------------------- + // Row | Column | Reference value | User-defined value | Absolute error + // ------------+------------+--------------------+--------------------+-------------------- + // 2 | 0 | 0.540302 | 0 | -0.540302 + // 2 | 1 | 0 | 0.540302 | 0.540302 + // 0 | 3 | 0.5 | 1 | 0.5 + // 3 | 3 | 1 | 0 | -1 + // ---------------------------------------------------------------------------------------- + // + // Here we can see that: + // - element (2, 1) should be (2, 0), + // - element (0, 3) is incorrect, + // - element (3, 3) is not defined at all + + // Check "good" Jacobian at `x0` and `x1` and times t = 1 and 2 + { + auto jac_comparison = JacobianCompare(MyJacobianGood(), MyRHS()); + + auto N_errors_1 = jac_comparison(x0, 1.0); + auto N_errors_2 = jac_comparison(x1, 2.0); + + std::cout << "Tested \"good\" Jacobian at x0, found " << N_errors_1 << " errors.\n"; + std::cout << "Tested \"good\" Jacobian at x1, found " << N_errors_2 << " errors.\n"; + } + + // Note that Jacobian comparisons are element-by-element and hence can be slow. + // These comparisons should be removed from the production runs. + + return 0; +} diff --git a/examples/jacobian_shape/jacobian_shape.cpp b/examples/jacobian_shape/jacobian_shape.cpp new file mode 100644 index 0000000..7aeeda5 --- /dev/null +++ b/examples/jacobian_shape/jacobian_shape.cpp @@ -0,0 +1,306 @@ +/* + * This example demonstrates how to define the shape (structure) of the Jacobian matrix. + * I.e., instead of providing the full analytic Jacobian (or not providing it at all, which is slow), + * the user can specify the positions of non-zero elements in the Jacobian. + * The solver will use automatic differentiation for the specified elements only. + * This works (nearly) as fast as the analytic Jacobian without requiring the user to differentiate + * the vector function manually. + * + * Similar to the `pervoskite_model`, here we solve a big DAE system that describes potential distribution + * and ion concentration in a perovskite solar cell: + * + * | dP/dt = d/dx(dP/dx + P * dPhi/dx), + * | d^2(Phi)/dx^2 = (1 - P)/lambda^2, + * + * where P is the ion concentration (dimensionless), + * Phi is the potential (dimensionless) along axis x, 0 <= x <= 1. + * Lambda parameter is a constant. + * + * Initial condition (t = 0): + * | P = 1 + * | Phi = 0 + * + * Boundary conditions: + * | (dP/dx + P * dPhi/dx) = 0 for x = 0 and x = 1, + * | Phi(t,x=0) = -t, + * | Phi(t,x=1) = t. + * + * The system will be solved using Finite Difference approach in the time interval t = [0, 10]. + * + * This example introduces `JacobianMatrixShape` class that allows us to define the positions + * of all non-zero elements in the Jacobian, and an extension of `VectorFunctionElements` class, + * that helps to define the vector function suitable for automatic differentiation according to + * the given Jacobian shape. + * + * Compare this example with `perovskite_model`. The simulation time should be similar, despite + * `perovskite_model` uses analytic Jacobian, and this example defines the Jacobian shape only. + * + * This file is part of dae-cpp. + * + * dae-cpp is licensed under the MIT license. + * A copy of the license can be found in the LICENSE file. + * + * Copyright (c) 2024 Ivan Korotkin + */ + +// Main dae-cpp header +#include + +// dae-cpp namespace +using namespace daecpp; + +/* + * DAE system parameters + */ +struct MyParams +{ + int_type N{4000}; // Number of discretization points + double L{1.0}; // Length of the domain + double lambda{1.0}; // Lambda parameter +}; + +/* + * Singular mass matrix in sparse format + */ +class MyMassMatrix +{ + const MyParams &p; // Parameters + +public: + explicit MyMassMatrix(const MyParams ¶ms) : p(params) {} + + /* + * Defines the mass matrix `M` of the DAE system `M dx/dt = f`. + * The mass matrix should be defined in sparse format (non-zero elements only) and can depend on time `t`. + * Matrix `M` is empty and should be filled with non-zero elements. + */ + void operator()(sparse_matrix &M, const double t) + { + M.reserve(p.N); // Reserves memory for `N` non-zero elements + + for (int_type i = 0; i < p.N; ++i) + { + M(i, i, 1.0); + } + } +}; + +/* + * The vector-function (RHS) of the problem. + * Based on `VectorFunctionElements` class. + * NOTE that we must derive the vector function class from `VectorFunctionElements` + * and override the `equations()` function in order to be able to use the Jacobian shape class `JacobianMatrixShape`. + */ +class MyRHS : public VectorFunctionElements +{ + const MyParams &p; // Parameters + + // Derived parameters + const double h = p.L / (double)(p.N - 1); // Cell size + const double invh2 = 1.0 / (h * h); // Inverse cell size squared + const double invlam2 = 1.0 / (p.lambda * p.lambda); // Inverse lambda squared + + /* + * RHS for the ion concentration P = dFlux/dx: + * dP/dt = d/dx(dP/dx + P * dPhi/dx) + */ + dual_type eq1(const state_type &x, const double t, const int_type i_global) const + { + const dual_type *P = x.data(); + const dual_type *Phi = x.data() + p.N; + + const int_type i = i_global; + + if (i == 0) + return (P[i + 1] - P[i] + 0.5 * (P[i + 1] + P[i]) * (Phi[i + 1] - Phi[i])) * invh2; // Left BC + else if (i == p.N - 1) + return -(P[i] - P[i - 1] + 0.5 * (P[i] + P[i - 1]) * (Phi[i] - Phi[i - 1])) * invh2; // Right BC + else + return (P[i + 1] - 2.0 * P[i] + P[i - 1] + 0.5 * ((P[i + 1] + P[i]) * (Phi[i + 1] - Phi[i]) - (P[i] + P[i - 1]) * (Phi[i] - Phi[i - 1]))) * invh2; + } + + /* + * RHS for the potential Phi: + * d^2(Phi)/dx^2 - (1 - P)/lambda^2 = 0 + */ + dual_type eq2(const state_type &x, const double t, const int_type i_global) const + { + const dual_type *P = x.data(); + const dual_type *Phi = x.data() + p.N; + + const int_type i = i_global - p.N; + + if (i == 0) + return Phi[i] + t; // Left BC + else if (i == p.N - 1) + return Phi[i] - t; // Right BC + else + return (Phi[i + 1] - 2.0 * Phi[i] + Phi[i - 1]) * invh2 - (1.0 - P[i]) * invlam2; + } + +public: + explicit MyRHS(const MyParams ¶ms) : p(params) {} + + /* + * All equations combined. + * This function returns the i-th component of the vector function. + */ + dual_type equations(const state_type &x, const double t, const int_type i) const + { + if (i < p.N) + return eq1(x, t, i); + else if (i < 2 * p.N) + return eq2(x, t, i); + else + { + ERROR("Equation system: index i is out of boundaries."); + } + } +}; + +/* + * User-defined Jacobian matrix shape (a list of non-zero elements). + * Note that unlike the `perovskite_model` example, we do not calculate each derivative manually, + * we only provide the positions (row, column) of each non-zero element in the Jacobian. + * The corresponding derivatives will be computed automatically. + */ +class MyJacobianShape : public JacobianMatrixShape +{ + const int_type N{0}; // The number of discretization points for each equation + + /* + * Defines all non-zero elements of the Jacobian matrix row by row. + * Here we use `add_element()` helper method from the `JacobianMatrixShape` class. + */ + void m_define_Jacobian_shape() + { + reserve(10 * N); // Reserve memory for (6N + 4N) non-zero elements + + for (int_type i = 0; i < 2 * N; ++i) + { + if (i == 0) + { + add_element(i, {i + 1, i, N + i + 1, N + i}); // The order does not matter + } + else if (i < N - 1) + { + add_element(i, {i - 1, i, i + 1, N + i - 1, N + i, N + i + 1}); + } + else if (i == N - 1) + { + add_element(i, {i - 1, i, N + i - 1, N + i}); + } + else if (i == N) + { + add_element(i, N); // Can pass one index instead of a vector + } + else if (i < 2 * N - 1) + { + add_element(i, {i - N, i - 1, i, i + 1}); + } + else if (i == 2 * N - 1) + { + add_element(i, 2 * N - 1); + } + else + { + ERROR("Jacobian shape: index i is out of boundaries."); + } + } + } + +public: + MyJacobianShape(MyRHS &rhs, const int_type N) : JacobianMatrixShape(rhs), N(N) + { + m_define_Jacobian_shape(); + } +}; + +/* + * User-defined Solution Manager to post-process solution every time step. + * In this example, it works as a passive observer and as an event function. + */ +class MySolutionManager +{ + // A reference to the solution holder object + SolutionHolder &m_sol; + +public: + explicit MySolutionManager(SolutionHolder &sol) : m_sol(sol) {} + + /* + * Solution Manager functor will be called every time step providing the time `t` and + * the corresponding solution `x` for further post-processing. + * If the functor returns an integer != 0 (`true`), the computation will immediately stop. + */ + int operator()(const state_vector &x, const double t) + { + m_sol.x.emplace_back(x); + m_sol.t.emplace_back(t); + + if (x[0] < 0) + { + return -1; // if x[0] is less than 0 (should never happen), then the solver will stop + } + else + { + return 0; + } + } +}; + +/* + * MAIN FUNCTION + * ============================================================================= + */ +int main() +{ + // Solution parameters + MyParams params; + params.N = 4000; + + // Fill initial condition + state_vector x0(2 * params.N); + for (int_type i = 0; i < params.N; ++i) + { + x0[i] = 1.0; + } + + // Solution interval: t = [0, t_end] + double t_end{10.0}; + + // Solution holder + SolutionHolder sol; + + // Solver options + SolverOptions opt; + opt.verbosity = verbosity::normal; // Prints computation time and basic info + opt.solution_variability_control = false; // Switches off solution variability control for better performance + + std::cout << "-- Using automatic Jacobian derived from the user-defined shape:\n\n"; + + // The vector function and Jacobian objects + MyRHS rhs = MyRHS(params); + MyJacobianShape jac = MyJacobianShape(rhs, params.N); + + // If we want to make sure that the Jacobian shape is correct and no elements are missing, + // we can compare it with the analytic Jacobian computed automatically. + // This can be slow (this is element-by-element comparison), so it should be only done for debugging purposes + // and removed from the production run: + JacobianCompare(jac, rhs)(x0, 0.0); + + // Solve the DAE system using automatic Jacobian computed from the user-defined shape + int status = solve(MyMassMatrix(params), rhs, jac, + x0, t_end, + MySolutionManager(sol), opt); + + // Soluton vs time `t` is in the `sol` object. + // Print P at the left boundary, and Phi at the right boundary of the domain. + std::cout << "MySolutionManager: " + << "t = " << sol.t.back() + << ", P_left = " << sol.x.back()[0] + << ", Phi_right = " << sol.x.back().back() + << '\n'; + + return status; +} diff --git a/googletest b/googletest index f8d7d77..b514bdc 160000 --- a/googletest +++ b/googletest @@ -1 +1 @@ -Subproject commit f8d7d77c06936315286eb55f8de22cd23c188571 +Subproject commit b514bdc898e2951020cbdca1304b75f5950d1f59 diff --git a/tests/test_integration-jacobian_shape.cpp b/tests/test_integration-jacobian_shape.cpp new file mode 100644 index 0000000..9b20d36 --- /dev/null +++ b/tests/test_integration-jacobian_shape.cpp @@ -0,0 +1,254 @@ +/* + * Integration test of the solver. + * Based on the `jacobian_shape` example. + * + * This file is part of dae-cpp. + * + * dae-cpp is licensed under the MIT license. + * A copy of the license can be found in the LICENSE file. + * + * Copyright (c) 2024 Ivan Korotkin + */ + +#include + +#include "gtest/gtest.h" + +namespace +{ + +using namespace daecpp; + +struct MyParams +{ + int_type N{4000}; // Number of discretization points + double L{1.0}; // Length of the domain + double lambda{1.0}; // Lambda parameter +}; + +class MyMassMatrix +{ + const MyParams &p; // Parameters + +public: + explicit MyMassMatrix(const MyParams ¶ms) : p(params) {} + + /* + * Defines the mass matrix `M` of the DAE system `M dx/dt = f`. + * The mass matrix should be defined in sparse format (non-zero elements only) and can depend on time `t`. + * Matrix `M` is empty and should be filled with non-zero elements. + */ + void operator()(sparse_matrix &M, const double t) + { + M.reserve(p.N); // Reserves memory for `N` non-zero elements + + for (int_type i = 0; i < p.N; ++i) + { + M(i, i, 1.0); + } + } +}; + +class MyRHS : public VectorFunctionElements +{ + const MyParams &p; // Parameters + + // Derived parameters + const double h = p.L / (double)(p.N - 1); // Cell size + const double invh2 = 1.0 / (h * h); // Inverse cell size squared + const double invlam2 = 1.0 / (p.lambda * p.lambda); // Inverse lambda squared + + /* + * RHS for the ion concentration P = dFlux/dx: + * dP/dt = d/dx(dP/dx + P * dPhi/dx) + */ + dual_type eq1(const state_type &x, const double t, const int_type i_global) const + { + const dual_type *P = x.data(); + const dual_type *Phi = x.data() + p.N; + + const int_type i = i_global; + + if (i == 0) + return (P[i + 1] - P[i] + 0.5 * (P[i + 1] + P[i]) * (Phi[i + 1] - Phi[i])) * invh2; // Left BC + else if (i == p.N - 1) + return -(P[i] - P[i - 1] + 0.5 * (P[i] + P[i - 1]) * (Phi[i] - Phi[i - 1])) * invh2; // Right BC + else + return (P[i + 1] - 2.0 * P[i] + P[i - 1] + 0.5 * ((P[i + 1] + P[i]) * (Phi[i + 1] - Phi[i]) - (P[i] + P[i - 1]) * (Phi[i] - Phi[i - 1]))) * invh2; + } + + /* + * RHS for the potential Phi: + * d^2(Phi)/dx^2 - (1 - P)/lambda^2 = 0 + */ + dual_type eq2(const state_type &x, const double t, const int_type i_global) const + { + const dual_type *P = x.data(); + const dual_type *Phi = x.data() + p.N; + + const int_type i = i_global - p.N; + + if (i == 0) + return Phi[i] + t; // Left BC + else if (i == p.N - 1) + return Phi[i] - t; // Right BC + else + return (Phi[i + 1] - 2.0 * Phi[i] + Phi[i - 1]) * invh2 - (1.0 - P[i]) * invlam2; + } + +public: + explicit MyRHS(const MyParams ¶ms) : p(params) {} + + /* + * All equations combined. + * This function returns the i-th component of the vector function. + */ + dual_type equations(const state_type &x, const double t, const int_type i) const + { + if (i < p.N) + return eq1(x, t, i); + else if (i < 2 * p.N) + return eq2(x, t, i); + else + { + ERROR("Equation system: index i is out of boundaries."); + } + } +}; + +class MyJacobianShape : public JacobianMatrixShape +{ + const int_type N{0}; // The number of discretization points for each equation + + /* + * Defines all non-zero elements of the Jacobian matrix row by row. + * Here we use `add_element()` helper method from the `JacobianMatrixShape` class. + */ + void m_define_Jacobian_shape() + { + reserve(10 * N); // Reserve memory for (6N + 4N) non-zero elements + + for (int_type i = 0; i < 2 * N; ++i) + { + if (i == 0) + { + add_element(i, {i + 1, i, N + i + 1, N + i}); // The order does not matter + } + else if (i < N - 1) + { + add_element(i, {i - 1, i, i + 1, N + i - 1, N + i, N + i + 1}); + } + else if (i == N - 1) + { + add_element(i, {i - 1, i, N + i - 1, N + i}); + } + else if (i == N) + { + add_element(i, N); // Can pass one index instead of a vector + } + else if (i < 2 * N - 1) + { + add_element(i, {i - N, i - 1, i, i + 1}); + } + else if (i == 2 * N - 1) + { + add_element(i, 2 * N - 1); + } + else + { + ERROR("Jacobian shape: index i is out of boundaries."); + } + } + } + +public: + MyJacobianShape(MyRHS &rhs, const int_type N) : JacobianMatrixShape(rhs), N(N) + { + m_define_Jacobian_shape(); + } +}; + +class MySolutionManager +{ + // A reference to the solution holder object + SolutionHolder &m_sol; + +public: + explicit MySolutionManager(SolutionHolder &sol) : m_sol(sol) {} + + virtual int operator()(const state_vector &x, const double t) + { + m_sol.x.emplace_back(x); + m_sol.t.emplace_back(t); + + if (x[0] < 0) + { + return -1; // if x[0] is less than 0 (should never happen), then the solver will stop + } + else + { + return 0; + } + } +}; + +// Absolute errors +constexpr double abs_err{1e-14}; + +TEST(Integration, JacobianShape) +{ + // Solution parameters + MyParams params; + params.N = 4000; + + // Initial condition + state_vector x0(2 * params.N); + for (int_type i = 0; i < params.N; ++i) + { + x0[i] = 1.0; + } + + // Solution interval: t = [0, t_end] + double t_end{10.0}; + + // Solution holder + SolutionHolder sol; + + // Solver options + SolverOptions opt; + opt.verbosity = verbosity::off; // Prints computation time and basic info + opt.solution_variability_control = false; // Switches off solution variability control for better performance + + // The vector function and Jacobian objects + MyRHS rhs = MyRHS(params); + MyJacobianShape jac = MyJacobianShape(rhs, params.N); + + ASSERT_EQ(JacobianCompare(jac, rhs)(x0, 0.0), 0); + + // Solve the DAE system using automatic Jacobian computed from the user-defined shape + int status = solve(MyMassMatrix(params), rhs, jac, + x0, t_end, + MySolutionManager(sol), opt); + + ASSERT_EQ(status, 0); + + // Soluton vs time `t` is in the `sol` object + ASSERT_GT(sol.x.size(), 0); + ASSERT_GT(sol.t.size(), 0); + + ASSERT_DOUBLE_EQ(sol.t.back(), t_end); + + // Comparison with MATLAB numerical solution + auto N = params.N; + EXPECT_NEAR(sol.x.back()[0], 19.9949, 0.05); + EXPECT_NEAR(sol.x.back()[(N - 1) / 10] * 0.1 + sol.x.back()[(N - 1) / 10 + 1] * 0.9, 2.72523, 1e-2); + EXPECT_NEAR(sol.x.back()[(N - 1) / 5] * 0.2 + sol.x.back()[(N - 1) / 5 + 1] * 0.8, 0.382148, 1e-3); + EXPECT_NEAR(sol.x.back()[N], -10.0, abs_err); + EXPECT_NEAR(sol.x.back()[N + (N - 1) / 5 * 1] * 0.2 + sol.x.back()[N + (N - 1) / 5 * 1 + 1] * 0.8, -6.04056, 1e-4); + EXPECT_NEAR(sol.x.back()[N + (N - 1) / 5 * 2] * 0.4 + sol.x.back()[N + (N - 1) / 5 * 2 + 1] * 0.6, -2.08970, 0.006); + EXPECT_NEAR(sol.x.back()[N + (N - 1) / 5 * 3] * 0.6 + sol.x.back()[N + (N - 1) / 5 * 3 + 1] * 0.4, 1.90021, 0.015); + EXPECT_NEAR(sol.x.back()[N + (N - 1) / 5 * 4] * 0.8 + sol.x.back()[N + (N - 1) / 5 * 4 + 1] * 0.2, 5.93011, 0.02); + EXPECT_NEAR(sol.x.back().back(), 10.0, abs_err); +} + +} // namespace diff --git a/tests/test_integration-perovskite_model.cpp b/tests/test_integration-perovskite_model.cpp index 57abf69..9bcc06f 100644 --- a/tests/test_integration-perovskite_model.cpp +++ b/tests/test_integration-perovskite_model.cpp @@ -199,6 +199,8 @@ TEST(Integration, PerovskiteModel) opt.verbosity = verbosity::off; // Prints computation time and basic info opt.solution_variability_control = false; // Switches off solution variability control for better performance + ASSERT_EQ(JacobianCompare(MyJacobian(params), MyRHS(params))(x0, 0.0), 0); + // Solve the DAE system int status = solve(MyMassMatrix(params), MyRHS(params), MyJacobian(params), x0, t_end, diff --git a/tests/test_jacobian-compare.cpp b/tests/test_jacobian-compare.cpp new file mode 100644 index 0000000..fba2402 --- /dev/null +++ b/tests/test_jacobian-compare.cpp @@ -0,0 +1,172 @@ +/* + * Testing: + * class JacobianCompare + * + * This file is part of dae-cpp. + * + * dae-cpp is licensed under the MIT license. + * A copy of the license can be found in the LICENSE file. + * + * Copyright (c) 2024 Ivan Korotkin + */ + +#include +#include + +#include "gtest/gtest.h" + +namespace +{ + +using namespace daecpp; + +struct MyRHS +{ + void operator()(state_type &f, const state_type &x, const double t) + { + // `dual_type` for automatic differentiation + dual_type u = x[0]; + dual_type v = x[1]; + dual_type w = x[2]; + dual_type z = x[3]; + + f[0] = z * 0.5; + f[1] = u * u + v * v + w * w; + f[2] = sin(u); + f[3] = exp(-v) + t * z; + } +}; + +struct MyJacobianBad +{ + void operator()(sparse_matrix &J, const state_vector &x, const double t) + { + float_type u = x[0]; + float_type v = x[1]; + float_type w = x[2]; + float_type z = x[3]; + + J.reserve(6); + + J(0, 3, 1.0); // Row 0, column 3 - error, should be `0.5` + J(1, 0, 2.0 * u); // Row 1, column 0 + J(1, 1, 2.0 * v); // Row 1, column 1 + J(1, 2, 2.0 * w); // Row 1, column 2 + J(2, 1, cos(u)); // Row 2, column 1 - error, should be column 0 + J(3, 1, -exp(-v)); // Row 3, column 1 + // Forgot to differentiate f[3] w.r.t. `z` - missing element (3, 3) + } +}; + +struct MyJacobianGood +{ + void operator()(sparse_matrix &J, const state_vector &x, const double t) + { + float_type u = x[0]; + float_type v = x[1]; + float_type w = x[2]; + float_type z = x[3]; + + J.reserve(7); + + J(0, 3, 0.5); // Row 0, column 3 + J(1, 0, 2.0 * u); // Row 1, column 0 + J(1, 1, 2.0 * v); // Row 1, column 1 + J(1, 2, 2.0 * w); // Row 1, column 2 + J(2, 0, cos(u)); // Row 2, column 1 + J(3, 1, -exp(-v)); // Row 3, column 1 + J(3, 3, t); // Row 3, column 3 + } +}; + +class MyRHSShape : public VectorFunctionElements +{ +public: + dual_type equations(const state_type &x, const double t, const int_type i) const + { + // `dual_type` for automatic differentiation + dual_type u = x[0]; + dual_type v = x[1]; + dual_type w = x[2]; + dual_type z = x[3]; + + if (i == 0) + return z * 0.5; + else if (i == 1) + return u * u + v * v + w * w; + else if (i == 2) + return sin(u); + else if (i == 3) + return exp(-v) + t * z; + else + { + ERROR("Index i in MyRHSShape is out of boundaries, i = " << i); + } + } +}; + +class MyJacobianShapeBad : public JacobianMatrixShape +{ +public: + MyJacobianShapeBad(MyRHSShape &rhs) : JacobianMatrixShape(rhs) + { + add_element(0, 3); + add_element(1, {0, 1, 2, 3}); // (1, 3) is not necessary (not an error) + add_element(2, 1); // Should be (2, 0) + add_element(3, {1, 2}); // Should be {1, 3} + } +}; + +class MyJacobianShapeGood : public JacobianMatrixShape +{ +public: + MyJacobianShapeGood(MyRHSShape &rhs) : JacobianMatrixShape(rhs) + { + add_element(0, 3); + add_element(1, {0, 1, 2, 3}); // (1, 3) is not necessary (not an error) + add_element(2, 0); + add_element(3, {1, 3}); + } +}; + +// Fill vectors x at which the Jacobian matrix will be tested +state_vector x0 = {1.0, 2.0, 3.0, 4.0}; +state_vector x1 = {-0.5, -0.1, 0.5, 0.1}; + +TEST(JacobianCompare, AnalyticJacobianBad) +{ + auto jac_comparison = JacobianCompare(MyJacobianBad(), MyRHS()); + + ASSERT_EQ(jac_comparison(x0, 1.0), 4); + ASSERT_EQ(jac_comparison(x1, 2.0), 4); +} + +TEST(JacobianCompare, AnalyticJacobianGood) +{ + auto jac_comparison = JacobianCompare(MyJacobianGood(), MyRHS()); + + ASSERT_EQ(jac_comparison(x0, 1.0), 0); + ASSERT_EQ(jac_comparison(x1, 2.0), 0); +} + +TEST(JacobianCompare, JacobianShapeBad) +{ + auto rhs = MyRHSShape(); + + auto jac_comparison = JacobianCompare(MyJacobianShapeBad(rhs), rhs); + + ASSERT_EQ(jac_comparison(x0, 1.0), 2); + ASSERT_EQ(jac_comparison(x1, 2.0), 2); +} + +TEST(JacobianCompare, JacobianShapeGood) +{ + auto rhs = MyRHSShape(); + + auto jac_comparison = JacobianCompare(MyJacobianShapeGood(rhs), rhs); + + ASSERT_EQ(jac_comparison(x0, 1.0), 0); + ASSERT_EQ(jac_comparison(x1, 2.0), 0); +} + +} // namespace diff --git a/tests/test_jacobian-shape.cpp b/tests/test_jacobian-shape.cpp new file mode 100644 index 0000000..0966190 --- /dev/null +++ b/tests/test_jacobian-shape.cpp @@ -0,0 +1,164 @@ +/* + * Testing: + * class JacobianMatrixShape + * + * This file is part of dae-cpp. + * + * dae-cpp is licensed under the MIT license. + * A copy of the license can be found in the LICENSE file. + * + * Copyright (c) 2024 Ivan Korotkin + */ + +#include + +#include "gtest/gtest.h" + +namespace +{ + +using namespace daecpp; + +struct TestRHS : VectorFunctionElements +{ + dual_type equations(const state_type &x, const double t, const int_type i) const + { + if (i == 0) + { + return x[0] * x[1]; + } + else if (i == 1) + { + return x[0] - x[1]; + } + else + { + ERROR("Incorrect index i: " << i); + } + } +}; + +TEST(JacobianMatrixShape, Definition) +{ + struct TestJacobian : JacobianMatrixShape + { + explicit TestJacobian(TestRHS rhs) : JacobianMatrixShape(rhs) + { + add_element(0, {0, 1}); + add_element(1, 1); // Missing element (1, 0) on purpose + } + }; + + TestJacobian jac(TestRHS{}); + + sparse_matrix J; + state_vector x{4.0, 6.0}; + + constexpr double t{10.0}; + + jac(J, x, t); + + J.check(); + + EXPECT_EQ(J.N_elements(), 3); + + auto Jd = J.dense(2); + + EXPECT_DOUBLE_EQ(Jd(0, 0), 6.0); + EXPECT_DOUBLE_EQ(Jd(0, 1), 4.0); + EXPECT_DOUBLE_EQ(Jd(1, 0), 0.0); + EXPECT_DOUBLE_EQ(Jd(1, 1), -1.0); +} + +TEST(JacobianMatrixShape, DefinitionInline) +{ + JacobianMatrixShape jac(TestRHS{}); + + jac.clear(); + jac.reserve(2); + + jac.add_element(0, {0, 1}); + jac.add_element(1, 1); // Missing element (1, 0) on purpose + + sparse_matrix J; + state_vector x{4.0, 6.0}; + + constexpr double t{10.0}; + + jac(J, x, t); + + J.check(); + + EXPECT_EQ(J.N_elements(), 3); + + auto Jd = J.dense(2); + + EXPECT_DOUBLE_EQ(Jd(0, 0), 6.0); + EXPECT_DOUBLE_EQ(Jd(0, 1), 4.0); + EXPECT_DOUBLE_EQ(Jd(1, 0), 0.0); + EXPECT_DOUBLE_EQ(Jd(1, 1), -1.0); +} + +TEST(JacobianMatrixShape, AddElement) +{ + JacobianMatrixShape jac(TestRHS{}); + + jac.add_element(0, {0, 1}); // Add as a vector + jac.add_element(1, 1); // Add as a single element + + sparse_matrix J; + state_vector x{4.0, 6.0}; + + constexpr double t{10.0}; + + jac(J, x, t); + + J.check(); + + EXPECT_EQ(J.N_elements(), 3); + + auto Jd = J.dense(2); + + EXPECT_DOUBLE_EQ(Jd(0, 0), 6.0); + EXPECT_DOUBLE_EQ(Jd(0, 1), 4.0); + EXPECT_DOUBLE_EQ(Jd(1, 0), 0.0); + EXPECT_DOUBLE_EQ(Jd(1, 1), -1.0); +} + +TEST(JacobianMatrixShape, Clear) +{ + JacobianMatrixShape jac(TestRHS{}); + + sparse_matrix J; + state_vector x{4.0, 6.0}; + + constexpr double t{10.0}; + + jac.add_element(0, {0, 1}); // Add as a vector + jac.add_element(1, 0); // Add as a single element + jac.clear(); + + jac(J, x, t); + + J.check(); + + EXPECT_EQ(J.N_elements(), 0); + + jac.add_element(0, {0, 1}); // Add as a vector + jac.add_element(1, 1); // Add as a single element (a different one) + + jac(J, x, t); + + J.check(); + + EXPECT_EQ(J.N_elements(), 3); + + auto Jd = J.dense(2); + + EXPECT_DOUBLE_EQ(Jd(0, 0), 6.0); + EXPECT_DOUBLE_EQ(Jd(0, 1), 4.0); + EXPECT_DOUBLE_EQ(Jd(1, 0), 0.0); + EXPECT_DOUBLE_EQ(Jd(1, 1), -1.0); +} + +} // namespace diff --git a/tests/test_vector-function.cpp b/tests/test_vector-function.cpp index cba0474..4765ac7 100644 --- a/tests/test_vector-function.cpp +++ b/tests/test_vector-function.cpp @@ -1,6 +1,6 @@ /* * Testing: - * class VectorFunction + * class VectorFunction, VectorFunctionElements * * This file is part of dae-cpp. * @@ -51,4 +51,42 @@ TEST(VectorFunction, Definition) EXPECT_EQ(f.size(), 2); } +TEST(VectorFunctionElements, Definition) +{ + struct TestVectorFunction : VectorFunctionElements + { + dual_type equations(const state_type &x, const double t, const int_type i) const + { + ASSERT(x.size() == 2, "Incorrect size of x: " << x.size()); + + if (i == 0) + return x[0]; + else if (i == 1) + return x[1] * t; + else + { + ERROR("Index i in TestVectorFunction is out of boundaries, i = " << i); + } + } + }; + + TestVectorFunction rhs; + + state_type x(2); + + x[0] = 4.0; + x[1] = 6.0; + + state_type f(2); + + constexpr double t{10.0}; + + rhs(f, x, t); + + EXPECT_DOUBLE_EQ(f[0].val(), 4.0); + EXPECT_DOUBLE_EQ(f[1].val(), 6.0 * t); + + EXPECT_EQ(f.size(), 2); +} + } // namespace