use std::collections::HashMap;
use std::ops::Range;
#[derive(Debug, Clone, PartialEq)]
pub struct ResolvedGenericConstraintBounds {
index: HashMap<(usize, i32), Range<usize>>,
entries: Vec<(Option<i32>, f64)>,
}
#[cfg(feature = "serde")]
mod serde_generic_bounds {
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use super::ResolvedGenericConstraintBounds;
#[derive(Serialize, Deserialize)]
struct WireEntry {
constraint_idx: usize,
stage_id: i32,
pairs: Vec<(Option<i32>, f64)>,
}
impl Serialize for ResolvedGenericConstraintBounds {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut keys: Vec<(usize, i32)> = self.index.keys().copied().collect();
keys.sort_unstable();
let wire: Vec<WireEntry> = keys
.into_iter()
.map(|(constraint_idx, stage_id)| {
let range = self.index[&(constraint_idx, stage_id)].clone();
WireEntry {
constraint_idx,
stage_id,
pairs: self.entries[range].to_vec(),
}
})
.collect();
wire.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for ResolvedGenericConstraintBounds {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let wire = Vec::<WireEntry>::deserialize(deserializer)?;
let mut index = std::collections::HashMap::new();
let mut entries = Vec::new();
for entry in wire {
let start = entries.len();
entries.extend_from_slice(&entry.pairs);
let end = entries.len();
if end > start {
index.insert((entry.constraint_idx, entry.stage_id), start..end);
}
}
Ok(ResolvedGenericConstraintBounds { index, entries })
}
}
}
impl ResolvedGenericConstraintBounds {
#[must_use]
pub fn empty() -> Self {
Self {
index: HashMap::new(),
entries: Vec::new(),
}
}
#[must_use]
pub fn new<I>(constraint_id_to_idx: &HashMap<i32, usize>, raw_bounds: I) -> Self
where
I: Iterator<Item = (i32, i32, Option<i32>, f64)>,
{
let mut index: HashMap<(usize, i32), Range<usize>> = HashMap::new();
let mut entries: Vec<(Option<i32>, f64)> = Vec::new();
let mut current_key: Option<(usize, i32)> = None;
let mut range_start: usize = 0;
for (constraint_id, stage_id, block_id, bound) in raw_bounds {
let Some(&constraint_idx) = constraint_id_to_idx.get(&constraint_id) else {
continue;
};
let key = (constraint_idx, stage_id);
if current_key != Some(key) {
if let Some(prev_key) = current_key {
let range_end = entries.len();
if range_end > range_start {
index.insert(prev_key, range_start..range_end);
}
}
range_start = entries.len();
current_key = Some(key);
}
entries.push((block_id, bound));
}
if let Some(last_key) = current_key {
let range_end = entries.len();
if range_end > range_start {
index.insert(last_key, range_start..range_end);
}
}
Self { index, entries }
}
#[inline]
#[must_use]
pub fn is_active(&self, constraint_idx: usize, stage_id: i32) -> bool {
self.index.contains_key(&(constraint_idx, stage_id))
}
#[inline]
#[must_use]
pub fn bounds_for_stage(&self, constraint_idx: usize, stage_id: i32) -> &[(Option<i32>, f64)] {
match self.index.get(&(constraint_idx, stage_id)) {
Some(range) => &self.entries[range.clone()],
None => &[],
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generic_bounds_empty() {
let t = ResolvedGenericConstraintBounds::empty();
assert!(!t.is_active(0, 0));
assert!(!t.is_active(99, -1));
assert!(t.bounds_for_stage(0, 0).is_empty());
assert!(t.bounds_for_stage(99, 5).is_empty());
}
#[test]
fn test_generic_bounds_sparse_active() {
let id_map: HashMap<i32, usize> = [(0, 0), (1, 1)].into_iter().collect();
let rows = vec![(0i32, 0i32, None::<i32>, 100.0f64)];
let t = ResolvedGenericConstraintBounds::new(&id_map, rows.into_iter());
assert!(t.is_active(0, 0), "constraint 0 at stage 0 must be active");
assert!(
!t.is_active(1, 0),
"constraint 1 at stage 0 must not be active"
);
assert!(
!t.is_active(0, 1),
"constraint 0 at stage 1 must not be active"
);
}
#[test]
fn test_generic_bounds_single_block_none() {
let id_map: HashMap<i32, usize> = [(0, 0)].into_iter().collect();
let rows = vec![(0i32, 0i32, None::<i32>, 100.0f64)];
let t = ResolvedGenericConstraintBounds::new(&id_map, rows.into_iter());
let slice = t.bounds_for_stage(0, 0);
assert_eq!(slice.len(), 1);
assert_eq!(slice[0], (None, 100.0));
}
#[test]
fn test_generic_bounds_multiple_blocks() {
let id_map: HashMap<i32, usize> = [(0, 0)].into_iter().collect();
let rows = vec![
(0i32, 2i32, None::<i32>, 50.0f64),
(0i32, 2i32, Some(0i32), 60.0f64),
(0i32, 2i32, Some(1i32), 70.0f64),
];
let t = ResolvedGenericConstraintBounds::new(&id_map, rows.into_iter());
assert!(t.is_active(0, 2));
let slice = t.bounds_for_stage(0, 2);
assert_eq!(slice.len(), 3);
assert_eq!(slice[0], (None, 50.0));
assert_eq!(slice[1], (Some(0), 60.0));
assert_eq!(slice[2], (Some(1), 70.0));
}
#[test]
fn test_generic_bounds_unknown_constraint_id_skipped() {
let id_map: HashMap<i32, usize> = [(0, 0)].into_iter().collect();
let rows = vec![(99i32, 0i32, None::<i32>, 1000.0f64)];
let t = ResolvedGenericConstraintBounds::new(&id_map, rows.into_iter());
assert!(!t.is_active(0, 0), "unknown constraint_id must be skipped");
assert!(t.bounds_for_stage(0, 0).is_empty());
}
#[test]
fn test_generic_bounds_no_rows() {
let id_map: HashMap<i32, usize> = [(0, 0), (1, 1)].into_iter().collect();
let t = ResolvedGenericConstraintBounds::new(&id_map, std::iter::empty());
assert!(!t.is_active(0, 0));
assert!(!t.is_active(1, 0));
assert!(t.bounds_for_stage(0, 0).is_empty());
}
#[test]
fn test_generic_bounds_two_stages_one_constraint() {
let id_map: HashMap<i32, usize> = [(0, 0), (1, 1)].into_iter().collect();
let rows = vec![
(0i32, 0i32, None::<i32>, 100.0f64),
(0i32, 1i32, None::<i32>, 200.0f64),
];
let t = ResolvedGenericConstraintBounds::new(&id_map, rows.into_iter());
assert!(t.is_active(0, 0));
assert!(t.is_active(0, 1));
assert!(!t.is_active(1, 0));
assert!(!t.is_active(1, 1));
let s0 = t.bounds_for_stage(0, 0);
assert_eq!(s0.len(), 1);
assert!((s0[0].1 - 100.0).abs() < f64::EPSILON);
let s1 = t.bounds_for_stage(0, 1);
assert_eq!(s1.len(), 1);
assert!((s1[0].1 - 200.0).abs() < f64::EPSILON);
}
#[test]
#[cfg(feature = "serde")]
fn test_generic_bounds_serde_roundtrip() {
let id_map: HashMap<i32, usize> = [(0, 0), (1, 1)].into_iter().collect();
let rows = vec![
(0i32, 0i32, None::<i32>, 100.0f64),
(0i32, 0i32, Some(1i32), 150.0f64),
(1i32, 2i32, None::<i32>, 300.0f64),
];
let original = ResolvedGenericConstraintBounds::new(&id_map, rows.into_iter());
let json = serde_json::to_string(&original).expect("serialize");
let restored: ResolvedGenericConstraintBounds =
serde_json::from_str(&json).expect("deserialize");
assert_eq!(original, restored);
}
}