use crate::api::expr::{BoolEx, Ex, SetEx};
use crate::base::errors::SymplexError;
use crate::base::interval::{Interval, IntervalKind};
use crate::base::node::ExprNode;
use super::support::{Kind, Piece, Support};
#[derive(Clone, Copy)]
pub(crate) enum Rel {
Gt,
Ge,
Eq,
}
pub(crate) struct Relation {
pub(crate) kind: Rel,
pub(crate) lhs: Ex,
pub(crate) rhs: Ex,
pub(crate) node: BoolEx,
}
pub(crate) fn flatten_relations(event: &BoolEx) -> Option<Vec<Relation>> {
let inner = event.inner.read();
let arena = &inner.arena;
let mut rels = Vec::new();
let mut stack = vec![event.raw_id()];
while let Some(id) = stack.pop() {
let (kind, l, r) = match arena.node(id) {
ExprNode::And(parts) => {
stack.extend(parts.iter().copied());
continue;
}
ExprNode::Gt(l, r) => (Rel::Gt, *l, *r),
ExprNode::Ge(l, r) => (Rel::Ge, *l, *r),
ExprNode::Eq_(l, r) => (Rel::Eq, *l, *r),
_ => return None,
};
rels.push(Relation {
kind,
lhs: event.wrap_as(l),
rhs: event.wrap_as(r),
node: event.wrap(id),
});
}
Some(rels)
}
fn constant(event: &BoolEx) -> Option<bool> {
let inner = event.inner.read();
match inner.arena.node(event.raw_id()) {
ExprNode::BoolTrue => Some(true),
ExprNode::BoolFalse => Some(false),
_ => None,
}
}
fn unsupported(event: &BoolEx) -> SymplexError {
SymplexError::NotImplemented(format!(
"the event `{event}`: supported are relations `X < a`, `X ≤ a`, `X > a`, `X ≥ a`, \
`X = a` in the random variable and their conjunctions (any bounds), and any \
boolean combination of relations in `X` with numeric bounds (`X² < 1`, \
`X < -1 ∨ X > 1`)"
))
}
pub(crate) fn event_region(x: &Ex, event: &BoolEx) -> Result<Support, SymplexError> {
let ctx = x.context();
match constant(event) {
Some(true) => return Ok(Support::reals(&ctx)),
Some(false) => return Ok(Support::from_pieces(Kind::Continuous, vec![])),
None => {}
}
if let Some(rels) = flatten_relations(event)
&& let Some(region) = linear_region(x, &rels)
{
return Ok(region);
}
if event.free_symbols().iter().any(|s| s != x) {
return Err(unsupported(event));
}
let set = SetEx::reduce_inequalities(std::slice::from_ref(event), x).map_err(|e| {
SymplexError::NotImplemented(format!(
"the event `{event}` could not be reduced to a set: {e}"
))
})?;
Support::from_set(Kind::Continuous, &set).ok_or_else(|| unsupported(event))
}
fn linear_region(x: &Ex, rels: &[Relation]) -> Option<Support> {
let ctx = x.context();
let mut lo = ctx.neg_infinity();
let mut hi = ctx.infinity();
let mut lo_open = true;
let mut hi_open = true;
let mut point: Option<Ex> = None;
for rel in rels {
let (bound, x_on_left) = if rel.lhs == *x && !rel.rhs.contains(x) {
(rel.rhs.clone(), true)
} else if rel.rhs == *x && !rel.lhs.contains(x) {
(rel.lhs.clone(), false)
} else {
return None;
};
let above = |lo: Ex, open: bool| {
Support::from_pieces(
Kind::Continuous,
vec![Piece::Interval(Interval {
lower: lo,
upper: ctx.infinity(),
kind: IntervalKind::from_open_ends(open, true),
})],
)
};
let below = |hi: Ex, open: bool| {
Support::from_pieces(
Kind::Continuous,
vec![Piece::Interval(Interval {
lower: ctx.neg_infinity(),
upper: hi,
kind: IntervalKind::from_open_ends(true, open),
})],
)
};
let raise = |lo: &mut Ex, lo_open: &mut bool, a: Ex, strict: bool| {
let one = above(lo.clone(), *lo_open);
let other = above(a, strict);
if let Some(r) = one.intersect(&other)
&& let Some(iv) = r.as_interval()
{
*lo = iv.lower.clone();
*lo_open = iv.kind.lower_open();
} else {
*lo = ctx.infinity();
*lo_open = true;
}
};
let lower = |hi: &mut Ex, hi_open: &mut bool, b: Ex, strict: bool| {
let one = below(hi.clone(), *hi_open);
let other = below(b, strict);
if let Some(r) = one.intersect(&other)
&& let Some(iv) = r.as_interval()
{
*hi = iv.upper.clone();
*hi_open = iv.kind.upper_open();
} else {
*hi = ctx.neg_infinity();
*hi_open = true;
}
};
match (rel.kind, x_on_left) {
(Rel::Gt, true) => raise(&mut lo, &mut lo_open, bound, true),
(Rel::Ge, true) => raise(&mut lo, &mut lo_open, bound, false),
(Rel::Gt, false) => lower(&mut hi, &mut hi_open, bound, true),
(Rel::Ge, false) => lower(&mut hi, &mut hi_open, bound, false),
(Rel::Eq, _) => {
match &point {
Some(p) => match p.equals(&bound) {
Some(true) => {}
Some(false) => {
return Some(Support::from_pieces(Kind::Continuous, vec![]));
}
None => return None,
},
None => point = Some(bound),
}
}
}
}
let interval = Support::from_pieces(
Kind::Continuous,
vec![Piece::Interval(Interval {
lower: lo,
upper: hi,
kind: IntervalKind::from_open_ends(lo_open, hi_open),
})],
);
let interval = interval.intersect(&Support::reals(&ctx))?; match point {
None => Some(interval),
Some(p) => {
let region = Support::from_pieces(Kind::Continuous, vec![Piece::Point(p.clone())]);
if interval.is_empty() {
return Some(interval);
}
match interval.contains(&p) {
Some(false) => Some(Support::from_pieces(Kind::Continuous, vec![])),
_ => Some(region),
}
}
}
}