From 6d08d8e9b24e8d467f97ba0e78ac77af18543f37 Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Mon, 30 Dec 2024 11:21:31 +0000 Subject: [PATCH 1/6] Add state_value as an alias to dual_type --- dae-cpp/assert-custom.hpp | 10 +++++---- dae-cpp/typedefs.hpp | 5 ++++- dae-cpp/vector-function.hpp | 2 +- .../flame_propagation/flame_propagation.cpp | 2 +- .../jacobian_compare/jacobian_compare.cpp | 10 ++++----- examples/jacobian_shape/jacobian_shape.cpp | 14 ++++++------ tests/test_integration-flame_propagation.cpp | 2 +- tests/test_integration-jacobian_shape.cpp | 14 ++++++------ tests/test_jacobian-compare.cpp | 22 +++++++++---------- tests/test_jacobian-shape.cpp | 2 +- tests/test_vector-function.cpp | 2 +- 11 files changed, 45 insertions(+), 40 deletions(-) diff --git a/dae-cpp/assert-custom.hpp b/dae-cpp/assert-custom.hpp index 0c5fce8..cc427bb 100644 --- a/dae-cpp/assert-custom.hpp +++ b/dae-cpp/assert-custom.hpp @@ -25,9 +25,11 @@ namespace daecpp_namespace_name * Example: * ERROR("v = " << v); */ -#define ERROR(msg) \ - std::cerr << "\nERROR: " << msg << "\nThis error is fatal." << std::endl; \ - exit(-1); +#define ERROR(msg) \ + { \ + std::cerr << "\nERROR: " << msg << "\nThis error is fatal." << std::endl; \ + exit(-1); \ + } /* * WARNING(warning message) @@ -62,7 +64,7 @@ namespace daecpp_namespace_name /* * PRINT(condition, message) * - * Prints a message if condition (e.g., verbosity level) is true. + * Prints a message if condition (e.g., verbosity level) is true and flushes the output. * * Example: * NOTE(verbosity > 1, "v = " << v); diff --git a/dae-cpp/typedefs.hpp b/dae-cpp/typedefs.hpp index 69e9d16..5ad2913 100644 --- a/dae-cpp/typedefs.hpp +++ b/dae-cpp/typedefs.hpp @@ -62,9 +62,12 @@ typedef float float_type; typedef double float_type; #endif -// Floating point dual number for automatic differentiation +// Floating point dual number for automatic differentiation (DEPRECATED: use state_value) typedef autodiff::real1st dual_type; +// Floating point dual number for automatic differentiation +typedef autodiff::real1st state_value; + namespace core { diff --git a/dae-cpp/vector-function.hpp b/dae-cpp/vector-function.hpp index 11c0819..1728d87 100644 --- a/dae-cpp/vector-function.hpp +++ b/dae-cpp/vector-function.hpp @@ -66,7 +66,7 @@ class VectorFunctionElements * 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 state_value equations(const state_type &x, const double t, const int_type i) const = 0; virtual ~VectorFunctionElements() {} }; diff --git a/examples/flame_propagation/flame_propagation.cpp b/examples/flame_propagation/flame_propagation.cpp index 61618cc..cdc56be 100644 --- a/examples/flame_propagation/flame_propagation.cpp +++ b/examples/flame_propagation/flame_propagation.cpp @@ -47,7 +47,7 @@ struct MyRHS */ void operator()(state_type &f, const state_type &x, const double t) { - dual_type y = x[0]; // Note `dual_type` (not `double`) because the solver will automatically differentiate `f` w.r.t. `y` + state_value y = x[0]; // Note `state_value` (not `double`) because the solver will automatically differentiate `f` w.r.t. `y` f[0] = y * y - y * y * y; } }; diff --git a/examples/jacobian_compare/jacobian_compare.cpp b/examples/jacobian_compare/jacobian_compare.cpp index cafc319..4750dca 100644 --- a/examples/jacobian_compare/jacobian_compare.cpp +++ b/examples/jacobian_compare/jacobian_compare.cpp @@ -35,11 +35,11 @@ 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]; + // `state_value` for automatic differentiation + state_value u = x[0]; + state_value v = x[1]; + state_value w = x[2]; + state_value z = x[3]; f[0] = z * 0.5; f[1] = u * u + v * v + w * w; diff --git a/examples/jacobian_shape/jacobian_shape.cpp b/examples/jacobian_shape/jacobian_shape.cpp index 7aeeda5..ede7577 100644 --- a/examples/jacobian_shape/jacobian_shape.cpp +++ b/examples/jacobian_shape/jacobian_shape.cpp @@ -104,10 +104,10 @@ class MyRHS : public VectorFunctionElements * 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 + state_value 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 state_value *P = x.data(); + const state_value *Phi = x.data() + p.N; const int_type i = i_global; @@ -123,10 +123,10 @@ class MyRHS : public VectorFunctionElements * 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 + state_value 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 state_value *P = x.data(); + const state_value *Phi = x.data() + p.N; const int_type i = i_global - p.N; @@ -145,7 +145,7 @@ class MyRHS : public VectorFunctionElements * 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 + state_value equations(const state_type &x, const double t, const int_type i) const { if (i < p.N) return eq1(x, t, i); diff --git a/tests/test_integration-flame_propagation.cpp b/tests/test_integration-flame_propagation.cpp index 5aa7125..1b1f564 100644 --- a/tests/test_integration-flame_propagation.cpp +++ b/tests/test_integration-flame_propagation.cpp @@ -23,7 +23,7 @@ struct MyRHS { void operator()(state_type &f, const state_type &x, const double t) { - dual_type y = x[0]; + state_value y = x[0]; f[0] = y * y - y * y * y; } }; diff --git a/tests/test_integration-jacobian_shape.cpp b/tests/test_integration-jacobian_shape.cpp index 9b20d36..b9b85f2 100644 --- a/tests/test_integration-jacobian_shape.cpp +++ b/tests/test_integration-jacobian_shape.cpp @@ -62,10 +62,10 @@ class MyRHS : public VectorFunctionElements * 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 + state_value 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 state_value *P = x.data(); + const state_value *Phi = x.data() + p.N; const int_type i = i_global; @@ -81,10 +81,10 @@ class MyRHS : public VectorFunctionElements * 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 + state_value 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 state_value *P = x.data(); + const state_value *Phi = x.data() + p.N; const int_type i = i_global - p.N; @@ -103,7 +103,7 @@ class MyRHS : public VectorFunctionElements * 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 + state_value equations(const state_type &x, const double t, const int_type i) const { if (i < p.N) return eq1(x, t, i); diff --git a/tests/test_jacobian-compare.cpp b/tests/test_jacobian-compare.cpp index fba2402..94fb8e8 100644 --- a/tests/test_jacobian-compare.cpp +++ b/tests/test_jacobian-compare.cpp @@ -24,11 +24,11 @@ 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]; + // `state_value` for automatic differentiation + state_value u = x[0]; + state_value v = x[1]; + state_value w = x[2]; + state_value z = x[3]; f[0] = z * 0.5; f[1] = u * u + v * v + w * w; @@ -82,13 +82,13 @@ struct MyJacobianGood class MyRHSShape : public VectorFunctionElements { public: - dual_type equations(const state_type &x, const double t, const int_type i) const + state_value 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]; + // `state_value` for automatic differentiation + state_value u = x[0]; + state_value v = x[1]; + state_value w = x[2]; + state_value z = x[3]; if (i == 0) return z * 0.5; diff --git a/tests/test_jacobian-shape.cpp b/tests/test_jacobian-shape.cpp index 0966190..be58834 100644 --- a/tests/test_jacobian-shape.cpp +++ b/tests/test_jacobian-shape.cpp @@ -21,7 +21,7 @@ using namespace daecpp; struct TestRHS : VectorFunctionElements { - dual_type equations(const state_type &x, const double t, const int_type i) const + state_value equations(const state_type &x, const double t, const int_type i) const { if (i == 0) { diff --git a/tests/test_vector-function.cpp b/tests/test_vector-function.cpp index 4765ac7..2803dd4 100644 --- a/tests/test_vector-function.cpp +++ b/tests/test_vector-function.cpp @@ -55,7 +55,7 @@ TEST(VectorFunctionElements, Definition) { struct TestVectorFunction : VectorFunctionElements { - dual_type equations(const state_type &x, const double t, const int_type i) const + state_value equations(const state_type &x, const double t, const int_type i) const { ASSERT(x.size() == 2, "Incorrect size of x: " << x.size()); From 1efee520c768bf52e499b0c983688f6c40db645a Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Wed, 8 Jan 2025 19:45:46 +0000 Subject: [PATCH 2/6] JacobianCompare to sort values by row instead of column --- dae-cpp/jacobian-matrix.hpp | 4 ++-- dae-cpp/timer.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dae-cpp/jacobian-matrix.hpp b/dae-cpp/jacobian-matrix.hpp index 81e813a..aac4078 100644 --- a/dae-cpp/jacobian-matrix.hpp +++ b/dae-cpp/jacobian-matrix.hpp @@ -313,9 +313,9 @@ class JacobianCompare 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) { - for (int_type i = 0; i < size; ++i) + for (int_type j = 0; j < size; ++j) { if (std::abs(Jd_auto(i, j) - Jd_user(i, j)) > DAECPP_SPARSE_MATRIX_ELEMENT_TOLERANCE) { diff --git a/dae-cpp/timer.hpp b/dae-cpp/timer.hpp index f14d6f0..13c2a0e 100644 --- a/dae-cpp/timer.hpp +++ b/dae-cpp/timer.hpp @@ -3,7 +3,7 @@ * * Usage example: * - * double time = 0.0; + * double time{}; * { * Timer timer(&time); * << TASK >> From 1c340922a5d7220ac80a3c207fc8b87ac8409d50 Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Sat, 11 Jan 2025 16:46:11 +0000 Subject: [PATCH 3/6] Relax const modifier in VectorFunctionElements --- dae-cpp/vector-function.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dae-cpp/vector-function.hpp b/dae-cpp/vector-function.hpp index 1728d87..4e75a20 100644 --- a/dae-cpp/vector-function.hpp +++ b/dae-cpp/vector-function.hpp @@ -50,7 +50,7 @@ class VectorFunctionElements * 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 + void operator()(state_type &f, const state_type &x, const double t) { const int_type size = static_cast(x.size()); // System size @@ -66,7 +66,7 @@ class VectorFunctionElements * I.e., it returns the i-th element of the vector function. * This function is pure virtual and must be overriden. */ - virtual state_value equations(const state_type &x, const double t, const int_type i) const = 0; + virtual state_value equations(const state_type &x, const double t, const int_type i) = 0; virtual ~VectorFunctionElements() {} }; From b0b961aa37308aeead458adcf4922d1c3ef3fb93 Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Sat, 11 Jan 2025 16:50:27 +0000 Subject: [PATCH 4/6] Revert const modifier in VectorFunctionElements --- dae-cpp/vector-function.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dae-cpp/vector-function.hpp b/dae-cpp/vector-function.hpp index 4e75a20..1728d87 100644 --- a/dae-cpp/vector-function.hpp +++ b/dae-cpp/vector-function.hpp @@ -50,7 +50,7 @@ class VectorFunctionElements * 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) + void operator()(state_type &f, const state_type &x, const double t) const { const int_type size = static_cast(x.size()); // System size @@ -66,7 +66,7 @@ class VectorFunctionElements * I.e., it returns the i-th element of the vector function. * This function is pure virtual and must be overriden. */ - virtual state_value equations(const state_type &x, const double t, const int_type i) = 0; + virtual state_value equations(const state_type &x, const double t, const int_type i) const = 0; virtual ~VectorFunctionElements() {} }; From 496d771ec8391b712e58957b61fc7f5c65c1bb4d Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Sat, 11 Jan 2025 17:05:39 +0000 Subject: [PATCH 5/6] Fixed the solver could not recover from divergence and redo time step correctly, added recover_from_linsolver_failure solver option --- dae-cpp/solver-options.hpp | 4 ++++ dae-cpp/solver.hpp | 19 +++++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/dae-cpp/solver-options.hpp b/dae-cpp/solver-options.hpp index 8d386f6..ea74721 100644 --- a/dae-cpp/solver-options.hpp +++ b/dae-cpp/solver-options.hpp @@ -88,6 +88,10 @@ struct SolverOptions // Default value is 3. unsigned int max_Newton_failed_attempts{3}; + // If `true`, the solver will try to recover from the linear solver failure by rolling back and decreasing the time step. + // Default value is `true`. + bool recover_from_linsolver_failure{true}; + // Time step amplification threshold delta (0 by default). Can be negative or positive integer number. // The solver increases the time step if the number of successful Newton iterations per time step is less than the threshold. // Example: If the solver increases the time step too often (too fast), decrease the time step amplification threshold by diff --git a/dae-cpp/solver.hpp b/dae-cpp/solver.hpp index c75edcf..28eb519 100644 --- a/dae-cpp/solver.hpp +++ b/dae-cpp/solver.hpp @@ -536,14 +536,19 @@ inline exit_code::status solve(Mass mass, RHS rhs, Jacobian jac, Manager mgr, co linsolver.compute(Jb); + c.n_fact_calls++; + if (linsolver.info() != Eigen::Success) { PRINT(opt.verbosity >= 2, " <- decomposition failed"); + if(opt.recover_from_linsolver_failure) + { + is_diverged = true; + break; + } error_msg = exit_code::linsolver_failed_decomposition; goto result; // Abort all loops and go straight to the results } - - c.n_fact_calls++; } // Solve linear system Jb dx = b @@ -552,15 +557,20 @@ inline exit_code::status solve(Mass mass, RHS rhs, Jacobian jac, Manager mgr, co dx = linsolver.solve(b); + c.n_lin_calls++; + if (linsolver.info() != Eigen::Success) { PRINT(opt.verbosity >= 2, " <- linear solver failed"); + if(opt.recover_from_linsolver_failure) + { + is_diverged = true; + break; + } error_msg = exit_code::linsolver_failed_solving; goto result; // Abort all loops and go straight to the results } - c.n_lin_calls++; - if (is_fact_enabled) { print_char(opt.verbosity >= 2, '#'); @@ -633,6 +643,7 @@ inline exit_code::status solve(Mass mass, RHS rhs, Jacobian jac, Manager mgr, co state.t = state.t_prev; n_steps--; dt /= opt.dt_decrease_factor; + xk = state.x[0]; if (dt < opt.dt_min) { PRINT(opt.verbosity >= 1, "The time step was reduced to `t_min` but the scheme failed to converge."); From 005fc91582a8a9e4275f32cdd7f23e8cfb3adac5 Mon Sep 17 00:00:00 2001 From: Ivan Korotkin Date: Sat, 11 Jan 2025 18:05:07 +0000 Subject: [PATCH 6/6] Update README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2b1ca86..bf3c010 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.1.0-blue) +![version](https://img.shields.io/badge/version-2.1.1-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).** @@ -20,6 +20,7 @@ to be solved in the interval $`t \in [0, t_\mathrm{end}]`$ with the initial cond - Header only, no pre-compilation required. - 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. +- The user can provide analytically derived Jacobian or the Jacobian matrix shape (positions of non-zero elements) to significantly speed up the computation for big systems. - 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 (contain empty rows).