1use std::collections::HashMap;
8use std::ops::Range;
9
10#[derive(Debug, Clone, PartialEq)]
28pub struct ResolvedGenericConstraintBounds {
29 index: HashMap<(usize, i32), Range<usize>>,
32 entries: Vec<GenericConstraintBoundEntry>,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq)]
44#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
45pub struct GenericConstraintBoundEntry {
46 pub block_id: Option<i32>,
48 pub bound_lower: Option<f64>,
50 pub bound_upper: Option<f64>,
52}
53
54#[cfg(feature = "serde")]
55mod serde_generic_bounds {
56 use serde::{Deserialize, Deserializer, Serialize, Serializer};
57
58 use super::{GenericConstraintBoundEntry, ResolvedGenericConstraintBounds};
59
60 #[derive(Serialize, Deserialize)]
64 struct WireEntry {
65 constraint_idx: usize,
66 stage_id: i32,
67 pairs: Vec<GenericConstraintBoundEntry>,
68 }
69
70 impl Serialize for ResolvedGenericConstraintBounds {
71 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
72 let mut keys: Vec<(usize, i32)> = self.index.keys().copied().collect();
74 keys.sort_unstable();
75
76 let wire: Vec<WireEntry> = keys
77 .into_iter()
78 .map(|(constraint_idx, stage_id)| {
79 let range = self.index[&(constraint_idx, stage_id)].clone();
80 WireEntry {
81 constraint_idx,
82 stage_id,
83 pairs: self.entries[range].to_vec(),
84 }
85 })
86 .collect();
87
88 wire.serialize(serializer)
89 }
90 }
91
92 impl<'de> Deserialize<'de> for ResolvedGenericConstraintBounds {
93 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
94 let wire = Vec::<WireEntry>::deserialize(deserializer)?;
95
96 let mut index = std::collections::HashMap::new();
97 let mut entries = Vec::new();
98
99 for entry in wire {
100 let start = entries.len();
101 entries.extend_from_slice(&entry.pairs);
102 let end = entries.len();
103 if end > start {
106 index.insert((entry.constraint_idx, entry.stage_id), start..end);
107 }
108 }
109
110 Ok(ResolvedGenericConstraintBounds { index, entries })
111 }
112 }
113}
114
115impl ResolvedGenericConstraintBounds {
116 #[must_use]
131 pub fn empty() -> Self {
132 Self {
133 index: HashMap::new(),
134 entries: Vec::new(),
135 }
136 }
137
138 #[must_use]
173 pub fn new<I>(constraint_id_to_idx: &HashMap<i32, usize>, raw_bounds: I) -> Self
174 where
175 I: Iterator<Item = (i32, i32, Option<i32>, Option<f64>, Option<f64>)>,
176 {
177 let mut index: HashMap<(usize, i32), Range<usize>> = HashMap::new();
178 let mut entries: Vec<GenericConstraintBoundEntry> = Vec::new();
179
180 let mut current_key: Option<(usize, i32)> = None;
181 let mut range_start: usize = 0;
182
183 for (constraint_id, stage_id, block_id, bound_lower, bound_upper) in raw_bounds {
184 let Some(&constraint_idx) = constraint_id_to_idx.get(&constraint_id) else {
185 continue;
186 };
187
188 let key = (constraint_idx, stage_id);
189
190 if current_key != Some(key) {
191 if let Some(prev_key) = current_key {
192 let range_end = entries.len();
193 if range_end > range_start {
194 index.insert(prev_key, range_start..range_end);
195 }
196 }
197 range_start = entries.len();
198 current_key = Some(key);
199 }
200
201 entries.push(GenericConstraintBoundEntry {
202 block_id,
203 bound_lower,
204 bound_upper,
205 });
206 }
207
208 if let Some(last_key) = current_key {
209 let range_end = entries.len();
210 if range_end > range_start {
211 index.insert(last_key, range_start..range_end);
212 }
213 }
214
215 Self { index, entries }
216 }
217
218 #[inline]
231 #[must_use]
232 pub fn is_active(&self, constraint_idx: usize, stage_id: i32) -> bool {
233 self.index.contains_key(&(constraint_idx, stage_id))
234 }
235
236 #[inline]
249 #[must_use]
250 pub fn bounds_for_stage(
251 &self,
252 constraint_idx: usize,
253 stage_id: i32,
254 ) -> &[GenericConstraintBoundEntry] {
255 match self.index.get(&(constraint_idx, stage_id)) {
256 Some(range) => &self.entries[range.clone()],
257 None => &[],
258 }
259 }
260}
261
262#[cfg(test)]
265mod tests {
266 use super::*;
267
268 #[test]
270 fn test_generic_bounds_empty() {
271 let t = ResolvedGenericConstraintBounds::empty();
272 assert!(!t.is_active(0, 0));
273 assert!(!t.is_active(99, -1));
274 assert!(t.bounds_for_stage(0, 0).is_empty());
275 assert!(t.bounds_for_stage(99, 5).is_empty());
276 }
277
278 #[test]
281 fn test_generic_bounds_sparse_active() {
282 let id_map: HashMap<i32, usize> = [(0, 0), (1, 1)].into_iter().collect();
283
284 let rows = vec![(0i32, 0i32, None::<i32>, Some(100.0f64), None::<f64>)];
285 let t = ResolvedGenericConstraintBounds::new(&id_map, rows.into_iter());
286
287 assert!(t.is_active(0, 0), "constraint 0 at stage 0 must be active");
288 assert!(
289 !t.is_active(1, 0),
290 "constraint 1 at stage 0 must not be active"
291 );
292 assert!(
293 !t.is_active(0, 1),
294 "constraint 0 at stage 1 must not be active"
295 );
296 }
297
298 #[test]
300 fn test_generic_bounds_single_block_none() {
301 let id_map: HashMap<i32, usize> = [(0, 0)].into_iter().collect();
302 let rows = vec![(0i32, 0i32, None::<i32>, Some(100.0f64), None::<f64>)];
303 let t = ResolvedGenericConstraintBounds::new(&id_map, rows.into_iter());
304
305 let slice = t.bounds_for_stage(0, 0);
306 assert_eq!(slice.len(), 1);
307 assert_eq!(
308 slice[0],
309 GenericConstraintBoundEntry {
310 block_id: None,
311 bound_lower: Some(100.0),
312 bound_upper: None,
313 }
314 );
315 }
316
317 #[test]
319 fn test_generic_bounds_multiple_blocks() {
320 let id_map: HashMap<i32, usize> = [(0, 0)].into_iter().collect();
321 let rows = vec![
322 (0i32, 2i32, None::<i32>, Some(50.0f64), None::<f64>),
323 (0i32, 2i32, Some(0i32), Some(60.0f64), None::<f64>),
324 (0i32, 2i32, Some(1i32), Some(70.0f64), None::<f64>),
325 ];
326 let t = ResolvedGenericConstraintBounds::new(&id_map, rows.into_iter());
327
328 assert!(t.is_active(0, 2));
329 let slice = t.bounds_for_stage(0, 2);
330 assert_eq!(slice.len(), 3);
331 assert_eq!(
332 slice[0],
333 GenericConstraintBoundEntry {
334 block_id: None,
335 bound_lower: Some(50.0),
336 bound_upper: None,
337 }
338 );
339 assert_eq!(
340 slice[1],
341 GenericConstraintBoundEntry {
342 block_id: Some(0),
343 bound_lower: Some(60.0),
344 bound_upper: None,
345 }
346 );
347 assert_eq!(
348 slice[2],
349 GenericConstraintBoundEntry {
350 block_id: Some(1),
351 bound_lower: Some(70.0),
352 bound_upper: None,
353 }
354 );
355 }
356
357 #[test]
360 fn test_generic_bounds_bound_upper_round_trips() {
361 let id_map: HashMap<i32, usize> = [(0, 0)].into_iter().collect();
362 let rows = vec![
363 (0i32, 0i32, None::<i32>, Some(50.0f64), Some(90.0f64)),
364 (0i32, 0i32, Some(0i32), Some(60.0f64), None::<f64>),
365 ];
366 let t = ResolvedGenericConstraintBounds::new(&id_map, rows.into_iter());
367
368 let slice = t.bounds_for_stage(0, 0);
369 assert_eq!(slice.len(), 2);
370 assert_eq!(
371 slice[0],
372 GenericConstraintBoundEntry {
373 block_id: None,
374 bound_lower: Some(50.0),
375 bound_upper: Some(90.0),
376 }
377 );
378 assert_eq!(
379 slice[1],
380 GenericConstraintBoundEntry {
381 block_id: Some(0),
382 bound_lower: Some(60.0),
383 bound_upper: None,
384 }
385 );
386 }
387
388 #[test]
390 fn test_generic_bounds_upper_only_round_trips() {
391 let id_map: HashMap<i32, usize> = [(0, 0)].into_iter().collect();
392 let rows = vec![(0i32, 0i32, None::<i32>, None::<f64>, Some(10.0f64))];
393 let t = ResolvedGenericConstraintBounds::new(&id_map, rows.into_iter());
394
395 let slice = t.bounds_for_stage(0, 0);
396 assert_eq!(slice.len(), 1);
397 assert_eq!(
398 slice[0],
399 GenericConstraintBoundEntry {
400 block_id: None,
401 bound_lower: None,
402 bound_upper: Some(10.0),
403 }
404 );
405 }
406
407 #[test]
409 fn test_generic_bounds_unknown_constraint_id_skipped() {
410 let id_map: HashMap<i32, usize> = [(0, 0)].into_iter().collect();
411 let rows = vec![(99i32, 0i32, None::<i32>, Some(1000.0f64), None::<f64>)];
412 let t = ResolvedGenericConstraintBounds::new(&id_map, rows.into_iter());
413
414 assert!(!t.is_active(0, 0), "unknown constraint_id must be skipped");
415 assert!(t.bounds_for_stage(0, 0).is_empty());
416 }
417
418 #[test]
420 fn test_generic_bounds_no_rows() {
421 let id_map: HashMap<i32, usize> = [(0, 0), (1, 1)].into_iter().collect();
422 let t = ResolvedGenericConstraintBounds::new(&id_map, std::iter::empty());
423
424 assert!(!t.is_active(0, 0));
425 assert!(!t.is_active(1, 0));
426 assert!(t.bounds_for_stage(0, 0).is_empty());
427 }
428
429 #[test]
431 fn test_generic_bounds_two_stages_one_constraint() {
432 let id_map: HashMap<i32, usize> = [(0, 0), (1, 1)].into_iter().collect();
433 let rows = vec![
434 (0i32, 0i32, None::<i32>, Some(100.0f64), None::<f64>),
435 (0i32, 1i32, None::<i32>, Some(200.0f64), None::<f64>),
436 ];
437 let t = ResolvedGenericConstraintBounds::new(&id_map, rows.into_iter());
438
439 assert!(t.is_active(0, 0));
440 assert!(t.is_active(0, 1));
441 assert!(!t.is_active(1, 0));
442 assert!(!t.is_active(1, 1));
443
444 let s0 = t.bounds_for_stage(0, 0);
445 assert_eq!(s0.len(), 1);
446 assert!((s0[0].bound_lower.expect("lower present") - 100.0).abs() < f64::EPSILON);
447
448 let s1 = t.bounds_for_stage(0, 1);
449 assert_eq!(s1.len(), 1);
450 assert!((s1[0].bound_lower.expect("lower present") - 200.0).abs() < f64::EPSILON);
451 }
452
453 #[test]
454 #[cfg(feature = "serde")]
455 fn test_generic_bounds_serde_roundtrip() {
456 let id_map: HashMap<i32, usize> = [(0, 0), (1, 1)].into_iter().collect();
457 let rows = vec![
458 (0i32, 0i32, None::<i32>, Some(100.0f64), None::<f64>),
459 (0i32, 0i32, Some(1i32), Some(150.0f64), Some(175.0f64)),
460 (1i32, 2i32, None::<i32>, Some(300.0f64), None::<f64>),
461 ];
462 let original = ResolvedGenericConstraintBounds::new(&id_map, rows.into_iter());
463 let json = serde_json::to_string(&original).expect("serialize");
464 let restored: ResolvedGenericConstraintBounds =
465 serde_json::from_str(&json).expect("deserialize");
466 assert_eq!(original, restored);
467 }
468}