use super::DCM;
use crate::astro::PhysicsResult;
use crate::math::rotation::Quaternion;
use crate::NaifId;
use nalgebra::{Matrix3, Vector3};
use ndarray::{Array1, Array2};
use numpy::{PyArray1, PyArray2, PyReadonlyArray1, PyReadonlyArray2, PyUntypedArrayMethods};
use pyo3::basic::CompareOp;
use pyo3::exceptions::{PyTypeError, PyValueError};
use pyo3::prelude::*;
use pyo3::types::PyType;
#[pymethods]
impl DCM {
#[new]
#[pyo3(signature=(np_rot_mat, from_id, to_id, np_rot_mat_dt=None))]
pub fn py_new<'py>(
np_rot_mat: PyReadonlyArray2<'py, f64>,
from_id: NaifId,
to_id: NaifId,
np_rot_mat_dt: Option<PyReadonlyArray2<'py, f64>>,
) -> PyResult<Self> {
if np_rot_mat.shape() != [3, 3] {
return Err(PyErr::new::<PyTypeError, _>("rotation matrix must be 3x3"));
}
let rot_mat = Matrix3::from_row_iterator(np_rot_mat.as_array().iter().copied());
let rot_mat_dt = if let Some(np_rot_mat_dt) = np_rot_mat_dt {
if np_rot_mat_dt.shape() != [3, 3] {
return Err(PyErr::new::<PyTypeError, _>(
"rotation matrix time derivative must be 3x3",
));
}
Some(Matrix3::from_row_iterator(
np_rot_mat_dt.as_array().iter().copied(),
))
} else {
None
};
Ok(Self {
rot_mat,
rot_mat_dt,
from: from_id,
to: to_id,
})
}
#[classmethod]
pub fn from_r1(
_cls: &Bound<'_, PyType>,
angle_rad: f64,
from_id: NaifId,
to_id: NaifId,
) -> Self {
Self::r1(angle_rad, from_id, to_id)
}
#[classmethod]
pub fn from_r2(
_cls: &Bound<'_, PyType>,
angle_rad: f64,
from_id: NaifId,
to_id: NaifId,
) -> Self {
Self::r2(angle_rad, from_id, to_id)
}
#[classmethod]
pub fn from_r3(
_cls: &Bound<'_, PyType>,
angle_rad: f64,
from_id: NaifId,
to_id: NaifId,
) -> Self {
Self::r3(angle_rad, from_id, to_id)
}
#[classmethod]
pub fn from_identity(_cls: &Bound<'_, PyType>, from_id: i32, to_id: i32) -> Self {
Self::identity(from_id, to_id)
}
#[classmethod]
pub fn from_align_and_clock<'py>(
_cls: &Bound<'_, PyType>,
primary_body_axis: PyReadonlyArray1<'py, f64>,
primary_inertial_vec: PyReadonlyArray1<'py, f64>,
secondary_body_axis: PyReadonlyArray1<'py, f64>,
secondary_inertial_vec: PyReadonlyArray1<'py, f64>,
from_id: i32,
to_id: i32,
) -> PyResult<Self> {
let to_vec3 = |arr: PyReadonlyArray1<'py, f64>, name: &str| -> PyResult<Vector3<f64>> {
let view = arr.as_array();
if view.len() != 3 {
return Err(PyValueError::new_err(format!(
"{} must be a length-3 vector, got length {}",
name,
view.len()
)));
}
Ok(Vector3::new(view[0], view[1], view[2]))
};
let p_body = to_vec3(primary_body_axis, "primary_body_axis")?;
let p_inertial = to_vec3(primary_inertial_vec, "primary_inertial_vec")?;
let s_body = to_vec3(secondary_body_axis, "secondary_body_axis")?;
let s_inertial = to_vec3(secondary_inertial_vec, "secondary_inertial_vec")?;
Self::align_and_clock(p_body, p_inertial, s_body, s_inertial, from_id, to_id)
.map_err(Into::into)
}
#[getter]
fn get_rot_mat<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyArray2<f64>>> {
let data: Vec<f64> = self.rot_mat.transpose().iter().copied().collect();
let rot_mat = Array2::from_shape_vec((3, 3), data).unwrap();
let py_rot_mat = PyArray2::<f64>::from_owned_array(py, rot_mat);
Ok(py_rot_mat)
}
#[getter]
fn get_rot_mat_dt<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyArray2<f64>>>> {
if self.rot_mat_dt.is_none() {
return Ok(None);
}
let data: Vec<f64> = self
.rot_mat_dt
.unwrap()
.transpose()
.iter()
.copied()
.collect();
let rot_mat_dt = Array2::from_shape_vec((3, 3), data).unwrap();
let py_rot_mat_dt = PyArray2::<f64>::from_owned_array(py, rot_mat_dt);
Ok(Some(py_rot_mat_dt))
}
#[getter]
fn get_from_id(&self) -> PyResult<NaifId> {
Ok(self.from)
}
#[getter]
fn get_to_id(&self) -> PyResult<NaifId> {
Ok(self.to)
}
fn get_state_dcm<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyArray2<f64>>> {
let data: Vec<f64> = self.state_dcm().transpose().iter().copied().collect();
let state_dcm = Array2::from_shape_vec((6, 6), data).unwrap();
let pt_state_dcm = PyArray2::<f64>::from_owned_array(py, state_dcm);
Ok(pt_state_dcm)
}
#[pyo3(name = "skew_symmetric")]
fn py_skew_symmetric<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyArray2<f64>>> {
self.skew_symmetric().map(|tilde| {
let data: Vec<f64> = tilde.transpose().iter().copied().collect();
let arr = Array2::from_shape_vec((3, 3), data).unwrap();
PyArray2::<f64>::from_owned_array(py, arr)
})
}
#[pyo3(name = "angular_velocity_rad_s")]
fn py_angular_velocity_rad_s<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyArray1<f64>>> {
self.angular_velocity_rad_s().map(|w| {
let data: Vec<f64> = w.transpose().iter().copied().collect();
let arr = Array1::from_shape_vec((3,), data).unwrap();
PyArray1::<f64>::from_owned_array(py, arr)
})
}
#[pyo3(name = "angular_velocity_deg_s")]
fn py_angular_velocity_deg_s<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyArray1<f64>>> {
self.angular_velocity_deg_s().map(|w| {
let data: Vec<f64> = w.transpose().iter().copied().collect();
let arr = Array1::from_shape_vec((3,), data).unwrap();
PyArray1::<f64>::from_owned_array(py, arr)
})
}
#[setter]
fn set_from_id(&mut self, from_id: i32) {
self.from = from_id;
}
#[setter]
fn set_to_id(&mut self, to_id: i32) {
self.to = to_id;
}
fn to_quaternion(&self) -> Quaternion {
Quaternion::from(*self)
}
fn __str__(&self) -> String {
format!("{self}")
}
fn __repr__(&self) -> String {
format!("{self} (@{self:p})")
}
fn __richcmp__(&self, other: &Self, op: CompareOp) -> Result<bool, PyErr> {
match op {
CompareOp::Eq => Ok(self == other),
CompareOp::Ne => Ok(self != other),
_ => Err(PyErr::new::<PyTypeError, _>(format!(
"{op:?} not available"
))),
}
}
fn __mul__(&self, dcm: Self) -> PhysicsResult<Self> {
*self * dcm
}
}