diff --git a/Readme.md b/Readme.md index 20db69d2..8975a44f 100644 --- a/Readme.md +++ b/Readme.md @@ -75,8 +75,8 @@ Variables ending with a `_` are wildcards that match to any subexpression. In the following example we try to match the pattern `f(w1_,w2_)`: ```python -from symbolica import Expression -x, y, w1_, w2_, f = Expression.symbols('x','y','w1_','w2_', 'f') +from symbolica import * +x, y, w1_, w2_, f = S('x','y','w1_','w2_', 'f') e = f(3,x)*y**2+5 r = e.replace_all(f(w1_,w2_), f(w1_ - 1, w2_**2)) print(r) @@ -88,9 +88,9 @@ which yields `y^2*f(2,x^2)+5`. Solve a linear system in `x` and `y` with a parameter `c`: ```python -from symbolica import Expression +from symbolica import * -x, y, c, f = Expression.symbols('x', 'y', 'c', 'f') +x, y, c, f = S('x', 'y', 'c', 'f') x_r, y_r = Expression.solve_linear_system( [f(c)*x + y + c, y + c**2], [x, y]) @@ -103,20 +103,19 @@ which yields `x = (-c+c^2)*f(c)^-1` and `y = -c^2`. Perform a series expansion in `x`: ```python -from symbolica import Expression -x = Expression.symbol('x') -e = Expression.parse('exp(5+x)/(1-x)').series(x, 0, 3) +from symbolica import * +e = E('exp(5+x)/(1-x)').series(S('x'), 0, 3) print(e) ``` -which yields `(exp(5))+(2*exp(5))*x+(5/2*exp(5))*x^2+(8/3*exp(5))*x^3+O(x^4)`. +which yields `(exp(5))+(2*exp(5))*x+(5/2*exp(5))*x^2+(8/3*exp(5))*x^3+𝒪(x^4)`. ### Rational arithmetic Symbolica is world-class in rational arithmetic, outperforming Mathematica, Maple, Form, Fermat, and other computer algebra packages. Simply convert an expression to a rational polynomial: ```python -from symbolica import Expression -p = Expression.parse('(x*y^2*5+5)^2/(2*x+5)+(x+4)/(6*x^2+1)').to_rational_polynomial() +from symbolica import * +p = E('(x*y^2*5+5)^2/(2*x+5)+(x+4)/(6*x^2+1)').to_rational_polynomial() print(p) ``` which yields `(45+13*x+50*x*y^2+152*x^2+25*x^2*y^4+300*x^3*y^2+150*x^4*y^4)/(5+2*x+30*x^2+12*x^3)`. diff --git a/examples/builder.rs b/examples/builder.rs index 29a3658a..27062b20 100644 --- a/examples/builder.rs +++ b/examples/builder.rs @@ -1,13 +1,11 @@ -use symbolica::{atom::Atom, fun, state::State}; +use symbolica::{fun, symb}; fn main() { - let x = Atom::parse("x").unwrap(); - let y = Atom::parse("y").unwrap(); - let f_id = State::get_symbol("f"); + let (x, y, f) = symb!("x", "y", "f"); - let f = fun!(f_id, x, y, Atom::new_num(2)); + let f = fun!(f, x, y, 2); - let xb = (-(&y + &x + 2) * &y * 6).npow(5) / &y * &f / 4; + let xb = (-(y + x + 2) * y * 6).npow(5) / y * f / 4; println!("{}", xb); } diff --git a/src/atom.rs b/src/atom.rs index 89f20cb1..ec43c3e2 100644 --- a/src/atom.rs +++ b/src/atom.rs @@ -782,7 +782,7 @@ impl Atom { } } -/// A constructor of a function, +/// A constructor of a function. Consider using the [`fun!`] macro instead. /// /// For example: /// ``` @@ -844,6 +844,54 @@ impl FunctionBuilder { } } +/// A trait that allows to add an argument to a function builder. +pub trait FunctionArgument { + fn add_arg_to_function_builder(&self, f: FunctionBuilder) -> FunctionBuilder; +} + +impl FunctionArgument for Atom { + fn add_arg_to_function_builder(&self, f: FunctionBuilder) -> FunctionBuilder { + f.add_arg(self.as_view()) + } +} + +impl FunctionArgument for &Atom { + fn add_arg_to_function_builder(&self, f: FunctionBuilder) -> FunctionBuilder { + f.add_arg(self.as_view()) + } +} + +impl FunctionArgument for &mut Atom { + fn add_arg_to_function_builder(&self, f: FunctionBuilder) -> FunctionBuilder { + f.add_arg(self.as_view()) + } +} + +impl<'a> FunctionArgument for AtomView<'a> { + fn add_arg_to_function_builder(&self, f: FunctionBuilder) -> FunctionBuilder { + f.add_arg(*self) + } +} + +impl<'a> FunctionArgument for &AtomView<'a> { + fn add_arg_to_function_builder(&self, f: FunctionBuilder) -> FunctionBuilder { + f.add_arg(**self) + } +} + +impl FunctionArgument for Symbol { + fn add_arg_to_function_builder(&self, f: FunctionBuilder) -> FunctionBuilder { + let t = InlineVar::new(*self); + f.add_arg(t.as_view()) + } +} + +impl<'a, T: Into + Clone> FunctionArgument for T { + fn add_arg_to_function_builder(&self, f: FunctionBuilder) -> FunctionBuilder { + f.add_arg(&Atom::new_num(self.clone())) + } +} + /// Create a new function by providing its name as the first argument, /// followed by the list of arguments. This macro uses [`FunctionBuilder`]. /// @@ -859,13 +907,37 @@ macro_rules! fun { { let mut f = $crate::atom::FunctionBuilder::new($name); $( - f = f.add_arg(&$id); + f = $crate::atom::FunctionArgument::add_arg_to_function_builder(&$id, f); )+ f.finish() } }; } +/// Create new symbols without special attributes. Use [`get_symbol_with_attributes()`](crate::state::State::get_symbol_with_attributes) +/// to define symbols with attributes. +/// +/// For example: +/// ``` +/// use symbolica::symb; +/// let (x, y, z) = symb!("x", "y", "z"); +/// ``` +#[macro_export] +macro_rules! symb { + ($id: expr) => { + $crate::state::State::get_symbol($id) + }; + ($($id: expr),*) => { + { + ( + $( + $crate::state::State::get_symbol(&$id), + )+ + ) + } + }; +} + impl Atom { /// Take the `self` to a numerical power `exp` pub fn npow>(&self, exp: T) -> Atom { @@ -938,28 +1010,16 @@ impl Atom { impl std::ops::Add for Atom { type Output = Atom; - fn add(self, mut rhs: Atom) -> Atom { - Workspace::get_local().with(|ws| { - let mut t = ws.new_atom(); - self.as_view().add_with_ws_into(ws, rhs.as_view(), &mut t); - std::mem::swap(&mut rhs, &mut t); - }); - - rhs + fn add(self, rhs: Atom) -> Atom { + self + rhs.as_view() } } impl std::ops::Add for &Atom { type Output = Atom; - fn add(self, mut rhs: Atom) -> Atom { - Workspace::get_local().with(|ws| { - let mut t = ws.new_atom(); - self.as_view().add_with_ws_into(ws, rhs.as_view(), &mut t); - std::mem::swap(&mut rhs, &mut t); - }); - - rhs + fn add(self, rhs: Atom) -> Atom { + rhs + self.as_view() } } @@ -1000,28 +1060,16 @@ impl std::ops::Sub for &Atom { impl std::ops::Mul for &Atom { type Output = Atom; - fn mul(self, mut rhs: Atom) -> Atom { - Workspace::get_local().with(|ws| { - let mut t = ws.new_atom(); - self.as_view().mul_with_ws_into(ws, rhs.as_view(), &mut t); - std::mem::swap(&mut rhs, &mut t); - }); - - rhs + fn mul(self, rhs: Atom) -> Atom { + rhs * self.as_view() } } impl std::ops::Mul for Atom { type Output = Atom; - fn mul(self, mut rhs: Atom) -> Atom { - Workspace::get_local().with(|ws| { - let mut t = ws.new_atom(); - self.as_view().mul_with_ws_into(ws, rhs.as_view(), &mut t); - std::mem::swap(&mut rhs, &mut t); - }); - - rhs + fn mul(self, rhs: Atom) -> Atom { + self * rhs.as_view() } } @@ -1057,11 +1105,7 @@ impl std::ops::Add<&Atom> for &Atom { type Output = Atom; fn add(self, rhs: &Atom) -> Atom { - Workspace::get_local().with(|ws| { - let mut t = ws.new_atom(); - self.as_view().add_with_ws_into(ws, rhs.as_view(), &mut t); - t.into_inner() - }) + self.as_view() + rhs.as_view() } } @@ -1069,14 +1113,7 @@ impl std::ops::Sub<&Atom> for &Atom { type Output = Atom; fn sub(self, rhs: &Atom) -> Atom { - Workspace::get_local().with(|ws| { - let mut t = ws.new_atom(); - self.as_view() - .sub_no_norm(ws, rhs.as_view()) - .as_view() - .normalize(ws, &mut t); - t.into_inner() - }) + self.as_view() - rhs.as_view() } } @@ -1084,11 +1121,7 @@ impl std::ops::Mul<&Atom> for &Atom { type Output = Atom; fn mul(self, rhs: &Atom) -> Atom { - Workspace::get_local().with(|ws| { - let mut t = ws.new_atom(); - self.as_view().mul_with_ws_into(ws, rhs.as_view(), &mut t); - t.into_inner() - }) + self.as_view() * rhs.as_view() } } @@ -1096,11 +1129,7 @@ impl std::ops::Div<&Atom> for &Atom { type Output = Atom; fn div(self, rhs: &Atom) -> Atom { - Workspace::get_local().with(|ws| { - let mut t = ws.new_atom(); - self.as_view().div_with_ws_into(ws, rhs.as_view(), &mut t); - t.into_inner() - }) + self.as_view() / rhs.as_view() } } @@ -1119,10 +1148,42 @@ impl std::ops::Neg for &Atom { impl std::ops::Add<&Atom> for Atom { type Output = Atom; - fn add(mut self, rhs: &Atom) -> Atom { + fn add(self, rhs: &Atom) -> Atom { + self + rhs.as_view() + } +} + +impl std::ops::Sub<&Atom> for Atom { + type Output = Atom; + + fn sub(self, rhs: &Atom) -> Atom { + self - rhs.as_view() + } +} + +impl std::ops::Mul<&Atom> for Atom { + type Output = Atom; + + fn mul(self, rhs: &Atom) -> Atom { + self * rhs.as_view() + } +} + +impl std::ops::Div<&Atom> for Atom { + type Output = Atom; + + fn div(self, rhs: &Atom) -> Atom { + self / rhs.as_view() + } +} + +impl<'a> std::ops::Add> for Atom { + type Output = Atom; + + fn add(mut self, rhs: AtomView) -> Atom { Workspace::get_local().with(|ws| { let mut t = ws.new_atom(); - self.as_view().add_with_ws_into(ws, rhs.as_view(), &mut t); + self.as_view().add_with_ws_into(ws, rhs, &mut t); std::mem::swap(&mut self, &mut t); }); @@ -1130,16 +1191,13 @@ impl std::ops::Add<&Atom> for Atom { } } -impl std::ops::Sub<&Atom> for Atom { +impl<'a> std::ops::Sub> for Atom { type Output = Atom; - fn sub(mut self, rhs: &Atom) -> Atom { + fn sub(mut self, rhs: AtomView<'a>) -> Atom { Workspace::get_local().with(|ws| { let mut t = ws.new_atom(); - self.as_view() - .sub_no_norm(ws, rhs.as_view()) - .as_view() - .normalize(ws, &mut t); + self.as_view().sub_with_ws_into(ws, rhs, &mut t); std::mem::swap(&mut self, &mut t); }); @@ -1147,13 +1205,13 @@ impl std::ops::Sub<&Atom> for Atom { } } -impl std::ops::Mul<&Atom> for Atom { +impl<'a> std::ops::Mul> for Atom { type Output = Atom; - fn mul(mut self, rhs: &Atom) -> Atom { + fn mul(mut self, rhs: AtomView<'a>) -> Atom { Workspace::get_local().with(|ws| { let mut t = ws.new_atom(); - self.as_view().mul_with_ws_into(ws, rhs.as_view(), &mut t); + self.as_view().mul_with_ws_into(ws, rhs, &mut t); std::mem::swap(&mut self, &mut t); }); @@ -1161,13 +1219,13 @@ impl std::ops::Mul<&Atom> for Atom { } } -impl std::ops::Div<&Atom> for Atom { +impl<'a> std::ops::Div> for Atom { type Output = Atom; - fn div(mut self, rhs: &Atom) -> Atom { + fn div(mut self, rhs: AtomView<'a>) -> Atom { Workspace::get_local().with(|ws| { let mut t = ws.new_atom(); - self.as_view().div_with_ws_into(ws, rhs.as_view(), &mut t); + self.as_view().div_with_ws_into(ws, rhs, &mut t); std::mem::swap(&mut self, &mut t); }); @@ -1175,6 +1233,82 @@ impl std::ops::Div<&Atom> for Atom { } } +impl std::ops::Add for Atom { + type Output = Atom; + + fn add(self, rhs: Symbol) -> Atom { + let v = InlineVar::new(rhs); + self + v.as_view() + } +} + +impl std::ops::Sub for Atom { + type Output = Atom; + + fn sub(self, rhs: Symbol) -> Atom { + let v = InlineVar::new(rhs); + self - v.as_view() + } +} + +impl std::ops::Mul for Atom { + type Output = Atom; + + fn mul(self, rhs: Symbol) -> Atom { + let v = InlineVar::new(rhs); + self * v.as_view() + } +} + +impl std::ops::Div for Atom { + type Output = Atom; + + fn div(self, rhs: Symbol) -> Atom { + let v = InlineVar::new(rhs); + self / v.as_view() + } +} + +impl std::ops::Add for Symbol { + type Output = Atom; + + fn add(self, rhs: Symbol) -> Atom { + let s = InlineVar::new(self); + let r = InlineVar::new(rhs); + s.as_view() + r.as_view() + } +} + +impl std::ops::Sub for Symbol { + type Output = Atom; + + fn sub(self, rhs: Symbol) -> Atom { + let s = InlineVar::new(self); + let r = InlineVar::new(rhs); + s.as_view() - r.as_view() + } +} + +impl std::ops::Mul for Symbol { + type Output = Atom; + + fn mul(self, rhs: Symbol) -> Atom { + let s = InlineVar::new(self); + let r = InlineVar::new(rhs); + s.as_view() * r.as_view() + } +} + +impl std::ops::Div for Symbol { + type Output = Atom; + + fn div(self, rhs: Symbol) -> Atom { + let s = InlineVar::new(self); + let r = InlineVar::new(rhs); + s.as_view() / r.as_view() + } +} + impl std::ops::Neg for Atom { type Output = Atom; diff --git a/src/derivative.rs b/src/derivative.rs index 57160130..9080066e 100644 --- a/src/derivative.rs +++ b/src/derivative.rs @@ -487,10 +487,30 @@ impl<'a> AtomView<'a> { AtomView::Pow(p) => { let (base, exp) = p.get_base_exp(); - let base_series = base.series_impl(x, expansion_point, info)?; + let mut base_series = base.series_impl(x, expansion_point, info)?; if let AtomView::Num(n) = exp { if let CoefficientView::Natural(n, d) = n.get_coeff_view() { + if n < 0 && base_series.is_zero() { + // in case of 1/0, grow the expansion depth of the base series + // it could be that the base series is exactly zero, + // to prevent an infinite loop, we stop the loop at ep^-1000 + let mut current_depth = info.relative_order(); + while base_series.is_zero() && current_depth < 1000.into() { + let info = Series::new( + &AtomField::new(), + None, + info.get_variable().clone(), + info.get_expansion_point().clone(), + ¤t_depth + + &(1.into(), current_depth.denominator()).into(), + ); + + base_series = base.series_impl(x, expansion_point, &info)?; + current_depth = ¤t_depth * &2.into(); + } + } + base_series.rpow((n, d).into()) } else { unimplemented!("Cannot series expand with large exponents yet") diff --git a/src/id.rs b/src/id.rs index f1c51727..cd85a29b 100644 --- a/src/id.rs +++ b/src/id.rs @@ -751,7 +751,7 @@ impl Pattern { impl Pattern { /// A quick check to see if a pattern can match. #[inline] - pub fn could_match(&self, target: AtomView) -> bool { + fn could_match(&self, target: AtomView) -> bool { match (self, target) { (Pattern::Fn(f1, _), AtomView::Fun(f2)) => { f1.get_wildcard_level() > 0 || *f1 == f2.get_symbol() diff --git a/src/poly/series.rs b/src/poly/series.rs index bb8ee434..0258eb55 100644 --- a/src/poly/series.rs +++ b/src/poly/series.rs @@ -374,6 +374,11 @@ impl Series { .unwrap() as usize } + #[inline] + pub fn is_zero(&self) -> bool { + self.coefficients.len() == 0 + } + #[inline] pub fn is_one(&self) -> bool { self.coefficients.len() == 1 && self.field.is_one(&self.coefficients[0]) && self.shift == 0