Common features

Below we list some common features of Symbolica.

Expanding

Symbolica can expand an expression:

from symbolica import Expression
x = Expression.symbol('x')
e = (1+x)^2
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
x, y = Expression.symbol('x', 'y')
e = (1+y)^100 + (1+x)^2
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
x, y = Expression.symbol('x', 'y')
e = 5*x + x * y + x**2 + 5

l = e.coefficient_list(x)
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
x, y = Expression.symbol('x', 'y')
e = 5*x + x * y + x**2 + 5

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

x, y = Expression.symbol('x', 'y')
var, coeff = Expression.symbol('var', 'coeff')

e = 5*x + x * y + x**2 + 5

e = e.collect(x, key_map=lambda x: symbol(x), coeff_map=lambda x: coeff(x))
print(e)
use symbolica::{
    fun,
    atom::{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(
        x,
        Some(Box::new(move |a, out| {
            out.set_from_view(&a);
            *out = fun!(key, out);
        })),
        Some(Box::new(move |a, out| {
            out.set_from_view(&a);
            *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
x, y, z = Expression.symbol('x', 'y', 'z')
e = (1/x + (2*x+y+z)/y).together()
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
x, y, z = Expression.symbol('x', 'y', 'z')
e = ((y+2*x**2+x*y+x*z)/(x*y)).apart(x)
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 *
p = Expression.parse('1+(y+1)^10*(x+1)/(x^2+2x+1)')
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
x = Expression.symbol('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
x = Expression.symbol('x')
e = Expression.parse('exp(5+x)/(1-x)').series(x, 0, 3)
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 *
e = Expression.parse('(6 + x)/(7776 + 6480*x + 2160*x^2 + 360*x^3 + 30*x^4 + x^5)')
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

x, y, c = Expression.symbol('x', 'y', 'c')
f = Expression.symbol('f')

x_r, y_r = Expression.solve_linear_system(
    [f(c)*x + y + c, y + c**2], [x, y])
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 *
a = E("x^2-2").nsolve(E("x"), 1.) # using float

b = E("x^2-2").nsolve(E("x"),
        Decimal("1.000000000000000000000000000000000000000000000000000000000000000000000000"),
        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 *
a = Expression.nsolve_system([E("5x^2+x*y^2+sin(2y)^2 - 2"), E("exp(2x-y)+4y - 3")],
        [S("x"), S("y")], [1., 1.], 1e-9)
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:

e1 = g(mu2, mu3)*fc(mu4, mu2, k1, mu4, k1, mu3)

and

e2 = g(mu3, mu2)*fc(k1, mu1, k1, mu2, mu1, mu3)

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 *
g = Expression.symbol('g', is_symmetric=True)
fc = Expression.symbol('fc', is_cyclesymmetric=True)
mu1, mu2, mu3, mu4, k1 = Expression.symbol('mu1', 'mu2', 'mu3', 'mu4', 'k1')

e = g(mu2, mu3)*fc(mu4, mu2, k1, mu4, k1, mu3)
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)