polydat_core/iteration/comprehension/predicate/
coordset.rs1use serde::{Deserialize, Serialize};
14
15use crate::iteration::comprehension::metadata::{IndexFn, Metadata};
16
17#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
20pub struct CoordInfo {
21 pub name: String,
23 pub kind: CoordKind,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
31#[serde(rename_all = "snake_case")]
32pub enum CoordKind {
33 Discrete,
35 Continuous,
37}
38
39#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
44pub struct CoordSet {
45 coords: Vec<CoordInfo>,
46}
47
48impl CoordSet {
49 pub fn new() -> Self {
51 Self { coords: Vec::new() }
52 }
53
54 pub fn push(&mut self, info: CoordInfo) {
56 self.coords.push(info);
57 }
58
59 pub fn all_discrete<I, S>(names: I) -> Self
62 where
63 I: IntoIterator<Item = S>,
64 S: Into<String>,
65 {
66 Self {
67 coords: names
68 .into_iter()
69 .map(|n| CoordInfo {
70 name: n.into(),
71 kind: CoordKind::Discrete,
72 })
73 .collect(),
74 }
75 }
76
77 pub fn from_metadata(coord_names: &[String], metadata: &Metadata) -> Self {
89 let kinds = classify_axes(metadata.index_addressable.as_ref(), coord_names.len());
90 let coords = coord_names
91 .iter()
92 .zip(kinds)
93 .map(|(name, kind)| CoordInfo {
94 name: name.clone(),
95 kind,
96 })
97 .collect();
98 Self { coords }
99 }
100
101 pub fn iter(&self) -> impl Iterator<Item = &CoordInfo> {
103 self.coords.iter()
104 }
105
106 pub fn names(&self) -> impl Iterator<Item = &str> {
108 self.coords.iter().map(|c| c.name.as_str())
109 }
110
111 pub fn get(&self, name: &str) -> Option<&CoordInfo> {
113 self.coords.iter().find(|c| c.name == name)
114 }
115
116 pub fn is_continuous(&self, name: &str) -> bool {
118 matches!(self.get(name).map(|c| c.kind), Some(CoordKind::Continuous))
119 }
120
121 pub fn contains(&self, name: &str) -> bool {
123 self.get(name).is_some()
124 }
125
126 pub fn len(&self) -> usize {
128 self.coords.len()
129 }
130
131 pub fn is_empty(&self) -> bool {
133 self.coords.is_empty()
134 }
135}
136
137impl Default for CoordSet {
138 fn default() -> Self {
139 Self::new()
140 }
141}
142
143fn classify_axes(idx: Option<&IndexFn>, expected_count: usize) -> Vec<CoordKind> {
145 match idx {
146 None => vec![CoordKind::Discrete; expected_count],
147 Some(IndexFn::Lattice { axis_sizes }) => {
148 vec![CoordKind::Discrete; axis_sizes.len()]
149 }
150 Some(IndexFn::Lockstep { .. }) | Some(IndexFn::Modular { .. }) => {
151 vec![CoordKind::Discrete; expected_count]
152 }
153 Some(IndexFn::Concatenation { .. }) => {
154 vec![CoordKind::Discrete; expected_count]
155 }
156 Some(IndexFn::Continuous { intervals, .. }) => {
157 vec![CoordKind::Continuous; intervals.len()]
158 }
159 Some(IndexFn::Hybrid {
160 discrete_axes,
161 continuous_axes,
162 ..
163 }) => {
164 let mut kinds = vec![CoordKind::Discrete; discrete_axes.len()];
165 kinds.extend(vec![CoordKind::Continuous; continuous_axes.len()]);
166 kinds
167 }
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174 use crate::iteration::comprehension::cardinality::{
175 CardinalityClass, Interval, ProductMeasure,
176 };
177 use crate::iteration::comprehension::metadata::{Materialization, NaturalOrder};
178
179 fn dummy_metadata(idx: Option<IndexFn>) -> Metadata {
180 Metadata {
181 cardinality: CardinalityClass::Bounded(0),
182 index_addressable: idx,
183 natural_order: NaturalOrder::Lex,
184 materialization: Materialization::Streaming,
185 }
186 }
187
188 #[test]
189 fn all_discrete_convenience() {
190 let s = CoordSet::all_discrete(["k", "limit"]);
191 assert_eq!(s.len(), 2);
192 assert!(!s.is_continuous("k"));
193 assert!(!s.is_continuous("limit"));
194 assert!(s.contains("k"));
195 assert!(!s.contains("missing"));
196 }
197
198 #[test]
199 fn from_metadata_lattice_all_discrete() {
200 let m = dummy_metadata(Some(IndexFn::Lattice {
201 axis_sizes: vec![3, 4],
202 }));
203 let s = CoordSet::from_metadata(&["k".to_string(), "limit".to_string()], &m);
204 assert_eq!(s.len(), 2);
205 assert!(!s.is_continuous("k"));
206 assert!(!s.is_continuous("limit"));
207 }
208
209 #[test]
210 fn from_metadata_continuous_all_continuous() {
211 let m = dummy_metadata(Some(IndexFn::Continuous {
212 intervals: vec![Interval::closed(0.0, 1.0), Interval::closed(0.0, 1.0)],
213 measure: ProductMeasure::Uniform,
214 }));
215 let s = CoordSet::from_metadata(&["alpha".to_string(), "beta".to_string()], &m);
216 assert!(s.is_continuous("alpha"));
217 assert!(s.is_continuous("beta"));
218 }
219
220 #[test]
221 fn from_metadata_hybrid_per_axis_split() {
222 let m = dummy_metadata(Some(IndexFn::Hybrid {
223 discrete_axes: vec![5],
224 continuous_axes: vec![Interval::closed(0.0, 1.0)],
225 measure: ProductMeasure::Uniform,
226 }));
227 let s = CoordSet::from_metadata(&["k".to_string(), "theta".to_string()], &m);
230 assert!(!s.is_continuous("k"));
231 assert!(s.is_continuous("theta"));
232 }
233
234 #[test]
235 fn from_metadata_none_index_fn_all_discrete() {
236 let m = dummy_metadata(None);
237 let s = CoordSet::from_metadata(&["a".to_string(), "b".to_string()], &m);
238 assert!(!s.is_continuous("a"));
241 assert!(!s.is_continuous("b"));
242 }
243}