use super::constraint::EmlConstraint;
use crate::tree::EmlNode;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Interval {
pub lo: f64,
pub hi: f64,
}
impl Interval {
pub fn new(lo: f64, hi: f64) -> Self {
Self { lo, hi }
}
pub fn point(v: f64) -> Self {
Self::new(v, v)
}
pub fn is_empty(&self) -> bool {
self.lo > self.hi || self.lo.is_nan() || self.hi.is_nan()
}
pub fn width(&self) -> f64 {
self.hi - self.lo
}
pub fn midpoint(&self) -> f64 {
(self.lo + self.hi) / 2.0
}
pub fn contains(&self, x: f64) -> bool {
x >= self.lo && x <= self.hi
}
pub fn split(&self) -> (Self, Self) {
let mid = self.midpoint();
(Self::new(self.lo, mid), Self::new(mid, self.hi))
}
pub fn intersect(&self, other: &Self) -> Self {
Self::new(self.lo.max(other.lo), self.hi.min(other.hi))
}
pub fn hull(&self, other: &Self) -> Self {
Self::new(self.lo.min(other.lo), self.hi.max(other.hi))
}
pub fn exp(&self) -> Self {
Self::new(self.lo.exp(), self.hi.exp())
}
pub fn ln(&self) -> Self {
if self.lo <= 0.0 || !self.lo.is_finite() || !self.hi.is_finite() {
Self::new(f64::INFINITY, f64::NEG_INFINITY)
} else {
Self::new(self.lo.ln(), self.hi.ln())
}
}
pub fn exp_inv(&self) -> Self {
self.ln()
}
pub fn ln_inv(&self) -> Self {
self.exp()
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum PropResult {
Changed,
Stable,
Conflict,
}
#[derive(Clone, Debug)]
pub struct IntervalDomain {
pub vars: Vec<Interval>,
}
impl IntervalDomain {
pub fn new(bounds: &[(f64, f64)], num_vars: usize) -> Self {
let vars = (0..num_vars)
.map(|i| {
if i < bounds.len() {
Interval::new(bounds[i].0, bounds[i].1)
} else {
Interval::new(-10.0, 10.0)
}
})
.collect();
Self { vars }
}
pub fn is_empty(&self) -> bool {
self.vars.iter().any(Interval::is_empty)
}
pub fn propagate(&mut self, c: &EmlConstraint) -> PropResult {
const MAX_ITERATIONS: usize = 20;
let mut changed_any = false;
for _ in 0..MAX_ITERATIONS {
let result = propagate_once(&mut self.vars, c);
match result {
PropResult::Conflict => return PropResult::Conflict,
PropResult::Changed => {
changed_any = true;
continue;
}
PropResult::Stable => break,
}
}
if changed_any {
PropResult::Changed
} else {
PropResult::Stable
}
}
}
fn eval_interval(node: &EmlNode, vars: &[Interval]) -> Interval {
match node {
EmlNode::One => Interval::new(1.0, 1.0),
EmlNode::Const(v) => Interval::new(*v, *v),
EmlNode::Var(i) => vars
.get(*i)
.copied()
.unwrap_or_else(|| Interval::new(-10.0, 10.0)),
EmlNode::Eml { left, right } => {
let l = eval_interval(left, vars);
let r = eval_interval(right, vars);
if l.is_empty() || r.is_empty() {
return Interval::new(f64::INFINITY, f64::NEG_INFINITY);
}
let exp_l = l.exp();
let ln_r = r.ln();
if ln_r.is_empty() {
return Interval::new(f64::INFINITY, f64::NEG_INFINITY);
}
Interval::new(exp_l.lo - ln_r.hi, exp_l.hi - ln_r.lo)
}
}
}
fn backward_propagate(
node: &EmlNode,
vars: &mut [Interval],
out_constraint: Interval,
) -> PropResult {
if out_constraint.is_empty() {
return PropResult::Conflict;
}
match node {
EmlNode::Var(i) => {
let idx = *i;
let old = vars
.get(idx)
.copied()
.unwrap_or_else(|| Interval::new(-10.0, 10.0));
let new_iv = old.intersect(&out_constraint);
if new_iv.is_empty() {
return PropResult::Conflict;
}
if new_iv.lo > old.lo + 1e-12 || new_iv.hi < old.hi - 1e-12 {
if idx < vars.len() {
vars[idx] = new_iv;
}
PropResult::Changed
} else {
PropResult::Stable
}
}
EmlNode::One => {
if !out_constraint.contains(1.0) {
PropResult::Conflict
} else {
PropResult::Stable
}
}
EmlNode::Const(v) => {
if !out_constraint.contains(*v) {
PropResult::Conflict
} else {
PropResult::Stable
}
}
EmlNode::Eml { left, right } => {
let left_iv = eval_interval(left, vars);
let right_iv = eval_interval(right, vars);
if left_iv.is_empty() || right_iv.is_empty() {
return PropResult::Conflict;
}
let exp_l = left_iv.exp();
let ln_r = right_iv.ln();
if ln_r.is_empty() {
return PropResult::Conflict;
}
let forward_out = Interval::new(exp_l.lo - ln_r.hi, exp_l.hi - ln_r.lo);
let out_c = out_constraint.intersect(&forward_out);
if out_c.is_empty() {
return PropResult::Conflict;
}
let exp_l_back = Interval::new(out_c.lo + ln_r.lo, out_c.hi + ln_r.hi);
let exp_l_c = exp_l.intersect(&exp_l_back);
if exp_l_c.is_empty() {
return PropResult::Conflict;
}
let left_c = left_iv.intersect(&exp_l_c.exp_inv());
let ln_r_back = Interval::new(exp_l.lo - out_c.hi, exp_l.hi - out_c.lo);
let ln_r_c = ln_r.intersect(&ln_r_back);
if ln_r_c.is_empty() {
return PropResult::Conflict;
}
let right_c = right_iv.intersect(&ln_r_c.ln_inv());
let r_left = backward_propagate(left, vars, left_c);
let r_right = backward_propagate(right, vars, right_c);
match (r_left, r_right) {
(PropResult::Conflict, _) | (_, PropResult::Conflict) => PropResult::Conflict,
(PropResult::Changed, _) | (_, PropResult::Changed) => PropResult::Changed,
_ => PropResult::Stable,
}
}
}
}
fn propagate_once(vars: &mut [Interval], c: &EmlConstraint) -> PropResult {
match c {
EmlConstraint::EqZero(tree) => {
let v = eval_interval(&tree.root, vars);
if v.is_empty() {
return PropResult::Conflict;
}
if v.lo > 0.0 || v.hi < 0.0 {
return PropResult::Conflict;
}
backward_propagate(&tree.root, vars, Interval::new(0.0, 0.0))
}
EmlConstraint::GtZero(tree) => {
let v = eval_interval(&tree.root, vars);
if v.is_empty() {
return PropResult::Conflict;
}
if v.hi <= 0.0 {
return PropResult::Conflict;
}
let eps = f64::EPSILON * 4.0;
let out_c = Interval::new(v.lo.max(eps), v.hi);
backward_propagate(&tree.root, vars, out_c)
}
EmlConstraint::GeZero(tree) => {
let v = eval_interval(&tree.root, vars);
if v.is_empty() {
return PropResult::Conflict;
}
if v.hi < 0.0 {
return PropResult::Conflict;
}
let out_c = Interval::new(v.lo.max(0.0), v.hi);
backward_propagate(&tree.root, vars, out_c)
}
EmlConstraint::LtZero(tree) => {
let v = eval_interval(&tree.root, vars);
if v.is_empty() {
return PropResult::Conflict;
}
if v.lo >= 0.0 {
return PropResult::Conflict;
}
let eps = f64::EPSILON * 4.0;
let out_c = Interval::new(v.lo, v.hi.min(-eps));
if out_c.is_empty() {
return PropResult::Conflict;
}
backward_propagate(&tree.root, vars, out_c)
}
EmlConstraint::LeZero(tree) => {
let v = eval_interval(&tree.root, vars);
if v.is_empty() {
return PropResult::Conflict;
}
if v.lo > 0.0 {
return PropResult::Conflict;
}
let out_c = Interval::new(v.lo, v.hi.min(0.0));
if out_c.is_empty() {
return PropResult::Conflict;
}
backward_propagate(&tree.root, vars, out_c)
}
EmlConstraint::NeZero(tree) => {
use super::helpers::NEZERO_EPS;
let v = eval_interval(&tree.root, vars);
if v.is_empty() {
return PropResult::Conflict;
}
if (v.hi - v.lo) < NEZERO_EPS && v.lo.abs() < NEZERO_EPS {
return PropResult::Conflict;
}
if let crate::tree::EmlNode::Var(i) = &*tree.root {
let idx = *i;
if let Some(iv) = vars.get(idx).copied() {
if iv.lo == 0.0 && iv.hi > 0.0 {
vars[idx] = Interval::new(0.0_f64.next_up(), iv.hi);
return PropResult::Changed;
}
if iv.hi == 0.0 && iv.lo < 0.0 {
vars[idx] = Interval::new(iv.lo, 0.0_f64.next_down());
return PropResult::Changed;
}
}
}
PropResult::Stable
}
EmlConstraint::Not(inner) => {
let nnf = (**inner).clone().to_nnf();
propagate_once(vars, &nnf)
}
EmlConstraint::And(constraints) => {
let mut any_changed = false;
for inner in constraints {
match propagate_once(vars, inner) {
PropResult::Conflict => return PropResult::Conflict,
PropResult::Changed => any_changed = true,
PropResult::Stable => {}
}
}
if any_changed {
PropResult::Changed
} else {
PropResult::Stable
}
}
EmlConstraint::Or(constraints) => {
if constraints.is_empty() {
return PropResult::Conflict;
}
let original: Vec<Interval> = vars.to_vec();
let mut survivors: Vec<Vec<Interval>> = Vec::with_capacity(constraints.len());
for inner in constraints {
let mut branch = original.clone();
let result = propagate_once(&mut branch, inner);
if result != PropResult::Conflict {
survivors.push(branch);
}
}
match survivors.len() {
0 => PropResult::Conflict,
1 => {
let branch = survivors.remove(0);
let mut any_changed = false;
for (i, (new_iv, orig_iv)) in branch.iter().zip(original.iter()).enumerate() {
if i < vars.len() {
let tightened = new_iv.intersect(orig_iv);
if !tightened.is_empty()
&& (tightened.lo > orig_iv.lo + 1e-12
|| tightened.hi < orig_iv.hi - 1e-12)
{
vars[i] = tightened;
any_changed = true;
}
}
}
if any_changed {
PropResult::Changed
} else {
PropResult::Stable
}
}
_ => {
let mut any_changed = false;
for i in 0..vars.len() {
let hull = survivors
.iter()
.filter_map(|b| b.get(i).copied())
.reduce(|acc, iv| acc.hull(&iv));
if let Some(hull_iv) = hull {
let tightened = hull_iv.intersect(&original[i]);
if !tightened.is_empty()
&& (tightened.lo > original[i].lo + 1e-12
|| tightened.hi < original[i].hi - 1e-12)
{
vars[i] = tightened;
any_changed = true;
}
}
}
if any_changed {
PropResult::Changed
} else {
PropResult::Stable
}
}
}
}
EmlConstraint::ForAll { .. } => PropResult::Stable,
EmlConstraint::Exists { .. } => PropResult::Stable,
}
}