use std::fmt;
use crate::api::context::Context;
use crate::api::expr::{Ex, SetEx};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Kind {
Continuous,
Discrete,
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum Piece {
Interval {
lo: Ex,
hi: Ex,
lo_open: bool,
hi_open: bool,
},
Point(Ex),
}
#[derive(Clone, Debug, PartialEq)]
pub struct Support {
kind: Kind,
pieces: Vec<Piece>,
}
pub(crate) fn is_pos_inf(e: &Ex) -> bool {
*e == e.context().infinity()
}
pub(crate) fn is_neg_inf(e: &Ex) -> bool {
*e == e.context().neg_infinity()
}
fn max_lo(a: &Ex, a_open: bool, b: &Ex, b_open: bool) -> (Ex, bool) {
if is_neg_inf(a) {
return (b.clone(), b_open);
}
if is_neg_inf(b) {
return (a.clone(), a_open);
}
match (a - b).is_positive() {
Some(true) => (a.clone(), a_open),
Some(false) => match (a - b).is_zero() {
Some(true) => (a.clone(), a_open || b_open),
_ => (b.clone(), b_open),
},
None => (a.max_with(b), a_open || b_open),
}
}
fn min_hi(a: &Ex, a_open: bool, b: &Ex, b_open: bool) -> (Ex, bool) {
if is_pos_inf(a) {
return (b.clone(), b_open);
}
if is_pos_inf(b) {
return (a.clone(), a_open);
}
match (b - a).is_positive() {
Some(true) => (a.clone(), a_open),
Some(false) => match (a - b).is_zero() {
Some(true) => (a.clone(), a_open || b_open),
_ => (b.clone(), b_open),
},
None => (a.min_with(b), a_open || b_open),
}
}
fn interval_empty(lo: &Ex, hi: &Ex, lo_open: bool, hi_open: bool) -> Option<bool> {
if is_neg_inf(lo) || is_pos_inf(hi) {
return Some(false);
}
if is_pos_inf(lo) || is_neg_inf(hi) {
return Some(true);
}
let d = hi - lo;
match d.is_positive() {
Some(true) => Some(false),
Some(false) => match d.is_zero() {
Some(true) => Some(lo_open || hi_open),
Some(false) => Some(true),
None => None,
},
None => None,
}
}
fn interval_contains(lo: &Ex, hi: &Ex, lo_open: bool, hi_open: bool, v: &Ex) -> Option<bool> {
let above = if is_neg_inf(lo) {
Some(true)
} else {
let d = v - lo;
if lo_open {
d.is_positive()
} else {
d.is_nonnegative()
}
};
let below = if is_pos_inf(hi) {
Some(true)
} else {
let d = hi - v;
if hi_open {
d.is_positive()
} else {
d.is_nonnegative()
}
};
match (above, below) {
(Some(false), _) | (_, Some(false)) => Some(false),
(Some(true), Some(true)) => Some(true),
_ => None,
}
}
impl Support {
pub fn reals(ctx: &Context) -> Self {
Support::interval(ctx.neg_infinity(), ctx.infinity())
}
pub fn interval(lo: Ex, hi: Ex) -> Self {
let lo_open = is_neg_inf(&lo);
let hi_open = is_pos_inf(&hi);
Support {
kind: Kind::Continuous,
pieces: vec![Piece::Interval {
lo,
hi,
lo_open,
hi_open,
}],
}
}
pub fn half_line(lo: Ex) -> Self {
let inf = lo.context().infinity();
Support::interval(lo, inf)
}
pub fn integers(ctx: &Context, lo: Option<Ex>, hi: Option<Ex>) -> Self {
let lo = lo.unwrap_or_else(|| ctx.neg_infinity());
let hi = hi.unwrap_or_else(|| ctx.infinity());
let lo_open = is_neg_inf(&lo);
let hi_open = is_pos_inf(&hi);
Support {
kind: Kind::Discrete,
pieces: vec![Piece::Interval {
lo,
hi,
lo_open,
hi_open,
}],
}
}
pub fn points(values: Vec<Ex>) -> Self {
Support {
kind: Kind::Discrete,
pieces: values.into_iter().map(Piece::Point).collect(),
}
}
pub fn from_pieces(kind: Kind, pieces: Vec<Piece>) -> Self {
Support { kind, pieces }
}
pub fn kind(&self) -> Kind {
self.kind
}
pub fn pieces(&self) -> &[Piece] {
&self.pieces
}
pub fn with_kind(mut self, kind: Kind) -> Self {
self.kind = kind;
self
}
pub fn is_empty(&self) -> bool {
self.pieces.is_empty()
}
pub fn is_interval(&self) -> bool {
matches!(self.pieces.as_slice(), [Piece::Interval { .. }])
}
pub fn as_interval(&self) -> Option<(&Ex, &Ex, bool, bool)> {
match self.pieces.as_slice() {
[
Piece::Interval {
lo,
hi,
lo_open,
hi_open,
},
] => Some((lo, hi, *lo_open, *hi_open)),
_ => None,
}
}
pub fn as_points(&self) -> Option<Vec<Ex>> {
self.pieces
.iter()
.map(|p| match p {
Piece::Point(v) => Some(v.clone()),
Piece::Interval { .. } => None,
})
.collect()
}
pub fn contains(&self, v: &Ex) -> Option<bool> {
let mut undecided = false;
for p in &self.pieces {
let inside = match p {
Piece::Point(w) => w.equals(v),
Piece::Interval {
lo,
hi,
lo_open,
hi_open,
} => {
let in_interval = interval_contains(lo, hi, *lo_open, *hi_open, v);
if self.kind == Kind::Discrete {
match (in_interval, v.is_integer()) {
(Some(false), _) | (_, Some(false)) => Some(false),
(Some(true), Some(true)) => Some(true),
_ => None,
}
} else {
in_interval
}
}
};
match inside {
Some(true) => return Some(true),
Some(false) => {}
None => undecided = true,
}
}
if undecided { None } else { Some(false) }
}
pub fn normalize_lattice(&self) -> Self {
if self.kind != Kind::Discrete {
return self.clone();
}
let pieces = self
.pieces
.iter()
.map(|p| match p {
Piece::Interval {
lo,
hi,
lo_open,
hi_open,
} => {
let ctx = lo.context();
let lo = if is_neg_inf(lo) {
lo.clone()
} else if *lo_open {
(lo.floor() + ctx.one()).simplify()
} else {
lo.ceiling().simplify()
};
let hi = if is_pos_inf(hi) {
hi.clone()
} else if *hi_open {
(hi.ceiling() - ctx.one()).simplify()
} else {
hi.floor().simplify()
};
Piece::Interval {
lo_open: is_neg_inf(&lo),
hi_open: is_pos_inf(&hi),
lo,
hi,
}
}
Piece::Point(v) => Piece::Point(v.clone()),
})
.collect();
Support {
kind: self.kind,
pieces,
}
}
pub fn intersect(&self, region: &Support) -> Option<Self> {
let me = self.normalize_lattice();
let other = if self.kind == Kind::Discrete {
region.clone().with_kind(Kind::Discrete).normalize_lattice()
} else {
region.clone()
};
let mut out = Vec::new();
for a in &me.pieces {
for b in &other.pieces {
match (a, b) {
(
Piece::Interval {
lo: alo,
hi: ahi,
lo_open: alo_o,
hi_open: ahi_o,
},
Piece::Interval {
lo: blo,
hi: bhi,
lo_open: blo_o,
hi_open: bhi_o,
},
) => {
let (lo, lo_open) = max_lo(alo, *alo_o, blo, *blo_o);
let (hi, hi_open) = min_hi(ahi, *ahi_o, bhi, *bhi_o);
if interval_empty(&lo, &hi, lo_open, hi_open) != Some(true) {
out.push(Piece::Interval {
lo,
hi,
lo_open,
hi_open,
});
}
}
(Piece::Point(v), Piece::Interval { .. }) => {
let single = Support::from_pieces(self.kind, vec![b.clone()]);
let single = single.with_kind(Kind::Continuous);
if single.contains(v)? {
out.push(Piece::Point(v.clone()));
}
}
(Piece::Interval { .. }, Piece::Point(v)) => {
let single = Support::from_pieces(self.kind, vec![a.clone()]);
match single.contains(v) {
Some(true) => out.push(Piece::Point(v.clone())),
Some(false) => {}
None => out.push(Piece::Point(v.clone())),
}
}
(Piece::Point(v), Piece::Point(w)) => match v.equals(w) {
Some(true) => out.push(Piece::Point(v.clone())),
Some(false) => {}
None => return None,
},
}
}
}
Some(Support {
kind: self.kind,
pieces: out,
})
}
pub fn to_set(&self, ctx: &Context) -> SetEx {
let mut acc: Option<SetEx> = None;
let mut points = Vec::new();
for p in &self.pieces {
match p {
Piece::Interval {
lo,
hi,
lo_open,
hi_open,
} => {
let s = ctx.interval(lo, hi, *lo_open, *hi_open);
acc = Some(match acc {
Some(a) => a.union(&s),
None => s,
});
}
Piece::Point(v) => points.push(v.clone()),
}
}
if !points.is_empty() {
let s = ctx.finite_set(&points);
acc = Some(match acc {
Some(a) => a.union(&s),
None => s,
});
}
acc.unwrap_or_else(|| ctx.empty_set())
}
pub fn from_set(kind: Kind, set: &SetEx) -> Option<Self> {
if let Some(values) = set.as_finite_set() {
return Some(Support {
kind,
pieces: values.into_iter().map(Piece::Point).collect(),
});
}
let parts = set.as_intervals()?;
let pieces = parts
.into_iter()
.map(|(lo, hi, lo_open, hi_open)| {
if lo == hi && !lo_open && !hi_open {
Piece::Point(lo)
} else {
Piece::Interval {
lo,
hi,
lo_open,
hi_open,
}
}
})
.collect();
Some(Support { kind, pieces })
}
}
impl fmt::Display for Support {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.pieces.is_empty() {
return f.write_str("∅");
}
let mut points = Vec::new();
let mut first = true;
for p in &self.pieces {
match p {
Piece::Interval {
lo,
hi,
lo_open,
hi_open,
} => {
if !first {
f.write_str(" ∪ ")?;
}
first = false;
let l = if *lo_open { "(" } else { "[" };
let r = if *hi_open { ")" } else { "]" };
write!(f, "{l}{lo}, {hi}{r}")?;
}
Piece::Point(v) => points.push(v.to_string()),
}
}
if !points.is_empty() {
if !first {
f.write_str(" ∪ ")?;
}
write!(f, "{{{}}}", points.join(", "))?;
}
if self.kind == Kind::Discrete
&& self
.pieces
.iter()
.any(|p| matches!(p, Piece::Interval { .. }))
{
f.write_str(" ∩ ℤ")?;
}
Ok(())
}
}