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 {
pub(crate) fn declared(arena: &Arena, var: ExprId) -> Domain {
use crate::base::assumptions::Props;
let ExprNode::Symbol(sid) = *arena.node(var) else {
return Domain::Complex;
};
let mut declared = arena.symbol_assumptions(sid);
declared.forward_chain();
if declared.query(Props::REAL) == Some(true) {
Domain::Real
} else {
Domain::Complex
}
}
fn is_real(self) -> bool {
!matches!(self, Domain::Complex)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Params {
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,
candidates: 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,
candidates: 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,
candidates: false,
}
}
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 with_params(self, params: Params) -> Self {
SolveCx { params, ..self }
}
pub(crate) fn with_real_unknown(self) -> Self {
SolveCx {
unknown: Domain::Real,
..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 as_candidates(self) -> Self {
SolveCx {
candidates: true,
..self
}
}
pub(crate) fn conditional_roots_as_candidates(&self) -> bool {
self.candidates
}
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,
}
}
fn legacy_unknown(&self) -> Domain {
if self.request == Request::SetDeclared {
Domain::Complex
} else {
Domain::Real
}
}
pub(crate) fn parameter_cache(
&self,
arena: &Arena,
e: ExprId,
) -> crate::base::assumptions::AssumptionCache {
use crate::base::assumptions::{AssumptionCache, Assumptions, Props};
let mut cache = AssumptionCache::new();
if self.params == Params::Real {
for s in crate::base::walk::free_symbols(arena, e) {
if let ExprNode::Symbol(sid) = *arena.node(s)
&& arena.symbol_assumptions(sid) == Assumptions::default()
{
let mut real = Assumptions::default();
real.known_true |= Props::REAL;
cache.set_symbol_assumptions(s, real);
}
}
}
cache
}
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)
}
}