use core::panic;
use std::{
cmp::Ordering,
ops::{Add, Div, Mul, Neg, Sub},
sync::Arc,
};
use crate::{
atom::{Atom, AtomCore, AtomView, FunctionBuilder, Symbol},
coefficient::CoefficientView,
domains::{
EuclideanDomain, InternalOrdering, Ring, SelfRing,
atom::AtomField,
integer::Integer,
rational::{Q, Rational},
},
printer::{PrintOptions, PrintState},
};
use super::PolyVariable;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum SeriesError {
NonPositiveRelativeDepth {
depth: SeriesDepth,
},
NonConstantIf {
expression: Atom,
},
FunctionArgumentPole {
expression: Atom,
},
UnsupportedComplexExponent {
expression: Atom,
},
UnsupportedLargeExponent {
expression: Atom,
},
NonRationalPower {
expression: Atom,
},
UnexpectedExpLogSimplification {
expression: Atom,
},
EssentialSingularity {
function: Symbol,
},
MissingLogCoefficient,
ConstantTermDependsOnExpansionVariable {
function: Symbol,
constant: Atom,
variable: PolyVariable,
},
NonIndeterminateSeriesVariable {
variable: PolyVariable,
},
ZeroPowerRequiresInfinitePrecision,
DivisionByZero,
}
impl std::error::Error for SeriesError {}
impl std::fmt::Display for SeriesError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SeriesError::NonPositiveRelativeDepth { depth } => {
write!(f, "relative series depth must be positive, got {depth:?}")
}
SeriesError::NonConstantIf { expression } => {
write!(
f,
"cannot series expand non-constant if function {expression}"
)
}
SeriesError::FunctionArgumentPole { expression } => write!(
f,
"cannot series expand {expression} since an argument has poles; define a 'series' attribute on the symbol that extracts the principal part"
),
SeriesError::UnsupportedComplexExponent { expression } => {
write!(
f,
"cannot series expand {expression} with a complex exponent"
)
}
SeriesError::UnsupportedLargeExponent { expression } => {
write!(
f,
"cannot series expand {expression} with a large exponent yet"
)
}
SeriesError::NonRationalPower { expression } => {
write!(f, "power of variable in {expression} must be rational")
}
SeriesError::UnexpectedExpLogSimplification { expression } => write!(
f,
"unexpected term {expression} in exp-log series simplification"
),
SeriesError::EssentialSingularity { function } => write!(
f,
"cannot series expand {function} of a series with poles; this would create an essential singularity"
),
SeriesError::MissingLogCoefficient => {
f.write_str("log argument needs to have a coefficient")
}
SeriesError::ConstantTermDependsOnExpansionVariable {
function,
constant,
variable,
} => write!(
f,
"cannot compute {function} of a series whose constant term {constant} depends on {variable}"
),
SeriesError::NonIndeterminateSeriesVariable { variable } => {
write!(f, "series variable {variable} is not an indeterminate")
}
SeriesError::ZeroPowerRequiresInfinitePrecision => f.write_str(
"cannot raise series to the power of zero, as this generates infinite precision 1",
),
SeriesError::DivisionByZero => {
f.write_str("cannot invert series with a zero constant term")
}
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum SeriesDepth {
Absolute(Rational),
Relative(Rational),
}
impl SeriesDepth {
pub fn absolute<T: Into<Rational>>(depth: T) -> Self {
Self::Absolute(depth.into())
}
pub fn relative<T: Into<Rational>>(depth: T) -> Self {
Self::Relative(depth.into())
}
pub(crate) fn order(&self) -> &Rational {
match self {
SeriesDepth::Absolute(depth) | SeriesDepth::Relative(depth) => depth,
}
}
pub(crate) fn is_absolute(&self) -> bool {
matches!(self, SeriesDepth::Absolute(_))
}
}
impl<T: Into<Rational>> From<T> for SeriesDepth {
fn from(depth: T) -> Self {
Self::absolute(depth.into())
}
}
#[derive(Clone)]
pub struct Series<F: Ring> {
coefficients: Vec<F::Element>,
variable: Arc<PolyVariable>,
expansion_point: F::Element,
field: F,
shift: isize, order: usize, ramification: usize, }
impl<F: Ring + std::fmt::Debug> std::fmt::Debug for Series<F> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
if self.order == 0 {
return write!(f, "[]");
}
let mut first = true;
write!(f, "[ ")?;
for c in self.coefficients.iter() {
if first {
first = false;
} else {
write!(f, ", ")?;
}
write!(f, "{{ {c:?} }}")?;
}
write!(f, " ]")
}
}
impl<F: Ring + std::fmt::Display> std::fmt::Display for Series<F> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
self.format(&PrintOptions::from_fmt(f), PrintState::from_fmt(f), f)
.map(|_| ())
}
}
impl<F: Ring> Series<F> {
#[inline]
pub fn new(
field: &F,
cap: Option<usize>,
variable: Arc<PolyVariable>,
expansion_point: F::Element,
order: Rational,
) -> Self {
if order.is_negative() {
panic!("The order of the series must be positive.");
}
Self {
coefficients: Vec::with_capacity(cap.unwrap_or(0)),
expansion_point,
field: field.clone(),
variable,
shift: 0,
order: order.numerator().to_i64().unwrap() as usize
* order.denominator().to_i64().unwrap() as usize,
ramification: order.denominator().to_i64().unwrap() as usize,
}
}
#[inline]
pub fn zero(&self) -> Self {
Self {
coefficients: vec![],
field: self.field.clone(),
variable: self.variable.clone(),
expansion_point: self.expansion_point.clone(),
shift: 0,
order: self.order,
ramification: 1,
}
}
#[inline]
pub fn constant(&self, coeff: F::Element) -> Self {
if self.field.is_zero(&coeff) {
return self.zero();
}
Self {
coefficients: vec![coeff],
field: self.field.clone(),
variable: self.variable.clone(),
expansion_point: self.expansion_point.clone(),
shift: 0,
order: self.order,
ramification: 1,
}
}
#[inline]
pub fn one(&self) -> Self {
Self {
coefficients: vec![self.field.one()],
field: self.field.clone(),
variable: self.variable.clone(),
expansion_point: self.expansion_point.clone(),
shift: 0,
order: self.order,
ramification: 1,
}
}
#[inline]
fn one_inf_prec(&self) -> Self {
Self {
coefficients: vec![self.field.one()],
field: self.field.clone(),
variable: self.variable.clone(),
expansion_point: self.expansion_point.clone(),
shift: 0,
order: i64::MAX as usize, ramification: 1,
}
}
#[inline]
pub fn monomial(&self, coeff: F::Element, exponent: Rational) -> Self {
if self.field.is_zero(&coeff) {
return self.zero();
}
let n = exponent.numerator().to_i64().unwrap() as isize;
let d = exponent.denominator().to_i64().unwrap() as usize;
let ram = Integer::from(self.ramification as i64)
.lcm(exponent.denominator_ref())
.to_i64()
.unwrap() as usize;
Self {
coefficients: vec![coeff],
field: self.field.clone(),
variable: self.variable.clone(),
expansion_point: self.expansion_point.clone(),
shift: n * d as isize,
order: self.order * ram / d,
ramification: ram,
}
}
#[inline]
pub fn shifted_variable(&self, coeff: F::Element) -> Self {
if self.field.is_zero(&coeff) {
return self.monomial(self.field.one(), (1, 1).into());
}
Self {
coefficients: vec![coeff, self.field.one()],
field: self.field.clone(),
variable: self.variable.clone(),
expansion_point: self.expansion_point.clone(),
shift: 0,
order: self.order,
ramification: 1,
}
}
#[inline]
pub fn get_field(&self) -> &F {
&self.field
}
#[inline]
fn get_exponent(&self, index: usize) -> Rational {
Self::get_exponent_from_shift_ram(index, self.shift, self.ramification)
}
fn get_exponent_from_shift_ram(index: usize, shift: isize, ram: usize) -> Rational {
(Rational::from(index as i64) + Rational::from(shift as i64)) / Rational::from(ram as i64)
}
#[inline]
fn get_index(&self, p: Rational) -> usize {
let r = p * &Rational::from(self.ramification as i64);
debug_assert!(r.is_integer());
let i = r.numerator().to_i64().unwrap() - self.shift as i64;
debug_assert!(i >= 0);
i as usize
}
#[inline]
pub fn relative_order(&self) -> Rational {
assert!(self.order < i64::MAX as usize);
(self.order as i64, self.ramification as i64).into()
}
#[inline]
pub fn absolute_order(&self) -> Rational {
assert!(self.order < i64::MAX as usize);
(
self.order as i64 + self.shift as i64,
self.ramification as i64,
)
.into()
}
#[inline]
pub fn get_expansion_point(&self) -> F::Element {
self.expansion_point.clone()
}
fn change_ramification(&mut self, ram: usize) {
let ram = Integer::from(self.ramification as i64)
.lcm(&Integer::from(ram as i64))
.to_i64()
.unwrap() as usize;
if ram == self.ramification {
return;
}
let mut s = Self {
coefficients: vec![
self.field.zero();
self.coefficients.len() * ram / self.ramification
],
field: self.field.clone(),
variable: self.variable.clone(),
expansion_point: self.expansion_point.clone(),
shift: self.shift * (ram / self.ramification) as isize,
order: self.order * (ram / self.ramification),
ramification: ram,
};
for (i, c) in std::mem::take(&mut self.coefficients)
.into_iter()
.enumerate()
{
let index = s.get_index(self.get_exponent(i));
s.coefficients[index] = c;
}
*self = s;
}
#[inline]
pub fn truncate_relative_order(&mut self, order: Rational) {
if self.relative_order() < order {
return;
}
if order.is_negative() {
panic!("Cannot series expand to negative depth");
}
let ram = order.denominator().to_i64().unwrap() as usize;
self.change_ramification(ram);
let new_order = (order.numerator().to_i64().unwrap() as usize) * self.ramification / ram;
self.coefficients.truncate(new_order);
self.order = new_order;
self.truncate();
}
#[inline]
pub fn truncate_absolute_order(&mut self, order: Rational) {
if self.absolute_order() < order {
return;
}
self.change_ramification(order.denominator().to_i64().unwrap() as usize);
for i in (0..self.coefficients.len()).rev() {
if self.get_exponent(i) >= order {
self.coefficients.pop();
} else {
break;
}
}
if self.coefficients.is_empty() {
self.order = 0;
} else {
self.order = self.get_index(order);
}
}
#[inline]
fn joint_ramification(&self, other: &Self) -> usize {
Integer::from(self.ramification as i64)
.lcm(&Integer::from(other.ramification as i64))
.to_i64()
.unwrap() as usize
}
#[inline]
pub fn is_zero(&self) -> bool {
self.coefficients.is_empty()
}
#[inline]
pub fn is_one(&self) -> bool {
self.coefficients.len() == 1 && self.field.is_one(&self.coefficients[0]) && self.shift == 0
}
#[inline]
pub fn is_constant(&self) -> bool {
self.coefficients.len() <= 1 && self.shift == 0
}
#[inline]
pub fn get_trailing_coefficient(&self) -> F::Element {
if self.coefficients.is_empty() {
self.field.zero()
} else {
self.coefficients[0].clone()
}
}
#[inline]
pub fn get_trailing_exponent(&self) -> Rational {
self.get_exponent(0)
}
#[inline]
pub fn get_ramification(&self) -> usize {
self.ramification
}
pub fn get_variable(&self) -> Arc<PolyVariable> {
self.variable.clone()
}
pub fn get_vars_ref(&self) -> &PolyVariable {
self.variable.as_ref()
}
pub fn lcoeff(&self) -> F::Element {
self.coefficients
.last()
.unwrap_or(&self.field.zero())
.clone()
}
pub fn degree(&self) -> Rational {
if self.order == 0 {
return 0.into();
}
self.get_exponent(self.coefficients.len() - 1)
}
pub fn npow(&self, mut pow: usize) -> Self {
if pow == 0 {
panic!("Cannot create one with infinite precision");
}
let mut x = self.clone();
let mut y = self.one_inf_prec();
while pow != 1 {
if pow % 2 == 1 {
y = &y * &x;
pow -= 1;
}
x = &x * &x;
pow /= 2;
}
x * &y
}
pub fn mul_exp_units(mut self, exp: isize) -> Self {
self.shift += exp;
self
}
pub fn mul_coeff(mut self, coeff: &F::Element) -> Self {
for c in &mut self.coefficients {
if !self.field.is_zero(c) {
self.field.mul_assign(c, coeff);
}
}
self.truncate();
self
}
fn truncate(&mut self) {
if self.coefficients.is_empty() {
return;
}
let d = self
.coefficients
.iter_mut()
.rev()
.position(|c| !self.field.is_zero(c))
.unwrap_or(self.coefficients.len());
self.coefficients.truncate(self.coefficients.len() - d);
if self.coefficients.is_empty() {
self.shift += self.order as isize;
self.order = 0;
return;
}
let d = self
.coefficients
.iter_mut()
.position(|c| !self.field.is_zero(c))
.unwrap_or(self.coefficients.len());
self.shift += d as isize;
self.order -= d;
self.coefficients.drain(0..d);
}
fn remove_constant(mut self) -> Self {
if self.order > 0 && self.shift == 0 {
self.coefficients[0] = self.field.zero();
self.truncate();
}
self
}
pub fn coefficient(&self, exponent: Rational) -> F::Element {
let r = exponent * &Rational::from(self.ramification as i64);
if !r.is_integer() {
return self.field.zero();
}
let i = r.numerator().to_i64().unwrap() - self.shift as i64;
if i >= 0 && i < self.coefficients.len() as i64 {
self.coefficients[i as usize].clone()
} else {
self.field.zero()
}
}
pub fn terms(&self) -> impl Iterator<Item = (Rational, &F::Element)> {
self.coefficients
.iter()
.enumerate()
.map(|(i, c)| (self.get_exponent(i), c))
}
pub fn map_coeff<M: Fn(&F::Element) -> F::Element>(&self, f: M) -> Series<F> {
let mut s = Series {
coefficients: self.coefficients.iter().map(f).collect(),
variable: self.variable.clone(),
expansion_point: self.expansion_point.clone(),
shift: self.shift,
ramification: self.ramification,
field: self.field.clone(),
order: self.order,
};
s.truncate();
s
}
}
impl<'a, R: Ring> IntoIterator for Series<R> {
type Item = (Rational, R::Element);
type IntoIter = std::vec::IntoIter<(Rational, R::Element)>;
fn into_iter(self) -> Self::IntoIter {
let shift = self.shift;
let ram = self.ramification;
let v: Vec<_> = self
.coefficients
.into_iter()
.enumerate()
.map(|(i, c)| (Self::get_exponent_from_shift_ram(i, shift, ram), c))
.collect();
v.into_iter()
}
}
impl<'a, R: Ring> IntoIterator for &'a Series<R> {
type Item = (Rational, &'a R::Element);
type IntoIter = std::vec::IntoIter<(Rational, &'a R::Element)>;
fn into_iter(self) -> Self::IntoIter {
let v: Vec<_> = self
.coefficients
.iter()
.enumerate()
.map(|(i, c)| (self.get_exponent(i), c))
.collect();
v.into_iter()
}
}
impl<F: Ring> SelfRing for Series<F> {
fn is_zero(&self) -> bool {
self.is_zero()
}
fn is_one(&self) -> bool {
self.is_one()
}
fn format<W: std::fmt::Write>(
&self,
opts: &PrintOptions,
mut state: PrintState,
f: &mut W,
) -> Result<bool, std::fmt::Error> {
let v = if self.field.is_zero(&self.expansion_point) {
self.variable.format_string(
opts,
PrintState {
in_exp_base: true,
..state
},
)
} else {
let v = self.variable.format_string(
opts,
PrintState {
in_sum: true,
..state
},
);
format!("({}-{})", v, self.field.printer(&self.expansion_point))
};
if self.coefficients.is_empty() {
let o = self.absolute_order();
if opts.mode.is_latex() {
write!(f, "\\mathcal{{O}}\\left({v}^{{{o}}}\\right)")?;
} else {
write!(f, "𝒪({v}^")?;
Q.format(&o, opts, state.step(false, false, true, false), f)?;
f.write_char(')')?;
}
return Ok(false);
}
let add_paren = state.in_product || state.in_exp || state.in_exp_base;
if add_paren {
if state.in_sum {
f.write_str("+")?;
state.in_sum = false;
}
state.in_product = false;
state.in_exp = false;
state.in_exp_base = false;
f.write_str("(")?;
}
let in_product = state.in_product;
for (e, c) in self.coefficients.iter().enumerate() {
if self.field.is_zero(c) {
continue;
}
let e = self.get_exponent(e);
state.in_product = in_product || !e.is_zero();
state.suppress_one = !e.is_zero();
let suppressed_one = self.field.format(
c,
opts,
state.step(state.in_sum, state.in_product, false, false),
f,
)?;
if !suppressed_one && !e.is_zero() {
f.write_char(opts.multiplication_operator)?;
}
if e.is_one() {
write!(f, "{v}")?;
} else if !e.is_zero() {
write!(f, "{v}^")?;
state.suppress_one = false;
if opts.mode.is_latex() {
f.write_char('{')?;
}
Q.format(&e, opts, state.step(false, false, true, false), f)?;
if opts.mode.is_latex() {
f.write_char('}')?;
}
}
state.in_sum = true;
}
let o = self.absolute_order();
if opts.mode.is_latex() {
write!(f, "+\\mathcal{{O}}\\left({v}^{{{o}}}\\right)")?;
} else {
write!(f, "+𝒪({v}^")?;
Q.format(&o, opts, state.step(false, false, true, false), f)?;
f.write_char(')')?;
}
if add_paren {
f.write_str(")")?;
}
Ok(false)
}
}
impl<F: Ring> InternalOrdering for Series<F> {
fn internal_cmp(&self, other: &Self) -> Ordering {
if self.variable != other.variable {
return self.variable.cmp(&other.variable);
}
if self.shift != other.shift {
return self.shift.cmp(&other.shift);
}
if self.ramification != other.ramification {
return self.ramification.cmp(&other.ramification);
}
if self.order != other.order {
return self.order.cmp(&other.order);
}
self.coefficients.internal_cmp(&other.coefficients)
}
}
impl<F: Ring> PartialEq for Series<F> {
#[inline]
fn eq(&self, other: &Self) -> bool {
if self.variable != other.variable {
if self.is_constant() != other.is_constant() {
return false;
}
if self.order != other.order {
return false;
}
if self.order == 0 {
return true;
}
if self.is_constant() {
return self.coefficients[0] == other.coefficients[0];
}
unimplemented!("Cannot compare non-constant series with different variable maps yet");
}
if self.degree() != other.degree() {
return false;
}
self.coefficients.eq(&other.coefficients)
}
}
impl<F: Ring> std::hash::Hash for Series<F> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.coefficients.hash(state);
self.variable.hash(state);
}
}
impl<F: Ring> Eq for Series<F> {}
impl<R: Ring> PartialOrd for Series<R> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.coefficients.internal_cmp(&other.coefficients))
}
}
impl<F: Ring> Add for Series<F> {
type Output = Self;
fn add(mut self, mut other: Self) -> Self::Output {
assert_eq!(self.field, other.field);
assert_eq!(self.variable, other.variable);
if self.shift == other.shift && self.ramification == other.ramification {
if self.coefficients.len() < other.coefficients.len() {
std::mem::swap(&mut self, &mut other);
}
self.order = self.order.min(other.order);
self.coefficients.truncate(self.order);
for (i, c) in other.coefficients.iter().enumerate() {
if i < self.coefficients.len() {
self.field.add_assign(&mut self.coefficients[i], c);
}
}
self.truncate();
self
} else {
let r = self.joint_ramification(&other);
let r_d_s = r / self.ramification;
let r_d_o = r / other.ramification;
let shift = (self.shift * r_d_s as isize).min(other.shift * r_d_o as isize);
let order_s = (self.order * r_d_s) as isize + self.shift * r_d_s as isize;
let order_o = (other.order * r_d_o) as isize + other.shift * r_d_o as isize;
let mut s = Self {
coefficients: vec![],
variable: self.variable.clone(),
expansion_point: self.expansion_point.clone(),
field: self.field.clone(),
shift,
order: (order_s.min(order_o) - shift) as usize,
ramification: r,
};
s.coefficients =
vec![self.field.zero(); self.coefficients.len().max(other.coefficients.len())];
for (i, c) in self.coefficients.iter().enumerate() {
let index = s.get_index(self.get_exponent(i));
if index < s.order {
if index >= s.coefficients.len() {
s.coefficients.resize(index * 2, self.field.zero());
}
s.coefficients[index] = c.clone(); }
}
for (i, c) in other.coefficients.iter().enumerate() {
let index = s.get_index(other.get_exponent(i));
if index < s.order {
if index >= s.coefficients.len() {
s.coefficients.resize(index * 2, self.field.zero());
}
s.field.add_assign(&mut s.coefficients[index], c);
}
}
s.truncate();
s
}
}
}
impl<'a, F: Ring> Add<&'a Series<F>> for &Series<F> {
type Output = Series<F>;
fn add(self, other: &'a Series<F>) -> Self::Output {
(self.clone()).add(other.clone())
}
}
impl<F: Ring> Sub for Series<F> {
type Output = Self;
fn sub(self, other: Self) -> Self::Output {
self.add(other.neg())
}
}
impl<'a, F: Ring> Sub<&'a Series<F>> for &Series<F> {
type Output = Series<F>;
fn sub(self, other: &'a Series<F>) -> Self::Output {
(self.clone()).add(other.clone().neg())
}
}
impl<F: Ring> Neg for Series<F> {
type Output = Self;
fn neg(mut self) -> Self::Output {
for c in &mut self.coefficients {
*c = self.field.neg(&*c);
}
self
}
}
impl<'a, F: Ring> Mul<&'a Series<F>> for &Series<F> {
type Output = Series<F>;
#[inline]
fn mul(self, rhs: &'a Series<F>) -> Self::Output {
assert_eq!(self.field, rhs.field);
assert_eq!(self.variable, rhs.variable);
let r = self.joint_ramification(rhs);
let r_d_s = r / self.ramification;
let r_d_o = r / rhs.ramification;
let mut res = Series {
coefficients: vec![],
field: self.field.clone(),
variable: self.variable.clone(),
expansion_point: self.expansion_point.clone(),
shift: self.shift * r_d_s as isize + rhs.shift * r_d_o as isize,
order: (self.order * r_d_s).min(rhs.order * r_d_o),
ramification: r,
};
res.coefficients =
vec![self.field.zero(); self.coefficients.len().max(rhs.coefficients.len())];
for (e1, c1) in self.coefficients.iter().enumerate() {
if self.field.is_zero(c1) {
continue;
}
let p1 = self.get_exponent(e1);
for (e2, c2) in rhs.coefficients.iter().enumerate() {
let p = &p1 + &rhs.get_exponent(e2);
if !self.field.is_zero(c2) {
let index = res.get_index(p);
if index < res.order {
if index >= res.coefficients.len() {
res.coefficients.resize(index * 2, self.field.zero());
}
self.field
.add_mul_assign(&mut res.coefficients[index], c1, c2);
}
}
}
}
res.truncate();
res
}
}
impl<'a, F: Ring> Mul<&'a Series<F>> for Series<F> {
type Output = Series<F>;
#[inline]
fn mul(self, rhs: &'a Series<F>) -> Self::Output {
(&self) * rhs
}
}
impl<'a> Div<&'a Series<AtomField>> for &Series<AtomField> {
type Output = Series<AtomField>;
fn div(self, other: &'a Series<AtomField>) -> Self::Output {
other.rpow((-1, 1).into()).unwrap() * self
}
}
impl<'a> Div<&'a Series<AtomField>> for Series<AtomField> {
type Output = Series<AtomField>;
fn div(self, other: &'a Series<AtomField>) -> Self::Output {
(&self).div(other)
}
}
impl<F: EuclideanDomain> Series<F> {
pub fn content(&self) -> F::Element {
if self.coefficients.is_empty() {
return self.field.zero();
}
let mut c = self.coefficients.first().unwrap().clone();
for cc in self.coefficients.iter().skip(1) {
if F::one_is_gcd_unit() && self.field.is_one(&c) {
break;
}
c = self.field.gcd(&c, cc);
}
c
}
pub fn div_coeff(mut self, other: &F::Element) -> Self {
for c in &mut self.coefficients {
let (quot, rem) = self.field.quot_rem(c, other);
debug_assert!(self.field.is_zero(&rem));
*c = quot;
}
self
}
pub fn make_primitive(self) -> Self {
let c = self.content();
self.div_coeff(&c)
}
}
impl Series<AtomField> {
fn extract_exp_log(&self, e: AtomView, s: AtomView) -> Result<Self, SeriesError> {
if !e.contains(s) {
return Ok(self.constant(e.to_owned()));
}
match e {
AtomView::Pow(p) => {
let (b, exp) = p.get_base_exp();
if b == s {
if let AtomView::Num(n) = exp {
if let CoefficientView::Natural(n, d, ni, _di) = n.get_coeff_view() {
if ni == 0 {
Ok(self.monomial(self.field.one(), (n, d).into()))
} else {
Err(SeriesError::UnsupportedComplexExponent {
expression: e.to_owned(),
})
}
} else {
Err(SeriesError::UnsupportedLargeExponent {
expression: e.to_owned(),
})
}
} else {
Err(SeriesError::NonRationalPower {
expression: e.to_owned(),
})
}
} else {
Err(SeriesError::UnexpectedExpLogSimplification {
expression: e.to_owned(),
})
}
}
AtomView::Var(_) => Ok(self.monomial(self.field.one(), (1, 1).into())),
AtomView::Mul(m) => {
let mut shift_series = self.one_inf_prec();
for a in m {
shift_series = &shift_series * &self.extract_exp_log(a, s)?;
}
Ok(shift_series)
}
_ => Err(SeriesError::UnexpectedExpLogSimplification {
expression: e.to_owned(),
}),
}
}
pub fn exp(&self) -> Result<Self, SeriesError> {
if self.shift < 0 {
return Err(SeriesError::EssentialSingularity {
function: Symbol::EXP,
});
}
if self.order == 0 {
return Ok(self.one_inf_prec()
+ Series::new(
&self.field,
Some(1),
self.variable.clone(),
self.expansion_point.clone(),
(self.shift as i64, 1).into(),
));
}
let c = if self.shift == 0 && self.order > 0 {
self.coefficients[0].clone()
} else {
Atom::new()
};
let e = FunctionBuilder::new(Symbol::EXP).add_arg(&c).finish();
let var = self.variable.to_atom() - &self.expansion_point;
let shift_series = self.extract_exp_log(e.as_view(), var.as_view())?;
let p = self.clone().remove_constant();
let mut r = self.one_inf_prec();
let mut sp = p.clone();
for i in 1..=self.order {
let s = sp
.clone()
.div_coeff(&Atom::num(Integer::factorial(i as u32)));
sp = sp * &p;
r = r + s;
}
Ok(r * &shift_series)
}
pub fn log(&self) -> Result<Self, SeriesError> {
if self.order == 0 {
return Err(SeriesError::MissingLogCoefficient);
}
let c = (self.variable.to_atom() - &self.expansion_point).pow(self.get_exponent(0))
* &self.coefficients[0];
let p = self
.clone()
.div_coeff(&self.coefficients[0])
.mul_exp_units(-self.shift)
- self.one();
let mut e = self.constant(FunctionBuilder::new(Symbol::LOG).add_arg(&c).finish());
let mut sp = p.clone();
for i in 1..=self.order {
let s = sp.clone().div_coeff(&Atom::num(i as i64));
sp = sp * &p;
if i % 2 == 0 {
e = e - s;
} else {
e = e + s;
}
}
Ok(e)
}
pub fn sin(&self) -> Result<Self, SeriesError> {
if self.shift < 0 {
return Err(SeriesError::EssentialSingularity {
function: Symbol::SIN,
});
}
if self.order == 0 {
return Ok(Series::new(
&self.field,
Some(1),
self.variable.clone(),
self.expansion_point.clone(),
(self.shift as i64, 1).into(),
));
}
let c = if self.shift == 0 {
self.coefficients[0].clone()
} else {
Atom::new()
};
if c.contains(self.variable.to_atom()) {
return Err(SeriesError::ConstantTermDependsOnExpansionVariable {
function: Symbol::SIN,
constant: c,
variable: self.variable.as_ref().clone(),
});
}
let p = self.clone().remove_constant();
let mut e = self.constant(FunctionBuilder::new(Symbol::SIN).add_arg(&c).finish());
let mut sp = p.clone();
for i in 1..=self.order {
let mut b = if i % 2 == 1 {
FunctionBuilder::new(Symbol::COS).add_arg(&c).finish()
} else {
FunctionBuilder::new(Symbol::SIN).add_arg(&c).finish()
};
if i % 4 >= 2 {
b = b.neg();
}
let s = sp
.clone()
.mul_coeff(&b)
.div_coeff(&Atom::num(Integer::factorial(i as u32)));
sp = sp * &p;
e = e + s;
}
Ok(e)
}
pub fn cos(&self) -> Result<Self, SeriesError> {
if self.shift < 0 {
return Err(SeriesError::EssentialSingularity {
function: Symbol::COS,
});
}
if self.order == 0 {
return Ok(self.one_inf_prec()
+ Series::new(
&self.field,
Some(1),
self.variable.clone(),
self.expansion_point.clone(),
(self.shift as i64 * 2, 1).into(),
));
}
let c = if self.shift == 0 {
self.coefficients[0].clone()
} else {
Atom::new()
};
if c.contains(self.variable.to_atom()) {
return Err(SeriesError::ConstantTermDependsOnExpansionVariable {
function: Symbol::COS,
constant: c,
variable: self.variable.as_ref().clone(),
});
}
let p = self.clone().remove_constant();
let mut e = self.constant(FunctionBuilder::new(Symbol::COS).add_arg(&c).finish());
let mut sp = p.clone();
for i in 1..=self.order {
let mut b = if i % 2 == 1 {
FunctionBuilder::new(Symbol::SIN).add_arg(&c).finish()
} else {
-FunctionBuilder::new(Symbol::COS).add_arg(&c).finish()
};
if i % 4 < 2 {
b = b.neg();
}
let s = sp
.clone()
.mul_coeff(&b)
.div_coeff(&Atom::num(Integer::factorial(i as u32)));
sp = sp * &p;
e = e + s;
}
Ok(e)
}
pub fn pow(&self, pow: &Self) -> Result<Self, SeriesError> {
(self.log()? * pow).exp()
}
pub fn rpow(&self, pow: Rational) -> Result<Self, SeriesError> {
if pow.is_zero() {
return Err(SeriesError::ZeroPowerRequiresInfinitePrecision);
}
if pow.is_integer() && !pow.is_negative() {
return Ok(self.npow(pow.numerator().to_i64().unwrap() as usize));
}
let c = self.get_trailing_coefficient();
if c.is_zero() {
if pow.is_negative() {
return Err(SeriesError::DivisionByZero);
}
let mut r = self.clone();
r.shift *= pow.numerator().to_i64().unwrap() as isize;
r.ramification *= pow.denominator().to_i64().unwrap() as usize;
return Ok(r);
}
let c = self.coefficients[0].clone();
let cc = self.clone().div_coeff(&c).mul_exp_units(-self.shift) - self.one();
let mut r = self.one();
let mut x = self.one();
let mut num = Rational::one();
for i in 1..=self.order {
num = &num * &(&pow - &(i as i64 - 1).into());
x = x * &cc;
let p = x
.clone()
.mul_coeff(&Atom::num(num.clone()))
.div_coeff(&Atom::num(Integer::factorial(i as u32)));
r = r + p;
}
let pow_ram = pow.denominator().to_i64().unwrap() as usize;
r.change_ramification(pow_ram);
let p = Rational::from((self.shift as i64, self.ramification as i64)) * &pow;
let shift = p.numerator().to_i64().unwrap() as isize
* (r.ramification / p.denominator().to_i64().unwrap() as usize) as isize;
Ok(r.mul_coeff(&c.pow(pow)).mul_exp_units(shift))
}
pub fn to_atom(&self) -> Atom {
let mut a = Atom::new();
self.to_atom_into(&mut a);
a
}
pub fn to_atom_into(&self, out: &mut Atom) {
out.to_num(0);
if self.order == 0 {
return;
}
let v = self.variable.to_atom() - &self.expansion_point;
for (e, c) in self.coefficients.iter().enumerate() {
let p = self.get_exponent(e);
if !c.is_zero() {
*out = &*out + &(v.pow(p) * c);
}
}
}
}
#[cfg(test)]
mod tests {
use crate::{
atom::{Atom, AtomCore},
parse,
poly::series::SeriesDepth,
symbol,
};
#[test]
fn map_coeff() {
let a = parse!("((v1+1)^2 - (v1^2 + 2v1 + 1))/v2")
.series(symbol!("v2"), Atom::Zero, SeriesDepth::absolute(0))
.unwrap();
assert_eq!(a.get_trailing_exponent(), -1);
let b = a.map_coeff(|x| x.expand());
assert_eq!(b.get_trailing_exponent(), 1);
}
}