165 lines
No EOL
5.4 KiB
C++
165 lines
No EOL
5.4 KiB
C++
#include <algorithm>
|
|
#include <array>
|
|
#include <boost/numeric/odeint.hpp>
|
|
#include <fstream>
|
|
#include <gsl/gsl_spline.h>
|
|
#include <iostream>
|
|
#include <numeric>
|
|
#include <string>
|
|
#include <stdexcept>
|
|
#include <vector>
|
|
|
|
#include "loadtxt.h"
|
|
|
|
|
|
|
|
// Our units are {kiloparsec, solar mass, gigayear}
|
|
constexpr double G = 4.498317481097514e-06;
|
|
|
|
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:
|
|
Interp(std::vector<double>& x, std::vector<double>& y)
|
|
{
|
|
acc = gsl_interp_accel_alloc();
|
|
spline = gsl_spline_alloc(gsl_interp_cspline, x.size());
|
|
gsl_spline_init(spline, x.data(), y.data(), x.size());
|
|
}
|
|
Interp() {}
|
|
inline double operator()(double x) const
|
|
{
|
|
return gsl_spline_eval(spline, x, acc);
|
|
}
|
|
|
|
private:
|
|
gsl_interp_accel *acc;
|
|
gsl_spline *spline;
|
|
};
|
|
|
|
class Plummer {
|
|
public:
|
|
Plummer(double M, double b)
|
|
: M(M), b(b) {}
|
|
void calc_acceleration(const double *pos, double *acc)
|
|
{
|
|
double r2 = (pos[0]*pos[0] + pos[1]*pos[1] + pos[2]*pos[2] + b*b);
|
|
double r = sqrt(r2);
|
|
double r3_inv = 1/(r*r2);
|
|
acc[0] = -G*M*pos[0]*r3_inv;
|
|
acc[1] = -G*M*pos[1]*r3_inv;
|
|
acc[2] = -G*M*pos[2]*r3_inv;
|
|
}
|
|
|
|
private:
|
|
double M, b;
|
|
};
|
|
|
|
class Galaxy {
|
|
public:
|
|
Galaxy(std::string file_name)
|
|
{
|
|
auto data = Loadtxt("file.dat", {1, 2, 3}).get_cols();
|
|
auto& t_data = data[0];
|
|
auto& halo_m_data = data[1];
|
|
auto& halo_b_data = data[2];
|
|
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; });
|
|
interp_halo_m = Interp(t_data, halo_m_data);
|
|
interp_halo_b = Interp(t_data, halo_b_data);
|
|
}
|
|
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_b = interp_halo_b(t);
|
|
Plummer plummer(halo_m, halo_b);
|
|
f[0] = y[3]; // vx -> x'
|
|
f[1] = y[4]; // vy -> y'
|
|
f[2] = y[5]; // vz -> z'
|
|
plummer.calc_acceleration(y.data(), f.data()+3); // a -> v'
|
|
}
|
|
|
|
private:
|
|
Interp interp_halo_m;
|
|
Interp interp_halo_b;
|
|
} galaxy("file.dat");
|
|
|
|
extern "C"
|
|
int integrate(const double y0[], const double t_max, const double stride_size, double y[])
|
|
{
|
|
using namespace boost::numeric::odeint;
|
|
using Coordinates = std::array<double, 6>;
|
|
auto stepper = bulirsch_stoer<Coordinates>(1E-7, 0);
|
|
auto function_wrapper = [](const Coordinates &x, Coordinates &dxdt, const double t) { return galaxy.func(x, dxdt, t); };
|
|
const int stride_count = t_max / stride_size;
|
|
Coordinates y_current;
|
|
std::copy(y0, y0+6, begin(y_current));
|
|
std::copy(y0, y0+6, y);
|
|
double t = 0;
|
|
const double h = 1./4096.;
|
|
for (int i=0; i<stride_count; i++) {
|
|
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;
|
|
}
|
|
}
|
|
|
|
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()
|
|
{
|
|
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 y0[] = {80,0,0,0,80,0};
|
|
for (int i=0; i<8000; i++)
|
|
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;
|
|
} |