From d214a48afcfa8cd24775fa3b617980270280c812 Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Tue, 10 Dec 2024 19:58:40 +0000 Subject: [PATCH 01/34] Add benchmark files and exclude from the repo for now --- .gitignore | 1 + 1 file changed, 1 insertion(+) 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 From b36cec58c4b6b4f480dabb1196945fbb4e41ace0 Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Tue, 10 Dec 2024 20:19:06 +0000 Subject: [PATCH 02/34] Update autodiff to v1.1.2 --- ...mit-2e2f3a2b16afcd9c04e76c8a689e9fd23ff78679} | 0 autodiff/common/meta.hpp | 16 ++++------------ autodiff/forward/dual/dual.hpp | 2 +- 3 files changed, 5 insertions(+), 13 deletions(-) rename autodiff/{commit-d66d8f4852146cc41168a31f750f3d09ef8f2e70 => commit-2e2f3a2b16afcd9c04e76c8a689e9fd23ff78679} (100%) 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> From 351e4c32f274afaeba2b3e6ea2a360e5868f2566 Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Tue, 10 Dec 2024 21:20:55 +0000 Subject: [PATCH 03/34] Update Eigen to the max possible version (commit d26e from 28 Mar 2024), newer commits are incompatible with autodiff --- Eigen/CholmodSupport | 2 - ...-d26e19714fca2faf544619c3604f88d980e5a207} | 0 Eigen/src/Core/Matrix.h | 11 +- Eigen/src/Core/PlainObjectBase.h | 19 +- Eigen/src/Core/arch/AVX/PacketMath.h | 31 +- Eigen/src/Core/arch/AVX/TypeCasting.h | 51 ++++ Eigen/src/Core/arch/AVX512/PacketMath.h | 1 + Eigen/src/Core/arch/SSE/PacketMath.h | 27 +- Eigen/src/Core/arch/SSE/TypeCasting.h | 33 ++ .../Core/products/TriangularMatrixVector.h | 4 + Eigen/src/Core/util/ConfigureVectorization.h | 3 + Eigen/src/Geometry/OrthoMethods.h | 2 +- Eigen/src/SparseCholesky/SimplicialCholesky.h | 287 ++++++++++++++++-- .../SparseCholesky/SimplicialCholesky_impl.h | 16 +- Eigen/src/SparseCore/SparseMatrix.h | 18 +- Eigen/src/SparseCore/SparseSelfAdjointView.h | 18 +- Eigen/src/SparseCore/SparseVector.h | 18 ++ Eigen/src/plugins/MatrixCwiseUnaryOps.inc | 11 +- 18 files changed, 468 insertions(+), 84 deletions(-) rename Eigen/{commit-d3cd3126520f1e81aeb2abb5e5ae77bd322f8193 => commit-d26e19714fca2faf544619c3604f88d980e5a207} (100%) 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. /// From 70c3247788c1e179b412001db3b624e5171f905b Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Tue, 10 Dec 2024 21:51:25 +0000 Subject: [PATCH 04/34] Update gtest to 1.15.2 --- googletest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/googletest b/googletest index f8d7d77..b514bdc 160000 --- a/googletest +++ b/googletest @@ -1 +1 @@ -Subproject commit f8d7d77c06936315286eb55f8de22cd23c188571 +Subproject commit b514bdc898e2951020cbdca1304b75f5950d1f59 From 9fff1062a083aaa6de0464bff962098c96425b21 Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Sun, 15 Dec 2024 14:49:03 +0000 Subject: [PATCH 05/34] Add new vector function and Jacobian classes for automatic differentiation according to the user-defined Jacobian shape --- dae-cpp/assert-custom.hpp | 2 +- dae-cpp/jacobian-matrix.hpp | 103 ++++++++++++++++++++++++++++++++++-- dae-cpp/solver.hpp | 6 +-- dae-cpp/sparse-matrix.hpp | 2 + dae-cpp/typedefs.hpp | 2 +- dae-cpp/vector-function.hpp | 40 ++++++++++++-- 6 files changed, 143 insertions(+), 12 deletions(-) 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..e7cf79f 100644 --- a/dae-cpp/jacobian-matrix.hpp +++ b/dae-cpp/jacobian-matrix.hpp @@ -14,6 +14,8 @@ #ifndef DAECPP_JACOBIAN_MATRIX_H #define DAECPP_JACOBIAN_MATRIX_H +#include // std::pair + #include "sparse-matrix.hpp" #include "vector-function.hpp" @@ -39,7 +41,102 @@ 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. + * This class is abstract and must be inherited. + */ +template +class JacobianMatrixShape +{ + // Array of non-zero elements + std::vector> m_Jn; + + // An estimation of the array (sparse Jacobian matrix) size + int_type m_N_elements{0}; + + // The RHS for differentiation (a copy) + RHS m_rhs; + +public: + explicit JacobianMatrixShape(RHS rhs) : m_rhs(rhs) {} + + /* + * 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 + if (m_N_elements) + { + J.reserve(m_N_elements); + } + + // 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))); + } + + // Update sparse Jacobian matrix size estimation + m_N_elements = static_cast(m_Jn.size()); + } + + /* + * 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 + */ + inline void clear() + { + m_Jn.clear(); + } + + /* + * Reserve memory for the array of non-zero elements and the Jacobian matrix + */ + inline void reserve(const int_type N_elements) + { + m_N_elements = N_elements; + m_Jn.reserve(N_elements); + } +}; + +/* + * Helper automatic (algorithmic) Jacobian class. * Performs algorithmic differentiation of the RHS using `autodiff` package. */ template @@ -66,7 +163,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,7 +183,7 @@ class JacobianAutomatic if (std::abs(val) > DAECPP_SPARSE_MATRIX_ELEMENT_TOLERANCE) { - J(i, j, val); + J(i, j, val); // Jacobian } } } diff --git a/dae-cpp/solver.hpp b/dae-cpp/solver.hpp index 4b14ed9..04defa9 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, "----------------------------------------------------------------"); 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, "----------------------------------------------------------------"); PRINT(v >= 1, "Total time: " << print_time(time.total, time.total) << std::left); - PRINT(v >= 1, "------------------------------------------------------------\n"); + PRINT(v >= 1, "----------------------------------------------------------------\n"); } /* 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..fe35c1f 100644 --- a/dae-cpp/typedefs.hpp +++ b/dae-cpp/typedefs.hpp @@ -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..86ef47c 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,38 @@ class VectorFunction virtual ~VectorFunction() {} }; +/* + * Vector function class used for automatic Jacobian computed from the user-defined Jacobian shape. + * Should be used together with `JacobianMatrixShape` class for the Jacobian matrix. + * This class is abstract and must be inherited. + */ +class VectorFunctionJacobianShape +{ +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`. + * 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 ~VectorFunctionJacobianShape() {} +}; + } // namespace daecpp_namespace_name #endif // DAECPP_VECTOR_FUNCTION_H From 5252c3fc0e58b5047302449d3c746bd1e49d784d Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Sun, 15 Dec 2024 16:45:42 +0000 Subject: [PATCH 06/34] Update version to 2.1.0 --- README.md | 2 +- dae-cpp/typedefs.hpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a1c0b74..ab173fe 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).** diff --git a/dae-cpp/typedefs.hpp b/dae-cpp/typedefs.hpp index fe35c1f..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 From da98ac4748f493e4a65f2f194820ac036078ea5f Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Sun, 15 Dec 2024 18:20:52 +0000 Subject: [PATCH 07/34] Add jacobian_matrix_shape example --- CMakeLists.txt | 2 +- .../jacobian_matrix_shape.cpp | 256 ++++++++++++++++++ 2 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 examples/jacobian_matrix_shape/jacobian_matrix_shape.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 153b493..609acc4 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_matrix_shape") include_directories(${PROJECT_SOURCE_DIR}) diff --git a/examples/jacobian_matrix_shape/jacobian_matrix_shape.cpp b/examples/jacobian_matrix_shape/jacobian_matrix_shape.cpp new file mode 100644 index 0000000..cbbbea2 --- /dev/null +++ b/examples/jacobian_matrix_shape/jacobian_matrix_shape.cpp @@ -0,0 +1,256 @@ +/* + * This example solves 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 Solution Manager, that will work as solution observer and a simple event function. + * It also shows how parameters, such as the number of discretization points, can be passed to the mass matrix, + * vector function, and Jacobian matrix. + * + * 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 +{ + MyParams p; // Parameters + +public: + explicit MyMassMatrix(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 `VectorFunctionJacobianShape` class. + */ +class JacShapeRHS : public VectorFunctionJacobianShape +{ + 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 JacShapeRHS(MyParams ¶ms) : p(params) {} + + /* + * All equations combined + */ + 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("Perovskite model: index i is out of boundaries."); + } + } +}; + +/* + * 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 << "-- Automatic Jacobian derived from the user-defined shape:\n\n"; + + JacobianMatrixShape jac = JacobianMatrixShape(JacShapeRHS(params)); + + // An alias for p.N - the number of discretization points for each equation + const int_type N = params.N; + + // Defines all non-zero elements of the Jacobian matrix row by row + for (int_type i = 0; i < 2 * N; ++i) + { + if (i == 0) + { + jac.add_element(i, {i + 1, i, N + i + 1, N + i}); // The order does not matter + } + else if (i < N - 1) + { + jac.add_element(i, {i - 1, i, i + 1, N + i - 1, N + i, N + i + 1}); + } + else if (i == N - 1) + { + jac.add_element(i, {i - 1, i, N + i - 1, N + i}); + } + else if (i == N) + { + jac.add_element(i, N); // Can pass one index instead of a vector + } + else if (i < 2 * N - 1) + { + jac.add_element(i, {i - N, i - 1, i, i + 1}); + } + else if (i == 2 * N - 1) + { + jac.add_element(i, 2 * N - 1); + } + else + { + ERROR("Perovskite model: index i is out of boundaries."); + } + } + + // Solve the DAE system using automatic Jacobian computed from the user-defined shape + solve(MyMassMatrix(params), JacShapeRHS(params), jac, + x0, t_end, + MySolutionManager(sol), opt); + } + + return 0; +} From bf135a12f73a4d150385d2274464e630e37c8a15 Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Sun, 15 Dec 2024 22:59:39 +0000 Subject: [PATCH 08/34] Update jacobian_matrix_shape example --- .../jacobian_matrix_shape.cpp | 42 +++++++++++++++---- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/examples/jacobian_matrix_shape/jacobian_matrix_shape.cpp b/examples/jacobian_matrix_shape/jacobian_matrix_shape.cpp index cbbbea2..f245ddd 100644 --- a/examples/jacobian_matrix_shape/jacobian_matrix_shape.cpp +++ b/examples/jacobian_matrix_shape/jacobian_matrix_shape.cpp @@ -1,5 +1,12 @@ /* - * This example solves a big DAE system that describes potential distribution + * 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), @@ -20,9 +27,13 @@ * * The system will be solved using Finite Difference approach in the time interval t = [0, 10]. * - * This example introduces Solution Manager, that will work as solution observer and a simple event function. - * It also shows how parameters, such as the number of discretization points, can be passed to the mass matrix, - * vector function, and Jacobian matrix. + * This example introduces `JacobianMatrixShape` class that allows us to define the positions + * of all non-zero elements in the Jacobian, and an extension of `VectorFunctionJacobianShape` 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. * @@ -77,6 +88,8 @@ class MyMassMatrix /* * The vector-function (RHS) of the problem. * Based on `VectorFunctionJacobianShape` class. + * NOTE that we must derive the vector function class from `VectorFunctionJacobianShape` in order to be able + * to use the Jacobian shape class `JacobianMatrixShape`. */ class JacShapeRHS : public VectorFunctionJacobianShape { @@ -129,7 +142,8 @@ class JacShapeRHS : public VectorFunctionJacobianShape explicit JacShapeRHS(MyParams ¶ms) : p(params) {} /* - * All equations combined + * 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 { @@ -139,7 +153,7 @@ class JacShapeRHS : public VectorFunctionJacobianShape return eq2(x, t, i); else { - ERROR("Perovskite model: index i is out of boundaries."); + ERROR("Equation system: index i is out of boundaries."); } } }; @@ -205,8 +219,12 @@ int main() opt.verbosity = verbosity::normal; // Prints computation time and basic info opt.solution_variability_control = false; // Switches off solution variability control for better performance + // Solver status + // FIXME: Adding solver status slows down Jacobian computation by a factor of 1.5 for some reason + // int status{-1}; + { - std::cout << "-- Automatic Jacobian derived from the user-defined shape:\n\n"; + std::cout << "-- Using automatic Jacobian derived from the user-defined shape:\n\n"; JacobianMatrixShape jac = JacobianMatrixShape(JacShapeRHS(params)); @@ -242,7 +260,7 @@ int main() } else { - ERROR("Perovskite model: index i is out of boundaries."); + ERROR("Jacobian shape: index i is out of boundaries."); } } @@ -252,5 +270,13 @@ int main() 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 0; } From 456a2fb9419271e219e1be0cd3ba64971e14e233 Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Mon, 16 Dec 2024 14:52:13 +0000 Subject: [PATCH 09/34] Update jacobian_matrix_shape example - move Jacobian shape definition to a separate class --- dae-cpp/jacobian-matrix.hpp | 2 + .../jacobian_matrix_shape.cpp | 122 ++++++++++-------- 2 files changed, 71 insertions(+), 53 deletions(-) diff --git a/dae-cpp/jacobian-matrix.hpp b/dae-cpp/jacobian-matrix.hpp index e7cf79f..87d2221 100644 --- a/dae-cpp/jacobian-matrix.hpp +++ b/dae-cpp/jacobian-matrix.hpp @@ -60,6 +60,8 @@ class JacobianMatrixShape 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. diff --git a/examples/jacobian_matrix_shape/jacobian_matrix_shape.cpp b/examples/jacobian_matrix_shape/jacobian_matrix_shape.cpp index f245ddd..f49f616 100644 --- a/examples/jacobian_matrix_shape/jacobian_matrix_shape.cpp +++ b/examples/jacobian_matrix_shape/jacobian_matrix_shape.cpp @@ -88,10 +88,10 @@ class MyMassMatrix /* * The vector-function (RHS) of the problem. * Based on `VectorFunctionJacobianShape` class. - * NOTE that we must derive the vector function class from `VectorFunctionJacobianShape` in order to be able - * to use the Jacobian shape class `JacobianMatrixShape`. + * NOTE that we must derive the vector function class from `VectorFunctionJacobianShape` + * and override the `equations()` function in order to be able to use the Jacobian shape class `JacobianMatrixShape`. */ -class JacShapeRHS : public VectorFunctionJacobianShape +class MyRHS : public VectorFunctionJacobianShape { const MyParams p; // Parameters @@ -139,7 +139,7 @@ class JacShapeRHS : public VectorFunctionJacobianShape } public: - explicit JacShapeRHS(MyParams ¶ms) : p(params) {} + explicit MyRHS(MyParams ¶ms) : p(params) {} /* * All equations combined. @@ -158,6 +158,62 @@ class JacShapeRHS : public VectorFunctionJacobianShape } }; +/* + * 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() + { + 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. @@ -219,56 +275,16 @@ int main() opt.verbosity = verbosity::normal; // Prints computation time and basic info opt.solution_variability_control = false; // Switches off solution variability control for better performance - // Solver status - // FIXME: Adding solver status slows down Jacobian computation by a factor of 1.5 for some reason - // int status{-1}; + std::cout << "-- Using automatic Jacobian derived from the user-defined shape:\n\n"; - { - std::cout << "-- Using automatic Jacobian derived from the user-defined shape:\n\n"; - - JacobianMatrixShape jac = JacobianMatrixShape(JacShapeRHS(params)); - - // An alias for p.N - the number of discretization points for each equation - const int_type N = params.N; - - // Defines all non-zero elements of the Jacobian matrix row by row - for (int_type i = 0; i < 2 * N; ++i) - { - if (i == 0) - { - jac.add_element(i, {i + 1, i, N + i + 1, N + i}); // The order does not matter - } - else if (i < N - 1) - { - jac.add_element(i, {i - 1, i, i + 1, N + i - 1, N + i, N + i + 1}); - } - else if (i == N - 1) - { - jac.add_element(i, {i - 1, i, N + i - 1, N + i}); - } - else if (i == N) - { - jac.add_element(i, N); // Can pass one index instead of a vector - } - else if (i < 2 * N - 1) - { - jac.add_element(i, {i - N, i - 1, i, i + 1}); - } - else if (i == 2 * N - 1) - { - jac.add_element(i, 2 * N - 1); - } - else - { - ERROR("Jacobian shape: index i is out of boundaries."); - } - } + // The vector function and Jacobian objects + MyRHS rhs = MyRHS(params); + MyJacobianShape jac = MyJacobianShape(rhs, params.N); - // Solve the DAE system using automatic Jacobian computed from the user-defined shape - solve(MyMassMatrix(params), JacShapeRHS(params), jac, - x0, t_end, - MySolutionManager(sol), opt); - } + // 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. @@ -278,5 +294,5 @@ int main() << ", Phi_right = " << sol.x.back().back() << '\n'; - return 0; + return status; } From 592c2637e7c6644b892be5a444a90d9b3859871f Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Mon, 16 Dec 2024 15:08:09 +0000 Subject: [PATCH 10/34] Reserve memory for automatic Jacobian derived from the user-defined shape --- dae-cpp/jacobian-matrix.hpp | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/dae-cpp/jacobian-matrix.hpp b/dae-cpp/jacobian-matrix.hpp index 87d2221..cfe3803 100644 --- a/dae-cpp/jacobian-matrix.hpp +++ b/dae-cpp/jacobian-matrix.hpp @@ -85,19 +85,13 @@ class JacobianMatrixShape }; // Reserve memory - if (m_N_elements) - { - J.reserve(m_N_elements); - } + J.reserve(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))); } - - // Update sparse Jacobian matrix size estimation - m_N_elements = static_cast(m_Jn.size()); } /* @@ -128,11 +122,10 @@ class JacobianMatrixShape } /* - * Reserve memory for the array of non-zero elements and the Jacobian matrix + * Reserve memory for the array of non-zero elements */ inline void reserve(const int_type N_elements) { - m_N_elements = N_elements; m_Jn.reserve(N_elements); } }; From 3abdce15fbc82b059a4c5c1a550b8b75893bc8f7 Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Mon, 16 Dec 2024 15:21:45 +0000 Subject: [PATCH 11/34] Add references to the example --- .../jacobian_matrix_shape/jacobian_matrix_shape.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/jacobian_matrix_shape/jacobian_matrix_shape.cpp b/examples/jacobian_matrix_shape/jacobian_matrix_shape.cpp index f49f616..114a403 100644 --- a/examples/jacobian_matrix_shape/jacobian_matrix_shape.cpp +++ b/examples/jacobian_matrix_shape/jacobian_matrix_shape.cpp @@ -64,10 +64,10 @@ struct MyParams */ class MyMassMatrix { - MyParams p; // Parameters + const MyParams &p; // Parameters public: - explicit MyMassMatrix(MyParams ¶ms) : p(params) {} + explicit MyMassMatrix(const MyParams ¶ms) : p(params) {} /* * Defines the mass matrix `M` of the DAE system `M dx/dt = f`. @@ -93,7 +93,7 @@ class MyMassMatrix */ class MyRHS : public VectorFunctionJacobianShape { - const MyParams p; // Parameters + const MyParams &p; // Parameters // Derived parameters const double h = p.L / (double)(p.N - 1); // Cell size @@ -139,7 +139,7 @@ class MyRHS : public VectorFunctionJacobianShape } public: - explicit MyRHS(MyParams ¶ms) : p(params) {} + explicit MyRHS(const MyParams ¶ms) : p(params) {} /* * All equations combined. @@ -208,7 +208,7 @@ class MyJacobianShape : public JacobianMatrixShape } public: - MyJacobianShape(MyRHS rhs, const int_type N) : JacobianMatrixShape(rhs), N(N) + MyJacobianShape(MyRHS &rhs, const int_type N) : JacobianMatrixShape(rhs), N(N) { m_define_Jacobian_shape(); } From 26b141d081b0d5f4e2099e35cfc620c64fa4b503 Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Mon, 16 Dec 2024 15:32:48 +0000 Subject: [PATCH 12/34] Fix Windows compiler warning --- dae-cpp/jacobian-matrix.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dae-cpp/jacobian-matrix.hpp b/dae-cpp/jacobian-matrix.hpp index cfe3803..cfd4ae9 100644 --- a/dae-cpp/jacobian-matrix.hpp +++ b/dae-cpp/jacobian-matrix.hpp @@ -85,7 +85,7 @@ class JacobianMatrixShape }; // Reserve memory - J.reserve(m_Jn.size()); + 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) From f3e93c1c6d25b98c087f8ce8c803ec5b0ea2c6eb Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Mon, 16 Dec 2024 17:44:48 +0000 Subject: [PATCH 13/34] Add Jacobian matrix shape integration test --- tests/test_integration-jacobian_shape.cpp | 250 ++++++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 tests/test_integration-jacobian_shape.cpp diff --git a/tests/test_integration-jacobian_shape.cpp b/tests/test_integration-jacobian_shape.cpp new file mode 100644 index 0000000..8e85568 --- /dev/null +++ b/tests/test_integration-jacobian_shape.cpp @@ -0,0 +1,250 @@ +/* + * Integration test of the solver. + * Based on the `jacobian_matrix_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 VectorFunctionJacobianShape +{ + 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() + { + 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); + + // 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 From 00593c5b6036d500d9dad7f59988e76117005f14 Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Mon, 16 Dec 2024 20:36:34 +0000 Subject: [PATCH 14/34] Add inline because the functions are defined in a header --- dae-cpp/solver.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dae-cpp/solver.hpp b/dae-cpp/solver.hpp index 04defa9..15f8439 100644 --- a/dae-cpp/solver.hpp +++ b/dae-cpp/solver.hpp @@ -227,7 +227,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 +791,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 +812,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 +833,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 +854,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); } From 5f25e20a43427c40c6ac50cba3221ce8764f6756 Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Mon, 16 Dec 2024 21:32:39 +0000 Subject: [PATCH 15/34] Remove unused variable --- dae-cpp/jacobian-matrix.hpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/dae-cpp/jacobian-matrix.hpp b/dae-cpp/jacobian-matrix.hpp index cfd4ae9..e0c73ea 100644 --- a/dae-cpp/jacobian-matrix.hpp +++ b/dae-cpp/jacobian-matrix.hpp @@ -51,9 +51,6 @@ class JacobianMatrixShape // Array of non-zero elements std::vector> m_Jn; - // An estimation of the array (sparse Jacobian matrix) size - int_type m_N_elements{0}; - // The RHS for differentiation (a copy) RHS m_rhs; From b0a9e416b48d81c3f269252704d1a5faa1142448 Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Mon, 16 Dec 2024 21:40:04 +0000 Subject: [PATCH 16/34] Reserve memory for array of non-zero elements --- examples/jacobian_matrix_shape/jacobian_matrix_shape.cpp | 2 ++ tests/test_integration-jacobian_shape.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/examples/jacobian_matrix_shape/jacobian_matrix_shape.cpp b/examples/jacobian_matrix_shape/jacobian_matrix_shape.cpp index 114a403..3ea51ca 100644 --- a/examples/jacobian_matrix_shape/jacobian_matrix_shape.cpp +++ b/examples/jacobian_matrix_shape/jacobian_matrix_shape.cpp @@ -174,6 +174,8 @@ class MyJacobianShape : public JacobianMatrixShape */ 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) diff --git a/tests/test_integration-jacobian_shape.cpp b/tests/test_integration-jacobian_shape.cpp index 8e85568..1757b91 100644 --- a/tests/test_integration-jacobian_shape.cpp +++ b/tests/test_integration-jacobian_shape.cpp @@ -126,6 +126,8 @@ class MyJacobianShape : public JacobianMatrixShape */ 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) From 9be6d750efc573f9f891645b4aaa45b9753832b1 Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Tue, 17 Dec 2024 10:58:40 +0000 Subject: [PATCH 17/34] Update output --- dae-cpp/solver.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dae-cpp/solver.hpp b/dae-cpp/solver.hpp index 15f8439..d925cd5 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'); } /* From 55bdc497dcf3f0431dd1468f25eee5f99c8dd59f Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Tue, 17 Dec 2024 17:41:17 +0000 Subject: [PATCH 18/34] Added helper class that compares user-defined Jacobian with automatic Jacobian computed from the vector function --- dae-cpp/jacobian-matrix.hpp | 161 +++++++++++++++++++++++++++++++++++- 1 file changed, 159 insertions(+), 2 deletions(-) diff --git a/dae-cpp/jacobian-matrix.hpp b/dae-cpp/jacobian-matrix.hpp index e0c73ea..39c6234 100644 --- a/dae-cpp/jacobian-matrix.hpp +++ b/dae-cpp/jacobian-matrix.hpp @@ -14,6 +14,7 @@ #ifndef DAECPP_JACOBIAN_MATRIX_H #define DAECPP_JACOBIAN_MATRIX_H +#include // Formatted table printing #include // std::pair #include "sparse-matrix.hpp" @@ -43,7 +44,6 @@ class JacobianMatrix /* * Helper Jacobian shape class. * Computes Jacobian matrix from the given shape (a list of non-zero elements) provided by the user. - * This class is abstract and must be inherited. */ template class JacobianMatrixShape @@ -102,7 +102,7 @@ class JacobianMatrixShape /* * 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) + void add_element(const int_type ind_i, const std::vector &ind_j) { for (const int_type &j : ind_j) { @@ -182,6 +182,163 @@ class JacobianAutomatic } }; +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(int_type i, int_type j, float_type M_ref, 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 + */ + 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: + /* + * 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; // Analytic (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(x.size()); + auto Jd_user = J_user.dense(x.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 Jacobian:\n"; + m_print_table(J_diff); + } + + return N_diff; + } +}; + } // namespace daecpp_namespace_name #endif // DAECPP_JACOBIAN_MATRIX_H From ea54f56c821654442b3e514b90e60f19603fb446 Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Tue, 17 Dec 2024 18:48:04 +0000 Subject: [PATCH 19/34] Update JacobianCompare class --- dae-cpp/jacobian-matrix.hpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/dae-cpp/jacobian-matrix.hpp b/dae-cpp/jacobian-matrix.hpp index 39c6234..e3c3a24 100644 --- a/dae-cpp/jacobian-matrix.hpp +++ b/dae-cpp/jacobian-matrix.hpp @@ -196,7 +196,8 @@ struct MatrixDiff float_type M_compare; // Value to compare float_type abs_err; // Absolute error - MatrixDiff(int_type i, int_type j, float_type M_ref, float_type M_compare) : i(i), j(j), M_ref(M_ref), M_compare(M_compare) + 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; } @@ -221,7 +222,7 @@ class JacobianCompare 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 + * Prints a horizontal line with the given delimiter for the table header/footer */ void m_table_line(const char delimiter) const { @@ -269,6 +270,8 @@ class JacobianCompare 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 @@ -293,7 +296,7 @@ class JacobianCompare JacobianAutomatic jac_auto = JacobianAutomatic(m_rhs); - sparse_matrix J_auto; // Analytic (reference) Jacobian + sparse_matrix J_auto; // Automatic (reference) Jacobian sparse_matrix J_user; // User-defined Jacobian const int_type size = static_cast(x.size()); // System size @@ -331,7 +334,7 @@ class JacobianCompare } else { - std::cout << "-- Found " << N_diff << " difference(s) compared to the automatic Jacobian:\n"; + std::cout << "-- Found " << N_diff << " difference(s) compared to the automatic (reference) Jacobian:\n"; m_print_table(J_diff); } From 0fa77eb6aa9ed8132d41392a7188606046dc4815 Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Tue, 17 Dec 2024 19:20:20 +0000 Subject: [PATCH 20/34] Add JacobianCompare to jacobian_matrix_shape example --- examples/jacobian_matrix_shape/jacobian_matrix_shape.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/examples/jacobian_matrix_shape/jacobian_matrix_shape.cpp b/examples/jacobian_matrix_shape/jacobian_matrix_shape.cpp index 3ea51ca..e9bbce1 100644 --- a/examples/jacobian_matrix_shape/jacobian_matrix_shape.cpp +++ b/examples/jacobian_matrix_shape/jacobian_matrix_shape.cpp @@ -283,6 +283,12 @@ int main() 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, From 799d9b5efd82918a1d1de3eaee11b5b4390dd5cb Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Tue, 17 Dec 2024 19:25:59 +0000 Subject: [PATCH 21/34] Add JacobianCompare to the integration tests --- tests/test_integration-jacobian_shape.cpp | 2 ++ tests/test_integration-perovskite_model.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/tests/test_integration-jacobian_shape.cpp b/tests/test_integration-jacobian_shape.cpp index 1757b91..916ee96 100644 --- a/tests/test_integration-jacobian_shape.cpp +++ b/tests/test_integration-jacobian_shape.cpp @@ -223,6 +223,8 @@ TEST(Integration, JacobianShape) 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, 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, From 4bdb936e1a25409d7509df54e75676ef8e1bd045 Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Wed, 18 Dec 2024 14:53:30 +0000 Subject: [PATCH 22/34] Rename VectorFunctionJacobianShape to VectorFunctionElements --- dae-cpp/vector-function.hpp | 6 ++++-- examples/jacobian_matrix_shape/jacobian_matrix_shape.cpp | 8 ++++---- tests/test_integration-jacobian_shape.cpp | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/dae-cpp/vector-function.hpp b/dae-cpp/vector-function.hpp index 86ef47c..11c0819 100644 --- a/dae-cpp/vector-function.hpp +++ b/dae-cpp/vector-function.hpp @@ -39,10 +39,11 @@ class 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 VectorFunctionJacobianShape +class VectorFunctionElements { public: /* @@ -62,11 +63,12 @@ class VectorFunctionJacobianShape /* * 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 ~VectorFunctionJacobianShape() {} + virtual ~VectorFunctionElements() {} }; } // namespace daecpp_namespace_name diff --git a/examples/jacobian_matrix_shape/jacobian_matrix_shape.cpp b/examples/jacobian_matrix_shape/jacobian_matrix_shape.cpp index e9bbce1..7aeeda5 100644 --- a/examples/jacobian_matrix_shape/jacobian_matrix_shape.cpp +++ b/examples/jacobian_matrix_shape/jacobian_matrix_shape.cpp @@ -28,7 +28,7 @@ * 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 `VectorFunctionJacobianShape` class, + * 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. * @@ -87,11 +87,11 @@ class MyMassMatrix /* * The vector-function (RHS) of the problem. - * Based on `VectorFunctionJacobianShape` class. - * NOTE that we must derive the vector function class from `VectorFunctionJacobianShape` + * 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 VectorFunctionJacobianShape +class MyRHS : public VectorFunctionElements { const MyParams &p; // Parameters diff --git a/tests/test_integration-jacobian_shape.cpp b/tests/test_integration-jacobian_shape.cpp index 916ee96..47e8bb1 100644 --- a/tests/test_integration-jacobian_shape.cpp +++ b/tests/test_integration-jacobian_shape.cpp @@ -49,7 +49,7 @@ class MyMassMatrix } }; -class MyRHS : public VectorFunctionJacobianShape +class MyRHS : public VectorFunctionElements { const MyParams &p; // Parameters From 72d138cdca1de8ee8ff27ef81ea6bf30e38243b6 Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Wed, 18 Dec 2024 15:00:03 +0000 Subject: [PATCH 23/34] Rename jacobian_shape example --- CMakeLists.txt | 2 +- .../jacobian_shape.cpp} | 0 tests/test_integration-jacobian_shape.cpp | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename examples/{jacobian_matrix_shape/jacobian_matrix_shape.cpp => jacobian_shape/jacobian_shape.cpp} (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 609acc4..84396ee 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" "jacobian_matrix_shape") +set(EXAMPLE_LIST "quick_start" "simple_dae" "perovskite_model" "flame_propagation" "jacobian_shape") include_directories(${PROJECT_SOURCE_DIR}) diff --git a/examples/jacobian_matrix_shape/jacobian_matrix_shape.cpp b/examples/jacobian_shape/jacobian_shape.cpp similarity index 100% rename from examples/jacobian_matrix_shape/jacobian_matrix_shape.cpp rename to examples/jacobian_shape/jacobian_shape.cpp diff --git a/tests/test_integration-jacobian_shape.cpp b/tests/test_integration-jacobian_shape.cpp index 47e8bb1..9b20d36 100644 --- a/tests/test_integration-jacobian_shape.cpp +++ b/tests/test_integration-jacobian_shape.cpp @@ -1,6 +1,6 @@ /* * Integration test of the solver. - * Based on the `jacobian_matrix_shape` example. + * Based on the `jacobian_shape` example. * * This file is part of dae-cpp. * From d74f46dee6194e003f3fb8fb945c62d21f3c2bfa Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Wed, 18 Dec 2024 16:43:40 +0000 Subject: [PATCH 24/34] Add jacobian_compare example --- CMakeLists.txt | 2 +- .../jacobian_compare/jacobian_compare.cpp | 169 ++++++++++++++++++ 2 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 examples/jacobian_compare/jacobian_compare.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 84396ee..256ed50 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" "jacobian_shape") +set(EXAMPLE_LIST "quick_start" "simple_dae" "perovskite_model" "flame_propagation" "jacobian_shape" "jacobian_compare") include_directories(${PROJECT_SOURCE_DIR}) diff --git a/examples/jacobian_compare/jacobian_compare.cpp b/examples/jacobian_compare/jacobian_compare.cpp new file mode 100644 index 0000000..f9f9074 --- /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 + * `JacobianComapre` 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]; + + 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 + // Forgot 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]; + + 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; +} From a842a175b01a0bc0222eb6113899a44803176590 Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Wed, 18 Dec 2024 18:07:27 +0000 Subject: [PATCH 25/34] Add jacobian-compare test --- CMakeLists.txt | 2 +- tests/test_jacobian-compare.cpp | 172 ++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 tests/test_jacobian-compare.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 256ed50..b1ecf0d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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/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 From 06593f6b76816bd7f4077e927be95bae350d72ce Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Wed, 18 Dec 2024 18:34:48 +0000 Subject: [PATCH 26/34] Add VectorFunctionElements class test --- tests/test_vector-function.cpp | 40 +++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) 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 From e17a7ec5d5f0b259fe15a4bcbce2bb459acf32e8 Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Wed, 18 Dec 2024 19:41:38 +0000 Subject: [PATCH 27/34] Fix warnings --- dae-cpp/jacobian-matrix.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dae-cpp/jacobian-matrix.hpp b/dae-cpp/jacobian-matrix.hpp index e3c3a24..3958d8a 100644 --- a/dae-cpp/jacobian-matrix.hpp +++ b/dae-cpp/jacobian-matrix.hpp @@ -306,8 +306,8 @@ class JacobianCompare m_jac_user(J_user, x, t); // Convert to the dense form - auto Jd_auto = J_auto.dense(x.size()); - auto Jd_user = J_user.dense(x.size()); + auto Jd_auto = J_auto.dense(size); + auto Jd_user = J_user.dense(size); // Vector of differences between the matrices std::vector J_diff; From b3f1489850ceeea2fa741ae3555adf09663148d8 Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Wed, 18 Dec 2024 21:11:45 +0000 Subject: [PATCH 28/34] Add jacobian-shape tests --- tests/test_jacobian-shape.cpp | 164 ++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 tests/test_jacobian-shape.cpp diff --git a/tests/test_jacobian-shape.cpp b/tests/test_jacobian-shape.cpp new file mode 100644 index 0000000..5751f5f --- /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[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}); + } + }; + + 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}); + + 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, 1); // 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 + + 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 From cd0d2b16859c6c65733459bb00369a1f22d22488 Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Thu, 19 Dec 2024 10:02:03 +0000 Subject: [PATCH 29/34] Update jacobian-shape test --- tests/test_jacobian-shape.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/test_jacobian-shape.cpp b/tests/test_jacobian-shape.cpp index 5751f5f..2453bc8 100644 --- a/tests/test_jacobian-shape.cpp +++ b/tests/test_jacobian-shape.cpp @@ -29,7 +29,7 @@ struct TestRHS : VectorFunctionElements } else if (i == 1) { - return -x[1]; + return x[0] - x[1]; } else { @@ -45,7 +45,7 @@ TEST(JacobianMatrixShape, Definition) explicit TestJacobian(TestRHS rhs) : JacobianMatrixShape(rhs) { add_element(0, {0, 1}); - add_element(1, {1}); + add_element(1, {1}); // Missing element (1, 0) on purpose } }; @@ -78,7 +78,7 @@ TEST(JacobianMatrixShape, DefinitionInline) jac.reserve(2); jac.add_element(0, {0, 1}); - jac.add_element(1, {1}); + jac.add_element(1, {1}); // Missing element (1, 0) on purpose sparse_matrix J; state_vector x{4.0, 6.0}; @@ -135,7 +135,7 @@ TEST(JacobianMatrixShape, Clear) constexpr double t{10.0}; jac.add_element(0, {0, 1}); // Add as a vector - jac.add_element(1, 1); // Add as a single element + jac.add_element(1, 0); // Add as a single element jac.clear(); jac(J, x, t); @@ -145,7 +145,7 @@ TEST(JacobianMatrixShape, Clear) 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 + jac.add_element(1, 1); // Add as a single element (a different one) jac(J, x, t); From 701969cd9857f129855a9590d7a2210e2efd28c2 Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Thu, 19 Dec 2024 11:13:12 +0000 Subject: [PATCH 30/34] Update README --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ab173fe..2b1ca86 100644 --- a/README.md +++ b/README.md @@ -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) From cac01aa4712c398879783bc288dbd30d55ae0291 Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Fri, 20 Dec 2024 13:54:49 +0000 Subject: [PATCH 31/34] Fix typos in comments, remove unused variables --- examples/jacobian_compare/jacobian_compare.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/jacobian_compare/jacobian_compare.cpp b/examples/jacobian_compare/jacobian_compare.cpp index f9f9074..cafc319 100644 --- a/examples/jacobian_compare/jacobian_compare.cpp +++ b/examples/jacobian_compare/jacobian_compare.cpp @@ -5,7 +5,7 @@ * 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 - * `JacobianComapre` can provide. + * `JacobianCompare` can provide. * * Note that `JacobianCompare` can work with Jacobians derived from the given Jacobian shapes as well. * @@ -24,7 +24,7 @@ using namespace daecpp; /* - * The vector-function (RHS) of the problem. + * The vector function (RHS) of the problem. */ struct MyRHS { @@ -68,7 +68,7 @@ struct MyJacobianBad float_type u = x[0]; float_type v = x[1]; float_type w = x[2]; - float_type z = x[3]; + // float_type z = x[3]; // Unused J.reserve(6); // Pre-allocates memory for 6 non-zero elements @@ -78,7 +78,7 @@ struct MyJacobianBad 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) + // Let's "forget" to differentiate f[3] w.r.t. `z` - missing element (3, 3) } }; @@ -98,7 +98,7 @@ struct MyJacobianGood float_type u = x[0]; float_type v = x[1]; float_type w = x[2]; - float_type z = x[3]; + // float_type z = x[3]; // Unused J.reserve(7); // Pre-allocates memory for 7 non-zero elements From b33cd72d658bbf1164d83f78e2daf09d755c6f0f Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Fri, 20 Dec 2024 14:39:18 +0000 Subject: [PATCH 32/34] Time derivative optimisations --- dae-cpp/solver.hpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/dae-cpp/solver.hpp b/dae-cpp/solver.hpp index d925cd5..c75edcf 100644 --- a/dae-cpp/solver.hpp +++ b/dae-cpp/solver.hpp @@ -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."); From d22775d993df99d7eb8dea747eaa3857e5c4613c Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Fri, 20 Dec 2024 18:25:35 +0000 Subject: [PATCH 33/34] Fix warnings in tests --- tests/test_jacobian-shape.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_jacobian-shape.cpp b/tests/test_jacobian-shape.cpp index 2453bc8..0966190 100644 --- a/tests/test_jacobian-shape.cpp +++ b/tests/test_jacobian-shape.cpp @@ -45,7 +45,7 @@ TEST(JacobianMatrixShape, Definition) explicit TestJacobian(TestRHS rhs) : JacobianMatrixShape(rhs) { add_element(0, {0, 1}); - add_element(1, {1}); // Missing element (1, 0) on purpose + add_element(1, 1); // Missing element (1, 0) on purpose } }; @@ -78,7 +78,7 @@ TEST(JacobianMatrixShape, DefinitionInline) jac.reserve(2); jac.add_element(0, {0, 1}); - jac.add_element(1, {1}); // Missing element (1, 0) on purpose + jac.add_element(1, 1); // Missing element (1, 0) on purpose sparse_matrix J; state_vector x{4.0, 6.0}; From 0bc205716e2ce90d541989791411fea9b77543af Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Fri, 20 Dec 2024 23:33:39 +0000 Subject: [PATCH 34/34] Remove inline from the class method definitions --- dae-cpp/jacobian-matrix.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dae-cpp/jacobian-matrix.hpp b/dae-cpp/jacobian-matrix.hpp index 3958d8a..81e813a 100644 --- a/dae-cpp/jacobian-matrix.hpp +++ b/dae-cpp/jacobian-matrix.hpp @@ -113,7 +113,7 @@ class JacobianMatrixShape /* * Clear arrays of non-zero elements */ - inline void clear() + void clear() { m_Jn.clear(); } @@ -121,7 +121,7 @@ class JacobianMatrixShape /* * Reserve memory for the array of non-zero elements */ - inline void reserve(const int_type N_elements) + void reserve(const int_type N_elements) { m_Jn.reserve(N_elements); }