use std::borrow::Cow;
use crate::base::arena::Arena;
use crate::base::node::{ExprId, ExprNode};
#[must_use]
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum Solutions {
Complete(RootSet),
Partial(RootSet, Missing),
Refused(Reason),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum RootSet {
Empty(Reason),
All,
Points(Vec<ExprId>),
Families {
roots: Vec<ExprId>,
n: ExprId,
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum Missing {
Alternative(Reason),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum Reason {
NotFound,
NoPreimage,
NestedPeriods,
Said(Cow<'static, str>),
}
impl Reason {
pub(crate) fn said(text: impl Into<Cow<'static, str>>) -> Self {
Reason::Said(text.into())
}
pub(crate) fn text(&self) -> Cow<'static, str> {
match self {
Reason::NotFound => Cow::Borrowed("no strategy found a root"),
Reason::NoPreimage => {
Cow::Borrowed("the inverted function does not take the value (range restriction)")
}
Reason::NestedPeriods => Cow::Borrowed(
"the general solution nests periodic families, which need independent integer \
parameters",
),
Reason::Said(s) => s.clone(),
}
}
}
impl Solutions {
pub(crate) fn found(roots: Vec<ExprId>) -> Self {
if roots.is_empty() {
Solutions::Refused(Reason::NotFound)
} else {
Solutions::Complete(RootSet::Points(roots))
}
}
pub(crate) fn exactly(roots: Vec<ExprId>, empty: Reason) -> Self {
if roots.is_empty() {
Solutions::Complete(RootSet::Empty(empty))
} else {
Solutions::Complete(RootSet::Points(roots))
}
}
pub(crate) fn none(reason: Reason) -> Self {
Solutions::Complete(RootSet::Empty(reason))
}
pub(crate) fn refused(reason: Reason) -> Self {
Solutions::Refused(reason)
}
pub(crate) fn has_roots(&self) -> bool {
self.roots().is_some()
}
pub(crate) fn roots(&self) -> Option<&[ExprId]> {
match self {
Solutions::Complete(set) | Solutions::Partial(set, _) => set.roots(),
Solutions::Refused(_) => None,
}
}
pub(crate) fn points_if_complete(self) -> Option<Vec<ExprId>> {
match self {
Solutions::Complete(RootSet::Empty(_)) => Some(Vec::new()),
Solutions::Complete(set) if set.roots().is_some() => Some(set.into_roots()),
_ => None,
}
}
pub(crate) fn claimed_points(self) -> Vec<ExprId> {
match self {
Solutions::Complete(set) | Solutions::Partial(set, _) => set.into_roots(),
Solutions::Refused(_) => Vec::new(),
}
}
pub(crate) fn into_found(self) -> Result<(Vec<ExprId>, Option<Missing>), Self> {
match self {
Solutions::Complete(set) if set.roots().is_some() => Ok((set.into_roots(), None)),
Solutions::Partial(set, m) if set.roots().is_some() => Ok((set.into_roots(), Some(m))),
other => Err(other),
}
}
pub(crate) fn with_missing(roots: Vec<ExprId>, missing: Option<Missing>) -> Self {
match missing {
None => Solutions::found(roots),
Some(Missing::Alternative(reason)) if roots.is_empty() => Solutions::Refused(reason),
Some(m) => Solutions::Partial(RootSet::Points(roots), m),
}
}
pub(crate) fn lacking(self, missing: Option<Missing>) -> Self {
match missing {
None => self,
Some(Missing::Alternative(reason)) => self.union(Solutions::Refused(reason)),
}
}
pub(crate) fn is_none_proved(&self) -> bool {
matches!(self, Solutions::Complete(RootSet::Empty(_)))
}
pub(crate) fn is_nested_periods(&self) -> bool {
matches!(self, Solutions::Refused(Reason::NestedPeriods))
}
pub(crate) fn map_roots(self, mut f: impl FnMut(ExprId) -> ExprId) -> Self {
let mut map = |roots: Vec<ExprId>| -> Vec<ExprId> {
let mut out: Vec<ExprId> = Vec::with_capacity(roots.len());
for r in roots {
let v = f(r);
if !out.contains(&v) {
out.push(v);
}
}
out
};
match self {
Solutions::Complete(set) => Solutions::Complete(set.map_roots(&mut map)),
Solutions::Partial(set, m) => Solutions::Partial(set.map_roots(&mut map), m),
refused @ Solutions::Refused(_) => refused,
}
}
pub(crate) fn union(self, other: Self) -> Self {
if self.is_nested_periods() {
return self;
}
if other.is_nested_periods() {
return other;
}
let all = |s: &Solutions| matches!(s, Solutions::Complete(RootSet::All));
if all(&self) {
return self;
}
if all(&other) {
return other;
}
let (a, a_missing) = self.into_parts();
let (b, b_missing) = other.into_parts();
let set = match (a, b) {
(Some(a), Some(b)) => Some(a.union(b)),
(a, b) => a.or(b),
};
let missing = a_missing.or(b_missing);
match (set, missing) {
(Some(set), None) => Solutions::Complete(set),
(Some(set), Some(m)) if set.roots().is_some() => Solutions::Partial(set, m),
(_, Some(Missing::Alternative(reason))) => Solutions::Refused(reason),
(None, None) => Solutions::Refused(Reason::NotFound),
}
}
fn into_parts(self) -> (Option<RootSet>, Option<Missing>) {
match self {
Solutions::Complete(set) => (Some(set), None),
Solutions::Partial(set, m) => (Some(set), Some(m)),
Solutions::Refused(reason) => (None, Some(Missing::Alternative(reason))),
}
}
pub(crate) fn with_families(self, n: ExprId) -> Self {
let sort = |set: RootSet| match set {
RootSet::Points(roots) => RootSet::Families { roots, n },
other => other,
};
match self {
Solutions::Complete(set) => Solutions::Complete(sort(set)),
Solutions::Partial(set, m) => Solutions::Partial(sort(set), m),
refused @ Solutions::Refused(_) => refused,
}
}
pub(crate) fn into_set(self, arena: &mut Arena, var: ExprId, expr: ExprId) -> ExprId {
let condition = |arena: &mut Arena| {
let zero = arena.zero;
let cond = arena.eq_(expr, zero);
arena.intern(ExprNode::ConditionSet(var, cond))
};
match self {
Solutions::Complete(set) => set.into_set(arena),
Solutions::Partial(set, _) => {
let known = set.into_set(arena);
let unknown = condition(arena);
arena.set_union(&[known, unknown])
}
Solutions::Refused(_) => condition(arena),
}
}
}
impl RootSet {
pub(crate) fn roots(&self) -> Option<&[ExprId]> {
match self {
RootSet::Points(roots) | RootSet::Families { roots, .. } if !roots.is_empty() => {
Some(roots)
}
_ => None,
}
}
fn into_roots(self) -> Vec<ExprId> {
match self {
RootSet::Points(roots) | RootSet::Families { roots, .. } => roots,
RootSet::Empty(_) | RootSet::All => Vec::new(),
}
}
fn union(self, other: Self) -> Self {
match (self, other) {
(RootSet::All, _) | (_, RootSet::All) => RootSet::All,
(empty @ RootSet::Empty(_), RootSet::Empty(_)) => empty,
(RootSet::Empty(_), set) | (set, RootSet::Empty(_)) => set,
(RootSet::Points(a), RootSet::Points(b)) => RootSet::Points(merged(a, b)),
(RootSet::Families { roots, n }, RootSet::Points(b))
| (RootSet::Families { roots, n }, RootSet::Families { roots: b, .. }) => {
RootSet::Families {
roots: merged(roots, b),
n,
}
}
(RootSet::Points(a), RootSet::Families { roots, n }) => RootSet::Families {
roots: merged(a, roots),
n,
},
}
}
fn map_roots(self, map: &mut impl FnMut(Vec<ExprId>) -> Vec<ExprId>) -> Self {
match self {
RootSet::Points(roots) => RootSet::Points(map(roots)),
RootSet::Families { roots, n } => RootSet::Families {
roots: map(roots),
n,
},
other => other,
}
}
fn into_set(self, arena: &mut Arena) -> ExprId {
match self {
RootSet::Empty(_) => arena.empty_set,
RootSet::All => arena.universal_set,
RootSet::Points(roots) => arena.finite_set(&roots),
RootSet::Families { roots, n } => {
let mut plain: Vec<ExprId> = Vec::new();
let mut parts: Vec<ExprId> = Vec::new();
for r in roots {
let v = crate::transforms::eval::eval(arena, r);
if crate::base::walk::has_free_var(arena, v, n) {
parts.push(crate::transforms::sets::image_set(arena, n, v));
} else {
plain.push(v);
}
}
if !plain.is_empty() {
parts.push(arena.finite_set(&plain));
}
let union = arena.set_union(&parts);
crate::transforms::sets::simplify_set(arena, union)
}
}
}
}
fn merged(a: Vec<ExprId>, b: Vec<ExprId>) -> Vec<ExprId> {
let mut out: Vec<ExprId> = Vec::with_capacity(a.len() + b.len());
for r in a.into_iter().chain(b) {
if !out.contains(&r) {
out.push(r);
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
fn points(arena: &mut Arena, ks: &[i64]) -> Vec<ExprId> {
ks.iter().map(|&k| arena.int(k)).collect()
}
#[test]
fn union_of_alternatives() {
let mut arena = Arena::new();
let p12 = points(&mut arena, &[1, 2]);
let p23 = points(&mut arena, &[2, 3]);
let p123 = points(&mut arena, &[1, 2, 3]);
let empty = || Solutions::none(Reason::NoPreimage);
let refused = || Solutions::refused(Reason::NotFound);
assert_eq!(
Solutions::found(p12.clone()).union(Solutions::found(p23.clone())),
Solutions::Complete(RootSet::Points(p123.clone()))
);
assert_eq!(empty().union(empty()), empty());
assert_eq!(
empty().union(Solutions::found(p12.clone())),
Solutions::found(p12.clone())
);
assert_eq!(empty().union(refused()), refused());
assert_eq!(refused().union(empty()), refused());
assert_eq!(
Solutions::found(p12.clone()).union(refused()),
Solutions::Partial(
RootSet::Points(p12.clone()),
Missing::Alternative(Reason::NotFound)
)
);
let partial = Solutions::found(p12.clone()).union(refused());
assert!(matches!(
partial.union(Solutions::found(p23)),
Solutions::Partial(RootSet::Points(ref r), _) if *r == p123
));
let all = Solutions::Complete(RootSet::All);
assert_eq!(refused().union(all.clone()), all);
let nested = Solutions::refused(Reason::NestedPeriods);
assert_eq!(all.clone().union(nested.clone()), nested);
assert_eq!(Solutions::found(p12).union(nested.clone()), nested);
}
#[test]
fn found_and_exactly_differ_on_no_roots() {
assert_eq!(
Solutions::found(Vec::new()),
Solutions::Refused(Reason::NotFound)
);
assert!(Solutions::exactly(Vec::new(), Reason::NoPreimage).is_none_proved());
assert!(!Solutions::found(Vec::new()).has_roots());
}
}