use std::collections::HashMap;
use cobre_core::{GenericConstraint, ResolvedGenericConstraintBounds};
use crate::constraints::GenericConstraintBoundsRow;
#[must_use]
pub fn resolve_generic_constraint_bounds(
constraints: &[GenericConstraint],
raw_bounds: &[GenericConstraintBoundsRow],
) -> ResolvedGenericConstraintBounds {
let id_to_idx: HashMap<i32, usize> = constraints
.iter()
.enumerate()
.map(|(idx, c)| (c.id.0, idx))
.collect();
ResolvedGenericConstraintBounds::new(
&id_to_idx,
raw_bounds.iter().map(|r| {
(
r.constraint_id,
r.stage_id,
r.block_id,
r.bound_lower,
r.bound_upper,
)
}),
)
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::too_many_lines,
clippy::float_cmp
)]
mod tests {
use super::*;
use cobre_core::EntityId;
use cobre_core::generic_constraint::ConstraintExpression;
use cobre_core::model::resolved::GenericConstraintBoundEntry;
fn make_constraint(id: i32) -> GenericConstraint {
use cobre_core::generic_constraint::SlackConfig;
GenericConstraint {
id: EntityId(id),
name: format!("c{id}"),
description: None,
expression: ConstraintExpression { terms: vec![] },
slack: SlackConfig {
enabled: false,
penalty: None,
},
bound_lower_affine: None,
bound_upper_affine: None,
}
}
fn make_row(
constraint_id: i32,
stage_id: i32,
block_id: Option<i32>,
bound_lower: f64,
) -> GenericConstraintBoundsRow {
GenericConstraintBoundsRow {
constraint_id,
stage_id,
block_id,
bound_lower: Some(bound_lower),
bound_upper: None,
}
}
#[test]
fn test_empty_constraints_empty_bounds() {
let table = resolve_generic_constraint_bounds(&[], &[]);
assert!(!table.is_active(0, 0));
assert!(table.bounds_for_stage(0, 0).is_empty());
}
#[test]
fn test_constraints_no_bounds() {
let constraints = vec![make_constraint(0), make_constraint(1)];
let table = resolve_generic_constraint_bounds(&constraints, &[]);
assert!(!table.is_active(0, 0));
assert!(!table.is_active(1, 0));
}
#[test]
fn test_two_constraints_sparse_bounds() {
let constraints = vec![make_constraint(0), make_constraint(1)];
let rows = vec![make_row(0, 0, None, 100.0), make_row(0, 1, None, 200.0)];
let table = resolve_generic_constraint_bounds(&constraints, &rows);
assert!(table.is_active(0, 0));
assert!(table.is_active(0, 1));
assert!(!table.is_active(1, 0));
assert!(!table.is_active(1, 1));
let s0 = table.bounds_for_stage(0, 0);
assert_eq!(s0.len(), 1);
assert!((s0[0].bound_lower.expect("lower present") - 100.0).abs() < f64::EPSILON);
assert!(s0[0].block_id.is_none());
let s1 = table.bounds_for_stage(0, 1);
assert_eq!(s1.len(), 1);
assert!((s1[0].bound_lower.expect("lower present") - 200.0).abs() < f64::EPSILON);
}
#[test]
fn test_block_specific_bounds() {
let constraints = vec![make_constraint(0)];
let rows = vec![
make_row(0, 0, None, 50.0),
make_row(0, 0, Some(0), 60.0),
make_row(0, 0, Some(1), 70.0),
];
let table = resolve_generic_constraint_bounds(&constraints, &rows);
assert!(table.is_active(0, 0));
let slice = table.bounds_for_stage(0, 0);
assert_eq!(slice.len(), 3);
assert_eq!(
slice[0],
GenericConstraintBoundEntry {
block_id: None,
bound_lower: Some(50.0),
bound_upper: None,
}
);
assert_eq!(
slice[1],
GenericConstraintBoundEntry {
block_id: Some(0),
bound_lower: Some(60.0),
bound_upper: None,
}
);
assert_eq!(
slice[2],
GenericConstraintBoundEntry {
block_id: Some(1),
bound_lower: Some(70.0),
bound_upper: None,
}
);
}
#[test]
fn test_bound_upper_flows_through_resolution() {
let constraints = vec![make_constraint(0)];
let mut row = make_row(0, 0, None, 50.0);
row.bound_upper = Some(90.0);
let table = resolve_generic_constraint_bounds(&constraints, &[row]);
let slice = table.bounds_for_stage(0, 0);
assert_eq!(slice.len(), 1);
assert_eq!(
slice[0],
GenericConstraintBoundEntry {
block_id: None,
bound_lower: Some(50.0),
bound_upper: Some(90.0),
}
);
}
#[test]
fn test_unknown_constraint_id_skipped() {
let constraints = vec![make_constraint(0)];
let rows = vec![
make_row(0, 0, None, 100.0),
make_row(99, 0, None, 9999.0), ];
let table = resolve_generic_constraint_bounds(&constraints, &rows);
assert!(table.is_active(0, 0));
let slice = table.bounds_for_stage(0, 0);
assert_eq!(slice.len(), 1);
assert!((slice[0].bound_lower.expect("lower present") - 100.0).abs() < f64::EPSILON);
}
#[test]
fn test_ac_is_active_true() {
let constraints = vec![make_constraint(0), make_constraint(1)];
let rows = vec![make_row(0, 0, None, 100.0), make_row(0, 1, None, 150.0)];
let table = resolve_generic_constraint_bounds(&constraints, &rows);
assert!(table.is_active(0, 0));
}
#[test]
fn test_ac_is_active_false() {
let constraints = vec![make_constraint(0), make_constraint(1)];
let rows = vec![make_row(0, 0, None, 100.0)];
let table = resolve_generic_constraint_bounds(&constraints, &rows);
assert!(!table.is_active(1, 0));
}
#[test]
fn test_ac_bounds_for_stage() {
let constraints = vec![make_constraint(0)];
let rows = vec![make_row(0, 0, None, 100.0)];
let table = resolve_generic_constraint_bounds(&constraints, &rows);
let slice = table.bounds_for_stage(0, 0);
assert_eq!(slice.len(), 1);
assert_eq!(
slice[0],
GenericConstraintBoundEntry {
block_id: None,
bound_lower: Some(100.0),
bound_upper: None,
}
);
}
}