use crate::deriv::SideCondition;
use crate::kernel::expr::PredicateKind;
use crate::kernel::{ExprData, ExprId, ExprPool};
use crate::logic::{satisfiable, Satisfiability};
use crate::simplify::assumptions::AssumptionContext;
pub mod fourier;
pub mod laplace;
pub mod ztransform;
pub use fourier::{
fourier_transform, fourier_transform_with_conditions, inverse_fourier_transform,
inverse_fourier_transform_with_conditions, FourierError,
};
pub use laplace::{
inverse_laplace_transform, inverse_laplace_transform_with_assumptions,
inverse_laplace_transform_with_conditions, laplace_transform,
laplace_transform_with_conditions, LaplaceError,
};
pub use ztransform::{
inverse_z_transform, inverse_z_transform_with_assumptions, inverse_z_transform_with_conditions,
z_shift_advance, z_shift_delay, z_transform, ZTransformError,
};
std::thread_local! {
static TRANSFORM_CONDITIONS: std::cell::RefCell<Vec<SideCondition>> =
const { std::cell::RefCell::new(Vec::new()) };
}
pub fn take_transform_side_conditions() -> Vec<SideCondition> {
TRANSFORM_CONDITIONS.with(|c| std::mem::take(&mut *c.borrow_mut()))
}
pub(crate) fn stash_transform_side_conditions(conds: Vec<SideCondition>) {
TRANSFORM_CONDITIONS.with(|c| *c.borrow_mut() = conds);
}
#[derive(Default)]
pub(crate) struct Genericity<'a> {
assumptions: Option<&'a AssumptionContext>,
conditions: Vec<SideCondition>,
}
impl<'a> Genericity<'a> {
pub(crate) fn new(assumptions: Option<&'a AssumptionContext>) -> Self {
Genericity {
assumptions,
conditions: Vec::new(),
}
}
pub(crate) fn into_conditions(self) -> Vec<SideCondition> {
self.conditions
}
pub(crate) fn need_nonzero(&mut self, e: ExprId, pool: &ExprPool) {
for f in irreducible_factors(e, pool) {
let cond = SideCondition::NonZero(f);
if self.holds(&cond, pool) {
continue;
}
self.push(cond);
}
}
pub(crate) fn need_positive(&mut self, e: ExprId, pool: &ExprPool) {
if let Some(base) = even_power_base(e, pool) {
self.need_nonzero(base, pool);
return;
}
let cond = SideCondition::Positive(e);
if self.holds(&cond, pool) {
return;
}
self.push(cond);
}
pub(crate) fn need_in_domain(&mut self, e: ExprId, d: crate::kernel::Domain, pool: &ExprPool) {
let cond = SideCondition::InDomain(e, d);
if self.holds(&cond, pool) {
return;
}
self.push(cond);
}
pub(crate) fn adopt(&mut self, cond: SideCondition, pool: &ExprPool) {
match cond {
SideCondition::NonZero(e) => self.need_nonzero(e, pool),
SideCondition::Positive(e) => self.need_positive(e, pool),
SideCondition::InDomain(e, d) => self.need_in_domain(e, d, pool),
}
}
pub(crate) fn known_positive(&self, e: ExprId, pool: &ExprPool) -> bool {
self.holds(&SideCondition::Positive(e), pool)
}
pub(crate) fn refuted(&self, cond: &SideCondition, pool: &ExprPool) -> bool {
let target = match cond {
SideCondition::Positive(e) => *e,
SideCondition::InDomain(e, d) if *d == crate::kernel::Domain::NonNegative => *e,
SideCondition::NonZero(_) | SideCondition::InDomain(..) => return false,
};
self.known_negative(target, pool)
}
pub(crate) fn known_negative(&self, e: ExprId, pool: &ExprPool) -> bool {
let neg = pool.mul(vec![pool.integer(-1_i32), e]);
let neg = crate::simplify::simplify(neg, pool).value;
self.holds(&SideCondition::Positive(neg), pool)
}
fn push(&mut self, cond: SideCondition) {
if !self.conditions.contains(&cond) {
self.conditions.push(cond);
}
}
fn holds(&self, cond: &SideCondition, pool: &ExprPool) -> bool {
if let SideCondition::InDomain(e, d) = cond {
return self.in_domain_holds(*e, *d, pool);
}
let (target, want_positive) = match cond {
SideCondition::NonZero(e) => (*e, false),
SideCondition::Positive(e) => (*e, true),
SideCondition::InDomain(..) => unreachable!("handled above"),
};
if let Some(r) = literal_rational(target, pool) {
return if want_positive { r > 0 } else { r != 0 };
}
if evidently_positive_constant(target, pool) {
return true;
}
let mut facts: Vec<SideCondition> = self
.assumptions
.map(|a| a.facts().to_vec())
.unwrap_or_default();
crate::simplify::assumptions::collect_static_domain_facts(target, pool, &mut facts);
if fact_entails(&facts, target, want_positive, pool) {
return true;
}
self.entailed_by_predicates(target, want_positive, pool)
}
fn in_domain_holds(&self, e: ExprId, d: crate::kernel::Domain, pool: &ExprPool) -> bool {
use crate::kernel::Domain;
if d != Domain::NonNegative {
return false;
}
if let Some(r) = literal_rational(e, pool) {
return r >= 0;
}
if let ExprData::Symbol { domain, .. } = pool.get(e) {
if matches!(domain, Domain::Positive | Domain::NonNegative) {
return true;
}
}
let mut facts: Vec<SideCondition> = self
.assumptions
.map(|a| a.facts().to_vec())
.unwrap_or_default();
crate::simplify::assumptions::collect_static_domain_facts(e, pool, &mut facts);
if facts
.iter()
.any(|f| matches!(f, SideCondition::Positive(id) if *id == e))
{
return true;
}
let Some(ctx) = self.assumptions else {
return false;
};
if ctx.predicates().is_empty() {
return false;
}
let zero = pool.integer(0_i32);
let negated = pool.predicate(PredicateKind::Lt, vec![e, zero]);
let mut conj = ctx.predicates().to_vec();
conj.push(negated);
let formula = pool.predicate(PredicateKind::And, conj);
matches!(satisfiable(formula, pool), Satisfiability::Unsat)
}
fn entailed_by_predicates(&self, target: ExprId, want_positive: bool, pool: &ExprPool) -> bool {
let Some(ctx) = self.assumptions else {
return false;
};
if ctx.predicates().is_empty() {
return false;
}
let zero = pool.integer(0_i32);
let negated = if want_positive {
pool.predicate(PredicateKind::Le, vec![target, zero])
} else {
pool.predicate(PredicateKind::Eq, vec![target, zero])
};
let mut conj = ctx.predicates().to_vec();
conj.push(negated);
let formula = pool.predicate(PredicateKind::And, conj);
matches!(satisfiable(formula, pool), Satisfiability::Unsat)
}
}
fn fact_entails(
facts: &[SideCondition],
target: ExprId,
want_positive: bool,
pool: &ExprPool,
) -> bool {
if facts.iter().any(|f| match f {
SideCondition::Positive(id) => *id == target,
SideCondition::NonZero(id) => !want_positive && *id == target,
SideCondition::InDomain(..) => false,
}) {
return true;
}
if want_positive {
return false;
}
let neg = crate::simplify::simplify(pool.mul(vec![pool.integer(-1_i32), target]), pool).value;
if facts.iter().any(|f| match f {
SideCondition::NonZero(id) | SideCondition::Positive(id) => *id == neg,
SideCondition::InDomain(..) => false,
}) {
return true;
}
match pool.get(target) {
ExprData::Mul(args) => args
.iter()
.all(|&a| fact_entails(facts, a, false, pool) || literal_nonzero(a, pool)),
ExprData::Pow { base, exp } => {
matches!(pool.get(exp), ExprData::Integer(_))
&& (fact_entails(facts, base, false, pool) || literal_nonzero(base, pool))
}
_ => false,
}
}
fn literal_nonzero(e: ExprId, pool: &ExprPool) -> bool {
literal_rational(e, pool).is_some_and(|r| r != 0)
}
fn literal_rational(expr: ExprId, pool: &ExprPool) -> Option<rug::Rational> {
match pool.get(expr) {
ExprData::Integer(n) => Some(rug::Rational::from(n.0.clone())),
ExprData::Rational(r) => Some(r.0.clone()),
_ => None,
}
}
fn evidently_positive_constant(e: ExprId, pool: &ExprPool) -> bool {
match pool.get(e) {
ExprData::Integer(_) | ExprData::Rational(_) => {
literal_rational(e, pool).is_some_and(|r| r > 0)
}
ExprData::Float(f) => f.inner.to_f64() > 0.0,
ExprData::Symbol { name, .. } => &*name == "pi",
ExprData::Mul(args) => {
!args.is_empty() && args.iter().all(|&a| evidently_positive_constant(a, pool))
}
ExprData::Pow { base, exp } => {
evidently_positive_constant(base, pool) && literal_rational(exp, pool).is_some()
}
_ => false,
}
}
fn even_power_base(e: ExprId, pool: &ExprPool) -> Option<ExprId> {
let ExprData::Pow { base, exp } = pool.get(e) else {
return None;
};
let ExprData::Integer(n) = pool.get(exp) else {
return None;
};
let k = n.0.to_i64()?;
if k >= 2 && k % 2 == 0 && evidently_real(base, pool) {
Some(base)
} else {
None
}
}
fn evidently_real(e: ExprId, pool: &ExprPool) -> bool {
match pool.get(e) {
ExprData::Integer(_) | ExprData::Rational(_) | ExprData::Float(_) => true,
ExprData::Symbol { domain, .. } => domain != crate::kernel::Domain::Complex,
ExprData::Add(args) | ExprData::Mul(args) => args.iter().all(|&a| evidently_real(a, pool)),
ExprData::Pow { base, exp } => {
matches!(pool.get(exp), ExprData::Integer(_)) && evidently_real(base, pool)
}
_ => false,
}
}
fn irreducible_factors(e: ExprId, pool: &ExprPool) -> Vec<ExprId> {
use crate::poly::{collect_free_vars, MultiPoly};
let vars = collect_free_vars(e, pool);
if vars.is_empty() {
return vec![e];
}
let Ok(mp) = MultiPoly::from_symbolic(e, vars, pool) else {
return vec![e];
};
let Some((_unit, factors)) = mp.factor_irreducible() else {
return vec![e];
};
let mut out = Vec::new();
for (f, _mult) in factors {
if f.total_degree() == 0 {
continue;
}
let fe = f.to_expr(pool);
if !out.contains(&fe) {
out.push(fe);
}
}
if out.is_empty() {
vec![e]
} else {
out
}
}