use crate::base::arena::Arena;
use crate::base::node::{ExprId, ExprNode};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) enum Domain {
Complex,
Real,
#[expect(
dead_code,
reason = "no entry point asks for it yet (planned: the summation index, a public \
`Ex::solutions(var, Domain)`)"
)]
Integer,
}
impl Domain {
fn is_real(self) -> bool {
!matches!(self, Domain::Complex)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Params {
#[expect(
dead_code,
reason = "every solve still takes its parameters real (declared parameters are \
planned)"
)]
AsDeclared,
Real,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Form {
Principal,
General {
n: ExprId,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Request {
Real,
PrincipalDeclared,
SetDeclared,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct SolveCx {
unknown: Domain,
params: Params,
form: Form,
request: Request,
all_branches: bool,
}
impl SolveCx {
pub(crate) fn principal(unknown: Domain) -> Self {
SolveCx {
unknown,
params: Params::Real,
form: Form::Principal,
request: if unknown == Domain::Complex {
Request::PrincipalDeclared
} else {
Request::Real
},
all_branches: false,
}
}
pub(crate) fn general(unknown: Domain, n: ExprId) -> Self {
SolveCx {
unknown,
params: Params::Real,
form: Form::General { n },
request: if unknown == Domain::Complex {
Request::SetDeclared
} else {
Request::Real
},
all_branches: true,
}
}
pub(crate) fn families(&self, n: ExprId) -> Self {
SolveCx {
unknown: self.legacy_unknown(),
form: Form::General { n },
all_branches: true,
..*self
}
}
pub(crate) fn substituted(&self) -> Self {
SolveCx {
unknown: if self.request == Request::Real {
Domain::Real
} else {
Domain::Complex
},
form: Form::Principal,
..*self
}
}
pub(crate) fn period(&self) -> Option<ExprId> {
match self.form {
Form::General { n } => Some(n),
Form::Principal => None,
}
}
pub(crate) fn all_branches(&self) -> bool {
self.all_branches
}
pub(crate) fn range_restricted(&self, arena: &mut Arena, f: ExprId, var: ExprId) -> bool {
self.real_over(arena, f, var, self.unknown)
}
pub(crate) fn real(&self, arena: &mut Arena, f: ExprId, var: ExprId) -> bool {
self.real_over(arena, f, var, self.unknown)
}
pub(crate) fn unknown_may_be_nonreal(&self, arena: &Arena, var: ExprId) -> bool {
use crate::base::assumptions::Props;
if self.unknown.is_real() {
return false;
}
match *arena.node(var) {
ExprNode::Symbol(sid) => arena.symbol_assumptions(sid).query(Props::REAL) != Some(true),
_ => true,
}
}
pub(crate) fn real_for_real_var(&self, arena: &mut Arena, e: ExprId, var: ExprId) -> bool {
self.real_over(arena, e, var, self.legacy_unknown())
}
pub(crate) fn variable_may_be_nonreal(&self, arena: &Arena, var: ExprId) -> bool {
use crate::base::assumptions::Props;
if self.legacy_unknown().is_real() {
return false;
}
match *arena.node(var) {
ExprNode::Symbol(sid) => arena.symbol_assumptions(sid).query(Props::REAL) != Some(true),
_ => true,
}
}
pub(crate) fn declared_principal(&self) -> bool {
self.unknown == Domain::Complex && self.legacy_unknown().is_real()
}
fn legacy_unknown(&self) -> Domain {
if self.request == Request::SetDeclared {
Domain::Complex
} else {
Domain::Real
}
}
fn real_over(&self, arena: &mut Arena, e: ExprId, var: ExprId, unknown: Domain) -> bool {
use crate::base::assumptions::{AssumptionCache, Assumptions, Props};
let mut cache = AssumptionCache::new();
for s in crate::base::walk::free_symbols(arena, e) {
let ExprNode::Symbol(sid) = *arena.node(s) else {
continue;
};
let declared = arena.symbol_assumptions(sid);
let take_real = if s == var {
unknown.is_real()
} else {
self.params == Params::Real && declared == Assumptions::default()
};
if take_real {
let mut real = declared;
real.known_true |= Props::REAL;
if !real.is_contradictory() {
cache.set_symbol_assumptions(s, real);
}
}
}
cache.query(arena, e, Props::REAL) == Some(true)
}
}