use itertools::Either;
use crate::{
NightId, TrajId,
observation_dataset::{
ObsDataset, ObsDatasetError, index::ObsMapIndex, observation::Observation,
},
observer::{Observer, dataset::ObserverId},
};
impl ObsDataset {
pub fn iter_observations(&self) -> impl Iterator<Item = &Observation> {
self.observations.iter()
}
pub fn iter_observer(
&self,
) -> Result<impl Iterator<Item = (ObserverId, &Observer)>, ObsDatasetError> {
let mpc_iter = self
.observer_dataset
.mpc_observers()?
.iter()
.map(|(code, obs)| (ObserverId::MpcCode(*code), obs));
let custom_observer_iter = self
.observer_dataset
.custom_observers
.iter()
.enumerate()
.map(|(idx, obs)| (ObserverId::IntId(idx), obs));
Ok(mpc_iter.chain(custom_observer_iter))
}
}
pub enum MemLayoutObservations<'a> {
Contiguous(&'a [Observation]),
Split(Vec<&'a Observation>),
}
impl<'a> MemLayoutObservations<'a> {
pub fn len(&self) -> usize {
match self {
MemLayoutObservations::Contiguous(slice) => slice.len(),
MemLayoutObservations::Split(vec) => vec.len(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn iter(&self) -> impl Iterator<Item = &'a Observation> + '_ {
match self {
MemLayoutObservations::Contiguous(slice) => Either::Left(slice.iter()),
MemLayoutObservations::Split(vec) => Either::Right(vec.iter().copied()),
}
}
pub fn collect_into_vec(&self) -> Vec<&'a Observation> {
self.iter().collect()
}
}
impl<'a> IntoIterator for MemLayoutObservations<'a> {
type Item = &'a Observation;
type IntoIter = Either<std::slice::Iter<'a, Observation>, std::vec::IntoIter<&'a Observation>>;
fn into_iter(self) -> Self::IntoIter {
match self {
MemLayoutObservations::Contiguous(slice) => Either::Left(slice.iter()),
MemLayoutObservations::Split(vec) => Either::Right(vec.into_iter()),
}
}
}
impl ObsDataset {
pub fn iter_night_observations(
&self,
night_id: &NightId,
) -> Option<impl Iterator<Item = &Observation>> {
self.index
.iter_night_obs_index(night_id)
.map(|indices| indices.map(|idx| &self.observations[idx]))
}
pub fn iter_full_night(&self) -> Option<impl Iterator<Item = (NightId, &Observation)>> {
self.index
.iter_full_night()
.map(|night_iter| night_iter.map(|(night_id, idx)| (night_id, &self.observations[idx])))
}
pub fn materialize_night(&self, night_id: &NightId) -> Option<MemLayoutObservations<'_>> {
let night_index = self.index.obs_index_by_night.as_ref()?.get(night_id)?;
match night_index {
ObsMapIndex::Split(indices) => Some(MemLayoutObservations::Split(
indices.iter().map(|idx| &self.observations[*idx]).collect(),
)),
ObsMapIndex::Contiguous { start, end } => Some(MemLayoutObservations::Contiguous(
&self.observations[*start..*end],
)),
}
}
pub fn iter_night_id(&self) -> Option<impl Iterator<Item = &NightId>> {
self.index.iter_night_id()
}
pub fn len_night(&self, night_id: &NightId) -> Option<usize> {
self.index.len_night(night_id)
}
pub fn nb_night(&self) -> Option<usize> {
self.iter_night_id().map(|iter| iter.count())
}
pub fn is_night_contiguous(&self, night_id: &NightId) -> Option<bool> {
let entry = self.index.obs_index_by_night.as_ref()?.get(night_id)?;
Some(matches!(entry, ObsMapIndex::Contiguous { .. }))
}
}
impl ObsDataset {
pub fn iter_trajectory_observations(
&self,
traj_id: impl Into<TrajId>,
) -> Option<impl Iterator<Item = &Observation>> {
self.index
.iter_traj_obs_index(traj_id)
.map(|indices| indices.map(|idx| &self.observations[idx]))
}
pub fn iter_full_trajectory(&self) -> Option<impl Iterator<Item = (TrajId, &Observation)>> {
self.index.iter_full_trajectory().map(|traj_iter| {
traj_iter.map(|(traj_id, idx)| (traj_id.clone(), &self.observations[idx]))
})
}
pub fn materialize_trajectory(
&self,
traj_id: impl Into<TrajId>,
) -> Option<MemLayoutObservations<'_>> {
let traj_id = traj_id.into();
let traj_index = self.index.obs_index_by_trajectory.as_ref()?.get(&traj_id)?;
match traj_index {
ObsMapIndex::Split(indices) => Some(MemLayoutObservations::Split(
indices.iter().map(|idx| &self.observations[*idx]).collect(),
)),
ObsMapIndex::Contiguous { start, end } => Some(MemLayoutObservations::Contiguous(
&self.observations[*start..*end],
)),
}
}
pub fn iter_traj_id(&self) -> Option<impl Iterator<Item = &TrajId>> {
self.index.iter_traj_id()
}
pub fn len_trajectory(&self, traj_id: impl Into<TrajId>) -> Option<usize> {
self.index.len_trajectory(traj_id)
}
pub fn is_traj_contiguous(&self, traj_id: impl Into<TrajId>) -> Option<bool> {
let traj_id = traj_id.into();
let entry = self.index.obs_index_by_trajectory.as_ref()?.get(&traj_id)?;
Some(matches!(entry, ObsMapIndex::Contiguous { .. }))
}
}
#[cfg(test)]
mod iter_tests {
use ahash::AHashMap;
use crate::{
NightId, TrajId,
coordinates::equatorial::EquCoord,
observation_dataset::{
ObsDataset,
index::{NightIndexMap, ObsMapIndex, TrajIndexMap},
observation::ObservationInput,
},
observer::error_model::ObsErrorModel,
photometry::{Filter, Photometry},
};
fn make_obs(id: u64, _index: usize) -> ObservationInput {
ObservationInput {
id,
equ_coord: EquCoord::new(0.5, 1e-5, 0.2, 1e-5),
photometry: Photometry {
magnitude: 15.0,
error: 0.1,
filter: Filter::String("G".to_string()),
},
mjd_tt: 60000.0 + id as f64,
observer: None,
}
}
fn make_dataset_with_index() -> ObsDataset {
let obs = vec![
make_obs(1, 0),
make_obs(2, 1),
make_obs(3, 2),
make_obs(4, 3),
];
let mut night_map: NightIndexMap = AHashMap::new();
night_map.insert(NightId(1), ObsMapIndex::Contiguous { start: 0, end: 2 });
night_map.insert(NightId(2), ObsMapIndex::Contiguous { start: 2, end: 4 });
let mut traj_map: TrajIndexMap = AHashMap::new();
traj_map.insert(TrajId::Int(10), ObsMapIndex::Split(vec![0, 2]));
traj_map.insert(TrajId::Int(20), ObsMapIndex::Split(vec![1, 3]));
ObsDataset::new(
obs,
vec![],
Some(ObsErrorModel::FCCT14),
Some(night_map),
Some(traj_map),
)
}
fn make_dataset_no_index() -> ObsDataset {
let obs = vec![make_obs(1, 0), make_obs(2, 1)];
ObsDataset::new(obs, vec![], Some(ObsErrorModel::FCCT14), None, None)
}
#[test]
fn iter_observations_count() {
let ds = make_dataset_with_index();
assert_eq!(ds.iter_observations().count(), 4);
}
#[test]
fn iter_observations_ids_in_order() {
let ds = make_dataset_with_index();
let ids: Vec<u64> = ds.iter_observations().map(|o| *o.id()).collect();
assert_eq!(ids, vec![1, 2, 3, 4]);
}
#[test]
fn mem_layout_contiguous_len_and_iter() {
let ds = make_dataset_with_index();
let night = ds.materialize_night(&NightId(1)).unwrap();
assert_eq!(night.len(), 2);
assert!(!night.is_empty());
let ids: Vec<u64> = night.iter().map(|o| *o.id()).collect();
assert_eq!(ids, vec![1, 2]);
}
#[test]
fn mem_layout_split_len_and_iter() {
let ds = make_dataset_with_index();
let traj = ds.materialize_trajectory(TrajId::Int(10)).unwrap();
assert_eq!(traj.len(), 2);
let ids: Vec<u64> = traj.iter().map(|o| *o.id()).collect();
assert_eq!(ids, vec![1, 3]);
}
#[test]
fn mem_layout_is_empty_false_for_non_empty() {
let ds = make_dataset_with_index();
assert!(!ds.materialize_night(&NightId(1)).unwrap().is_empty());
}
#[test]
fn mem_layout_into_iter_contiguous() {
let ds = make_dataset_with_index();
let night = ds.materialize_night(&NightId(2)).unwrap();
let ids: Vec<u64> = night.into_iter().map(|o| *o.id()).collect();
assert_eq!(ids, vec![3, 4]);
}
#[test]
fn mem_layout_into_iter_split() {
let ds = make_dataset_with_index();
let traj = ds.materialize_trajectory(TrajId::Int(20)).unwrap();
let ids: Vec<u64> = traj.into_iter().map(|o| *o.id()).collect();
assert_eq!(ids, vec![2, 4]);
}
#[test]
fn iter_night_observations_some_for_existing() {
let ds = make_dataset_with_index();
assert!(ds.iter_night_observations(&NightId(1)).is_some());
}
#[test]
fn iter_night_observations_none_for_missing() {
let ds = make_dataset_with_index();
assert!(ds.iter_night_observations(&NightId(99)).is_none());
}
#[test]
fn iter_night_observations_none_without_index() {
let ds = make_dataset_no_index();
assert!(ds.iter_night_observations(&NightId(1)).is_none());
}
#[test]
fn iter_night_observations_count() {
let ds = make_dataset_with_index();
let count = ds.iter_night_observations(&NightId(1)).unwrap().count();
assert_eq!(count, 2);
}
#[test]
fn iter_full_night_some_with_index() {
let ds = make_dataset_with_index();
assert!(ds.iter_full_night().is_some());
}
#[test]
fn iter_full_night_none_without_index() {
let ds = make_dataset_no_index();
assert!(ds.iter_full_night().is_none());
}
#[test]
fn iter_full_night_total_count() {
let ds = make_dataset_with_index();
assert_eq!(ds.iter_full_night().unwrap().count(), 4);
}
#[test]
fn iter_night_id_count() {
let ds = make_dataset_with_index();
assert_eq!(ds.iter_night_id().unwrap().count(), 2);
}
#[test]
fn len_night_correct() {
let ds = make_dataset_with_index();
assert_eq!(ds.len_night(&NightId(1)), Some(2));
assert_eq!(ds.len_night(&NightId(99)), None);
}
#[test]
fn iter_trajectory_observations_some_for_existing() {
let ds = make_dataset_with_index();
assert!(ds.iter_trajectory_observations(&TrajId::Int(10)).is_some());
}
#[test]
fn iter_trajectory_observations_none_for_missing() {
let ds = make_dataset_with_index();
assert!(ds.iter_trajectory_observations(&TrajId::Int(99)).is_none());
}
#[test]
fn iter_trajectory_observations_none_without_index() {
let ds = make_dataset_no_index();
assert!(ds.iter_trajectory_observations(&TrajId::Int(10)).is_none());
}
#[test]
fn iter_trajectory_observations_count() {
let ds = make_dataset_with_index();
let count = ds
.iter_trajectory_observations(&TrajId::Int(10))
.unwrap()
.count();
assert_eq!(count, 2);
}
#[test]
fn iter_full_trajectory_some_with_index() {
let ds = make_dataset_with_index();
assert!(ds.iter_full_trajectory().is_some());
}
#[test]
fn iter_full_trajectory_none_without_index() {
let ds = make_dataset_no_index();
assert!(ds.iter_full_trajectory().is_none());
}
#[test]
fn iter_full_trajectory_total_count() {
let ds = make_dataset_with_index();
assert_eq!(ds.iter_full_trajectory().unwrap().count(), 4);
}
#[test]
fn iter_traj_id_count() {
let ds = make_dataset_with_index();
assert_eq!(ds.iter_traj_id().unwrap().count(), 2);
}
#[test]
fn len_trajectory_correct() {
let ds = make_dataset_with_index();
assert_eq!(ds.len_trajectory(TrajId::Int(10)), Some(2));
assert_eq!(ds.len_trajectory(TrajId::Int(99)), None);
}
}