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