use serde::{Deserialize, Serialize};
use crate::iteration::comprehension::metadata::{IndexFn, Metadata};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CoordInfo {
pub name: String,
pub kind: CoordKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CoordKind {
Discrete,
Continuous,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CoordSet {
coords: Vec<CoordInfo>,
}
impl CoordSet {
pub fn new() -> Self {
Self { coords: Vec::new() }
}
pub fn push(&mut self, info: CoordInfo) {
self.coords.push(info);
}
pub fn all_discrete<I, S>(names: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self {
coords: names
.into_iter()
.map(|n| CoordInfo {
name: n.into(),
kind: CoordKind::Discrete,
})
.collect(),
}
}
pub fn from_metadata(coord_names: &[String], metadata: &Metadata) -> Self {
let kinds = classify_axes(metadata.index_addressable.as_ref(), coord_names.len());
let coords = coord_names
.iter()
.zip(kinds)
.map(|(name, kind)| CoordInfo {
name: name.clone(),
kind,
})
.collect();
Self { coords }
}
pub fn iter(&self) -> impl Iterator<Item = &CoordInfo> {
self.coords.iter()
}
pub fn names(&self) -> impl Iterator<Item = &str> {
self.coords.iter().map(|c| c.name.as_str())
}
pub fn get(&self, name: &str) -> Option<&CoordInfo> {
self.coords.iter().find(|c| c.name == name)
}
pub fn is_continuous(&self, name: &str) -> bool {
matches!(
self.get(name).map(|c| c.kind),
Some(CoordKind::Continuous)
)
}
pub fn contains(&self, name: &str) -> bool {
self.get(name).is_some()
}
pub fn len(&self) -> usize {
self.coords.len()
}
pub fn is_empty(&self) -> bool {
self.coords.is_empty()
}
}
impl Default for CoordSet {
fn default() -> Self {
Self::new()
}
}
fn classify_axes(idx: Option<&IndexFn>, expected_count: usize) -> Vec<CoordKind> {
match idx {
None => vec![CoordKind::Discrete; expected_count],
Some(IndexFn::Lattice { axis_sizes }) => {
vec![CoordKind::Discrete; axis_sizes.len()]
}
Some(IndexFn::Lockstep { .. }) | Some(IndexFn::Modular { .. }) => {
vec![CoordKind::Discrete; expected_count]
}
Some(IndexFn::Concatenation { .. }) => {
vec![CoordKind::Discrete; expected_count]
}
Some(IndexFn::Continuous { intervals, .. }) => {
vec![CoordKind::Continuous; intervals.len()]
}
Some(IndexFn::Hybrid {
discrete_axes,
continuous_axes,
..
}) => {
let mut kinds = vec![CoordKind::Discrete; discrete_axes.len()];
kinds.extend(vec![CoordKind::Continuous; continuous_axes.len()]);
kinds
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::iteration::comprehension::cardinality::{
CardinalityClass, Interval, ProductMeasure,
};
use crate::iteration::comprehension::metadata::{Materialization, NaturalOrder};
fn dummy_metadata(idx: Option<IndexFn>) -> Metadata {
Metadata {
cardinality: CardinalityClass::Bounded(0),
index_addressable: idx,
natural_order: NaturalOrder::Lex,
materialization: Materialization::Streaming,
}
}
#[test]
fn all_discrete_convenience() {
let s = CoordSet::all_discrete(["k", "limit"]);
assert_eq!(s.len(), 2);
assert!(!s.is_continuous("k"));
assert!(!s.is_continuous("limit"));
assert!(s.contains("k"));
assert!(!s.contains("missing"));
}
#[test]
fn from_metadata_lattice_all_discrete() {
let m = dummy_metadata(Some(IndexFn::Lattice { axis_sizes: vec![3, 4] }));
let s = CoordSet::from_metadata(&["k".to_string(), "limit".to_string()], &m);
assert_eq!(s.len(), 2);
assert!(!s.is_continuous("k"));
assert!(!s.is_continuous("limit"));
}
#[test]
fn from_metadata_continuous_all_continuous() {
let m = dummy_metadata(Some(IndexFn::Continuous {
intervals: vec![Interval::closed(0.0, 1.0), Interval::closed(0.0, 1.0)],
measure: ProductMeasure::Uniform,
}));
let s = CoordSet::from_metadata(&["alpha".to_string(), "beta".to_string()], &m);
assert!(s.is_continuous("alpha"));
assert!(s.is_continuous("beta"));
}
#[test]
fn from_metadata_hybrid_per_axis_split() {
let m = dummy_metadata(Some(IndexFn::Hybrid {
discrete_axes: vec![5],
continuous_axes: vec![Interval::closed(0.0, 1.0)],
measure: ProductMeasure::Uniform,
}));
let s = CoordSet::from_metadata(&["k".to_string(), "theta".to_string()], &m);
assert!(!s.is_continuous("k"));
assert!(s.is_continuous("theta"));
}
#[test]
fn from_metadata_none_index_fn_all_discrete() {
let m = dummy_metadata(None);
let s = CoordSet::from_metadata(&["a".to_string(), "b".to_string()], &m);
assert!(!s.is_continuous("a"));
assert!(!s.is_continuous("b"));
}
}