Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).


## [Unreleased]
## [v2.1.6]

### Added

Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ members = ["src/kete_core", "src/kete_stats"]
default-members = ["src/kete_core", "src/kete_stats"]

[workspace.package]
version = "2.1.5"
version = "2.1.6"
edition = "2024"
rust-version = "1.90"
license = "BSD-3-Clause"
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "kete"
version = "2.1.5"
version = "2.1.6"
description = "Kete Asteroid Survey Tools"
readme = "README.md"
authors = [{name = "Dar Dahlen", email = "dardahlen@gmail.com"},
Expand Down
15 changes: 11 additions & 4 deletions src/kete/rust/flux/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,16 +244,19 @@ pub fn neatm_thermal_py(
Some(C_V),
Some(v_albedo),
Some(diameter),
)
.unwrap();
)?;
let params = NeatmParams {
obs_bands: vec![BandInfo::new(wavelength, 1.0, f64::NAN, None)],
band_albedos: vec![0.0],
hg_params,
emissivity,
beaming,
};
Ok(params.apparent_thermal_flux(&sun2obj, &sun2obs).unwrap()[0])
params.apparent_thermal_flux(&sun2obj, &sun2obs)
.and_then(|fluxes| fluxes.first().copied())
.ok_or_else(|| pyo3::exceptions::PyValueError::new_err(
"Failed to compute thermal flux. Check input parameters."
))
}

/// Calculate the flux from an object using the FRM thermal model in Jansky.
Expand Down Expand Up @@ -315,7 +318,11 @@ pub fn frm_thermal_py(
hg_params,
emissivity,
};
Ok(params.apparent_thermal_flux(&sun2obj, &sun2obs).unwrap()[0])
params.apparent_thermal_flux(&sun2obj, &sun2obs)
.and_then(|fluxes| fluxes.first().copied())
.ok_or_else(|| pyo3::exceptions::PyValueError::new_err(
"Failed to compute thermal flux. Check input parameters."
))
}

/// Given the M1/K1 and M2/K2 values, compute the apparent Comet visible magnitudes.
Expand Down
38 changes: 24 additions & 14 deletions src/kete/rust/flux/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ impl PyNeatmParams {
&self,
sun2obj_vecs: Vec<VectorLike>,
sun2obs_vecs: Vec<VectorLike>,
) -> Vec<PyModelResults> {
) -> PyResult<Vec<PyModelResults>> {
sun2obj_vecs
.into_par_iter()
.zip(sun2obs_vecs)
Expand All @@ -330,8 +330,10 @@ impl PyNeatmParams {

self.0
.apparent_total_flux(&sun2obj, &sun2obs)
.unwrap()
.into()
.ok_or_else(|| pyo3::exceptions::PyValueError::new_err(
"Failed to compute flux. Ensure diameter and visible albedo are available."
))
.map(|r| r.into())
})
.collect()
}
Expand Down Expand Up @@ -363,13 +365,13 @@ impl PyNeatmParams {
/// Diameter of the object in km.
#[getter]
pub fn diam(&self) -> f64 {
self.0.hg_params.diam().unwrap()
self.0.hg_params.diam().unwrap_or(f64::NAN)
}

/// Albedo in V band.
#[getter]
pub fn vis_albedo(&self) -> f64 {
self.0.hg_params.vis_albedo().unwrap()
self.0.hg_params.vis_albedo().unwrap_or(f64::NAN)
}

/// Beaming parameter.
Expand Down Expand Up @@ -611,7 +613,7 @@ impl PyFrmParams {
&self,
sun2obj_vecs: Vec<VectorLike>,
sun2obs_vecs: Vec<VectorLike>,
) -> Vec<PyModelResults> {
) -> PyResult<Vec<PyModelResults>> {
sun2obj_vecs
.into_par_iter()
.zip(sun2obs_vecs)
Expand All @@ -621,8 +623,10 @@ impl PyFrmParams {

self.0
.apparent_total_flux(&sun2obj, &sun2obs)
.unwrap()
.into()
.ok_or_else(|| pyo3::exceptions::PyValueError::new_err(
"Failed to compute flux. Ensure diameter and visible albedo are available."
))
.map(|r| r.into())
})
.collect()
}
Expand Down Expand Up @@ -653,14 +657,20 @@ impl PyFrmParams {

/// Diameter of the object in km.
#[getter]
pub fn diam(&self) -> f64 {
self.0.hg_params.diam().unwrap()
pub fn diam(&self) -> PyResult<f64> {
self.0.hg_params.diam()
.ok_or_else(|| pyo3::exceptions::PyValueError::new_err(
"Diameter is not available for this object. Provide diameter or (h_mag + vis_albedo) to compute it."
))
}

/// Albedo in V band.
#[getter]
pub fn vis_albedo(&self) -> f64 {
self.0.hg_params.vis_albedo().unwrap()
pub fn vis_albedo(&self) -> PyResult<f64> {
self.0.hg_params.vis_albedo()
.ok_or_else(|| pyo3::exceptions::PyValueError::new_err(
"Visible albedo is not available for this object. Provide vis_albedo or (h_mag + diameter) to compute it."
))
}

/// G Phase parameter.
Expand Down Expand Up @@ -689,8 +699,8 @@ impl PyFrmParams {
self.band_wavelength(),
self.band_albedos(),
self.h_mag(),
self.diam(),
self.vis_albedo(),
self.diam().ok(),
self.vis_albedo().ok(),
self.g_param(),
self.emissivity(),
self.zero_mags(),
Expand Down
12 changes: 8 additions & 4 deletions src/kete/rust/flux/reflected.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use kete_core::constants;
use kete_core::flux::HGParams;
use kete_core::flux::cometary_dust_phase_curve_correction;
use kete_core::flux::hg_phase_curve_correction;
use pyo3::PyResult;
use pyo3::pyfunction;

/// This computes the phase curve correction in the IAU format.
Expand Down Expand Up @@ -102,7 +103,7 @@ pub fn hg_apparent_flux_py(
h_mag: Option<f64>,
diameter: Option<f64>,
c_hg: Option<f64>,
) -> f64 {
) -> PyResult<f64> {
let c_hg = c_hg.unwrap_or(constants::C_V);
let sun2obj = sun2obj.into_vector(PyFrames::Equatorial).into();
let sun2obs = sun2obs.into_vector(PyFrames::Equatorial).into();
Expand All @@ -113,11 +114,14 @@ pub fn hg_apparent_flux_py(
Some(c_hg),
Some(v_albedo),
diameter,
)
.unwrap();
)?;
params
.apparent_flux(&sun2obj, &sun2obs, wavelength, v_albedo)
.unwrap()
.ok_or_else(|| {
pyo3::exceptions::PyValueError::new_err(
"Failed to compute apparent flux. Ensure h_mag or diameter is provided.",
)
})
}

/// Compute the apparent magnitude of an object using the absolute magnitude H, G, and
Expand Down
12 changes: 8 additions & 4 deletions src/kete/rust/fovs/definitions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1044,8 +1044,10 @@ impl PyZtfField {
/// List containing all of the CCD FOVs.
/// These must have matching metadata.
#[new]
pub fn new(ztf_ccd_fields: Vec<PyZtfCcdQuad>) -> Self {
PyZtfField(fov::ZtfField::new(ztf_ccd_fields.into_iter().map(|x| x.0).collect()).unwrap())
pub fn new(ztf_ccd_fields: Vec<PyZtfCcdQuad>) -> PyResult<Self> {
Ok(PyZtfField(fov::ZtfField::new(
ztf_ccd_fields.into_iter().map(|x| x.0).collect(),
)?))
}

/// State of the observer for this FOV.
Expand Down Expand Up @@ -1270,8 +1272,10 @@ impl PyPtfField {
/// List containing all of the CCD FOVs.
/// These must have matching metadata.
#[new]
pub fn new(ptf_ccd_fields: Vec<PyPtfCcd>) -> Self {
PyPtfField(fov::PtfField::new(ptf_ccd_fields.into_iter().map(|x| x.0).collect()).unwrap())
pub fn new(ptf_ccd_fields: Vec<PyPtfCcd>) -> PyResult<Self> {
Ok(PyPtfField(fov::PtfField::new(
ptf_ccd_fields.into_iter().map(|x| x.0).collect(),
)?))
}

/// State of the observer for this FOV.
Expand Down
37 changes: 29 additions & 8 deletions src/kete/rust/spice/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,12 @@ pub fn find_obs_code_py(name: &str) -> PyResult<(f64, f64, f64, String, String)>

/// Predict the state of an object in earths orbit from the two line elements
#[pyfunction]
pub fn predict_tle(line1: String, line2: String, time: PyTime) -> ([f64; 3], [f64; 3]) {
let elements = sgp4::Elements::from_tle(None, line1.as_bytes(), line2.as_bytes()).unwrap();
pub fn predict_tle(line1: String, line2: String, time: PyTime) -> PyResult<([f64; 3], [f64; 3])> {
let elements =
sgp4::Elements::from_tle(None, line1.as_bytes(), line2.as_bytes()).map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!("Invalid TLE format: {}", e))
})?;

let orbit = sgp4::Orbit::from_kozai_elements(
&sgp4::WGS84,
elements.inclination * (core::f64::consts::PI / 180.0),
Expand All @@ -98,7 +102,9 @@ pub fn predict_tle(line1: String, line2: String, time: PyTime) -> ([f64; 3], [f6
elements.mean_anomaly * (core::f64::consts::PI / 180.0),
elements.mean_motion * (core::f64::consts::PI / 720.0),
)
.expect("Failed to load orbit values");
.map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!("Failed to create orbit from TLE: {}", e))
})?;

let constants = Constants::new(
sgp4::WGS84,
Expand All @@ -107,14 +113,29 @@ pub fn predict_tle(line1: String, line2: String, time: PyTime) -> ([f64; 3], [f6
elements.drag_term,
orbit,
)
.expect("Failed to load orbit values");
.map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!(
"Failed to initialize SGP4 constants: {}",
e
))
})?;

let time = time.0.utc();

let naive_time = time
.to_datetime()
.map_err(|_| pyo3::exceptions::PyValueError::new_err("Failed to convert time to datetime"))?
.naive_utc();

let min_diff = elements
.datetime_to_minutes_since_epoch(&time.to_datetime().unwrap().naive_utc())
.unwrap();
.datetime_to_minutes_since_epoch(&naive_time)
.map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!("Failed to convert time: {}", e))
})?;

let state = constants.propagate(min_diff).map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!("Failed to propagate TLE: {}", e))
})?;

let state = constants.propagate(min_diff).expect("Failed to propagate");
(state.position, state.velocity)
Ok((state.position, state.velocity))
}
32 changes: 24 additions & 8 deletions src/kete/rust/spice/spk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,29 +70,45 @@ pub fn spk_loaded_objects_py() -> Vec<String> {
/// Reset the contents of the SPK shared memory.
#[pyfunction]
#[pyo3(name = "spk_reset")]
pub fn spk_reset_py() {
LOADED_SPK.write().unwrap().reset()
pub fn spk_reset_py() -> PyResult<()> {
LOADED_SPK
.write()
.map_err(|_| pyo3::exceptions::PyRuntimeError::new_err("SPK lock poisoned"))?
.reset();
Ok(())
}

/// Reload the core SPK files.
#[pyfunction]
#[pyo3(name = "spk_load_core")]
pub fn spk_load_core_py() {
LOADED_SPK.write().unwrap().load_core().unwrap()
pub fn spk_load_core_py() -> PyResult<()> {
LOADED_SPK
.write()
.map_err(|_| pyo3::exceptions::PyRuntimeError::new_err("SPK lock poisoned"))?
.load_core()?;
Ok(())
}

/// Reload the core PCK files.
#[pyfunction]
#[pyo3(name = "pck_load_core")]
pub fn pck_load_core_py() {
LOADED_PCK.write().unwrap().load_core().unwrap()
pub fn pck_load_core_py() -> PyResult<()> {
LOADED_PCK
.write()
.map_err(|_| pyo3::exceptions::PyRuntimeError::new_err("PCK lock poisoned"))?
.load_core()?;
Ok(())
}

/// Reload the cache SPK files.
#[pyfunction]
#[pyo3(name = "spk_load_cache")]
pub fn spk_load_cache_py() {
LOADED_SPK.write().unwrap().load_cache().unwrap()
pub fn spk_load_cache_py() -> PyResult<()> {
LOADED_SPK
.write()
.map_err(|_| pyo3::exceptions::PyRuntimeError::new_err("SPK lock poisoned"))?
.load_cache()?;
Ok(())
}

/// Calculates the :class:`~kete.State` of the target object at the
Expand Down