diff --git a/doc/equations/harmonic_oscillator.png b/doc/equations/harmonic_oscillator.png
new file mode 100644
index 0000000000..427a3ea508
Binary files /dev/null and b/doc/equations/harmonic_oscillator.png differ
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..7e46490c61 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.qbk]
[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..95c3de0367
--- /dev/null
+++ b/doc/quadrature/symplectic.qbk
@@ -0,0 +1,106 @@
+[/
+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 and methods by [@https://www.sciencedirect.com/science/article/pii/S0377042701004927?fr=RR-2&ref=pdf_download&rr=a1b51ec059e1fbec Blanes and Moan].
+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
+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::vector dHdp(std::vector p)
+ {
+ // calculate the partial derivatives with respect to each p_i
+ }
+
+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.
+The 4th order and 6th order symplectic Runge-Kutta-Nystrom methods from Table 3 of Blanes and Moan
+are available using the strings "SRKNB6" and "SRKNB11".
+
+[optional_policy]
+
+References:
+
+Yoshida, Haruo. [`Construction of higher order symplectic integrators], Physics Letters A, 150.5-7 (1990): 262-268.
+
+Blanes, Sergio, and Per Christian Moan. [`Practical symplectic partitioned runge–kutta and runge–kutta–nyström methods`], Journal of Computational and Applied Mathematics 142.2 (2002): 313-330.
+
+[endsect] [/section:symplectic Symplectic Integration]
+
diff --git a/include/boost/math/quadrature/symplectic.hpp b/include/boost/math/quadrature/symplectic.hpp
new file mode 100644
index 0000000000..3f82d69ec8
--- /dev/null
+++ b/include/boost/math/quadrature/symplectic.hpp
@@ -0,0 +1,340 @@
+// 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
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace boost{ namespace math { namespace quadrature { namespace detail {
+
+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 RealType dt,
+ Func dHdp,
+ Func dHdq)
+{
+ RandomAccessContainer p = p0;
+ RandomAccessContainer q = q0;
+
+ // Half step in q
+ RandomAccessContainer dHdp_val = dHdp(p);
+ RandomAccessContainer dq = mult_prefactor(dHdp_val, 0.5 * dt);
+ q = add(q, dq);
+
+ // Full step in p
+ RandomAccessContainer dHdq_val = dHdq(q);
+ RandomAccessContainer dp = mult_prefactor(dHdq_val, -dt);
+ p = add(p, dp);
+
+ // Half step in q
+ dHdp_val = dHdp(p);
+ dq = mult_prefactor(dHdp_val, 0.5 * dt);
+ q = add(q, dq);
+
+ return std::make_pair(p, q);
+}
+
+template
+std::pair fourth_order_yoshida(const RandomAccessContainer p0,
+ const RandomAccessContainer q0,
+ const RealType dt,
+ Func dHdp,
+ Func dHdq)
+{
+ BOOST_MATH_STD_USING
+
+ RandomAccessContainer p = p0;
+ RandomAccessContainer q = q0;
+
+ // RealType x0 = -(std::pow(2, 1/3) / (2 - std::pow(2, 1/3)));
+ RealType x1 = 1. / (2. - std::cbrt(2.));
+ RealType x0 = 1. - 2. * x1;
+
+ 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 RandomAccessContainer p0,
+ const RandomAccessContainer q0,
+ const RealType dt,
+ Func dHdp,
+ Func dHdq)
+{
+ RandomAccessContainer p = p0;
+ RandomAccessContainer q = q0;
+
+ // Choosing "System A" solution
+ // 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};
+
+ 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 SRKN_b_order_6(const RandomAccessContainer p0,
+ const RandomAccessContainer q0,
+ 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
+
+ RandomAccessContainer p = p0;
+ RandomAccessContainer 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];
+
+ 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 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,
+ 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)";
+
+ if ((dt <= 0) || !(boost::math::isfinite)(dt))
+ {
+ boost::math::policies::raise_domain_error(function, "Time step must be positive and finite but got: dt = %1%.\n", dt, pol);
+ }
+
+ // Check if method is available
+ std::vector available_methods = {"Y6", "Y4", "Y2"};
+
+ typedef std::pair (*stepperType)(RandomAccessContainer, RandomAccessContainer, RealType, Func, Func);
+
+ std::map m{{"Y6", sixth_order_yoshida},
+ {"Y4", fourth_order_yoshida},
+ {"Y2", second_order_yoshida},
+ {"SRKNB6", SRKN_b_order_6},
+ {"SRKNB11", SRKN_b_order_11}};
+ stepperType stepper = m.at(method);
+
+ std::vector p(steps);
+ std::vector q(steps);
+ p[0] = p0;
+ q[0] = 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);
+ p[i] = p_current;
+ q[i] = q_current;
+ }
+ return std::make_pair(p, q);
+}
+} // namespace detail
+
+template
+std::pair, std::vector > integrate_hamiltonian(const RandomAccessContainer p0,
+ const RandomAccessContainer 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 RandomAccessContainer p0,
+ const RandomAccessContainer 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 RandomAccessContainer p0,
+ const RandomAccessContainer 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
diff --git a/test/Jamfile.v2 b/test/Jamfile.v2
index 18d5d9ae01..b19a34ff27 100644
--- a/test/Jamfile.v2
+++ b/test/Jamfile.v2
@@ -1343,6 +1343,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 ]
;
test-suite autodiff :
diff --git a/test/test_symplectic.cpp b/test/test_symplectic.cpp
new file mode 100644
index 0000000000..8be7cf102b
--- /dev/null
+++ b/test/test_symplectic.cpp
@@ -0,0 +1,228 @@
+/*
+ * 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)
+ */
+#define BOOST_TEST_MODULE symplectic_quadrature
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#if __has_include()
+# include
+#endif
+
+using boost::math::quadrature::integrate_hamiltonian;
+
+// Equations of motion for simple harmonic oscillator
+template
+Real oscillator_dHdp(Real p) { return p; }
+template
+Real oscillator_dHdq(Real 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
+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[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;
+}
+
+// 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()
+{
+ RealType q0 = 1;
+ RealType p0 = 0;
+ // Negative timestep
+ 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, oscillator_dHdp, oscillator_dHdq, "InvalidMethod"), std::out_of_range);
+}
+
+/* Test if SHO energy fluctuations are below a given tolerance*/
+template
+void test_harmonic_oscillator(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;
+
+ 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, 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];
+ q_val = q[i];
+
+ abs_energy_error[i] = std::abs(std::pow(p_val, 2) + std::pow(q_val, 2) - 1);
+ }
+
+ 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_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() * 0.5};
+ 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);
+}
+
+/* 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;
+ unsigned int steps = 20000;
+
+ std::vector q0 = {0.5, 0};
+ std::vector p0 = {0, 0.25};
+
+ 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] = abs(hh_energy(p_val, q_val) - total_energy);
+ sum += abs_energy_error[i];
+ }
+
+ 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();
+
+ // Test doubles
+ // Simple Harmonic Oscillator 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");
+ 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");
+
+ // Henon Heiles Model
+ 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");
+
+ // Test floats
+ test_harmonic_oscillator(6e-6, "Y6");
+ test_pendulum(5e-6, "Y6");
+ test_hh_model(5e-6, "Y6");
+
+ // Test long doubles
+ test_harmonic_oscillator(1e-10, "Y6");
+ test_pendulum(1e-10, "Y6");
+ test_hh_model(1e-14, "Y6");
+
+ // Test multiprecision
+ test_hh_model(1e-16, "Y6");
+}