use std::ffi::CString;
use std::path::Path;
use librebound_sys::Simulation;
use crate::ffi;
use crate::{Error, Result};
pub struct Ephemeris {
ptr: *mut ffi::assist_ephem,
}
impl Ephemeris {
pub fn from_paths(planets: &Path, asteroids: &Path) -> Result<Self> {
let planets_cstr = path_to_cstring(planets)?;
let asteroids_cstr = path_to_cstring(asteroids)?;
let ptr =
unsafe { ffi::assist_ephem_create(planets_cstr.as_ptr(), asteroids_cstr.as_ptr()) };
if ptr.is_null() {
return Err(Error::EphemerisError(
"assist_ephem_create returned null — check file paths".into(),
));
}
Ok(Self { ptr })
}
pub fn as_ptr(&self) -> *const ffi::assist_ephem {
self.ptr
}
pub fn jd_ref(&self) -> f64 {
unsafe { ffi::assist_rs_ephem_get_jd_ref(self.ptr) }
}
pub fn set_jd_ref(&mut self, jd: f64) {
unsafe { ffi::assist_rs_ephem_set_jd_ref(self.ptr, jd) }
}
pub fn c_au_per_day(&self) -> f64 {
unsafe { ffi::assist_rs_ephem_get_c_au_per_day(self.ptr) }
}
pub fn mjd_to_assist_time(&self, mjd_tdb: f64) -> f64 {
(mjd_tdb + 2_400_000.5) - self.jd_ref()
}
pub fn earth_radius_au(&self) -> f64 {
unsafe { ffi::assist_rs_ephem_get_re_eq(self.ptr) }
}
pub fn emrat(&self) -> f64 {
unsafe { ffi::assist_rs_ephem_get_emrat(self.ptr) }
}
pub fn get_body_state(&self, body_id: i32, t: f64) -> Result<ffi::reb_particle> {
let mut error: i32 = 0;
let p = unsafe { ffi::assist_get_particle_with_error(self.ptr, body_id, t, &mut error) };
if error != 0 {
return Err(Error::EphemerisError(format!(
"assist_get_particle failed for body {body_id} at t={t}: error code {error}"
)));
}
Ok(p)
}
pub fn get_body_state_array(&self, body_id: i32, t: f64) -> Result<[f64; 6]> {
let p = self.get_body_state(body_id, t)?;
Ok([p.x, p.y, p.z, p.vx, p.vy, p.vz])
}
}
impl Drop for Ephemeris {
fn drop(&mut self) {
if !self.ptr.is_null() {
unsafe { ffi::assist_ephem_free(self.ptr) }
}
}
}
unsafe impl Send for Ephemeris {}
unsafe impl Sync for Ephemeris {}
pub struct AssistSim {
sim: Simulation,
ax: *mut ffi::assist_extras,
particle_params: Option<Vec<f64>>,
}
impl AssistSim {
pub fn new(mut sim: Simulation, ephem: &Ephemeris) -> Result<Self> {
let ax = unsafe { ffi::assist_attach(sim.as_mut_ptr(), ephem.ptr) };
if ax.is_null() {
return Err(Error::Other("assist_attach returned null".into()));
}
sim.set_exact_finish_time(true);
Ok(Self {
sim,
ax,
particle_params: None,
})
}
pub fn set_forces(&mut self, flags: i32) {
unsafe { ffi::assist_rs_extras_set_forces(self.ax, flags) }
}
pub fn forces(&self) -> i32 {
unsafe { ffi::assist_rs_extras_get_forces(self.ax) }
}
pub fn sim(&self) -> &Simulation {
&self.sim
}
pub fn sim_mut(&mut self) -> &mut Simulation {
&mut self.sim
}
pub fn set_alpha(&mut self, v: f64) {
unsafe { ffi::assist_rs_extras_set_alpha(self.ax, v) }
}
pub fn alpha(&self) -> f64 {
unsafe { ffi::assist_rs_extras_get_alpha(self.ax) }
}
pub fn set_nk(&mut self, v: f64) {
unsafe { ffi::assist_rs_extras_set_nk(self.ax, v) }
}
pub fn nk(&self) -> f64 {
unsafe { ffi::assist_rs_extras_get_nk(self.ax) }
}
pub fn set_nm(&mut self, v: f64) {
unsafe { ffi::assist_rs_extras_set_nm(self.ax, v) }
}
pub fn nm(&self) -> f64 {
unsafe { ffi::assist_rs_extras_get_nm(self.ax) }
}
pub fn set_nn(&mut self, v: f64) {
unsafe { ffi::assist_rs_extras_set_nn(self.ax, v) }
}
pub fn nn(&self) -> f64 {
unsafe { ffi::assist_rs_extras_get_nn(self.ax) }
}
pub fn set_r0(&mut self, v: f64) {
unsafe { ffi::assist_rs_extras_set_r0(self.ax, v) }
}
pub fn r0(&self) -> f64 {
unsafe { ffi::assist_rs_extras_get_r0(self.ax) }
}
pub fn set_particle_params(&mut self, mut params: Vec<f64>) {
let n = self.sim.n_particles();
assert_eq!(
params.len(),
3 * n,
"particle_params length must equal 3 * n_particles (got {}, expected {})",
params.len(),
3 * n
);
let ptr = params.as_mut_ptr();
unsafe { ffi::assist_rs_extras_set_particle_params(self.ax, ptr) }
self.particle_params = Some(params);
}
pub fn integrate(&mut self, tmax: f64) -> Result<()> {
self.sim.integrate(tmax).map_err(Error::from)
}
pub fn integrate_or_interpolate(&mut self, t: f64) -> Result<()> {
unsafe { ffi::assist_integrate_or_interpolate(self.ax, t) }
Ok(())
}
#[doc(hidden)]
pub fn raw_sim_ptr_mut(&mut self) -> *mut librebound_sys::ffi::reb_simulation {
self.sim.as_mut_ptr()
}
pub fn reset_integrator(&mut self) {
unsafe {
librebound_sys::ffi::assist_rs_ias15_zero_state(self.sim.as_mut_ptr());
ffi::assist_rs_ephem_cache_reset(self.ax);
}
}
pub fn update_nongrav_coeffs(&mut self, a1: f64, a2: f64, a3: f64) -> Result<()> {
let params = self.particle_params.as_mut().ok_or_else(|| {
Error::Other(
"update_nongrav_coeffs called before set_particle_params; \
install a particle_params array first"
.into(),
)
})?;
params[0] = a1;
params[1] = a2;
params[2] = a3;
Ok(())
}
}
impl Drop for AssistSim {
fn drop(&mut self) {
if !self.ax.is_null() {
unsafe {
ffi::assist_detach(self.sim.as_mut_ptr(), self.ax);
ffi::assist_free(self.ax);
}
}
}
}
fn path_to_cstring(path: &Path) -> Result<CString> {
let s = path.to_str().ok_or_else(|| {
Error::Other(format!(
"path contains non-UTF8 characters: {}",
path.display()
))
})?;
CString::new(s).map_err(|e| Error::Other(format!("path contains null byte: {e}")))
}