Coefficients

By default a coefficient of a term in Symbolica is a rational number. For example in the expression

\[ 3 x \epsilon + \frac{1}{2}x \]

the coefficients are \(3\) and \(\frac{1}{2}\).

For many applications it is favourable to extend the coefficient ring to include variables. If we consider \(\epsilon\) to be part of the coefficient, we get:

\[ \frac{1 + 6 \epsilon}{2} x \]

For example, when coding the following iterative replacement rule:

\[ I(n) = I(n-1) \frac{2 + (4-2 \epsilon) - 2 n}{2(n - 1) m^2}\,; n \geq 1 \]

a possible Symbolica code is:

from symbolica import *
ep, m, n_ = Expression.vars('ep', 'm', 'n_')
topo = Expression.fun('topo')
T = Transformer()

e = topo(10).transform().repeat(
    T.stats('ibp',
            T.expand().
            replace_all(topo(n_),
                        topo(n_ - 1) * (2 + (4-2*ep) - 2 * n_) /
                        (2*(n_ - 1)) / m**2,
                        n_.req_gt(1)))
).execute()
print(e)

which yields

1/72*ep*m^-18*topo(1)+223/10080*ep^2*m^-18*topo(1)+1/5670*ep^3*m^-18*topo(1)
-101/5760*ep^4*m^-18*topo(1)-229/17280*ep^5*m^-18*topo(1)-13/2880*ep^6*m^-18*topo(1)
-7/8640*ep^7*m^-18*topo(1)-1/13440*ep^8*m^-18*topo(1)-1/362880*ep^9*m^-18*topo(1)

At each stage, the pattern matcher will match and replace a topo(n) in every term, and will create new terms for every power of ep. This overhead can be avoided by considering ep to be a part of the coefficient. Then we have topo(2)*ep+topo(2) = topo(2)*(ep+1).

We use Function.COEFF (State::COEFF in Rust) that converts its rational polynomial argument to a coefficient:

e = topo(10).transform().repeat(
    T.stats('ibp_num',
            T.replace_all(topo(n_),
                          topo(n_ - 1)
                          *Function.COEFF((2 + (4-2*ep) -
                                         2 * n_)/(2*(n_ - 1))) / m**2,
                          n_.req_gt(1)))
).execute()
print(e)

This code is much faster and the expression looks like:

[(5040*ep+8028*ep^2+64*ep^3-6363*ep^4-4809*ep^5-1638*ep^6-294*ep^7-27*ep^8-ep^9)/362880]*m^-18*topo(1)

where [...] indicates that the contained rational polynomial is a coefficient.

Since it is a coefficient, we cannot pattern match on the internal structure. To do these operations, we can convert back from a number to an atom using from_coeff:

e = e.replace_all(n_, n_.transform().from_coeff(), n_.req_type(AtomType.Num))

Now we get:

1/362880*m^-18*(5040*ep+8028*ep^2+64*ep^3-6363*ep^4-4809*ep^5-1638*ep^6-294*ep^7-27*ep^8-ep^9)*topo(1)

Coefficient rings

The coefficient ring can also be changed using the set_coefficient_ring function which takes the variables that should be considered part of the coefficient as an argument. It will then rewrite the expression.

from symbolica import Expression
x, y, z = Expression.vars('x', 'y', 'z')
e = x*z+x*(y+2)**-1*(y+z+1)
print(e.set_coefficient_ring([y, z]))
use std::sync::Arc;

use symbolica::{
    representations::Atom,
    state::{ResettableBuffer, State, Workspace},
};

fn main() {
    let mut state = State::new();
    let workspace = Workspace::default();

    let expr = Atom::parse("x*z+x*(y+2)^-1*(y+z+1)", &mut state, &workspace).unwrap();
    println!("> In: {}", expr.printer(&state));

    let mut expr_yz = Atom::new();
    expr.as_view().set_coefficient_ring(
        &Arc::new(vec![
            state.get_or_insert_var("y").into(),
            state.get_or_insert_var("z").into(),
        ]),
        &state,
        &workspace,
        &mut expr_yz,
    );
    println!("> Coefficient ring y,z: {}", expr_yz.printer(&state));
}

which yields

[(1+3*z+y+y*z)/(2+y)]*x