Matrices

Symbolica documentation for getting started, symbolic expressions, numerical evaluation, pattern matching, and APIs in Python and Rust.

Run Symbolica in your browser

Python examples with a Run button can be edited and run here. The first run loads Python, Symbolica, hover documentation, and code completion.

The Matrix data structure allows users to do linear algebra operations in Symbolica.

Construction

Constructors validate matrix shapes: nested rows must have equal lengths, and linear data must have exactly rows * columns entries. Matrices can be created through one of the following constructors:

from symbolica import *
i = Matrix.identity(2)
e = Matrix.eye([1, 2, 3])
l = Matrix.from_linear(3, 2, [1, 2, 3, 4, 5, 6])
n = Matrix.from_nested([[1, 2, 3], [4, 5, 6]])

print("i =", i)
print("e =", e)
print("l =", l)
n
use symbolica::prelude::*;

fn main() {
    let i = Matrix::identity(2, IntegerRing::new());
    let e = Matrix::eye(&[1.into(), 2.into(), 3.into()], IntegerRing::new());
    let l = Matrix::from_linear(
        vec![
            1u64.into(),
            2u64.into(),
            3u64.into(),
            4u64.into(),
            5u64.into(),
            6u64.into(),
        ],
        3,
        2,
        IntegerRing::new(),
    )
    .unwrap();
    let n = Matrix::from_nested_vec(
        vec![
            vec![1.into(), 2.into(), 3.into()],
            vec![4.into(), 5.into(), 6.into()],
        ],
        IntegerRing::new(),
    )
    .unwrap();
}

Output

i = {{1,0},{0,1}}
e = {{1,0,0},{0,2,0},{0,0,3}}
l = {{1,2},{3,4},{5,6}}
n = {{1,2,3},{4,5,6}}

Arithmetic

Matrices can be multiplied, added and subtracted using overloaded operators.

from symbolica import *

l = Matrix.from_linear(3, 2, [1, 2, 3, 4, 5, 6])
n = Matrix.from_nested([[1, 2, 3], [4, 5, 6]])
l * n + Matrix.identity(3)
use symbolica::prelude::*;

fn main() {
    let l = Matrix::from_linear(vec![1.into(), 2.into(), 3.into(), 4.into(), 5.into(), 6.into()], 3, 2, Z).unwrap();
    let n = Matrix::from_nested_vec(vec![vec![1.into(), 2.into(), 3.into()], vec![4.into(), 5.into(), 6.into()]], Z).unwrap();
    let c = &(&l * &n) + &Matrix::identity(3, Z);
    println!("{c}");
}

Output

{{10,12,15},{19,27,33},{29,40,52}}

In Rust, the type of argument of the matrix can be selected, e.g. Matrix<Q>. In Python, the default is a matrix over rational polynomials, i.e. Matrix<RationalPolynomial<Integer, u16>> in Rust.

Common operations

For matrices over fields, determinants and inverses can be computed. Inverting a singular matrix raises an error in Python and returns an error in Rust.

from symbolica import *

t = Expression.symbol('t')

a = Matrix.from_nested([[t, 2], [3, 4]])
print('a       =', a)
print('a^-1    =', a.inv())
print('det(a)  =', a.det())

Here we specialize t = 1 and use rational coefficients.

use symbolica::prelude::*;

fn main() {
    let a: Matrix<Q> = Matrix::from_nested_vec(
        vec![vec![1.into(), 2.into()], vec![3.into(), 4.into()]], Q,
    ).unwrap();
    println!("a      = {}", a);
    println!("a^-1   = {}", a.inv().unwrap());
    println!("det(a) = {}", a.det().unwrap());
}

Output

a       = {{t,2},{3,4}}
a^-1    = {{2/(-3+2*t),-1/(-3+2*t)},{-3/(-6+4*t),t/(-6+4*t)}}
det(a)  = -6+4*t

The matrix equation \[\mathbf{A} \cdot \vec{x} = \vec{b}\] can be solved as well:

from symbolica import *
t = Expression.symbol('t')

a = Matrix.from_nested([[t, 2], [3, 4]])
a.solve(Matrix.vec([4, 3]))

Here we specialize t = 1 and use rational coefficients.

use symbolica::prelude::*;

fn main() {
    let a: Matrix<Q> = Matrix::from_nested_vec(
        vec![vec![1.into(), 2.into()], vec![3.into(), 4.into()]], Q,
    ).unwrap();
    let b = Matrix::from_nested_vec(vec![vec![4.into()], vec![3.into()]], Q).unwrap();
    let r = a.solve(&b).unwrap();
    println!("{} . x = {} => x = {}", a, b, r);
}

Output

{{5/(-3+2*t)},{(-12+3*t)/(-6+4*t)}}

Matrices can also be indexed using a (row, column) tuple, which yields the entry.