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.symbols('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.symbols('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.symbols('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.symbols('x', 'y')
var, coeff = Expression.symbols('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.symbols('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.symbols('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)

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)

Solving linear system

Symbolica can solve linear systems:

from symbolica import Expression

x, y, c = Expression.symbols('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);
    }
}