Moved from GSL Runge-Kutta to Boost Bulirsch-Stoer

This commit is contained in:
Yohai Meiron 2020-04-21 20:05:56 -04:00
parent 9ec0633094
commit c2051e9596
3 changed files with 90 additions and 43 deletions

View file

@ -1,5 +1,6 @@
OPTIMIZATION ?= 3 OPTIMIZATION ?= 3
CXXFLAGS += -O$(OPTIMIZATION) CXXFLAGS += -O$(OPTIMIZATION)
INC += -I/home/meiron/boost_1_72_0
LIB += -lgsl LIB += -lgsl
EXECUTABLE ?= main EXECUTABLE ?= main

112
main.cpp
View file

@ -1,8 +1,7 @@
#include <algorithm> #include <algorithm>
#include <array>
#include <boost/numeric/odeint.hpp>
#include <fstream> #include <fstream>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_odeiv2.h>
#include <gsl/gsl_spline.h> #include <gsl/gsl_spline.h>
#include <iostream> #include <iostream>
#include <numeric> #include <numeric>
@ -13,12 +12,17 @@
#include "loadtxt.h" #include "loadtxt.h"
extern "C" const int gsl_success() { return GSL_SUCCESS; } // It's zero, but just for clarity sake.
// Our units are {kiloparsec, solar mass, gigayear} // Our units are {kiloparsec, solar mass, gigayear}
constexpr double G = 4.498317481097514e-06; constexpr double G = 4.498317481097514e-06;
class Interp { class Interp {
// This is a wrapper around GSL spline interpolation. I tried to use
// boost::math::interpolators but as of version 1.72 none were suitable:
// barycentric_rational is the one suitalbe for non-uniform sampling but it
// is very slow. I also tried to resample the data uniformly using
// barycentric rational interpolation and then using cardinal_cubic_b_spline
// on the result, but was still slower than GSL.
public: public:
Interp(std::vector<double>& x, std::vector<double>& y) Interp(std::vector<double>& x, std::vector<double>& y)
{ {
@ -63,12 +67,12 @@ public:
auto& t_data = data[0]; auto& t_data = data[0];
auto& halo_m_data = data[1]; auto& halo_m_data = data[1];
auto& halo_b_data = data[2]; auto& halo_b_data = data[2];
std::transform(t_data.begin(), t_data.end(), t_data.begin(), [](const double& x){ return x-2.145; }); std::transform(begin(t_data), end(t_data), begin(t_data), [t0=t_data[0]](const double& x){ return x-t0; });
std::transform(halo_b_data.begin(), halo_b_data.end(), halo_b_data.begin(), [](const double& x){ return x*0.7664209365408798; }); std::transform(halo_b_data.begin(), halo_b_data.end(), halo_b_data.begin(), [](const double& x){ return x*0.7664209365408798; });
interp_halo_m = Interp(t_data, halo_m_data); interp_halo_m = Interp(t_data, halo_m_data);
interp_halo_b = Interp(t_data, halo_b_data); interp_halo_b = Interp(t_data, halo_b_data);
} }
int func(double t, const double y[], double f[], void *params) void func(const std::array<double, 6> &y, std::array<double, 6> &f, const double t)
{ {
double halo_m = interp_halo_m(t); double halo_m = interp_halo_m(t);
double halo_b = interp_halo_b(t); double halo_b = interp_halo_b(t);
@ -76,56 +80,86 @@ public:
f[0] = y[3]; // vx -> x' f[0] = y[3]; // vx -> x'
f[1] = y[4]; // vy -> y' f[1] = y[4]; // vy -> y'
f[2] = y[5]; // vz -> z' f[2] = y[5]; // vz -> z'
plummer.calc_acceleration(y, f+3); // a -> v' plummer.calc_acceleration(y.data(), f.data()+3); // a -> v'
return GSL_SUCCESS;
} }
private: private:
Interp interp_halo_m; Interp interp_halo_m;
Interp interp_halo_b; Interp interp_halo_b;
} galaxy("file.dat"); } galaxy("file.dat");
// Not very nice to have it as a global variable but GSL will have problem otherwise.
int jac(double t, const double y[], double *dfdy, double dfdt[], void *params) { return GSL_SUCCESS; }
inline int func(double t, const double y[], double f[], void *params)
{
return galaxy.func(t, y, f, params);
}
extern "C" extern "C"
int integrate(const double y0[], const double t_max, const double step_size, double y[]) int integrate(const double y0[], const double t_max, const double stride_size, double y[])
{ {
double t = 0; using namespace boost::numeric::odeint;
constexpr double h = 1./4096.; using Coordinates = std::array<double, 6>;
auto stepper = bulirsch_stoer<Coordinates>(1E-7, 0);
if (step_size/h - (int)(step_size/h) != 0) throw std::runtime_error("step_size must be a multiple of h"); auto function_wrapper = [](const Coordinates &x, Coordinates &dxdt, const double t) { return galaxy.func(x, dxdt, t); };
constexpr double epsabs = 1e-7; const int stride_count = t_max / stride_size;
constexpr double epsrel = 0; Coordinates y_current;
const gsl_odeiv2_step_type *T = gsl_odeiv2_step_rk8pd; std::copy(y0, y0+6, begin(y_current));
gsl_odeiv2_step *s = gsl_odeiv2_step_alloc(T, 6);
gsl_odeiv2_evolve *e = gsl_odeiv2_evolve_alloc(6);
gsl_odeiv2_control *c = gsl_odeiv2_control_y_new(epsabs, 0);
gsl_odeiv2_system sys = {func, jac, 6, nullptr};
gsl_odeiv2_driver *d = gsl_odeiv2_driver_alloc_y_new(&sys, T, h, epsabs, epsrel);
int step = 0;
const int step_max = t_max / step_size;
std::copy(y0, y0+6, y); std::copy(y0, y0+6, y);
for (int step=0; step<step_max; step++) { double t = 0;
std::copy(y+step*6, y+(step+1)*6, y+(step+1)*6); const double h = 1./4096.;
int status = gsl_odeiv2_driver_apply(d, &t, (step+1)*step_size, y+(step+1)*6); for (int i=0; i<stride_count; i++) {
if (status != GSL_SUCCESS) return status; integrate_adaptive(stepper, function_wrapper, y_current, t, t+stride_size, h);
// NOTE h here is just a recommended initial step size for the stepper,
// the actual step is adapted as needed. Since the integration is
// interrupted here in order for the data to be saved, the result
// somewhat depends on stride_size.
std::copy(begin(y_current), end(y_current), y+(i+1)*6);
t += stride_size;
} }
return GSL_SUCCESS;
} }
double circular_energy(double(*pot)(double), double L)
{
auto effective_potential = [&](double r) { return pot(r)+0.5*(L*L)/(r*r); };
return pot(3);
}
/*def circular_energy(Phi, L, PhiPrime=None, InitialGuess=1.0, GetRadius=False):
EffectivePotential = lambda r: Phi(r) + 0.5*(L/r)**2
EffectivePotentialPrime = None
if not PhiPrime is None: EffectivePotentialPrime = lambda r: PhiPrime(r) - L**2/r**3
Minimization = scipy.optimize.minimize(EffectivePotential, InitialGuess, method='BFGS', jac=EffectivePotentialPrime, tol=1.0E-08)
# Minimization = scipy.optimize.minimize(EffectivePotential, InitialGuess, method='L-BFGS-B', bounds=[(0,None)], jac=EffectivePotentialPrime)
# There is some risk that the negative value of r will be found as the minimum. To avoid this we can either set boundary conditions which might complicate the the numerical procedure, or just accept this and return the absolute value, which we can do here.
if GetRadius: return Minimization.fun, abs(Minimization.x[0])
return Minimization.fun*/
int main() int main()
{ {
std::cout << "bye" << std::endl; std::cout << "Hi" << std::endl;
/*std::vector<double> x(500);
std::vector<double> y(500);
for (int i=0; i<500; i++) {
x[i] = i;
y[i] = 2*i;
}
// populate x, y, then:
boost::math::barycentric_rational<double> interpolant(std::move(x), std::move(y));
std::cout << interpolant(5.5) << std::endl;*/
double y[12]; double y[12];
double y0[] = {80,0,0,0,80,0}; double y0[] = {80,0,0,0,80,0};
for (int i=0; i<30000; i++) for (int i=0; i<8000; i++)
integrate(y0, 10, 10, y); integrate(y0, 10, 10, y);
/*double y0[] = {80,0,0,0,80,0};
double y[12];
integrate(y0, 10, 10, y);
for (int i=0; i<12; i++) std::cout << y[i] << std::endl;*/
//std::vector<double> w = {8, 0.12, 0.04, -0.08, 200, -0.001};
std::cout << "Bye" << std::endl;
return 0; return 0;
} }

20
plot.py
View file

@ -1,10 +1,13 @@
#pylint: disable=W0401,W0614,W0622
#All pylint codes: http://pylint.pycqa.org/en/latest/technical_reference/features.html
import numpy as np import numpy as np
import ctypes, subprocess import ctypes, subprocess
recompile = True recompile = True
if recompile: if recompile:
p = subprocess.Popen('g++ -shared -o libmain.so -fPIC loadtxt.cpp main.cpp -lgsl'.split()) p = subprocess.Popen('g++ -I/home/meiron/boost_1_72_0 -shared -o libmain.so -fPIC loadtxt.cpp main.cpp -lgsl'.split())
p.wait() p.wait()
if p.returncode != 0: raise RuntimeError(p.returncode) if p.returncode != 0: raise RuntimeError(p.returncode)
@ -18,12 +21,21 @@ def integrate(y0, t_max, step_size=None):
y = np.array(y).reshape(size,6) y = np.array(y).reshape(size,6)
return np.array(y), status return np.array(y), status
gsl_success = libmain.gsl_success() def integrate_boost(y0, t_max, step_size=None):
y0 = (ctypes.c_double*6)(*y0)
if step_size is None: step_size = t_max
size = int(t_max // step_size) + 1
y = (ctypes.c_double*size*6)()
libmain.integrate_boost(y0, ctypes.c_double(t_max), ctypes.c_double(step_size), y)
y = np.array(y).reshape(size,6)
return np.array(y), 0
#gsl_success = libmain.gsl_success()
from pylab import * from pylab import *
t_max = 10 t_max = 10
ic = [80,0,0,0,80,0] ic = [80,0,0,0,80,0]
res = integrate(ic, t_max, step_size=1/4096) res = integrate(ic, t_max, step_size=32/4096)
x, y, z, vx, vy, vz = res[0].T x, y, z, vx, vy, vz = res[0].T
zzz = x[-1] zzz = x[-1]
plot(x,y) plot(x,y)
@ -32,5 +44,5 @@ res = integrate(ic, t_max)
x, y, z, vx, vy, vz = res[0].T x, y, z, vx, vy, vz = res[0].T
plot(x,y,'o') plot(x,y,'o')
print(zzz - x[-1]) print(zzz - x[-1])
# gca().set_aspect('equal') gca().set_aspect('equal')
show() show()