From eb64a0681627b561478d37886d50d5a9e143a28e Mon Sep 17 00:00:00 2001 From: Jacob Hass Date: Sat, 20 Jun 2026 15:26:34 -0700 Subject: [PATCH 01/22] Initial symplectic integrator --- .../quadrature/symplectic_integration.hpp | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 include/boost/math/quadrature/symplectic_integration.hpp diff --git a/include/boost/math/quadrature/symplectic_integration.hpp b/include/boost/math/quadrature/symplectic_integration.hpp new file mode 100644 index 0000000000..51ba121b6b --- /dev/null +++ b/include/boost/math/quadrature/symplectic_integration.hpp @@ -0,0 +1,72 @@ +/* Based on "Construction of higher order symplectic integrators" by Haruo Yoshida*/ + +#include +#include +#include + +typedef std::valarray derivative(std::valarray); + +template +std::pair, std::valarray > second_order_yoshida(const std::valarray p0, + const std::valarray q0, + RealType dt, + derivative dHdp, + derivative dHdq) +{ + std::valarray p = p0; + std::valarray q = q0; + + q = q + dt / 2 * dHdp(p); + p = p - dt * dHdq(q); + q = q + dt / 2 * dHdp(p); + + return std::make_pair(p, q); +} + +template +std::pair, std::valarray > fourth_order_yoshida(const std::valarray p0, + const std::valarray q0, + RealType dt, + derivative dHdp, + derivative dHdq) +{ + std::valarray p = p0; + std::valarray q = q0; + + RealType x0 = -(std::pow(2, 1/3) / (2 - std::pow(2, 1/3))); + RealType x1 = 1 / (2 - std::pow(2, 1/3)); + + std::vector weights = { x1, x0, x1 }; + + for (unsigned i=0; i < weights.size(); i++) + { + std::tie(p, q) = second_order_yoshida(p, q, weights[i] * dt, dHdp, dHdq); + } + + return std::make_pair(p, q); +} + +template +std::pair, std::valarray > sixth_order_yoshida(const std::valarray p0, + const std::valarray q0, + RealType dt, + derivative dHdp, + derivative dHdq) +{ + std::valarray p = p0; + std::valarray q = q0; + + // Choosing "System A" solution + RealType w3 = 0.784513610477560; + RealType w2 = 0.235573213359357; + RealType w1 = -1.17767998417887; + RealType w0 = 1 - 2 * (w1 + w2 + w3); + std::vector weights = { w3, w2, w1, w0, w1, w2, w3}; + + for (unsigned i=0; i < weights.size(); i++) + { + std::tie(p, q) = second_order_yoshida(p, q, weights[i] * dt, dHdp, dHdq); + } + + return std::make_pair(p, q); +} \ No newline at end of file From 62c80b7078c7e5a4595394e09f5d72b51b78cce2 Mon Sep 17 00:00:00 2001 From: Jacob Hass Date: Sun, 21 Jun 2026 12:27:16 -0700 Subject: [PATCH 02/22] Generalized to floats --- .../quadrature/symplectic_integration.hpp | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/include/boost/math/quadrature/symplectic_integration.hpp b/include/boost/math/quadrature/symplectic_integration.hpp index 51ba121b6b..5bc50515cf 100644 --- a/include/boost/math/quadrature/symplectic_integration.hpp +++ b/include/boost/math/quadrature/symplectic_integration.hpp @@ -6,15 +6,15 @@ typedef std::valarray derivative(std::valarray); -template -std::pair, std::valarray > second_order_yoshida(const std::valarray p0, - const std::valarray q0, - RealType dt, - derivative dHdp, - derivative dHdq) +template +std::pair second_order_yoshida(const ReturnType p0, + const ReturnType q0, + RealType dt, + Func dHdp, + Func dHdq) { - std::valarray p = p0; - std::valarray q = q0; + ReturnType p = p0; + ReturnType q = q0; q = q + dt / 2 * dHdp(p); p = p - dt * dHdq(q); @@ -23,15 +23,15 @@ std::pair, std::valarray > second_order_yoshid return std::make_pair(p, q); } -template -std::pair, std::valarray > fourth_order_yoshida(const std::valarray p0, - const std::valarray q0, - RealType dt, - derivative dHdp, - derivative dHdq) +template +std::pair fourth_order_yoshida(const ReturnType p0, + const ReturnType q0, + const RealType dt, + Func dHdp, + Func dHdq) { - std::valarray p = p0; - std::valarray q = q0; + ReturnType p = p0; + ReturnType q = q0; RealType x0 = -(std::pow(2, 1/3) / (2 - std::pow(2, 1/3))); RealType x1 = 1 / (2 - std::pow(2, 1/3)); @@ -46,15 +46,15 @@ std::pair, std::valarray > fourth_order_yoshid return std::make_pair(p, q); } -template -std::pair, std::valarray > sixth_order_yoshida(const std::valarray p0, - const std::valarray q0, - RealType dt, - derivative dHdp, - derivative dHdq) +template +std::pair sixth_order_yoshida(const ReturnType p0, + const ReturnType q0, + RealType dt, + Func dHdp, + Func dHdq) { - std::valarray p = p0; - std::valarray q = q0; + ReturnType p = p0; + ReturnType q = q0; // Choosing "System A" solution RealType w3 = 0.784513610477560; From 45ebaff28077aceea69c4f48c5fd75e33a420d37 Mon Sep 17 00:00:00 2001 From: Jacob Hass Date: Sun, 21 Jun 2026 14:17:54 -0700 Subject: [PATCH 03/22] Added driver function --- include/boost/math/quadrature/symplectic.hpp | 133 ++++++++++++++++++ .../quadrature/symplectic_integration.hpp | 72 ---------- 2 files changed, 133 insertions(+), 72 deletions(-) create mode 100644 include/boost/math/quadrature/symplectic.hpp delete mode 100644 include/boost/math/quadrature/symplectic_integration.hpp diff --git a/include/boost/math/quadrature/symplectic.hpp b/include/boost/math/quadrature/symplectic.hpp new file mode 100644 index 0000000000..300c757713 --- /dev/null +++ b/include/boost/math/quadrature/symplectic.hpp @@ -0,0 +1,133 @@ +/* Based on "Construction of higher order symplectic integrators" by Haruo Yoshida*/ + +#ifndef BOOST_MATH_QUADRATURE_SYMPLECTIC_HPP +#define BOOST_MATH_QUADRATURE_SYMPLECTIC_HPP + +#include +#include +#include +#include +#include +#include + +namespace boost{ namespace math {namespace quadrature { + +template +std::pair second_order_yoshida(const ReturnType p0, + const ReturnType q0, + RealType dt, + Func dHdp, + Func dHdq) +{ + ReturnType p = p0; + ReturnType q = q0; + + q = q + dt / 2 * dHdp(p); + p = p - dt * dHdq(q); + q = q + dt / 2 * dHdp(p); + + return std::make_pair(p, q); +} + +template +std::pair fourth_order_yoshida(const ReturnType p0, + const ReturnType q0, + const RealType dt, + Func dHdp, + Func dHdq) +{ + ReturnType p = p0; + ReturnType q = q0; + + RealType x0 = -(std::pow(2, 1/3) / (2 - std::pow(2, 1/3))); + RealType x1 = 1 / (2 - std::pow(2, 1/3)); + + std::vector weights = { x1, x0, x1 }; + + for (unsigned i=0; i < weights.size(); i++) + { + std::tie(p, q) = second_order_yoshida(p, q, weights[i] * dt, dHdp, dHdq); + } + + return std::make_pair(p, q); +} + +template +std::pair sixth_order_yoshida(const ReturnType p0, + const ReturnType q0, + RealType dt, + Func dHdp, + Func dHdq) +{ + ReturnType p = p0; + ReturnType q = q0; + + // Choosing "System A" solution + RealType w3 = 0.784513610477560; + RealType w2 = 0.235573213359357; + RealType w1 = -1.17767998417887; + RealType w0 = 1 - 2 * (w1 + w2 + w3); + std::vector weights = { w3, w2, w1, w0, w1, w2, w3}; + + for (unsigned i=0; i < weights.size(); i++) + { + std::tie(p, q) = second_order_yoshida(p, q, weights[i] * dt, dHdp, dHdq); + } + + return std::make_pair(p, q); +} + +template +std::pair, std::vector > integrate_hamiltonian(const ReturnType p0, + const ReturnType q0, + const RealType dt, + const unsigned steps, + Func dHdp, + Func dHdq, + const Policy& pol, + std::string method="Y6") +{ + // Not sure how to make this function string nicer + static const char* function = "boost::math::quadrature::integrate_hamiltonian(p0, q0, %1%, steps, dHdp, dHdq)"; + + if ((dt <= 0) || !(boost::math::isfinite)(dt)) + { // Need to figure out what the correct return type is here + RealType val = (boost::math::policies::raise_domain_error(function, "Time step must be positive and finite but got: dt = %1%.\n", dt, pol)); + std::vector nan_vec = {ReturnType(val)}; + return std::make_pair(nan_vec, nan_vec); + } + + // Check if method is available + std::vector available_methods = {"Y6", "Y4", "Y2"}; + + std::pair (*stepper)(ReturnType, ReturnType, RealType, Func, Func); + + if (method == "Y6"){ + stepper = sixth_order_yoshida; + } + else if (method == "Y4"){ + stepper = fourth_order_yoshida; + } + else if (method == "Y2") + { + stepper = second_order_yoshida; + } + + std::vector p(steps); + std::vector q(steps); + p[0] = p0; + q[0] = q0; + + ReturnType p_current = p0; + ReturnType q_current = q0; + for (unsigned i=1; i < steps; i++) + { + std::tie(p_current, q_current) = stepper(p_current, q_current, dt, dHdp, dHdq); + p[i] = p_current; + q[i] = q_current; + } + return std::make_pair(p, q); +} + +}}} +#endif diff --git a/include/boost/math/quadrature/symplectic_integration.hpp b/include/boost/math/quadrature/symplectic_integration.hpp deleted file mode 100644 index 5bc50515cf..0000000000 --- a/include/boost/math/quadrature/symplectic_integration.hpp +++ /dev/null @@ -1,72 +0,0 @@ -/* Based on "Construction of higher order symplectic integrators" by Haruo Yoshida*/ - -#include -#include -#include - -typedef std::valarray derivative(std::valarray); - -template -std::pair second_order_yoshida(const ReturnType p0, - const ReturnType q0, - RealType dt, - Func dHdp, - Func dHdq) -{ - ReturnType p = p0; - ReturnType q = q0; - - q = q + dt / 2 * dHdp(p); - p = p - dt * dHdq(q); - q = q + dt / 2 * dHdp(p); - - return std::make_pair(p, q); -} - -template -std::pair fourth_order_yoshida(const ReturnType p0, - const ReturnType q0, - const RealType dt, - Func dHdp, - Func dHdq) -{ - ReturnType p = p0; - ReturnType q = q0; - - RealType x0 = -(std::pow(2, 1/3) / (2 - std::pow(2, 1/3))); - RealType x1 = 1 / (2 - std::pow(2, 1/3)); - - std::vector weights = { x1, x0, x1 }; - - for (unsigned i=0; i < weights.size(); i++) - { - std::tie(p, q) = second_order_yoshida(p, q, weights[i] * dt, dHdp, dHdq); - } - - return std::make_pair(p, q); -} - -template -std::pair sixth_order_yoshida(const ReturnType p0, - const ReturnType q0, - RealType dt, - Func dHdp, - Func dHdq) -{ - ReturnType p = p0; - ReturnType q = q0; - - // Choosing "System A" solution - RealType w3 = 0.784513610477560; - RealType w2 = 0.235573213359357; - RealType w1 = -1.17767998417887; - RealType w0 = 1 - 2 * (w1 + w2 + w3); - std::vector weights = { w3, w2, w1, w0, w1, w2, w3}; - - for (unsigned i=0; i < weights.size(); i++) - { - std::tie(p, q) = second_order_yoshida(p, q, weights[i] * dt, dHdp, dHdq); - } - - return std::make_pair(p, q); -} \ No newline at end of file From 34d3cd81d4d06ae9b38603d4fd1c25a6f8d2d3da Mon Sep 17 00:00:00 2001 From: Jacob Hass Date: Sun, 21 Jun 2026 17:12:35 -0700 Subject: [PATCH 04/22] Added error handling and default arguments --- include/boost/math/quadrature/symplectic.hpp | 47 ++++++++++++++++++-- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/include/boost/math/quadrature/symplectic.hpp b/include/boost/math/quadrature/symplectic.hpp index 300c757713..75e5ea614d 100644 --- a/include/boost/math/quadrature/symplectic.hpp +++ b/include/boost/math/quadrature/symplectic.hpp @@ -6,7 +6,8 @@ #include #include #include -#include +#include +#include #include #include @@ -84,14 +85,14 @@ std::pair, std::vector > integrate_hamiltoni const unsigned steps, Func dHdp, Func dHdq, - const Policy& pol, - std::string method="Y6") + std::string method, + const Policy& pol) { // Not sure how to make this function string nicer static const char* function = "boost::math::quadrature::integrate_hamiltonian(p0, q0, %1%, steps, dHdp, dHdq)"; if ((dt <= 0) || !(boost::math::isfinite)(dt)) - { // Need to figure out what the correct return type is here + { RealType val = (boost::math::policies::raise_domain_error(function, "Time step must be positive and finite but got: dt = %1%.\n", dt, pol)); std::vector nan_vec = {ReturnType(val)}; return std::make_pair(nan_vec, nan_vec); @@ -112,6 +113,10 @@ std::pair, std::vector > integrate_hamiltoni { stepper = second_order_yoshida; } + else + { + throw std::domain_error("Method must be in {'Y6', 'Y4', 'Y2'} but got: method = " + method); + } std::vector p(steps); std::vector q(steps); @@ -129,5 +134,39 @@ std::pair, std::vector > integrate_hamiltoni return std::make_pair(p, q); } +template +std::pair, std::vector > integrate_hamiltonian(const ReturnType p0, + const ReturnType q0, + const RealType dt, + const unsigned steps, + Func dHdp, + Func dHdq, + const Policy& pol) +{ + return integrate_hamiltonian(p0, q0, dt, steps, dHdp, dHdq, "Y6", pol); +} + +template +std::pair, std::vector > integrate_hamiltonian(const ReturnType p0, + const ReturnType q0, + const RealType dt, + const unsigned steps, + Func dHdp, + Func dHdq, + std::string method) +{ + return integrate_hamiltonian(p0, q0, dt, steps, dHdp, dHdq, method, boost::math::policies::policy<>()); +} + +template +std::pair, std::vector > integrate_hamiltonian(const ReturnType p0, + const ReturnType q0, + const RealType dt, + const unsigned steps, + Func dHdp, + Func dHdq) +{ + return integrate_hamiltonian(p0, q0, dt, steps, dHdp, dHdq, "Y6", boost::math::policies::policy<>()); +} }}} #endif From 098ab7ddf424718c48641942b0c446ea62db1d59 Mon Sep 17 00:00:00 2001 From: Jacob Hass Date: Sun, 21 Jun 2026 18:05:33 -0700 Subject: [PATCH 05/22] Initial testing --- test/Jamfile.v2 | 1 + test/test_symplectic.cpp | 66 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 test/test_symplectic.cpp diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 5b67e474df..7426c26fd6 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -1341,6 +1341,7 @@ test-suite quadrature : [ run test_trapezoidal.cpp /boost/test//boost_unit_test_framework : : : release [ requires cxx11_lambdas cxx11_auto_declarations cxx11_decltype cxx11_unified_initialization_syntax cxx11_variadic_templates ] $(float128_type) ] + [ run test_symplectic.cpp /boost/test//boost_unit_test_framework ] ; test-suite autodiff : diff --git a/test/test_symplectic.cpp b/test/test_symplectic.cpp new file mode 100644 index 0000000000..c3f2e06a12 --- /dev/null +++ b/test/test_symplectic.cpp @@ -0,0 +1,66 @@ +/* + * Copyright Nick Thompson, 2017 + * Use, modification and distribution are subject to the + * Boost Software License, Version 1.0. (See accompanying file + * LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#define BOOST_TEST_MODULE symplectic_quadrature + +#include +#include +#include +#include +#include +#include +#include +#ifdef BOOST_HAS_FLOAT128 +#include +#endif + +#if __has_include() +# include +#endif + +using boost::math::quadrature::integrate_hamiltonian; + +template +Real singleton_dHdp(Real p) { return p; } +template +Real singleton_dHdq(Real q) { return q; } + +template +std::valarray vector_dHdp(std::valarray p) { return p; } +template +std::valarray vector_dHdq(std::valarray q) { return q; } + +template +void test_input_singleton_vs_vector() +{ + RealType dt = 0.05; + unsigned int steps = 3; + RealType q0 = 1; + RealType p0 = 0; + + + std::vector p; + std::vector q; + + std::tie(p, q) = boost::math::quadrature::integrate_hamiltonian(p0, q0, dt, steps, singleton_dHdp, singleton_dHdq); + + std::valarray q0_vector = {1}; + std::valarray p0_vector = {0}; + std::vector > p_vector; + std::vector > q_vector; + + std::tie(p_vector, q_vector) = boost::math::quadrature::integrate_hamiltonian(p0_vector, q0_vector, dt, steps, vector_dHdp, vector_dHdq); + + BOOST_CHECK_EQUAL(p[0], p_vector[0][0]); + BOOST_CHECK_EQUAL(p[1], p_vector[1][0]); + BOOST_CHECK_EQUAL(p[2], p_vector[2][0]); + +} + +BOOST_AUTO_TEST_CASE(symplectic_quadrature) +{ + test_input_singleton_vs_vector(); +} From fd343b732123f36e72e262de421620821a184b63 Mon Sep 17 00:00:00 2001 From: Jacob Hass Date: Mon, 22 Jun 2026 17:18:16 -0700 Subject: [PATCH 06/22] Added SHO test --- include/boost/math/quadrature/symplectic.hpp | 12 ------ test/test_symplectic.cpp | 41 ++++++++++++++++++++ 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/include/boost/math/quadrature/symplectic.hpp b/include/boost/math/quadrature/symplectic.hpp index 75e5ea614d..12edb742a4 100644 --- a/include/boost/math/quadrature/symplectic.hpp +++ b/include/boost/math/quadrature/symplectic.hpp @@ -134,18 +134,6 @@ std::pair, std::vector > integrate_hamiltoni return std::make_pair(p, q); } -template -std::pair, std::vector > integrate_hamiltonian(const ReturnType p0, - const ReturnType q0, - const RealType dt, - const unsigned steps, - Func dHdp, - Func dHdq, - const Policy& pol) -{ - return integrate_hamiltonian(p0, q0, dt, steps, dHdp, dHdq, "Y6", pol); -} - template std::pair, std::vector > integrate_hamiltonian(const ReturnType p0, const ReturnType q0, diff --git a/test/test_symplectic.cpp b/test/test_symplectic.cpp index c3f2e06a12..252ddf5405 100644 --- a/test/test_symplectic.cpp +++ b/test/test_symplectic.cpp @@ -6,6 +6,7 @@ */ #define BOOST_TEST_MODULE symplectic_quadrature +#include #include #include #include @@ -23,6 +24,7 @@ using boost::math::quadrature::integrate_hamiltonian; +// Equations of motion for simple harmonic oscillator template Real singleton_dHdp(Real p) { return p; } template @@ -33,6 +35,7 @@ std::valarray vector_dHdp(std::valarray p) { return p; } template std::valarray vector_dHdq(std::valarray q) { return q; } +// Sanity check that passing as a 1D vector does not effect results template void test_input_singleton_vs_vector() { @@ -60,7 +63,45 @@ void test_input_singleton_vs_vector() } +template +void test_invalid_parameters() +{ + // Negative timestep + BOOST_CHECK_THROW(boost::math::quadrature::integrate_hamiltonian(1, 0, -0.1, 10, singleton_dHdp, singleton_dHdq), std::domain_error); + + // Method not in {'Y6', 'Y4', 'Y2'} + BOOST_CHECK_THROW(boost::math::quadrature::integrate_hamiltonian(1, 0, 0.1, 10, singleton_dHdp, singleton_dHdq, "InvalidMethod"), std::domain_error); +} + +/* Test if SHO energy fluctuations are below a given tolerance*/ +template +void test_harmonic_oscillator(RealType tol) +{ + RealType dt = 0.05; + RealType t_end = 100; + unsigned int steps = t_end / dt; + + RealType q0 = 1; + RealType p0 = 0; + + std::vector p; + std::vector q; + + std::tie(p, q) = boost::math::quadrature::integrate_hamiltonian(p0, q0, dt, steps, singleton_dHdp, singleton_dHdq); + + std::valarray p_val(p.data(), p.size()); + std::valarray q_val(q.data(), q.size()); + std::valarray energy_error = std::pow(p_val, 2) + std::pow(q_val, 2) - 1; + std::valarray abs_energy_error = std::abs(energy_error); + RealType max_error = abs_energy_error.max(); + std::cout << max_error << std::endl; + BOOST_CHECK_LE(max_error, tol); + +} + BOOST_AUTO_TEST_CASE(symplectic_quadrature) { test_input_singleton_vs_vector(); + test_invalid_parameters(); + test_harmonic_oscillator(1e-10); } From 63eac980d285c4c37654342e1ca41e603393c08d Mon Sep 17 00:00:00 2001 From: Jacob Hass Date: Mon, 22 Jun 2026 18:11:21 -0700 Subject: [PATCH 07/22] Switched to map functionality --- include/boost/math/quadrature/symplectic.hpp | 23 ++++++-------------- test/test_symplectic.cpp | 2 +- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/include/boost/math/quadrature/symplectic.hpp b/include/boost/math/quadrature/symplectic.hpp index 12edb742a4..1b270ed3c5 100644 --- a/include/boost/math/quadrature/symplectic.hpp +++ b/include/boost/math/quadrature/symplectic.hpp @@ -1,10 +1,11 @@ -/* Based on "Construction of higher order symplectic integrators" by Haruo Yoshida*/ +/* Copyright Jacob Hass, 2026*/ #ifndef BOOST_MATH_QUADRATURE_SYMPLECTIC_HPP #define BOOST_MATH_QUADRATURE_SYMPLECTIC_HPP #include #include +#include #include #include #include @@ -101,22 +102,12 @@ std::pair, std::vector > integrate_hamiltoni // Check if method is available std::vector available_methods = {"Y6", "Y4", "Y2"}; - std::pair (*stepper)(ReturnType, ReturnType, RealType, Func, Func); + typedef std::pair (*stepperType)(ReturnType, ReturnType, RealType, Func, Func); - if (method == "Y6"){ - stepper = sixth_order_yoshida; - } - else if (method == "Y4"){ - stepper = fourth_order_yoshida; - } - else if (method == "Y2") - { - stepper = second_order_yoshida; - } - else - { - throw std::domain_error("Method must be in {'Y6', 'Y4', 'Y2'} but got: method = " + method); - } + std::map m{{"Y6", sixth_order_yoshida}, + {"Y4", fourth_order_yoshida}, + {"Y2", second_order_yoshida}}; + stepperType stepper = m.at(method); std::vector p(steps); std::vector q(steps); diff --git a/test/test_symplectic.cpp b/test/test_symplectic.cpp index 252ddf5405..f2608dd29c 100644 --- a/test/test_symplectic.cpp +++ b/test/test_symplectic.cpp @@ -70,7 +70,7 @@ void test_invalid_parameters() BOOST_CHECK_THROW(boost::math::quadrature::integrate_hamiltonian(1, 0, -0.1, 10, singleton_dHdp, singleton_dHdq), std::domain_error); // Method not in {'Y6', 'Y4', 'Y2'} - BOOST_CHECK_THROW(boost::math::quadrature::integrate_hamiltonian(1, 0, 0.1, 10, singleton_dHdp, singleton_dHdq, "InvalidMethod"), std::domain_error); + BOOST_CHECK_THROW(boost::math::quadrature::integrate_hamiltonian(1, 0, 0.1, 10, singleton_dHdp, singleton_dHdq, "InvalidMethod"), std::out_of_range); } /* Test if SHO energy fluctuations are below a given tolerance*/ From a3a916f117c930de4b3189377002af076354a785 Mon Sep 17 00:00:00 2001 From: Jacob Hass Date: Wed, 24 Jun 2026 20:15:20 -0700 Subject: [PATCH 08/22] Added documentation --- doc/equations/harmonic_oscillator.png | Bin 0 -> 1689 bytes doc/equations/harmonic_oscillator.svg | 32 ++++++ doc/html/quadrature.html | 1 + doc/math.qbk | 1 + doc/quadrature/symplectic.qbk | 103 +++++++++++++++++++ include/boost/math/quadrature/symplectic.hpp | 12 ++- test/Jamfile.v2 | 2 +- 7 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 doc/equations/harmonic_oscillator.png create mode 100644 doc/equations/harmonic_oscillator.svg create mode 100644 doc/quadrature/symplectic.qbk diff --git a/doc/equations/harmonic_oscillator.png b/doc/equations/harmonic_oscillator.png new file mode 100644 index 0000000000000000000000000000000000000000..427a3ea50877449cde9658f2b6f46ed8c86016c8 GIT binary patch literal 1689 zcmV;K24?w*P)rN00009a7bBm000ie z000ie0hKEb8vp8(>!P3f7mtc*GCa5SBv{<7CRMfIa zDppG=7)z}vFgLxT1m@)QC%>Kh(rMMsSIW)#wjVBQX+}qTk6qH=R!J zy?w9mwRc|MoaCgPx%a($XXcx4zGdbqm7ORQHVUs2whEUE4^j>e5x7lQtd-vsUhlfa zAquYvZ_>&ugfChAz5|2@yJ3qL`N9LV@^iwK7QW|T;Y{I|!ijF!?7;-#hr&UYz1QW! zTZL7^|Adp=P}zae!tKJtEPlt4PH5~wiEx$h5SGt!LX*9~;4c==r`-beekE1CuTk*# zdEqSKbY5TPgeE(I_{-JGi@E$m*LoDMjT62@L0T$&)nJtqn(V}E3P}k&x?H<9kab6A zSeu;C^fG*bm?8YpxONgde}WSlhajvGK5o>_Vds}Pp>YVpPGP-K_q6c63~WoBLNkgE zMYC|4>KqX5S11zh7nT_Y@*l#fPG}OwaN$A4ooqQh5Q=NHkDr4iAy)pM~oz>$qatGbdQ^E;%th_j} zmx#80R`^B8wJ{W^*_H`S4Yi*Q+65D8XiWCc5oul~uhpE4(B;Y3Bx^&iH%4foxB%A> z8>x2iy+_3SJ{wiv$An<-w59R;DpW^$1v0TDYC?9WNw!63A&2;cX0u_>6jS@28nAR; z$ZP&SqSJ-7bn$`l!oAeoV7v169fe{cg$7>KV1w|U_C#nQhxmj>w`az4(pRwF-vWYN zi;Jm=5xo0=9@lBg3*hbMeHiN_4GxQX_*UT$!rKG*Sd#V()Y&ttYgRdmE1b|IjiWSs z1~HYhS&!`>8#E)fkrUqsyx`^hf1c`L0MEB))^wwV;?Cg|1ZctD;DA0Bn*0Rc%RF*Z zOD!>_n-GY;1!O44a$Kr|aU#EHW`tMuxdk(M8I9iFW_K}<1tHoJGx)85`t8O{H*s-- z?{#c|J%bp{7LHwR0)2?!-S6oL4>U$-5?>3oCi$JmsjgAvD3-eADQUFPu+5{rxg&wk z-~`5t>3BFiqg~y2RhK65eXwbkhS*18oacn5NA`?A*-mD;faP;TK8LLTEn0abSDK`N z0sbYzGip$oxhJK+3r#JTCLgeAe$xbDhUQUi<#Ghi>0jjq?|dMLm++yyeERJxC=}bc zw&rSf4)RZ*DKCRu!zIVT4xz!b^pp-Z&VNTuvxwsh+`wh4fNnQFc8s|c_Ij}g`xu@wl%CCWR9 zcPQ)5QC=i@L^jRnIgB$*rFv~s9l4S?(UhV9V*=k#jStQKiSl+H6e@?5ForI#4ur$^ z2h?X0p!r)==d3c~ML#KTuaH9uSRLYNAvC@-gWCn;^uYkFau+8=u^alH1GBC@pmXsn z5PLJYUGwRXoNv_o(fCu`(Dxj}xb~a)h}y=-GFUqr8xO8SCb9mXD6H zUHLR_%~U}PC&>^u^yx(zRd_Ya=-_&K4(;rCJzVgv@`8_bTHOL_E1Qr*H~L{W#OVie zJL@$gvW6O|jH-Q#asQ{v+Xt=V8sXcPwQCVQs6lRs)8DaMRObYoAE&rX_)UP9@|W`B zPC9Tpv(=(@A^!BSGl0_%9NXBa!ZWrTsDX5xDg0A)L`(rh!S~P(vH=L9zO4Fwzj40@ z&ikx1;u__~J-d|`3DtpQ-RnjjBFo6zAP1AD;{Aa{qysh^OH!%wPCq)N02sB+s_XHJ zW}~C^EU=F^gf|;?>okpoY<^tHS%mpl1%wmol-)n~W<7?}I9zM+3E?K~zG~I6_f>am z_o=SGnh9@e7Tw*ZnA(LU68l^pHQKeF?R7{2cM9JRVS{$x65gu=XN0~Auz`@vLeA&9 j7(JD$h<0s?yVL&%h;d{n+q~X_00000NkvXXu0mjfKGipv literal 0 HcmV?d00001 diff --git a/doc/equations/harmonic_oscillator.svg b/doc/equations/harmonic_oscillator.svg new file mode 100644 index 0000000000..7320acb737 --- /dev/null +++ b/doc/equations/harmonic_oscillator.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/html/quadrature.html b/doc/html/quadrature.html index 9041b488ae..dadad954c4 100644 --- a/doc/html/quadrature.html +++ b/doc/html/quadrature.html @@ -51,6 +51,7 @@
Fourier Integrals
Naive Monte Carlo Integration
+
Symplectic Integration
Wavelet Transforms
Numerical Differentiation
Automatic Differentiation
diff --git a/doc/math.qbk b/doc/math.qbk index f48c87b88c..d2575db293 100644 --- a/doc/math.qbk +++ b/doc/math.qbk @@ -772,6 +772,7 @@ and as a CD ISBN 0-9504833-2-X 978-0-9504833-2-0, Classification 519.2-dc22. [include quadrature/double_exponential.qbk] [include quadrature/ooura_fourier_integrals.qbk] [include quadrature/naive_monte_carlo.qbk] +[include quadrature/symplectic.qpk] [include quadrature/wavelet_transforms.qbk] [include differentiation/numerical_differentiation.qbk] [include differentiation/autodiff.qbk] diff --git a/doc/quadrature/symplectic.qbk b/doc/quadrature/symplectic.qbk new file mode 100644 index 0000000000..b52ee70467 --- /dev/null +++ b/doc/quadrature/symplectic.qbk @@ -0,0 +1,103 @@ +[/ +Copyright (c) 2026 Jacob Hass +Use, modification and distribution are subject to the +Boost Software License, Version 1.0. (See accompanying file +LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section:symplectic Symplectic Integration] + +[heading Synopsis] + + #include + namespace boost{ namespace math{ namespace quadrature { + + template + std::pair, std::vector > integrate_hamiltonian(const ReturnType p0, + const ReturnType q0, + const RealType dt, + const unsigned steps, + Func dHdp, + Func dHdq, + std::string method = "Y6") + + template + std::pair, std::vector > integrate_hamiltonian(const ReturnType p0, + const ReturnType q0, + const RealType dt, + const unsigned steps, + Func dHdp, + Func dHdq, + std::string method, + const ``__Policy``& pol) + + }}} // namespaces + +[heading Description] + +The functional `integrate_hamiltonian` calculates the phase space trajectory for a given Hamiltonian. +The trajectories are calculated using symplectic integration which preserves the energy of +a system. Even higher order traditional numerical integration algorithms will gain or lose +energy at long times. The functional implements the methods in [@https://www.sciencedirect.com/science/article/abs/pii/0375960190900923 Yoshida's] +landmark paper. We assume that the Hamiltonian is separable so that it can be written as `H = T(p) + V(q)`. + +We now give an example for a simple harmonic oscillator with the Hamiltonian + +[equation harmonic_oscillator] + +For simplicity we will assume `k = m = 1`. Then the partial derivatives of the Hamiltonian +with respect to `p` and `q` are + + double dHdp(double p) + { + return p; + } + + double dHdq(double q) + { + return q; + } + +Note that the functional can be readily generalized to multiple coordinates by changing the +signature of `dHdp` to + + std::valarray dHdp(std::valarray p) + { + // calculate the partial derivatives with respect to each p_i + } + +The function must return a `valarray` type, as opposed to `std::vector`, because we must be +able to perform arithmetic operations on the return type. We then define the timestep size +and number of steps of the algorithm to go from `t=0` to `t=100` + + RealType dt = 0.05; + RealType t_end = 100; + unsigned int steps = t_end / dt; + +Lastly, we define the initial conditions so that the oscillator starts from rest at `x=1` + + RealType q0 = 1; + RealType p0 = 0; + +We now evolve the system using the following + + std::vector p; + std::vector q; + + std::tie(p, q) = boost::math::quadrature::integrate_hamiltonian(p0, q0, dt, steps, dHdp, dHdq, "Y6"); + +The ouput vectors `q, p` are the position and momentum of the system at each time. In +higher dimensions the output will be a vector of vectors. + +The last argument that we pass to `integrate_hamiltonian`, "Y6" here, sets the integration +method to use. The string "Y6" stands for Yoshida's 6th order integrator. Yoshida's 2nd and +4th order methods are also available by passing the string "Y2", or "Y4" respectively. + +[optional_policy] + +References: + +Yoshida, Haruo. [`Construction of higher order symplectic integrators], Physics Letters A, 150.5-7 (1990): 262-268. + +[endsect] [/section:symplectic Symplectic Integration] + diff --git a/include/boost/math/quadrature/symplectic.hpp b/include/boost/math/quadrature/symplectic.hpp index 1b270ed3c5..da5ae6547d 100644 --- a/include/boost/math/quadrature/symplectic.hpp +++ b/include/boost/math/quadrature/symplectic.hpp @@ -65,9 +65,15 @@ std::pair sixth_order_yoshida(const ReturnType p0, ReturnType q = q0; // Choosing "System A" solution - RealType w3 = 0.784513610477560; - RealType w2 = 0.235573213359357; - RealType w1 = -1.17767998417887; + // The following Mathematica command can calculate these coefficients to arbitrary precision + // FindRoot[{w0+2(w1+w2+w3)==1, + // w0^3 + 2(w1^3 + w2^3+w3^3)==0, + // w0^5 + 2(w1^5 + w2^5+w3^5)==0, + // 1/6(w0^2w1^3-w0^4*w1) - 1/6(w0^3w1^2-w0*w1^4)+1/6((w0+2w1)^2w2^3-(w0+2w1)(w0^3+2w1^3)w2) - 1/6((w0^3+2w1^3)w2^2-(w0+2w1)w2^4) +1/6((w0+2w1+2w2)^2w3^3-(w0+2w1+2w2)(w0^3+2w1^3+2w2^3)w3)-1/6((w0^3+2w1^3+2w2^3)w3^2-(w0+2w1+2w2)w3^4)==0}, {{w0,1.3151863206839063}, {w1,-1.17767998417887}, {w2,0.235573213359357}, {w3,0.784513610477560}}, WorkingPrecision->64] + RealType w1 = static_cast(-1.17767998417887100694641568096431573463926925263459848447536851379674155618156L); + RealType w2 = static_cast(0.23557321335935813368479318297853460168646808210340111900349313095621471215223L); + RealType w3 = static_cast(0.78451361047755726381949763386634987577682441745149338456794779895125997479548L); + // w0 = 1.31518632068391121888424972823886251435195350615940796180785516777853373846773 RealType w0 = 1 - 2 * (w1 + w2 + w3); std::vector weights = { w3, w2, w1, w0, w1, w2, w3}; diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 7426c26fd6..694ccefdde 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -1341,7 +1341,7 @@ test-suite quadrature : [ run test_trapezoidal.cpp /boost/test//boost_unit_test_framework : : : release [ requires cxx11_lambdas cxx11_auto_declarations cxx11_decltype cxx11_unified_initialization_syntax cxx11_variadic_templates ] $(float128_type) ] - [ run test_symplectic.cpp /boost/test//boost_unit_test_framework ] + [ run test_symplectic.cpp ] ; test-suite autodiff : From 12818850d14e1dc9531c795a2c5a14ca714c257a Mon Sep 17 00:00:00 2001 From: Jacob Hass Date: Wed, 24 Jun 2026 20:23:52 -0700 Subject: [PATCH 09/22] Fixed typo --- doc/math.qbk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/math.qbk b/doc/math.qbk index d2575db293..7e46490c61 100644 --- a/doc/math.qbk +++ b/doc/math.qbk @@ -772,7 +772,7 @@ and as a CD ISBN 0-9504833-2-X 978-0-9504833-2-0, Classification 519.2-dc22. [include quadrature/double_exponential.qbk] [include quadrature/ooura_fourier_integrals.qbk] [include quadrature/naive_monte_carlo.qbk] -[include quadrature/symplectic.qpk] +[include quadrature/symplectic.qbk] [include quadrature/wavelet_transforms.qbk] [include differentiation/numerical_differentiation.qbk] [include differentiation/autodiff.qbk] From 5e7705b913aeac205b31ccfd900cacf68c1baf71 Mon Sep 17 00:00:00 2001 From: Jacob Hass Date: Fri, 26 Jun 2026 15:44:03 -0700 Subject: [PATCH 10/22] Updated docs and max function --- doc/quadrature/symplectic.qbk | 2 +- include/boost/math/quadrature/symplectic.hpp | 6 +++++- test/test_symplectic.cpp | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/doc/quadrature/symplectic.qbk b/doc/quadrature/symplectic.qbk index b52ee70467..1765a3482d 100644 --- a/doc/quadrature/symplectic.qbk +++ b/doc/quadrature/symplectic.qbk @@ -42,7 +42,7 @@ energy at long times. The functional implements the methods in [@https://www.sci landmark paper. We assume that the Hamiltonian is separable so that it can be written as `H = T(p) + V(q)`. We now give an example for a simple harmonic oscillator with the Hamiltonian - +[/ $H = \frac{p^2}{2m} + \frac{1}{2}kx^2$ ] [equation harmonic_oscillator] For simplicity we will assume `k = m = 1`. Then the partial derivatives of the Hamiltonian diff --git a/include/boost/math/quadrature/symplectic.hpp b/include/boost/math/quadrature/symplectic.hpp index da5ae6547d..f07486ab59 100644 --- a/include/boost/math/quadrature/symplectic.hpp +++ b/include/boost/math/quadrature/symplectic.hpp @@ -1,4 +1,8 @@ -/* Copyright Jacob Hass, 2026*/ +// Copyright Jacob Hass, 2026 +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) #ifndef BOOST_MATH_QUADRATURE_SYMPLECTIC_HPP #define BOOST_MATH_QUADRATURE_SYMPLECTIC_HPP diff --git a/test/test_symplectic.cpp b/test/test_symplectic.cpp index f2608dd29c..efdf7f79dd 100644 --- a/test/test_symplectic.cpp +++ b/test/test_symplectic.cpp @@ -93,7 +93,7 @@ void test_harmonic_oscillator(RealType tol) std::valarray q_val(q.data(), q.size()); std::valarray energy_error = std::pow(p_val, 2) + std::pow(q_val, 2) - 1; std::valarray abs_energy_error = std::abs(energy_error); - RealType max_error = abs_energy_error.max(); + RealType max_error = *std::max_element(std::begin(abs_energy_error), std::end(abs_energy_error)); std::cout << max_error << std::endl; BOOST_CHECK_LE(max_error, tol); From e31880b78252b5850d6c99600cd52cee980c9ace Mon Sep 17 00:00:00 2001 From: Jacob Hass Date: Fri, 26 Jun 2026 16:29:13 -0700 Subject: [PATCH 11/22] Changed functions to only accept vectors --- include/boost/math/quadrature/symplectic.hpp | 22 +++++-- test/test_symplectic.cpp | 67 ++++++-------------- 2 files changed, 38 insertions(+), 51 deletions(-) diff --git a/include/boost/math/quadrature/symplectic.hpp b/include/boost/math/quadrature/symplectic.hpp index f07486ab59..b71558b501 100644 --- a/include/boost/math/quadrature/symplectic.hpp +++ b/include/boost/math/quadrature/symplectic.hpp @@ -28,10 +28,24 @@ std::pair second_order_yoshida(const ReturnType p0, ReturnType p = p0; ReturnType q = q0; - q = q + dt / 2 * dHdp(p); - p = p - dt * dHdq(q); - q = q + dt / 2 * dHdp(p); - + // Half step in q + auto dHdp_val = dHdp(p); + for (unsigned i=0; i < q.size(); i++){ + q[i] = q[i] + dt / 2 * dHdp_val[i]; + } + + // Full step in p + auto dHdq_val = dHdq(q); + for (unsigned i=0; i < p.size(); i++){ + p[i] = p[i] - dt * dHdq_val[i]; + } + + // Half step in q + dHdp_val = dHdp(p); + for (unsigned i=0; i < q.size(); i++){ + q[i] = q[i] + dt / 2 * dHdp_val[i]; + } + return std::make_pair(p, q); } diff --git a/test/test_symplectic.cpp b/test/test_symplectic.cpp index efdf7f79dd..3748579792 100644 --- a/test/test_symplectic.cpp +++ b/test/test_symplectic.cpp @@ -1,5 +1,5 @@ /* - * Copyright Nick Thompson, 2017 + * Copyright Jacob Hass, 2026 * Use, modification and distribution are subject to the * Boost Software License, Version 1.0. (See accompanying file * LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -24,53 +24,22 @@ using boost::math::quadrature::integrate_hamiltonian; -// Equations of motion for simple harmonic oscillator +// Equations of motion for simple harmonic oscillator template -Real singleton_dHdp(Real p) { return p; } +std::vector vector_dHdp(std::vector p) { return p; } template -Real singleton_dHdq(Real q) { return q; } - -template -std::valarray vector_dHdp(std::valarray p) { return p; } -template -std::valarray vector_dHdq(std::valarray q) { return q; } - -// Sanity check that passing as a 1D vector does not effect results -template -void test_input_singleton_vs_vector() -{ - RealType dt = 0.05; - unsigned int steps = 3; - RealType q0 = 1; - RealType p0 = 0; - - - std::vector p; - std::vector q; - - std::tie(p, q) = boost::math::quadrature::integrate_hamiltonian(p0, q0, dt, steps, singleton_dHdp, singleton_dHdq); - - std::valarray q0_vector = {1}; - std::valarray p0_vector = {0}; - std::vector > p_vector; - std::vector > q_vector; - - std::tie(p_vector, q_vector) = boost::math::quadrature::integrate_hamiltonian(p0_vector, q0_vector, dt, steps, vector_dHdp, vector_dHdq); - - BOOST_CHECK_EQUAL(p[0], p_vector[0][0]); - BOOST_CHECK_EQUAL(p[1], p_vector[1][0]); - BOOST_CHECK_EQUAL(p[2], p_vector[2][0]); - -} +std::vector vector_dHdq(std::vector q) { return q; } template void test_invalid_parameters() { + std::vector q0 = {1}; + std::vector p0 = {0}; // Negative timestep - BOOST_CHECK_THROW(boost::math::quadrature::integrate_hamiltonian(1, 0, -0.1, 10, singleton_dHdp, singleton_dHdq), std::domain_error); + BOOST_CHECK_THROW(boost::math::quadrature::integrate_hamiltonian(q0, p0, -0.1, 10, vector_dHdp, vector_dHdq), std::domain_error); // Method not in {'Y6', 'Y4', 'Y2'} - BOOST_CHECK_THROW(boost::math::quadrature::integrate_hamiltonian(1, 0, 0.1, 10, singleton_dHdp, singleton_dHdq, "InvalidMethod"), std::out_of_range); + BOOST_CHECK_THROW(boost::math::quadrature::integrate_hamiltonian(q0, p0, 0.1, 10, vector_dHdp, vector_dHdq, "InvalidMethod"), std::out_of_range); } /* Test if SHO energy fluctuations are below a given tolerance*/ @@ -81,16 +50,21 @@ void test_harmonic_oscillator(RealType tol) RealType t_end = 100; unsigned int steps = t_end / dt; - RealType q0 = 1; - RealType p0 = 0; + std::vector q0 = {1}; + std::vector p0 = {0}; - std::vector p; - std::vector q; + std::vector > p; + std::vector > q; - std::tie(p, q) = boost::math::quadrature::integrate_hamiltonian(p0, q0, dt, steps, singleton_dHdp, singleton_dHdq); + std::tie(p, q) = boost::math::quadrature::integrate_hamiltonian(p0, q0, dt, steps, vector_dHdp, vector_dHdp); - std::valarray p_val(p.data(), p.size()); - std::valarray q_val(q.data(), q.size()); + std::valarray p_val(p.size()); + std::valarray q_val(q.size()); + for (unsigned i=0; i < p.size(); i++) + { + p_val[i] = p[i][0]; + q_val[i] = q[i][0]; + } std::valarray energy_error = std::pow(p_val, 2) + std::pow(q_val, 2) - 1; std::valarray abs_energy_error = std::abs(energy_error); RealType max_error = *std::max_element(std::begin(abs_energy_error), std::end(abs_energy_error)); @@ -101,7 +75,6 @@ void test_harmonic_oscillator(RealType tol) BOOST_AUTO_TEST_CASE(symplectic_quadrature) { - test_input_singleton_vs_vector(); test_invalid_parameters(); test_harmonic_oscillator(1e-10); } From 645aa1a8981c80b2173e5a5a1924815bd3d5771b Mon Sep 17 00:00:00 2001 From: Jacob Hass Date: Fri, 26 Jun 2026 19:21:07 -0700 Subject: [PATCH 12/22] Completely eliminated valarrays --- test/test_symplectic.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/test/test_symplectic.cpp b/test/test_symplectic.cpp index 3748579792..a1088ef2b9 100644 --- a/test/test_symplectic.cpp +++ b/test/test_symplectic.cpp @@ -7,7 +7,6 @@ #define BOOST_TEST_MODULE symplectic_quadrature #include -#include #include #include #include @@ -46,6 +45,8 @@ void test_invalid_parameters() template void test_harmonic_oscillator(RealType tol) { + BOOST_MATH_STD_USING; + RealType dt = 0.05; RealType t_end = 100; unsigned int steps = t_end / dt; @@ -58,15 +59,17 @@ void test_harmonic_oscillator(RealType tol) std::tie(p, q) = boost::math::quadrature::integrate_hamiltonian(p0, q0, dt, steps, vector_dHdp, vector_dHdp); - std::valarray p_val(p.size()); - std::valarray q_val(q.size()); + RealType p_val; + RealType q_val; + std::vector abs_energy_error(p.size()); for (unsigned i=0; i < p.size(); i++) { - p_val[i] = p[i][0]; - q_val[i] = q[i][0]; + p_val = p[i][0]; + q_val = q[i][0]; + + abs_energy_error[i] = std::abs(std::pow(p_val, 2) + std::pow(q_val, 2) - 1); } - std::valarray energy_error = std::pow(p_val, 2) + std::pow(q_val, 2) - 1; - std::valarray abs_energy_error = std::abs(energy_error); + RealType max_error = *std::max_element(std::begin(abs_energy_error), std::end(abs_energy_error)); std::cout << max_error << std::endl; BOOST_CHECK_LE(max_error, tol); From cdd64cf715877afa6c2495df7117b0837c4ee92d Mon Sep 17 00:00:00 2001 From: Jacob Hass Date: Sun, 5 Jul 2026 16:41:48 -0700 Subject: [PATCH 13/22] Added a couple more tests --- include/boost/math/quadrature/symplectic.hpp | 5 ++++- test/test_symplectic.cpp | 12 ++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/include/boost/math/quadrature/symplectic.hpp b/include/boost/math/quadrature/symplectic.hpp index b71558b501..d2c6e3e788 100644 --- a/include/boost/math/quadrature/symplectic.hpp +++ b/include/boost/math/quadrature/symplectic.hpp @@ -56,11 +56,14 @@ std::pair fourth_order_yoshida(const ReturnType p0, Func dHdp, Func dHdq) { + BOOST_MATH_STD_USING + ReturnType p = p0; ReturnType q = q0; - RealType x0 = -(std::pow(2, 1/3) / (2 - std::pow(2, 1/3))); + // RealType x0 = -(std::pow(2, 1/3) / (2 - std::pow(2, 1/3))); RealType x1 = 1 / (2 - std::pow(2, 1/3)); + RealType x0 = 1 - 2 * x1; std::vector weights = { x1, x0, x1 }; diff --git a/test/test_symplectic.cpp b/test/test_symplectic.cpp index a1088ef2b9..853597fea3 100644 --- a/test/test_symplectic.cpp +++ b/test/test_symplectic.cpp @@ -43,9 +43,9 @@ void test_invalid_parameters() /* Test if SHO energy fluctuations are below a given tolerance*/ template -void test_harmonic_oscillator(RealType tol) +void test_harmonic_oscillator(const RealType tol, const std::string method) { - BOOST_MATH_STD_USING; + BOOST_MATH_STD_USING RealType dt = 0.05; RealType t_end = 100; @@ -57,7 +57,7 @@ void test_harmonic_oscillator(RealType tol) std::vector > p; std::vector > q; - std::tie(p, q) = boost::math::quadrature::integrate_hamiltonian(p0, q0, dt, steps, vector_dHdp, vector_dHdp); + std::tie(p, q) = boost::math::quadrature::integrate_hamiltonian(p0, q0, dt, steps, vector_dHdp, vector_dHdp, method); RealType p_val; RealType q_val; @@ -71,13 +71,13 @@ void test_harmonic_oscillator(RealType tol) } RealType max_error = *std::max_element(std::begin(abs_energy_error), std::end(abs_energy_error)); - std::cout << max_error << std::endl; BOOST_CHECK_LE(max_error, tol); - } BOOST_AUTO_TEST_CASE(symplectic_quadrature) { test_invalid_parameters(); - test_harmonic_oscillator(1e-10); + test_harmonic_oscillator(1e-10, "Y6"); + test_harmonic_oscillator(7e-4, "Y4"); + test_harmonic_oscillator(7e-4, "Y2"); } From 290e3266034820c389e7654d852236c6f5552d64 Mon Sep 17 00:00:00 2001 From: Jacob Hass Date: Sun, 5 Jul 2026 16:58:30 -0700 Subject: [PATCH 14/22] Changed function namespaces --- include/boost/math/quadrature/symplectic.hpp | 32 ++++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/include/boost/math/quadrature/symplectic.hpp b/include/boost/math/quadrature/symplectic.hpp index d2c6e3e788..45168d78c5 100644 --- a/include/boost/math/quadrature/symplectic.hpp +++ b/include/boost/math/quadrature/symplectic.hpp @@ -16,7 +16,7 @@ #include #include -namespace boost{ namespace math {namespace quadrature { +namespace boost{ namespace math { namespace quadrature { namespace detail { template std::pair second_order_yoshida(const ReturnType p0, @@ -107,14 +107,14 @@ std::pair sixth_order_yoshida(const ReturnType p0, } template -std::pair, std::vector > integrate_hamiltonian(const ReturnType p0, - const ReturnType q0, - const RealType dt, - const unsigned steps, - Func dHdp, - Func dHdq, - std::string method, - const Policy& pol) +std::pair, std::vector > integrate_hamiltonian_imp(const ReturnType p0, + const ReturnType q0, + const RealType dt, + const unsigned steps, + Func dHdp, + Func dHdq, + std::string method, + const Policy& pol) { // Not sure how to make this function string nicer static const char* function = "boost::math::quadrature::integrate_hamiltonian(p0, q0, %1%, steps, dHdp, dHdq)"; @@ -151,6 +151,20 @@ std::pair, std::vector > integrate_hamiltoni } return std::make_pair(p, q); } +} // namespace detail + +template +std::pair, std::vector > integrate_hamiltonian(const ReturnType p0, + const ReturnType q0, + const RealType dt, + const unsigned steps, + Func dHdp, + Func dHdq, + std::string method, + const Policy& pol) +{ + return detail::integrate_hamiltonian_imp(p0, q0, dt, steps, dHdp, dHdq, method, pol); +} template std::pair, std::vector > integrate_hamiltonian(const ReturnType p0, From bb09041a0a94c2993020bc1f925ed45cce88779c Mon Sep 17 00:00:00 2001 From: Jacob Hass Date: Sun, 5 Jul 2026 18:51:04 -0700 Subject: [PATCH 15/22] Added SRKN from paper --- include/boost/math/quadrature/symplectic.hpp | 53 +++++++++++++++++++- test/test_symplectic.cpp | 1 + 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/include/boost/math/quadrature/symplectic.hpp b/include/boost/math/quadrature/symplectic.hpp index 45168d78c5..ba7bec42b3 100644 --- a/include/boost/math/quadrature/symplectic.hpp +++ b/include/boost/math/quadrature/symplectic.hpp @@ -95,7 +95,7 @@ std::pair sixth_order_yoshida(const ReturnType p0, RealType w2 = static_cast(0.23557321335935813368479318297853460168646808210340111900349313095621471215223L); RealType w3 = static_cast(0.78451361047755726381949763386634987577682441745149338456794779895125997479548L); // w0 = 1.31518632068391121888424972823886251435195350615940796180785516777853373846773 - RealType w0 = 1 - 2 * (w1 + w2 + w3); + RealType w0 = 1. - 2. * (w1 + w2 + w3); std::vector weights = { w3, w2, w1, w0, w1, w2, w3}; for (unsigned i=0; i < weights.size(); i++) @@ -106,6 +106,54 @@ std::pair sixth_order_yoshida(const ReturnType p0, return std::make_pair(p, q); } +template +std::pair SRKN_b_order_6(const ReturnType p0, + const ReturnType q0, + RealType dt, + Func dHdp, + Func dHdq) +{ // This method implements SRKN_b^6 in Table 3 here + // https://www.sciencedirect.com/science/article/pii/S0377042701004927 + + ReturnType p = p0; + ReturnType q = q0; + + RealType b1 = static_cast(0.0829844064174052); + RealType b2 = static_cast(0.396309801498368); + RealType b3 = static_cast(-0.0390563049223486); + RealType b4 = 1. - 2. * (b1 + b2 + b3); + + RealType a1 = static_cast(0.245298957184271); + RealType a2 = static_cast(0.604872665711080); + RealType a3 = 0.5 - (a1 + a2); + + std::vector b_weights = {b1, b2, b3, b4, b3, b2}; + std::vector a_weights = {a1, a2, a3, a3, a2, a1}; + + RealType a, b; + for (unsigned int i=0; i < b_weights.size(); i++) + { + b = b_weights[i]; + a = a_weights[i]; + + auto dHdp_val = dHdp(p); + for (unsigned j=0; j < q.size(); j++){ + q[j] = q[j] + b * dt * dHdp_val[j]; + } + + auto dHdq_val = dHdq(q); + for (unsigned j=0; j < p.size(); j++){ + p[j] = p[j] - a * dt * dHdq_val[j]; + } + } + // Have to do one more step in p + auto dHdp_val = dHdp(p); + for (unsigned j=0; j < q.size(); j++){ + q[j] = q[j] + b1 * dt * dHdp_val[j]; + } + return std::make_pair(p, q); +} + template std::pair, std::vector > integrate_hamiltonian_imp(const ReturnType p0, const ReturnType q0, @@ -133,7 +181,8 @@ std::pair, std::vector > integrate_hamiltoni std::map m{{"Y6", sixth_order_yoshida}, {"Y4", fourth_order_yoshida}, - {"Y2", second_order_yoshida}}; + {"Y2", second_order_yoshida}, + {"SRKNB6", SRKN_b_order_6}}; stepperType stepper = m.at(method); std::vector p(steps); diff --git a/test/test_symplectic.cpp b/test/test_symplectic.cpp index 853597fea3..6dc60098a9 100644 --- a/test/test_symplectic.cpp +++ b/test/test_symplectic.cpp @@ -80,4 +80,5 @@ BOOST_AUTO_TEST_CASE(symplectic_quadrature) test_harmonic_oscillator(1e-10, "Y6"); test_harmonic_oscillator(7e-4, "Y4"); test_harmonic_oscillator(7e-4, "Y2"); + test_harmonic_oscillator(1e-11, "SRKNB6"); } From e6d67b015c1df2c9d63d7181404782315b07a352 Mon Sep 17 00:00:00 2001 From: Jacob Hass Date: Sun, 5 Jul 2026 20:22:24 -0700 Subject: [PATCH 16/22] Added simple pendulum test --- test/test_symplectic.cpp | 60 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/test/test_symplectic.cpp b/test/test_symplectic.cpp index 6dc60098a9..e4124e4aaf 100644 --- a/test/test_symplectic.cpp +++ b/test/test_symplectic.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #ifdef BOOST_HAS_FLOAT128 #include #endif @@ -29,6 +30,21 @@ std::vector vector_dHdp(std::vector p) { return p; } template std::vector vector_dHdq(std::vector q) { return q; } +// Equations of motion for simple pendulum +template +std::vector pendulum_vector_dHdp(std::vector p) { return p; } +template +std::vector pendulum_vector_dHdq(std::vector q) +{ + BOOST_MATH_STD_USING + std::vector q_return(q.size()); + for (unsigned i=0; i void test_invalid_parameters() { @@ -57,7 +73,7 @@ void test_harmonic_oscillator(const RealType tol, const std::string method) std::vector > p; std::vector > q; - std::tie(p, q) = boost::math::quadrature::integrate_hamiltonian(p0, q0, dt, steps, vector_dHdp, vector_dHdp, method); + std::tie(p, q) = boost::math::quadrature::integrate_hamiltonian(p0, q0, dt, steps, vector_dHdp, vector_dHdq, method); RealType p_val; RealType q_val; @@ -74,11 +90,53 @@ void test_harmonic_oscillator(const RealType tol, const std::string method) BOOST_CHECK_LE(max_error, tol); } +/* Test if SHO energy fluctuations are below a given tolerance*/ +template +void test_pendulum(const RealType tol, const std::string method) +{ + BOOST_MATH_STD_USING + + RealType dt = 0.05; + RealType t_end = 100; + unsigned int steps = t_end / dt; + + std::vector q0 = {boost::math::constants::pi() / 2}; + std::vector p0 = {0}; + + std::vector > p; + std::vector > q; + + std::tie(p, q) = boost::math::quadrature::integrate_hamiltonian(p0, q0, dt, steps, pendulum_vector_dHdp, pendulum_vector_dHdq, method); + + RealType p_val; + RealType q_val; + std::vector abs_energy_error(p.size()); + for (unsigned i=0; i < p.size(); i++) + { + p_val = p[i][0]; + q_val = q[i][0]; + // The energy of the system is H = p^2 / 2 + (1-cos(q)). With our initial condition + // this yields H = 1. Thus, we just want p^2 / 2 - cos(q) = 0 + abs_energy_error[i] = std::abs(std::pow(p_val, 2) / 2 - std::cos(q_val)); + } + + RealType max_error = *std::max_element(std::begin(abs_energy_error), std::end(abs_energy_error)); + BOOST_CHECK_LE(max_error, tol); +} + BOOST_AUTO_TEST_CASE(symplectic_quadrature) { test_invalid_parameters(); + + // SHO Tests test_harmonic_oscillator(1e-10, "Y6"); test_harmonic_oscillator(7e-4, "Y4"); test_harmonic_oscillator(7e-4, "Y2"); test_harmonic_oscillator(1e-11, "SRKNB6"); + + // Pendulum Tests + test_pendulum(1e-10, "Y6"); + test_pendulum(5e-4, "Y4"); + test_pendulum(5e-4, "Y2"); + test_pendulum(1e-8, "SRKNB6"); } From bc972a6ff358ed42c4e86a7653cad7a0b3c8039d Mon Sep 17 00:00:00 2001 From: Jacob Hass Date: Mon, 6 Jul 2026 20:49:31 -0700 Subject: [PATCH 17/22] Changed template to RandomAccessContainer --- include/boost/math/quadrature/symplectic.hpp | 150 ++++++++++--------- 1 file changed, 78 insertions(+), 72 deletions(-) diff --git a/include/boost/math/quadrature/symplectic.hpp b/include/boost/math/quadrature/symplectic.hpp index ba7bec42b3..3e584564d6 100644 --- a/include/boost/math/quadrature/symplectic.hpp +++ b/include/boost/math/quadrature/symplectic.hpp @@ -18,15 +18,15 @@ namespace boost{ namespace math { namespace quadrature { namespace detail { -template -std::pair second_order_yoshida(const ReturnType p0, - const ReturnType q0, - RealType dt, - Func dHdp, - Func dHdq) +template +std::pair second_order_yoshida(const RandomAccessContainer p0, + const RandomAccessContainer q0, + const typename RandomAccessContainer::value_type dt, + Func dHdp, + Func dHdq) { - ReturnType p = p0; - ReturnType q = q0; + RandomAccessContainer p = p0; + RandomAccessContainer q = q0; // Half step in q auto dHdp_val = dHdp(p); @@ -49,17 +49,18 @@ std::pair second_order_yoshida(const ReturnType p0, return std::make_pair(p, q); } -template -std::pair fourth_order_yoshida(const ReturnType p0, - const ReturnType q0, - const RealType dt, - Func dHdp, - Func dHdq) +template +std::pair fourth_order_yoshida(const RandomAccessContainer p0, + const RandomAccessContainer q0, + const typename RandomAccessContainer::value_type dt, + Func dHdp, + Func dHdq) { BOOST_MATH_STD_USING - - ReturnType p = p0; - ReturnType q = q0; + using RealType = typename RandomAccessContainer::value_type; + + RandomAccessContainer p = p0; + RandomAccessContainer q = q0; // RealType x0 = -(std::pow(2, 1/3) / (2 - std::pow(2, 1/3))); RealType x1 = 1 / (2 - std::pow(2, 1/3)); @@ -75,15 +76,17 @@ std::pair fourth_order_yoshida(const ReturnType p0, return std::make_pair(p, q); } -template -std::pair sixth_order_yoshida(const ReturnType p0, - const ReturnType q0, - RealType dt, - Func dHdp, - Func dHdq) +template +std::pair sixth_order_yoshida(const RandomAccessContainer p0, + const RandomAccessContainer q0, + const typename RandomAccessContainer::value_type dt, + Func dHdp, + Func dHdq) { - ReturnType p = p0; - ReturnType q = q0; + using RealType = typename RandomAccessContainer::value_type; + + RandomAccessContainer p = p0; + RandomAccessContainer q = q0; // Choosing "System A" solution // The following Mathematica command can calculate these coefficients to arbitrary precision @@ -106,17 +109,19 @@ std::pair sixth_order_yoshida(const ReturnType p0, return std::make_pair(p, q); } -template -std::pair SRKN_b_order_6(const ReturnType p0, - const ReturnType q0, - RealType dt, - Func dHdp, - Func dHdq) +template +std::pair SRKN_b_order_6(const RandomAccessContainer p0, + const RandomAccessContainer q0, + const typename RandomAccessContainer::value_type dt, + Func dHdp, + Func dHdq) { // This method implements SRKN_b^6 in Table 3 here // https://www.sciencedirect.com/science/article/pii/S0377042701004927 - ReturnType p = p0; - ReturnType q = q0; + using RealType = typename RandomAccessContainer::value_type; + + RandomAccessContainer p = p0; + RandomAccessContainer q = q0; RealType b1 = static_cast(0.0829844064174052); RealType b2 = static_cast(0.396309801498368); @@ -154,30 +159,31 @@ std::pair SRKN_b_order_6(const ReturnType p0, return std::make_pair(p, q); } -template -std::pair, std::vector > integrate_hamiltonian_imp(const ReturnType p0, - const ReturnType q0, - const RealType dt, - const unsigned steps, - Func dHdp, - Func dHdq, - std::string method, - const Policy& pol) +template +std::pair, std::vector > integrate_hamiltonian_imp(const RandomAccessContainer p0, + const RandomAccessContainer q0, + const typename RandomAccessContainer::value_type dt, + const unsigned steps, + Func dHdp, + Func dHdq, + std::string method, + const Policy& pol) { + using RealType = typename RandomAccessContainer::value_type; // Not sure how to make this function string nicer static const char* function = "boost::math::quadrature::integrate_hamiltonian(p0, q0, %1%, steps, dHdp, dHdq)"; if ((dt <= 0) || !(boost::math::isfinite)(dt)) { RealType val = (boost::math::policies::raise_domain_error(function, "Time step must be positive and finite but got: dt = %1%.\n", dt, pol)); - std::vector nan_vec = {ReturnType(val)}; + std::vector nan_vec = {RandomAccessContainer(val)}; return std::make_pair(nan_vec, nan_vec); } // Check if method is available std::vector available_methods = {"Y6", "Y4", "Y2"}; - typedef std::pair (*stepperType)(ReturnType, ReturnType, RealType, Func, Func); + typedef std::pair (*stepperType)(RandomAccessContainer, RandomAccessContainer, RealType, Func, Func); std::map m{{"Y6", sixth_order_yoshida}, {"Y4", fourth_order_yoshida}, @@ -185,13 +191,13 @@ std::pair, std::vector > integrate_hamiltoni {"SRKNB6", SRKN_b_order_6}}; stepperType stepper = m.at(method); - std::vector p(steps); - std::vector q(steps); + std::vector p(steps); + std::vector q(steps); p[0] = p0; q[0] = q0; - ReturnType p_current = p0; - ReturnType q_current = q0; + RandomAccessContainer p_current = p0; + RandomAccessContainer q_current = q0; for (unsigned i=1; i < steps; i++) { std::tie(p_current, q_current) = stepper(p_current, q_current, dt, dHdp, dHdq); @@ -202,38 +208,38 @@ std::pair, std::vector > integrate_hamiltoni } } // namespace detail -template -std::pair, std::vector > integrate_hamiltonian(const ReturnType p0, - const ReturnType q0, - const RealType dt, - const unsigned steps, - Func dHdp, - Func dHdq, - std::string method, - const Policy& pol) +template +std::pair, std::vector > integrate_hamiltonian(const RandomAccessContainer p0, + const RandomAccessContainer q0, + const typename RandomAccessContainer::value_type dt, + const unsigned steps, + Func dHdp, + Func dHdq, + std::string method, + const Policy& pol) { return detail::integrate_hamiltonian_imp(p0, q0, dt, steps, dHdp, dHdq, method, pol); } -template -std::pair, std::vector > integrate_hamiltonian(const ReturnType p0, - const ReturnType q0, - const RealType dt, - const unsigned steps, - Func dHdp, - Func dHdq, - std::string method) +template +std::pair, std::vector > integrate_hamiltonian(const RandomAccessContainer p0, + const RandomAccessContainer q0, + const typename RandomAccessContainer::value_type dt, + const unsigned steps, + Func dHdp, + Func dHdq, + std::string method) { return integrate_hamiltonian(p0, q0, dt, steps, dHdp, dHdq, method, boost::math::policies::policy<>()); } -template -std::pair, std::vector > integrate_hamiltonian(const ReturnType p0, - const ReturnType q0, - const RealType dt, - const unsigned steps, - Func dHdp, - Func dHdq) +template +std::pair, std::vector > integrate_hamiltonian(const RandomAccessContainer p0, + const RandomAccessContainer q0, + const typename RandomAccessContainer::value_type dt, + const unsigned steps, + Func dHdp, + Func dHdq) { return integrate_hamiltonian(p0, q0, dt, steps, dHdp, dHdq, "Y6", boost::math::policies::policy<>()); } From 40eea88c566fcde0705382e82527bcf4c8b250ea Mon Sep 17 00:00:00 2001 From: Jacob Hass Date: Sat, 11 Jul 2026 19:58:55 -0700 Subject: [PATCH 18/22] Added singleton support --- include/boost/math/quadrature/symplectic.hpp | 137 ++++++++++++------- test/test_symplectic.cpp | 28 ++-- 2 files changed, 104 insertions(+), 61 deletions(-) diff --git a/include/boost/math/quadrature/symplectic.hpp b/include/boost/math/quadrature/symplectic.hpp index 3e584564d6..310d23c46a 100644 --- a/include/boost/math/quadrature/symplectic.hpp +++ b/include/boost/math/quadrature/symplectic.hpp @@ -18,10 +18,64 @@ namespace boost{ namespace math { namespace quadrature { namespace detail { -template +template +using void_t = void; + +template +struct has_plus : std::false_type {}; + +template +struct has_plus() + std::declval())> > : std::true_type {}; + +template +typename std::enable_if::value, T>::type +add(T x, U y) +{ + return x + y; +} + +template +typename std::enable_if::value, T>::type +add(T vec1, U vec2) +{ + for (unsigned i=0; i < vec1.size(); i++) + { + vec1[i] = vec1[i] + vec2[i]; + } + return vec1; +} + +template +using void_t = void; + +template +struct has_mult : std::false_type {}; + +template +struct has_mult() * std::declval())> > : std::true_type {}; + +template +typename std::enable_if::value, T>::type +mult_prefactor(T x, U prefactor) +{ + return x * prefactor; +} + +template +typename std::enable_if::value, T>::type +mult_prefactor(T vec1, U prefactor) +{ + for (unsigned i=0; i < vec1.size(); i++) + { + vec1[i] = vec1[i] * prefactor; + } + return vec1; +} + +template std::pair second_order_yoshida(const RandomAccessContainer p0, const RandomAccessContainer q0, - const typename RandomAccessContainer::value_type dt, + const RealType dt, Func dHdp, Func dHdq) { @@ -29,35 +83,31 @@ std::pair second_order_yoshida(con RandomAccessContainer q = q0; // Half step in q - auto dHdp_val = dHdp(p); - for (unsigned i=0; i < q.size(); i++){ - q[i] = q[i] + dt / 2 * dHdp_val[i]; - } + RandomAccessContainer dHdp_val = dHdp(p); + RandomAccessContainer dq = mult_prefactor(dHdp_val, dt / 2); + q = add(q, dq); // Full step in p - auto dHdq_val = dHdq(q); - for (unsigned i=0; i < p.size(); i++){ - p[i] = p[i] - dt * dHdq_val[i]; - } + RandomAccessContainer dHdq_val = dHdq(q); + RandomAccessContainer dp = mult_prefactor(dHdq_val, -dt); + p = add(p, dp); // Half step in q dHdp_val = dHdp(p); - for (unsigned i=0; i < q.size(); i++){ - q[i] = q[i] + dt / 2 * dHdp_val[i]; - } + dq = mult_prefactor(dHdp_val, dt / 2); + q = add(q, dq); return std::make_pair(p, q); } -template +template std::pair fourth_order_yoshida(const RandomAccessContainer p0, const RandomAccessContainer q0, - const typename RandomAccessContainer::value_type dt, + const RealType dt, Func dHdp, Func dHdq) { BOOST_MATH_STD_USING - using RealType = typename RandomAccessContainer::value_type; RandomAccessContainer p = p0; RandomAccessContainer q = q0; @@ -76,15 +126,13 @@ std::pair fourth_order_yoshida(con return std::make_pair(p, q); } -template +template std::pair sixth_order_yoshida(const RandomAccessContainer p0, const RandomAccessContainer q0, - const typename RandomAccessContainer::value_type dt, + const RealType dt, Func dHdp, Func dHdq) { - using RealType = typename RandomAccessContainer::value_type; - RandomAccessContainer p = p0; RandomAccessContainer q = q0; @@ -109,17 +157,15 @@ std::pair sixth_order_yoshida(cons return std::make_pair(p, q); } -template +template std::pair SRKN_b_order_6(const RandomAccessContainer p0, const RandomAccessContainer q0, - const typename RandomAccessContainer::value_type dt, + const RealType dt, Func dHdp, Func dHdq) { // This method implements SRKN_b^6 in Table 3 here // https://www.sciencedirect.com/science/article/pii/S0377042701004927 - using RealType = typename RandomAccessContainer::value_type; - RandomAccessContainer p = p0; RandomAccessContainer q = q0; @@ -141,35 +187,32 @@ std::pair SRKN_b_order_6(const Ran b = b_weights[i]; a = a_weights[i]; - auto dHdp_val = dHdp(p); - for (unsigned j=0; j < q.size(); j++){ - q[j] = q[j] + b * dt * dHdp_val[j]; - } - - auto dHdq_val = dHdq(q); - for (unsigned j=0; j < p.size(); j++){ - p[j] = p[j] - a * dt * dHdq_val[j]; - } - } - // Have to do one more step in p - auto dHdp_val = dHdp(p); - for (unsigned j=0; j < q.size(); j++){ - q[j] = q[j] + b1 * dt * dHdp_val[j]; + RandomAccessContainer dHdp_val = dHdp(p); + RandomAccessContainer dq = mult_prefactor(dHdp_val, dt * b); + q = add(q, dq); + + RandomAccessContainer dHdq_val = dHdq(q); + RandomAccessContainer dp = mult_prefactor(dHdq_val, -a * dt); + p = add(p, dp); } + // Need to do one more step in q + RandomAccessContainer dHdp_val = dHdp(p); + RandomAccessContainer dq = mult_prefactor(dHdp_val, dt * b1); + q = add(q, dq); + return std::make_pair(p, q); } -template +template std::pair, std::vector > integrate_hamiltonian_imp(const RandomAccessContainer p0, const RandomAccessContainer q0, - const typename RandomAccessContainer::value_type dt, + const RealType dt, const unsigned steps, Func dHdp, Func dHdq, std::string method, const Policy& pol) { - using RealType = typename RandomAccessContainer::value_type; // Not sure how to make this function string nicer static const char* function = "boost::math::quadrature::integrate_hamiltonian(p0, q0, %1%, steps, dHdp, dHdq)"; @@ -208,10 +251,10 @@ std::pair, std::vector } } // namespace detail -template +template std::pair, std::vector > integrate_hamiltonian(const RandomAccessContainer p0, const RandomAccessContainer q0, - const typename RandomAccessContainer::value_type dt, + const RealType dt, const unsigned steps, Func dHdp, Func dHdq, @@ -221,10 +264,10 @@ std::pair, std::vector return detail::integrate_hamiltonian_imp(p0, q0, dt, steps, dHdp, dHdq, method, pol); } -template +template std::pair, std::vector > integrate_hamiltonian(const RandomAccessContainer p0, const RandomAccessContainer q0, - const typename RandomAccessContainer::value_type dt, + const RealType dt, const unsigned steps, Func dHdp, Func dHdq, @@ -233,10 +276,10 @@ std::pair, std::vector return integrate_hamiltonian(p0, q0, dt, steps, dHdp, dHdq, method, boost::math::policies::policy<>()); } -template +template std::pair, std::vector > integrate_hamiltonian(const RandomAccessContainer p0, const RandomAccessContainer q0, - const typename RandomAccessContainer::value_type dt, + const RealType dt, const unsigned steps, Func dHdp, Func dHdq) diff --git a/test/test_symplectic.cpp b/test/test_symplectic.cpp index e4124e4aaf..121214a60e 100644 --- a/test/test_symplectic.cpp +++ b/test/test_symplectic.cpp @@ -26,9 +26,9 @@ using boost::math::quadrature::integrate_hamiltonian; // Equations of motion for simple harmonic oscillator template -std::vector vector_dHdp(std::vector p) { return p; } +Real oscillator_dHdp(Real p) { return p; } template -std::vector vector_dHdq(std::vector q) { return q; } +Real oscillator_dHdq(Real q) { return q; } // Equations of motion for simple pendulum template @@ -48,13 +48,13 @@ std::vector pendulum_vector_dHdq(std::vector q) template void test_invalid_parameters() { - std::vector q0 = {1}; - std::vector p0 = {0}; + RealType q0 = 1; + RealType p0 = 0; // Negative timestep - BOOST_CHECK_THROW(boost::math::quadrature::integrate_hamiltonian(q0, p0, -0.1, 10, vector_dHdp, vector_dHdq), std::domain_error); + BOOST_CHECK_THROW(boost::math::quadrature::integrate_hamiltonian(q0, p0, -0.1, 10, oscillator_dHdp, oscillator_dHdq), std::domain_error); // Method not in {'Y6', 'Y4', 'Y2'} - BOOST_CHECK_THROW(boost::math::quadrature::integrate_hamiltonian(q0, p0, 0.1, 10, vector_dHdp, vector_dHdq, "InvalidMethod"), std::out_of_range); + BOOST_CHECK_THROW(boost::math::quadrature::integrate_hamiltonian(q0, p0, 0.1, 10, oscillator_dHdp, oscillator_dHdq, "InvalidMethod"), std::out_of_range); } /* Test if SHO energy fluctuations are below a given tolerance*/ @@ -67,21 +67,21 @@ void test_harmonic_oscillator(const RealType tol, const std::string method) RealType t_end = 100; unsigned int steps = t_end / dt; - std::vector q0 = {1}; - std::vector p0 = {0}; + RealType q0 = 1; + RealType p0 = 0; - std::vector > p; - std::vector > q; + std::vector p; + std::vector q; - std::tie(p, q) = boost::math::quadrature::integrate_hamiltonian(p0, q0, dt, steps, vector_dHdp, vector_dHdq, method); + std::tie(p, q) = boost::math::quadrature::integrate_hamiltonian(p0, q0, dt, steps, oscillator_dHdp, oscillator_dHdq, method); RealType p_val; RealType q_val; std::vector abs_energy_error(p.size()); for (unsigned i=0; i < p.size(); i++) { - p_val = p[i][0]; - q_val = q[i][0]; + p_val = p[i]; + q_val = q[i]; abs_energy_error[i] = std::abs(std::pow(p_val, 2) + std::pow(q_val, 2) - 1); } @@ -128,7 +128,7 @@ BOOST_AUTO_TEST_CASE(symplectic_quadrature) { test_invalid_parameters(); - // SHO Tests + // Simple Harmonic Oscillator Tests test_harmonic_oscillator(1e-10, "Y6"); test_harmonic_oscillator(7e-4, "Y4"); test_harmonic_oscillator(7e-4, "Y2"); From bba624144280fc373a3d4c13adb3ac396f505b91 Mon Sep 17 00:00:00 2001 From: Jacob Hass Date: Sun, 12 Jul 2026 10:13:51 -0700 Subject: [PATCH 19/22] Added higher order method --- test/test_symplectic.cpp | 79 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 3 deletions(-) diff --git a/test/test_symplectic.cpp b/test/test_symplectic.cpp index 121214a60e..fc89c8ad0f 100644 --- a/test/test_symplectic.cpp +++ b/test/test_symplectic.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #ifdef BOOST_HAS_FLOAT128 #include #endif @@ -45,6 +46,35 @@ std::vector pendulum_vector_dHdq(std::vector q) return q_return; } +// Equations of motion for Henon Heiles potential +template +std::vector hh_dHdp(std::vector p) +{ + return p; +} + +template +std::vector hh_dHdq(std::vector q) +{ + BOOST_MATH_STD_USING + std::vector dHdq(q.size()); + dHdq.at(0) = q.at(0) + 2 * q.at(0) * q.at(1); // x + 2*xy + dHdq.at(1) = q.at(1) + pow(q.at(1), 2.0) - pow(q.at(1), 2.0); // y + x^2 - y^2 + return dHdq; +} + +// The energy of the system is H = 1/2 (px^2 + py^2) + 1/2(x^2 + y^2) + x^2 y - y^3/3 +template +Real hh_energy(std::vector p, std::vector q) +{ + BOOST_MATH_STD_USING + Real x = q[0]; + Real y = q[1]; + Real px = p[0]; + Real py = p[1]; + return 0.5 * (pow(px, 2.) + pow(py, 2.)) + 0.5 * (pow(x, 2.) + pow(y, 2.)) + pow(x, 2.) * y - pow(y, 3.) / 3.; +} + template void test_invalid_parameters() { @@ -100,8 +130,8 @@ void test_pendulum(const RealType tol, const std::string method) RealType t_end = 100; unsigned int steps = t_end / dt; - std::vector q0 = {boost::math::constants::pi() / 2}; - std::vector p0 = {0}; + std::vector q0 = {boost::math::constants::pi() * 0.5}; + std::vector p0 = {0.}; std::vector > p; std::vector > q; @@ -117,13 +147,51 @@ void test_pendulum(const RealType tol, const std::string method) q_val = q[i][0]; // The energy of the system is H = p^2 / 2 + (1-cos(q)). With our initial condition // this yields H = 1. Thus, we just want p^2 / 2 - cos(q) = 0 - abs_energy_error[i] = std::abs(std::pow(p_val, 2) / 2 - std::cos(q_val)); + abs_energy_error[i] = std::abs(std::pow(p_val, 2.) / 2. - std::cos(q_val)); } RealType max_error = *std::max_element(std::begin(abs_energy_error), std::end(abs_energy_error)); BOOST_CHECK_LE(max_error, tol); } +/* Test if SHO energy fluctuations are below a given tolerance*/ +template +void test_hh_model(const RealType tol, const std::string method) +{ + BOOST_MATH_STD_USING + + RealType dt = 0.005; + RealType t_end = 100.; + unsigned int steps = t_end / dt; + + std::vector q0 = {0.05, 0}; + std::vector p0 = {0, 0.025}; + + RealType total_energy = hh_energy(p0, q0); + + std::vector > p; + std::vector > q; + + std::tie(p, q) = boost::math::quadrature::integrate_hamiltonian(p0, q0, dt, steps, hh_dHdp, hh_dHdq, method); + + std::vector p_val; + std::vector q_val; + std::vector abs_energy_error(p.size()); + RealType sum = 0; + for (unsigned i=0; i < p.size(); i++) + { + p_val = p[i]; + q_val = q[i]; + + abs_energy_error[i] = std::abs(hh_energy(p_val, q_val) - total_energy); + sum += abs_energy_error[i]; + } + + std::cout << std::setprecision(16) << sum / p.size() << std::endl; + RealType max_error = *std::max_element(std::begin(abs_energy_error), std::end(abs_energy_error)); + BOOST_CHECK_LE(max_error, tol); +} + BOOST_AUTO_TEST_CASE(symplectic_quadrature) { test_invalid_parameters(); @@ -139,4 +207,9 @@ BOOST_AUTO_TEST_CASE(symplectic_quadrature) test_pendulum(5e-4, "Y4"); test_pendulum(5e-4, "Y2"); test_pendulum(1e-8, "SRKNB6"); + + test_hh_model(1e-16, "SRKNB11"); + // test_hh_model(5e-16, "Y4"); + // test_hh_model(5e-16, "Y2"); + // test_hh_model(1e-16, "SRKNB6"); } From 89ef4c030a75376d056d44a7aead38c5f9cfc97a Mon Sep 17 00:00:00 2001 From: Jacob Hass Date: Sun, 12 Jul 2026 10:15:03 -0700 Subject: [PATCH 20/22] Added higher order method (previous commit was wrong file) --- include/boost/math/quadrature/symplectic.hpp | 62 ++++++++++++++++++-- test/test_symplectic.cpp | 2 +- 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/include/boost/math/quadrature/symplectic.hpp b/include/boost/math/quadrature/symplectic.hpp index 310d23c46a..241ceeae69 100644 --- a/include/boost/math/quadrature/symplectic.hpp +++ b/include/boost/math/quadrature/symplectic.hpp @@ -84,7 +84,7 @@ std::pair second_order_yoshida(con // Half step in q RandomAccessContainer dHdp_val = dHdp(p); - RandomAccessContainer dq = mult_prefactor(dHdp_val, dt / 2); + RandomAccessContainer dq = mult_prefactor(dHdp_val, 0.5 * dt); q = add(q, dq); // Full step in p @@ -94,7 +94,7 @@ std::pair second_order_yoshida(con // Half step in q dHdp_val = dHdp(p); - dq = mult_prefactor(dHdp_val, dt / 2); + dq = mult_prefactor(dHdp_val, 0.5 * dt); q = add(q, dq); return std::make_pair(p, q); @@ -113,8 +113,8 @@ std::pair fourth_order_yoshida(con RandomAccessContainer q = q0; // RealType x0 = -(std::pow(2, 1/3) / (2 - std::pow(2, 1/3))); - RealType x1 = 1 / (2 - std::pow(2, 1/3)); - RealType x0 = 1 - 2 * x1; + RealType x1 = 1. / (2. - std::cbrt(2.)); + RealType x0 = 1. - 2. * x1; std::vector weights = { x1, x0, x1 }; @@ -203,6 +203,57 @@ std::pair SRKN_b_order_6(const Ran return std::make_pair(p, q); } +template +std::pair SRKN_b_order_11(const RandomAccessContainer p0, + const RandomAccessContainer q0, + const RealType dt, + Func dHdp, + Func dHdq) +{ // This method implements SRKN_b^11 in Table 3 here + // https://www.sciencedirect.com/science/article/pii/S0377042701004927 + + RandomAccessContainer p = p0; + RandomAccessContainer q = q0; + + RealType b1 = static_cast(0.0414649985182624); + RealType b2 = static_cast(0.198128671918067); + RealType b3 = static_cast(-0.0400061921041533); + RealType b4 = static_cast(0.0752539843015807); + RealType b5 = static_cast(-0.0115113874206879); + RealType b6 = 0.5 - (b1 + b2 + b3 + b4 + b5); + + RealType a1 = static_cast(0.123229775946271); + RealType a2 = static_cast(0.290553797799558); + RealType a3 = static_cast(-0.127049212625417); + RealType a4 = static_cast(-0.246331761062075); + RealType a5 = static_cast(0.357208872795928); + RealType a6 = 1. - 2. * (a1 + a2 + a3 + a4 + a5); + + std::vector b_weights = {b1, b2, b3, b4, b5, b6, b6, b5, b4, b3, b2}; + std::vector a_weights = {a1, a2, a3, a4, a5, a6, a5, a4, a3, a2, a1}; + + RealType a, b; + for (unsigned int i=0; i < b_weights.size(); i++) + { + b = b_weights[i]; + a = a_weights[i]; + + RandomAccessContainer dHdp_val = dHdp(p); + RandomAccessContainer dq = mult_prefactor(dHdp_val, dt * b); + q = add(q, dq); + + RandomAccessContainer dHdq_val = dHdq(q); + RandomAccessContainer dp = mult_prefactor(dHdq_val, -a * dt); + p = add(p, dp); + } + // Need to do one more step in q + RandomAccessContainer dHdp_val = dHdp(p); + RandomAccessContainer dq = mult_prefactor(dHdp_val, dt * b1); + q = add(q, dq); + + return std::make_pair(p, q); +} + template std::pair, std::vector > integrate_hamiltonian_imp(const RandomAccessContainer p0, const RandomAccessContainer q0, @@ -231,7 +282,8 @@ std::pair, std::vector std::map m{{"Y6", sixth_order_yoshida}, {"Y4", fourth_order_yoshida}, {"Y2", second_order_yoshida}, - {"SRKNB6", SRKN_b_order_6}}; + {"SRKNB6", SRKN_b_order_6}, + {"SRKNB11", SRKN_b_order_11}}; stepperType stepper = m.at(method); std::vector p(steps); diff --git a/test/test_symplectic.cpp b/test/test_symplectic.cpp index fc89c8ad0f..99965f2cbc 100644 --- a/test/test_symplectic.cpp +++ b/test/test_symplectic.cpp @@ -208,7 +208,7 @@ BOOST_AUTO_TEST_CASE(symplectic_quadrature) test_pendulum(5e-4, "Y2"); test_pendulum(1e-8, "SRKNB6"); - test_hh_model(1e-16, "SRKNB11"); + // test_hh_model(1e-16, "SRKNB11"); // test_hh_model(5e-16, "Y4"); // test_hh_model(5e-16, "Y2"); // test_hh_model(1e-16, "SRKNB6"); From 7ab85402f77bc7f611538b1ff03a793503d64295 Mon Sep 17 00:00:00 2001 From: Jacob Hass Date: Sun, 12 Jul 2026 10:18:39 -0700 Subject: [PATCH 21/22] Added order 11 tests --- test/test_symplectic.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/test_symplectic.cpp b/test/test_symplectic.cpp index 99965f2cbc..dc94c65580 100644 --- a/test/test_symplectic.cpp +++ b/test/test_symplectic.cpp @@ -201,12 +201,14 @@ BOOST_AUTO_TEST_CASE(symplectic_quadrature) test_harmonic_oscillator(7e-4, "Y4"); test_harmonic_oscillator(7e-4, "Y2"); test_harmonic_oscillator(1e-11, "SRKNB6"); + test_harmonic_oscillator(2e-14, "SRKNB11"); // Pendulum Tests test_pendulum(1e-10, "Y6"); test_pendulum(5e-4, "Y4"); test_pendulum(5e-4, "Y2"); test_pendulum(1e-8, "SRKNB6"); + test_pendulum(1e-10, "SRKNB11"); // test_hh_model(1e-16, "SRKNB11"); // test_hh_model(5e-16, "Y4"); From f304b0ac5427c31b8d717af507fb32443c77c8e7 Mon Sep 17 00:00:00 2001 From: Jacob Hass Date: Sun, 12 Jul 2026 10:37:50 -0700 Subject: [PATCH 22/22] Fixed hh test derivatives --- test/test_symplectic.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/test/test_symplectic.cpp b/test/test_symplectic.cpp index dc94c65580..2f2e8c3212 100644 --- a/test/test_symplectic.cpp +++ b/test/test_symplectic.cpp @@ -58,8 +58,8 @@ std::vector hh_dHdq(std::vector q) { BOOST_MATH_STD_USING std::vector dHdq(q.size()); - dHdq.at(0) = q.at(0) + 2 * q.at(0) * q.at(1); // x + 2*xy - dHdq.at(1) = q.at(1) + pow(q.at(1), 2.0) - pow(q.at(1), 2.0); // y + x^2 - y^2 + dHdq[0] = q[0] + 2.0 * q[0] * q[1]; // x + 2*xy + dHdq[1] = q[1] + pow(q[0], 2.0) - pow(q[1], 2.0); // y + x^2 - y^2 return dHdq; } @@ -164,8 +164,8 @@ void test_hh_model(const RealType tol, const std::string method) RealType t_end = 100.; unsigned int steps = t_end / dt; - std::vector q0 = {0.05, 0}; - std::vector p0 = {0, 0.025}; + std::vector q0 = {0.5, 0}; + std::vector p0 = {0, 0.25}; RealType total_energy = hh_energy(p0, q0); @@ -210,8 +210,9 @@ BOOST_AUTO_TEST_CASE(symplectic_quadrature) test_pendulum(1e-8, "SRKNB6"); test_pendulum(1e-10, "SRKNB11"); - // test_hh_model(1e-16, "SRKNB11"); - // test_hh_model(5e-16, "Y4"); - // test_hh_model(5e-16, "Y2"); - // test_hh_model(1e-16, "SRKNB6"); + test_hh_model(1e-14, "SRKNB11"); + test_hh_model(1e-14, "Y6"); + test_hh_model(5e-11, "Y4"); + test_hh_model(5e-6, "Y2"); + test_hh_model(1e-13, "SRKNB6"); }