Common features
Below we list some common features of Symbolica.
Expanding
Symbolica can expand an expression:
from symbolica import Expression
= Expression.symbol('x')
x = (1+x)^2
e print(e.expand())
use symbolica::atom::Atom;
fn main() {
let e = Atom::parse("(1+x)^2").unwrap();
println!("{}", e.expand(None));
}
yielding
x^2+2x+1
Expansions can also be performed in a particular variable:
from symbolica import Expression
= Expression.symbol('x', 'y')
x, y = (1+y)^100 + (1+x)^2
e print(e.expand(x))
use symbolica::atom::Atom;
use symbolica::state::State;
fn main() {
let x = State::get_symbol("x");
let e = Atom::parse("(1+y)^100 + (1+x)^2").unwrap();
println!("{}", e.expand(Some(x)));
}
yielding
x^2+2x+1 + (1+y)^100
Collecting
A coefficient list in a variable can be obtained:
from symbolica import Expression
= Expression.symbol('x', 'y')
x, y = 5*x + x * y + x**2 + 5
e
= e.coefficient_list(x)
l for key, value in l:
print(key, value)
use symbolica::atom::Atom;
fn main() {
let x_symb = Atom::get_symbol("x");
let e = Atom::parse("5*x + x * y + x**2 + 5").unwrap();
for (k, v) in e.coefficient_list(x_symb).0 {
println!("{}, {}", k, v);
}
}
yielding
x^2 1
x y+5
1 5
You can also collect in a variable:
from symbolica import Expression
= Expression.symbol('x', 'y')
x, y = 5*x + x * y + x**2 + 5
e
print(e.collect(x))
use symbolica::atom::Atom;
fn main() {
let x_symb = Atom::get_symbol("x");
let e = Atom::parse("5*x + x * y + x**2 + 5").unwrap();
println!("{}", e.collect(x_symb, None, None));
}
yielding
x^2+x*(y+5)+5
You can also provide a user-defined function to map the key and one to map the coefficient. For example:
from symbolica import Expression
= Expression.symbol('x', 'y')
x, y = Expression.symbol('var', 'coeff')
var, coeff
= 5*x + x * y + x**2 + 5
e
= e.collect(x, key_map=lambda x: symbol(x), coeff_map=lambda x: coeff(x))
e print(e)
use symbolica::{
,
funatom::{Atom, FunctionBuilder},
state::State,
};
fn main() {
let input = Atom::parse("x*(1+a)+x*5*y+f(5,x)+2+y^2+x^2 + x^3").unwrap();
let x = State::get_symbol("x");
let key = State::get_symbol("key");
let coeff = State::get_symbol("coeff");
println!("> Collect in x with wrapping:");
let out = input.collect(
,
xSome(Box::new(move |a, out| {
.set_from_view(&a);
out*out = fun!(key, out);
})),
Some(Box::new(move |a, out| {
.set_from_view(&a);
out*out = fun!(coeff, out);
})),
;
)println!("\t{}", out);
}
yielding symbol(1)*coeff(5)+symbol(x)*coeff(y+5)+symbol(x^2)*coeff(1)
Together
To write an expression using a common denominator, use together
:
from symbolica import Expression
= Expression.symbol('x', 'y', 'z')
x, y, z = (1/x + (2*x+y+z)/y).together()
e print(e)
use symbolica::{atom::Atom};
fn main() {
let input = Atom::parse("1/x + (2*x+y+z)/y").unwrap();
println!("{}", input.together());
}
yielding
(x*y)^-1*(y+2*x^2+x*y+x*z)
Apart
To apply partial fraction decomposition in a variable x
, use apart(x)
:
from symbolica import Expression
= Expression.symbol('x', 'y', 'z')
x, y, z = ((y+2*x**2+x*y+x*z)/(x*y)).apart(x)
e print(e)
use symbolica::{atom::Atom, state::State};
fn main() {
let input = Atom::parse("(y+2*x^2+x*y+x*z)/(x*y)").unwrap();
println!("{}", input.apart(State::get_symbol("x")));
}
yielding
x^-1+y^-1*(2*x+y+z)
Cancel
Symbolica can cancel common factors between numerators and denominators, leaving all other parts of the expression untouched:
from symbolica import *
= Expression.parse('1+(y+1)^10*(x+1)/(x^2+2x+1)')
p print(p.cancel())
use symbolica::atom::Atom;
fn main() {
let a = Atom::parse("1+(y+1)^10*(x+1)/(x^2+2x+1)").unwrap();
println!("{}", a.cancel());
}
yielding
1+(y+1)**10/(x+1)
Derivatives
Symbolica can derive expressions with built-in and user-defined functions:
from symbolica import Expression
= Expression.symbol('x')
x
print(Expression.parse("(1+2*x)^(5+x)").derivative(x))
print(Expression.parse("log(2*x) + exp(3*x) + sin(4*x) + cos(y*x)").derivative(x))
print(Expression.parse("f(x^2,x)").derivative(x))
print(Expression.parse("der(0,1,f(x,x^3))").derivative(x))
use symbolica::{atom::Atom, state::State};
fn main() {
let x = State::get_symbol("x");
let inputs = [
"(1+2*x)^(5+x)",
"log(2*x) + exp(3*x) + sin(4*x) + cos(y*x)",
"f(x^2,x)",
"der(0,1,f(x,x^3))",
;
]
for input in inputs {
let input = Atom::parse(input).unwrap();
let a = input.derivative(x);
println!("d({})/dx = {}:", input, a);
}
}
yielding
(2*x+1)^(x+5)*log(2*x+1)+2*(x+5)*(2*x+1)^(x+4)
2*(2*x)^-1+3*exp(3*x)+4*cos(4*x)-y*sin(x*y)
der(0,1,f(x^2,x))+2*x*der(1,0,f(x^2,x))
der(1,1,f(x,x^3))+3*x^2*der(0,2,f(x,x^3))
The built-in der
function keeps counters of the number of derivatives per argument position.
Series expansion
Symbolica can also produce a Puiseux series, with additional support for log(x)
around x=0
. The function series
returns a series object that support efficient arithmetic with other series.
from symbolica import Expression
= Expression.symbol('x')
x = Expression.parse('exp(5+x)/(1-x)').series(x, 0, 3) e
use symbolica::{atom::Atom, state::State};
fn main() {
let x = State::get_symbol("x");
let a = Atom::parse("exp(5+x)/(1-x)").unwrap();
let out = a.series(x, Atom::new().as_view(), 3);
println!("{}", out);
}
yielding
(exp(5))+(2*exp(5))*x+(5/2*exp(5))*x^2+(8/3*exp(5))*x^3+O(x^4)
Factorization
Symbolica can factor an expression:
from symbolica import *
= Expression.parse('(6 + x)/(7776 + 6480*x + 2160*x^2 + 360*x^3 + 30*x^4 + x^5)')
e e.factor()
use symbolica::atom::Atom;
fn main() {
let a = Atom::parse("(6 + x)/(7776 + 6480*x + 2160*x^2 + 360*x^3 + 30*x^4 + x^5)").unwrap();
println!("{}", a.factor());
}
yielding
(x+6)^-4
Solving linear system
Symbolica can solve linear systems:
from symbolica import Expression
= Expression.symbol('x', 'y', 'c')
x, y, c = Expression.symbol('f')
f
= Expression.solve_linear_system(
x_r, y_r *x + y + c, y + c**2], [x, y])
[f(c)print('x =', x_r, ', y =', y_r)
x = (-c+c^2)*f(c)^-1 , y = -c^2
use symbolica::{
atom::{Atom, AtomView},
state::State,
};
fn main() {
let x = State::get_symbol("x");
let y = State::get_symbol("y");
let eqs = ["f(c)*x + y + c", "y + c**2"];
let atoms: Vec<_> = eqs.iter().map(|e| Atom::parse(e).unwrap()).collect();
let system: Vec<_> = atoms.iter().map(|x| x.as_view()).collect();
let sol = AtomView::solve_linear_system::<u8>(&system, &[x, y]).unwrap();
for (v, s) in ["x", "y"].iter().zip(&sol) {
println!("{} = {}", v, s);
}
}
Numerically solving non-linear system
Symbolica can use Newton’s method to find roots of expressions:
from symbolica import *
= E("x^2-2").nsolve(E("x"), 1.) # using float
a
= E("x^2-2").nsolve(E("x"),
b "1.000000000000000000000000000000000000000000000000000000000000000000000000"),
Decimal(1e-74, 1000000)
use symbolica::{
atom::Atom,
state::State,
};
fn find_root() {
let x = State::get_symbol("x");
let a = Atom::parse("x^2 - 2").unwrap();
let a = a.as_view();
let root = a.nsolve(x, 1.0, 1e-10, 1000).unwrap();
assert!((root - 2f64.sqrt()).abs() < 1e-10);
}
1.414213562373095048801688724209698078569671875376948073176679737990732478
Or it can solve systems:
\[ \begin{align} 5x^2+x y^2+\sin(2y)^2 - 2 &= 0\\ \exp(2x-y)+4y - 3 &= 0 \end{align} \]
from symbolica import *
= Expression.nsolve_system([E("5x^2+x*y^2+sin(2y)^2 - 2"), E("exp(2x-y)+4y - 3")],
a "x"), S("y")], [1., 1.], 1e-9) [S(
use symbolica::{
atom::{Atom, AtomView},
state::State,
};
fn solve_system_newton() {
let a = Atom::parse("5x^2+x*y^2+sin(2y)^2 - 2").unwrap();
let b = Atom::parse("exp(2x-y)+4y - 3").unwrap();
let r = AtomView::nsolve_system(
&[a.as_view(), b.as_view()],
&[State::get_symbol("x"), State::get_symbol("y")],
&[F64::from(1.), F64::from(1.)],
F64::from(1e-10),
100,
).unwrap();
}
[0.5672973499396123, -0.30944227920271095]
Canonizing tensors
It is often convenient to detect if terms with tensors are equivalent, for example:
= g(mu2, mu3)*fc(mu4, mu2, k1, mu4, k1, mu3) e1
and
= g(mu3, mu2)*fc(k1, mu1, k1, mu2, mu1, mu3) e2
If they are isomorphic, we can merge the two terms in e1+e2
into one and prevent double work.
Symbolica can canonize tensors with dummy/contracted indices:
from symbolica import *
= Expression.symbol('g', is_symmetric=True)
g = Expression.symbol('fc', is_cyclesymmetric=True)
fc = Expression.symbol('mu1', 'mu2', 'mu3', 'mu4', 'k1')
mu1, mu2, mu3, mu4, k1
= g(mu2, mu3)*fc(mu4, mu2, k1, mu4, k1, mu3)
e print(e.canonize_tensors([mu1, mu2, mu3, mu4]))
use symbolica::{atom::Atom, state::{FunctionAttribute, State}};
fn main() {
let _ = State::get_symbol_with_attributes("g", &[FunctionAttribute::Symmetric]).unwrap();
let _ = State::get_symbol_with_attributes("fc", &[FunctionAttribute::Cyclesymmetric]).unwrap();
let a = Atom::parse("g(mu2,mu3)*fc(mu4,mu2,k1,mu4,k1,mu3)").unwrap();
let mu1 = Atom::parse("mu1").unwrap();
let mu2 = Atom::parse("mu2").unwrap();
let mu3 = Atom::parse("mu3").unwrap();
let mu4 = Atom::parse("mu4").unwrap();
let r = a.canonize_tensors(&[mu1.as_view(), mu2.as_view(), mu3.as_view(), mu4.as_view()], None).unwrap();
println!("{}", r);
}
which yields g(mu1,mu2)*fc(mu1,mu3,mu2,k1,mu3,k1)
.
Anti-symmetric tensors are supported as well. For example, the canonization turns
f(mu3,mu2,mu3,mu1)*f_anti(mu1,mu2)
into
-f(mu1,mu2,mu1,mu3)*f_anti(mu2,mu3)