#[pyclass(name = "EphemerisSource", module = "brahe._brahe", eq, eq_int, from_py_object)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PyEphemerisSource {
LowPrecision = 0,
DE440s = 1,
DE440 = 2,
}
impl From<PyEphemerisSource> for propagators::force_model_config::EphemerisSource {
fn from(source: PyEphemerisSource) -> Self {
match source {
PyEphemerisSource::LowPrecision => propagators::force_model_config::EphemerisSource::LowPrecision,
PyEphemerisSource::DE440s => propagators::force_model_config::EphemerisSource::DE440s,
PyEphemerisSource::DE440 => propagators::force_model_config::EphemerisSource::DE440,
}
}
}
impl TryFrom<PyEphemerisSource> for spice::SPKKernel {
type Error = PyErr;
fn try_from(source: PyEphemerisSource) -> Result<Self, Self::Error> {
let source = propagators::force_model_config::EphemerisSource::from(source);
spice::SPKKernel::try_from(source)
.map_err(|e| exceptions::PyValueError::new_err(e.to_string()))
}
}
fn de_kernel_from_py_source(source: PyEphemerisSource) -> PyResult<spice::SPKKernel> {
spice::SPKKernel::try_from(source)
}
#[pyfunction]
#[pyo3(name = "sun_position")]
fn py_sun_position<'py>(py: Python<'py>, epc: &PyEpoch) -> PyResult<Bound<'py, PyArray<f64, Ix1>>> {
let r = orbit_dynamics::sun_position(epc.obj);
Ok(vector_to_numpy!(py, r, 3, f64))
}
#[pyfunction]
#[pyo3(name = "moon_position")]
fn py_moon_position<'py>(py: Python<'py>, epc: &PyEpoch) -> PyResult<Bound<'py, PyArray<f64, Ix1>>> {
let r = orbit_dynamics::moon_position(epc.obj);
Ok(vector_to_numpy!(py, r, 3, f64))
}
#[pyfunction]
#[pyo3(name = "sun_position_de")]
fn py_sun_position_de<'py>(py: Python<'py>, epc: &PyEpoch, source: PyEphemerisSource) -> PyResult<Bound<'py, PyArray<f64, Ix1>>> {
let kernel = de_kernel_from_py_source(source)?;
let r = spice::sun_position_de(epc.obj, kernel)
.map_err(|e| exceptions::PyRuntimeError::new_err(e.to_string()))?;
Ok(vector_to_numpy!(py, r, 3, f64))
}
#[pyfunction]
#[pyo3(name = "moon_position_de")]
fn py_moon_position_de<'py>(py: Python<'py>, epc: &PyEpoch, source: PyEphemerisSource) -> PyResult<Bound<'py, PyArray<f64, Ix1>>> {
let kernel = de_kernel_from_py_source(source)?;
let r = spice::moon_position_de(epc.obj, kernel)
.map_err(|e| exceptions::PyRuntimeError::new_err(e.to_string()))?;
Ok(vector_to_numpy!(py, r, 3, f64))
}
#[pyfunction]
#[pyo3(name = "mercury_position_de")]
fn py_mercury_position_de<'py>(py: Python<'py>, epc: &PyEpoch, source: PyEphemerisSource) -> PyResult<Bound<'py, PyArray<f64, Ix1>>> {
let kernel = de_kernel_from_py_source(source)?;
let r = spice::mercury_position_de(epc.obj, kernel)
.map_err(|e| exceptions::PyRuntimeError::new_err(e.to_string()))?;
Ok(vector_to_numpy!(py, r, 3, f64))
}
#[pyfunction]
#[pyo3(name = "venus_position_de")]
fn py_venus_position_de<'py>(py: Python<'py>, epc: &PyEpoch, source: PyEphemerisSource) -> PyResult<Bound<'py, PyArray<f64, Ix1>>> {
let kernel = de_kernel_from_py_source(source)?;
let r = spice::venus_position_de(epc.obj, kernel)
.map_err(|e| exceptions::PyRuntimeError::new_err(e.to_string()))?;
Ok(vector_to_numpy!(py, r, 3, f64))
}
#[pyfunction]
#[pyo3(name = "mars_position_de")]
fn py_mars_position_de<'py>(py: Python<'py>, epc: &PyEpoch, source: PyEphemerisSource) -> PyResult<Bound<'py, PyArray<f64, Ix1>>> {
let kernel = de_kernel_from_py_source(source)?;
let r = spice::mars_position_de(epc.obj, kernel)
.map_err(|e| exceptions::PyRuntimeError::new_err(e.to_string()))?;
Ok(vector_to_numpy!(py, r, 3, f64))
}
#[pyfunction]
#[pyo3(name = "jupiter_position_de")]
fn py_jupiter_position_de<'py>(py: Python<'py>, epc: &PyEpoch, source: PyEphemerisSource) -> PyResult<Bound<'py, PyArray<f64, Ix1>>> {
let kernel = de_kernel_from_py_source(source)?;
let r = spice::jupiter_position_de(epc.obj, kernel)
.map_err(|e| exceptions::PyRuntimeError::new_err(e.to_string()))?;
Ok(vector_to_numpy!(py, r, 3, f64))
}
#[pyfunction]
#[pyo3(name = "saturn_position_de")]
fn py_saturn_position_de<'py>(py: Python<'py>, epc: &PyEpoch, source: PyEphemerisSource) -> PyResult<Bound<'py, PyArray<f64, Ix1>>> {
let kernel = de_kernel_from_py_source(source)?;
let r = spice::saturn_position_de(epc.obj, kernel)
.map_err(|e| exceptions::PyRuntimeError::new_err(e.to_string()))?;
Ok(vector_to_numpy!(py, r, 3, f64))
}
#[pyfunction]
#[pyo3(name = "uranus_position_de")]
fn py_uranus_position_de<'py>(py: Python<'py>, epc: &PyEpoch, source: PyEphemerisSource) -> PyResult<Bound<'py, PyArray<f64, Ix1>>> {
let kernel = de_kernel_from_py_source(source)?;
let r = spice::uranus_position_de(epc.obj, kernel)
.map_err(|e| exceptions::PyRuntimeError::new_err(e.to_string()))?;
Ok(vector_to_numpy!(py, r, 3, f64))
}
#[pyfunction]
#[pyo3(name = "neptune_position_de")]
fn py_neptune_position_de<'py>(py: Python<'py>, epc: &PyEpoch, source: PyEphemerisSource) -> PyResult<Bound<'py, PyArray<f64, Ix1>>> {
let kernel = de_kernel_from_py_source(source)?;
let r = spice::neptune_position_de(epc.obj, kernel)
.map_err(|e| exceptions::PyRuntimeError::new_err(e.to_string()))?;
Ok(vector_to_numpy!(py, r, 3, f64))
}
#[pyfunction]
#[pyo3(name = "solar_system_barycenter_position_de")]
fn py_solar_system_barycenter_position_de<'py>(py: Python<'py>, epc: &PyEpoch, source: PyEphemerisSource) -> PyResult<Bound<'py, PyArray<f64, Ix1>>> {
let kernel = de_kernel_from_py_source(source)?;
let r = spice::solar_system_barycenter_position_de(epc.obj, kernel)
.map_err(|e| exceptions::PyRuntimeError::new_err(e.to_string()))?;
Ok(vector_to_numpy!(py, r, 3, f64))
}
#[pyfunction]
#[pyo3(name = "ssb_position_de")]
fn py_ssb_position_de<'py>(py: Python<'py>, epc: &PyEpoch, source: PyEphemerisSource) -> PyResult<Bound<'py, PyArray<f64, Ix1>>> {
let kernel = de_kernel_from_py_source(source)?;
let r = spice::ssb_position_de(epc.obj, kernel)
.map_err(|e| exceptions::PyRuntimeError::new_err(e.to_string()))?;
Ok(vector_to_numpy!(py, r, 3, f64))
}
#[pyfunction]
#[pyo3(name = "initialize_ephemeris")]
fn py_initialize_ephemeris() -> PyResult<()> {
spice::initialize_ephemeris()
.map_err(|e| exceptions::PyRuntimeError::new_err(format!("Failed to initialize ephemeris: {}", e)))
}
#[pyfunction]
#[pyo3(name = "accel_third_body_sun")]
fn py_accel_third_body_sun<'py>(
py: Python<'py>,
epc: &PyEpoch,
r_object: PyReadonlyArray1<f64>,
) -> PyResult<Bound<'py, PyArray<f64, Ix1>>> {
let len = r_object.len();
let a = if len == 3 {
let r_obj = numpy_to_vector3!(r_object);
orbit_dynamics::accel_third_body_sun(epc.obj, r_obj)
} else if len == 6 {
let x_obj = numpy_to_vector6!(r_object);
orbit_dynamics::accel_third_body_sun(epc.obj, x_obj)
} else {
return Err(pyo3::exceptions::PyValueError::new_err(
"r_object must be length 3 (position) or 6 (state)"
));
};
Ok(vector_to_numpy!(py, a, 3, f64))
}
#[pyfunction]
#[pyo3(name = "accel_third_body_moon")]
fn py_accel_third_body_moon<'py>(
py: Python<'py>,
epc: &PyEpoch,
r_object: PyReadonlyArray1<f64>,
) -> PyResult<Bound<'py, PyArray<f64, Ix1>>> {
let len = r_object.len();
let a = if len == 3 {
let r_obj = numpy_to_vector3!(r_object);
orbit_dynamics::accel_third_body_moon(epc.obj, r_obj)
} else if len == 6 {
let x_obj = numpy_to_vector6!(r_object);
orbit_dynamics::accel_third_body_moon(epc.obj, x_obj)
} else {
return Err(pyo3::exceptions::PyValueError::new_err(
"r_object must be length 3 (position) or 6 (state)"
));
};
Ok(vector_to_numpy!(py, a, 3, f64))
}
#[pyfunction]
#[pyo3(name = "accel_third_body_sun_de")]
fn py_accel_third_body_sun_de<'py>(
py: Python<'py>,
epc: &PyEpoch,
r_object: PyReadonlyArray1<f64>,
source: PyEphemerisSource,
) -> PyResult<Bound<'py, PyArray<f64, Ix1>>> {
let len = r_object.len();
let a = if len == 3 {
let r_obj = numpy_to_vector3!(r_object);
orbit_dynamics::accel_third_body_sun_de(epc.obj, r_obj, source.into())
} else if len == 6 {
let x_obj = numpy_to_vector6!(r_object);
orbit_dynamics::accel_third_body_sun_de(epc.obj, x_obj, source.into())
} else {
return Err(pyo3::exceptions::PyValueError::new_err(
"r_object must be length 3 (position) or 6 (state)"
));
};
Ok(vector_to_numpy!(py, a, 3, f64))
}
#[pyfunction]
#[pyo3(name = "accel_third_body_moon_de")]
fn py_accel_third_body_moon_de<'py>(
py: Python<'py>,
epc: &PyEpoch,
r_object: PyReadonlyArray1<f64>,
source: PyEphemerisSource,
) -> PyResult<Bound<'py, PyArray<f64, Ix1>>> {
let len = r_object.len();
let a = if len == 3 {
let r_obj = numpy_to_vector3!(r_object);
orbit_dynamics::accel_third_body_moon_de(epc.obj, r_obj, source.into())
} else if len == 6 {
let x_obj = numpy_to_vector6!(r_object);
orbit_dynamics::accel_third_body_moon_de(epc.obj, x_obj, source.into())
} else {
return Err(pyo3::exceptions::PyValueError::new_err(
"r_object must be length 3 (position) or 6 (state)"
));
};
Ok(vector_to_numpy!(py, a, 3, f64))
}
#[pyfunction]
#[pyo3(name = "accel_third_body_mercury_de")]
fn py_accel_third_body_mercury_de<'py>(
py: Python<'py>,
epc: &PyEpoch,
r_object: PyReadonlyArray1<f64>,
source: PyEphemerisSource,
) -> PyResult<Bound<'py, PyArray<f64, Ix1>>> {
let len = r_object.len();
let a = if len == 3 {
let r_obj = numpy_to_vector3!(r_object);
orbit_dynamics::accel_third_body_mercury_de(epc.obj, r_obj, source.into())
} else if len == 6 {
let x_obj = numpy_to_vector6!(r_object);
orbit_dynamics::accel_third_body_mercury_de(epc.obj, x_obj, source.into())
} else {
return Err(pyo3::exceptions::PyValueError::new_err(
"r_object must be length 3 (position) or 6 (state)"
));
};
Ok(vector_to_numpy!(py, a, 3, f64))
}
#[pyfunction]
#[pyo3(name = "accel_third_body_venus_de")]
fn py_accel_third_body_venus_de<'py>(
py: Python<'py>,
epc: &PyEpoch,
r_object: PyReadonlyArray1<f64>,
source: PyEphemerisSource,
) -> PyResult<Bound<'py, PyArray<f64, Ix1>>> {
let len = r_object.len();
let a = if len == 3 {
let r_obj = numpy_to_vector3!(r_object);
orbit_dynamics::accel_third_body_venus_de(epc.obj, r_obj, source.into())
} else if len == 6 {
let x_obj = numpy_to_vector6!(r_object);
orbit_dynamics::accel_third_body_venus_de(epc.obj, x_obj, source.into())
} else {
return Err(pyo3::exceptions::PyValueError::new_err(
"r_object must be length 3 (position) or 6 (state)"
));
};
Ok(vector_to_numpy!(py, a, 3, f64))
}
#[pyfunction]
#[pyo3(name = "accel_third_body_mars_de")]
fn py_accel_third_body_mars_de<'py>(
py: Python<'py>,
epc: &PyEpoch,
r_object: PyReadonlyArray1<f64>,
source: PyEphemerisSource,
) -> PyResult<Bound<'py, PyArray<f64, Ix1>>> {
let len = r_object.len();
let a = if len == 3 {
let r_obj = numpy_to_vector3!(r_object);
orbit_dynamics::accel_third_body_mars_de(epc.obj, r_obj, source.into())
} else if len == 6 {
let x_obj = numpy_to_vector6!(r_object);
orbit_dynamics::accel_third_body_mars_de(epc.obj, x_obj, source.into())
} else {
return Err(pyo3::exceptions::PyValueError::new_err(
"r_object must be length 3 (position) or 6 (state)"
));
};
Ok(vector_to_numpy!(py, a, 3, f64))
}
#[pyfunction]
#[pyo3(name = "accel_third_body_jupiter_de")]
fn py_accel_third_body_jupiter_de<'py>(
py: Python<'py>,
epc: &PyEpoch,
r_object: PyReadonlyArray1<f64>,
source: PyEphemerisSource,
) -> PyResult<Bound<'py, PyArray<f64, Ix1>>> {
let len = r_object.len();
let a = if len == 3 {
let r_obj = numpy_to_vector3!(r_object);
orbit_dynamics::accel_third_body_jupiter_de(epc.obj, r_obj, source.into())
} else if len == 6 {
let x_obj = numpy_to_vector6!(r_object);
orbit_dynamics::accel_third_body_jupiter_de(epc.obj, x_obj, source.into())
} else {
return Err(pyo3::exceptions::PyValueError::new_err(
"r_object must be length 3 (position) or 6 (state)"
));
};
Ok(vector_to_numpy!(py, a, 3, f64))
}
#[pyfunction]
#[pyo3(name = "accel_third_body_saturn_de")]
fn py_accel_third_body_saturn_de<'py>(
py: Python<'py>,
epc: &PyEpoch,
r_object: PyReadonlyArray1<f64>,
source: PyEphemerisSource,
) -> PyResult<Bound<'py, PyArray<f64, Ix1>>> {
let len = r_object.len();
let a = if len == 3 {
let r_obj = numpy_to_vector3!(r_object);
orbit_dynamics::accel_third_body_saturn_de(epc.obj, r_obj, source.into())
} else if len == 6 {
let x_obj = numpy_to_vector6!(r_object);
orbit_dynamics::accel_third_body_saturn_de(epc.obj, x_obj, source.into())
} else {
return Err(pyo3::exceptions::PyValueError::new_err(
"r_object must be length 3 (position) or 6 (state)"
));
};
Ok(vector_to_numpy!(py, a, 3, f64))
}
#[pyfunction]
#[pyo3(name = "accel_third_body_uranus_de")]
fn py_accel_third_body_uranus_de<'py>(
py: Python<'py>,
epc: &PyEpoch,
r_object: PyReadonlyArray1<f64>,
source: PyEphemerisSource,
) -> PyResult<Bound<'py, PyArray<f64, Ix1>>> {
let len = r_object.len();
let a = if len == 3 {
let r_obj = numpy_to_vector3!(r_object);
orbit_dynamics::accel_third_body_uranus_de(epc.obj, r_obj, source.into())
} else if len == 6 {
let x_obj = numpy_to_vector6!(r_object);
orbit_dynamics::accel_third_body_uranus_de(epc.obj, x_obj, source.into())
} else {
return Err(pyo3::exceptions::PyValueError::new_err(
"r_object must be length 3 (position) or 6 (state)"
));
};
Ok(vector_to_numpy!(py, a, 3, f64))
}
#[pyfunction]
#[pyo3(name = "accel_third_body_neptune_de")]
fn py_accel_third_body_neptune_de<'py>(
py: Python<'py>,
epc: &PyEpoch,
r_object: PyReadonlyArray1<f64>,
source: PyEphemerisSource,
) -> PyResult<Bound<'py, PyArray<f64, Ix1>>> {
let len = r_object.len();
let a = if len == 3 {
let r_obj = numpy_to_vector3!(r_object);
orbit_dynamics::accel_third_body_neptune_de(epc.obj, r_obj, source.into())
} else if len == 6 {
let x_obj = numpy_to_vector6!(r_object);
orbit_dynamics::accel_third_body_neptune_de(epc.obj, x_obj, source.into())
} else {
return Err(pyo3::exceptions::PyValueError::new_err(
"r_object must be length 3 (position) or 6 (state)"
));
};
Ok(vector_to_numpy!(py, a, 3, f64))
}
#[pyfunction]
#[pyo3(name = "accel_point_mass_gravity")]
fn py_accel_point_mass_gravity<'py>(
py: Python<'py>,
r_object: PyReadonlyArray1<f64>,
r_central_body: PyReadonlyArray1<f64>,
gm: f64,
) -> PyResult<Bound<'py, PyArray<f64, Ix1>>> {
let len = r_object.len();
let r_cb = numpy_to_vector3!(r_central_body);
let a = if len == 3 {
let r_obj = numpy_to_vector3!(r_object);
orbit_dynamics::accel_point_mass_gravity(r_obj, r_cb, gm)
} else if len == 6 {
let x_obj = numpy_to_vector6!(r_object);
orbit_dynamics::accel_point_mass_gravity(x_obj, r_cb, gm)
} else {
return Err(pyo3::exceptions::PyValueError::new_err(
"r_object must be length 3 (position) or 6 (state)"
));
};
Ok(vector_to_numpy!(py, a, 3, f64))
}
#[pyclass(module = "brahe._brahe", from_py_object)]
#[pyo3(name = "GravityModelType")]
#[derive(Clone)]
pub struct PyGravityModelType {
pub(crate) model: orbit_dynamics::GravityModelType,
}
#[pymethods]
impl PyGravityModelType {
#[classattr]
#[allow(non_snake_case)]
fn EGM2008_360() -> Self {
PyGravityModelType {
model: orbit_dynamics::GravityModelType::EGM2008_360,
}
}
#[classattr]
#[allow(non_snake_case)]
fn GGM05S() -> Self {
PyGravityModelType {
model: orbit_dynamics::GravityModelType::GGM05S,
}
}
#[classattr]
#[allow(non_snake_case)]
fn JGM3() -> Self {
PyGravityModelType {
model: orbit_dynamics::GravityModelType::JGM3,
}
}
#[staticmethod]
fn from_file(filepath: String) -> PyResult<Self> {
let path = std::path::Path::new(&filepath);
if !path.exists() {
return Err(exceptions::PyFileNotFoundError::new_err(format!(
"Gravity model file not found: {}",
filepath
)));
}
if !path.is_file() {
return Err(exceptions::PyIsADirectoryError::new_err(format!(
"Gravity model path is not a file: {}",
filepath
)));
}
Ok(PyGravityModelType {
model: orbit_dynamics::GravityModelType::FromFile(filepath),
})
}
fn __str__(&self) -> String {
match &self.model {
orbit_dynamics::GravityModelType::FromFile(path) => format!("FromFile({})", path),
_ => format!("{:?}", self.model),
}
}
fn __repr__(&self) -> String {
match &self.model {
orbit_dynamics::GravityModelType::FromFile(path) => format!("GravityModelType.FromFile('{}')", path),
_ => format!("GravityModelType.{:?}", self.model),
}
}
fn __richcmp__(&self, other: &Self, op: CompareOp) -> PyResult<bool> {
match op {
CompareOp::Eq => Ok(match (&self.model, &other.model) {
(orbit_dynamics::GravityModelType::FromFile(a), orbit_dynamics::GravityModelType::FromFile(b)) => a == b,
_ => std::mem::discriminant(&self.model) == std::mem::discriminant(&other.model),
}),
CompareOp::Ne => Ok(match (&self.model, &other.model) {
(orbit_dynamics::GravityModelType::FromFile(a), orbit_dynamics::GravityModelType::FromFile(b)) => a != b,
_ => std::mem::discriminant(&self.model) != std::mem::discriminant(&other.model),
}),
_ => Err(exceptions::PyNotImplementedError::new_err("Comparison not supported")),
}
}
}
#[pyclass(module = "brahe._brahe", from_py_object)]
#[pyo3(name = "GravityModelTideSystem")]
#[derive(Clone)]
pub struct PyGravityModelTideSystem {
pub(crate) tide_system: orbit_dynamics::GravityModelTideSystem,
}
#[pymethods]
impl PyGravityModelTideSystem {
#[classattr]
#[allow(non_snake_case)]
fn ZeroTide() -> Self {
PyGravityModelTideSystem {
tide_system: orbit_dynamics::GravityModelTideSystem::ZeroTide,
}
}
#[classattr]
#[allow(non_snake_case)]
fn TideFree() -> Self {
PyGravityModelTideSystem {
tide_system: orbit_dynamics::GravityModelTideSystem::TideFree,
}
}
#[classattr]
#[allow(non_snake_case)]
fn MeanTide() -> Self {
PyGravityModelTideSystem {
tide_system: orbit_dynamics::GravityModelTideSystem::MeanTide,
}
}
#[classattr]
#[allow(non_snake_case)]
fn Unknown() -> Self {
PyGravityModelTideSystem {
tide_system: orbit_dynamics::GravityModelTideSystem::Unknown,
}
}
fn __str__(&self) -> String {
format!("{:?}", self.tide_system)
}
fn __repr__(&self) -> String {
format!("GravityModelTideSystem.{:?}", self.tide_system)
}
fn __richcmp__(&self, other: &Self, op: CompareOp) -> PyResult<bool> {
match op {
CompareOp::Eq => Ok(std::mem::discriminant(&self.tide_system) == std::mem::discriminant(&other.tide_system)),
CompareOp::Ne => Ok(std::mem::discriminant(&self.tide_system) != std::mem::discriminant(&other.tide_system)),
_ => Err(exceptions::PyNotImplementedError::new_err("Comparison not supported")),
}
}
}
#[pyclass(module = "brahe._brahe", from_py_object)]
#[pyo3(name = "GravityModelErrors")]
#[derive(Clone)]
pub struct PyGravityModelErrors {
pub(crate) errors: orbit_dynamics::GravityModelErrors,
}
#[pymethods]
impl PyGravityModelErrors {
#[classattr]
#[allow(non_snake_case)]
fn No() -> Self {
PyGravityModelErrors {
errors: orbit_dynamics::GravityModelErrors::No,
}
}
#[classattr]
#[allow(non_snake_case)]
fn Calibrated() -> Self {
PyGravityModelErrors {
errors: orbit_dynamics::GravityModelErrors::Calibrated,
}
}
#[classattr]
#[allow(non_snake_case)]
fn Formal() -> Self {
PyGravityModelErrors {
errors: orbit_dynamics::GravityModelErrors::Formal,
}
}
#[classattr]
#[allow(non_snake_case)]
fn CalibratedAndFormal() -> Self {
PyGravityModelErrors {
errors: orbit_dynamics::GravityModelErrors::CalibratedAndFormal,
}
}
fn __str__(&self) -> String {
format!("{:?}", self.errors)
}
fn __repr__(&self) -> String {
format!("GravityModelErrors.{:?}", self.errors)
}
fn __richcmp__(&self, other: &Self, op: CompareOp) -> PyResult<bool> {
match op {
CompareOp::Eq => Ok(std::mem::discriminant(&self.errors) == std::mem::discriminant(&other.errors)),
CompareOp::Ne => Ok(std::mem::discriminant(&self.errors) != std::mem::discriminant(&other.errors)),
_ => Err(exceptions::PyNotImplementedError::new_err("Comparison not supported")),
}
}
}
#[pyclass(module = "brahe._brahe", from_py_object)]
#[pyo3(name = "GravityModelNormalization")]
#[derive(Clone)]
pub struct PyGravityModelNormalization {
pub(crate) normalization: orbit_dynamics::GravityModelNormalization,
}
#[pymethods]
impl PyGravityModelNormalization {
#[classattr]
#[allow(non_snake_case)]
fn FullyNormalized() -> Self {
PyGravityModelNormalization {
normalization: orbit_dynamics::GravityModelNormalization::FullyNormalized,
}
}
#[classattr]
#[allow(non_snake_case)]
fn Unnormalized() -> Self {
PyGravityModelNormalization {
normalization: orbit_dynamics::GravityModelNormalization::Unnormalized,
}
}
fn __str__(&self) -> String {
format!("{:?}", self.normalization)
}
fn __repr__(&self) -> String {
format!("GravityModelNormalization.{:?}", self.normalization)
}
fn __richcmp__(&self, other: &Self, op: CompareOp) -> PyResult<bool> {
match op {
CompareOp::Eq => Ok(std::mem::discriminant(&self.normalization) == std::mem::discriminant(&other.normalization)),
CompareOp::Ne => Ok(std::mem::discriminant(&self.normalization) != std::mem::discriminant(&other.normalization)),
_ => Err(exceptions::PyNotImplementedError::new_err("Comparison not supported")),
}
}
}
#[pyclass(module = "brahe._brahe")]
#[pyo3(name = "GravityModel")]
pub struct PyGravityModel {
pub(crate) model: orbit_dynamics::GravityModel,
}
#[pymethods]
impl PyGravityModel {
#[getter]
fn tide_system(&self) -> PyGravityModelTideSystem {
PyGravityModelTideSystem {
tide_system: self.model.tide_system,
}
}
#[getter]
fn n_max(&self) -> usize {
self.model.n_max
}
#[getter]
fn m_max(&self) -> usize {
self.model.m_max
}
#[getter]
fn gm(&self) -> f64 {
self.model.gm
}
#[getter]
fn radius(&self) -> f64 {
self.model.radius
}
#[getter]
fn model_name(&self) -> String {
self.model.model_name.clone()
}
#[getter]
fn model_errors(&self) -> PyGravityModelErrors {
PyGravityModelErrors {
errors: self.model.model_errors,
}
}
#[getter]
fn normalization(&self) -> PyGravityModelNormalization {
PyGravityModelNormalization {
normalization: self.model.normalization,
}
}
#[classmethod]
fn from_file(_cls: &Bound<'_, PyType>, filepath: &str) -> PyResult<Self> {
let path = Path::new(filepath);
let model = orbit_dynamics::GravityModel::from_file(path)
.map_err(|e| exceptions::PyRuntimeError::new_err(format!("Failed to load gravity model: {}", e)))?;
Ok(PyGravityModel { model })
}
#[classmethod]
fn from_model_type(_cls: &Bound<'_, PyType>, model_type: &PyGravityModelType) -> PyResult<Self> {
let grav_model = orbit_dynamics::GravityModel::from_model_type(&model_type.model)
.map_err(|e| exceptions::PyRuntimeError::new_err(format!("Failed to load gravity model: {}", e)))?;
Ok(PyGravityModel { model: grav_model })
}
fn get(&self, n: usize, m: usize) -> PyResult<(f64, f64)> {
self.model.get(n, m)
.map_err(|e| exceptions::PyValueError::new_err(format!("Failed to get coefficient: {}", e)))
}
fn compute_spherical_harmonics<'py>(
&self,
py: Python<'py>,
r_body: PyReadonlyArray1<f64>,
n_max: usize,
m_max: usize,
) -> PyResult<Bound<'py, PyArray<f64, Ix1>>> {
let r = numpy_to_vector3!(r_body);
let a = self.model.compute_spherical_harmonics(r, n_max, m_max)
.map_err(|e| exceptions::PyValueError::new_err(format!("Failed to compute spherical harmonics: {}", e)))?;
Ok(vector_to_numpy!(py, a, 3, f64))
}
fn set_max_degree_order(&mut self, n: usize, m: usize) -> PyResult<()> {
self.model.set_max_degree_order(n, m)
.map_err(|e| exceptions::PyValueError::new_err(format!("Failed to set max degree/order: {}", e)))
}
fn __repr__(&self) -> String {
format!(
"GravityModel(name='{}', n_max={}, m_max={})",
self.model.model_name, self.model.n_max, self.model.m_max
)
}
fn __str__(&self) -> String {
format!(
"GravityModel '{}' ({}x{}, GM={:.6e} m³/s², R={:.3e} m)",
self.model.model_name, self.model.n_max, self.model.m_max, self.model.gm, self.model.radius
)
}
}
#[pyfunction]
#[pyo3(name = "accel_gravity_spherical_harmonics")]
#[allow(non_snake_case)]
fn py_accel_gravity_spherical_harmonics<'py>(
py: Python<'py>,
r_eci: PyReadonlyArray1<f64>,
R_i2b: PyReadonlyArray2<f64>,
gravity_model: &PyGravityModel,
n_max: usize,
m_max: usize,
) -> PyResult<Bound<'py, PyArray<f64, Ix1>>> {
let len = r_eci.len();
let rot = numpy_to_smatrix3!(R_i2b);
let a = if len == 3 {
let r = numpy_to_vector3!(r_eci);
orbit_dynamics::accel_gravity_spherical_harmonics(r, rot, &gravity_model.model, n_max, m_max)
} else if len == 6 {
let x = numpy_to_vector6!(r_eci);
orbit_dynamics::accel_gravity_spherical_harmonics(x, rot, &gravity_model.model, n_max, m_max)
} else {
return Err(pyo3::exceptions::PyValueError::new_err(
"r_eci must be length 3 (position) or 6 (state)"
));
};
Ok(vector_to_numpy!(py, a, 3, f64))
}
#[pyfunction]
#[pyo3(name = "accel_drag")]
#[allow(non_snake_case)]
fn py_accel_drag<'py>(
py: Python<'py>,
x_object: PyReadonlyArray1<f64>,
density: f64,
mass: f64,
area: f64,
drag_coefficient: f64,
T: PyReadonlyArray2<f64>,
) -> PyResult<Bound<'py, PyArray<f64, Ix1>>> {
let x_obj = numpy_to_vector6!(x_object);
let t_mat = numpy_to_smatrix3!(T);
let a = orbit_dynamics::accel_drag(x_obj, density, mass, area, drag_coefficient, t_mat);
Ok(vector_to_numpy!(py, a, 3, f64))
}
#[pyfunction]
#[pyo3(name = "accel_solar_radiation_pressure")]
fn py_accel_solar_radiation_pressure<'py>(
py: Python<'py>,
r_object: PyReadonlyArray1<f64>,
r_sun: PyReadonlyArray1<f64>,
mass: f64,
cr: f64,
area: f64,
p0: f64,
) -> PyResult<Bound<'py, PyArray<f64, Ix1>>> {
let len = r_object.len();
let r_s = numpy_to_vector3!(r_sun);
let a = if len == 3 {
let r_obj = numpy_to_vector3!(r_object);
orbit_dynamics::accel_solar_radiation_pressure(r_obj, r_s, mass, cr, area, p0)
} else if len == 6 {
let x_obj = numpy_to_vector6!(r_object);
orbit_dynamics::accel_solar_radiation_pressure(x_obj, r_s, mass, cr, area, p0)
} else {
return Err(pyo3::exceptions::PyValueError::new_err(
"r_object must be length 3 (position) or 6 (state)"
));
};
Ok(vector_to_numpy!(py, a, 3, f64))
}
#[pyfunction]
#[pyo3(name = "eclipse_conical")]
fn py_eclipse_conical(
r_object: PyReadonlyArray1<f64>,
r_sun: PyReadonlyArray1<f64>,
) -> PyResult<f64> {
let len = r_object.len();
let r_s = numpy_to_vector3!(r_sun);
let nu = if len == 3 {
let r_obj = numpy_to_vector3!(r_object);
orbit_dynamics::eclipse_conical(r_obj, r_s)
} else if len == 6 {
let x_obj = numpy_to_vector6!(r_object);
orbit_dynamics::eclipse_conical(x_obj, r_s)
} else {
return Err(pyo3::exceptions::PyValueError::new_err(
"r_object must be length 3 (position) or 6 (state)"
));
};
Ok(nu)
}
#[pyfunction]
#[pyo3(name = "eclipse_cylindrical")]
fn py_eclipse_cylindrical(
r_object: PyReadonlyArray1<f64>,
r_sun: PyReadonlyArray1<f64>,
) -> PyResult<f64> {
let len = r_object.len();
let r_s = numpy_to_vector3!(r_sun);
let nu = if len == 3 {
let r_obj = numpy_to_vector3!(r_object);
orbit_dynamics::eclipse_cylindrical(r_obj, r_s)
} else if len == 6 {
let x_obj = numpy_to_vector6!(r_object);
orbit_dynamics::eclipse_cylindrical(x_obj, r_s)
} else {
return Err(pyo3::exceptions::PyValueError::new_err(
"r_object must be length 3 (position) or 6 (state)"
));
};
Ok(nu)
}
#[pyfunction]
#[pyo3(name = "accel_relativity")]
fn py_accel_relativity<'py>(
py: Python<'py>,
x_object: PyReadonlyArray1<f64>,
) -> PyResult<Bound<'py, PyArray<f64, Ix1>>> {
let x_obj = numpy_to_vector6!(x_object);
let a = orbit_dynamics::accel_relativity(x_obj);
Ok(vector_to_numpy!(py, a, 3, f64))
}