From eb64a0681627b561478d37886d50d5a9e143a28e Mon Sep 17 00:00:00 2001 From: Jacob Hass Date: Sat, 20 Jun 2026 15:26:34 -0700 Subject: [PATCH 01/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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);