Printing the output rounded to 3 decimals in SymPy

Question:

I got a SymPy matrix M

In [1]: from sympy import *
In [2]: M = 1/10**6 * Matrix(([1, 10, 100], [1000, 10000, 100000]))
In [3]: M
Out[3]: 
Matrix([
[1.0e-6, 1.0e-5, 0.0001],
[ 0.001,   0.01,    0.1]])

I want to print the output rounded to 3 decimals, as follows:

In [3]: M
Out[3]: 
Matrix([
[ 0.000, 0.000, 0.000],
[ 0.001, 0.010, 0.100]])

In normal Python, I would go like this:

In [5]: '{:.3f}'.format(1/10**6)
Out[5]: '0.000'

But how to do in a SymPy matrix?

Moreover, a more general case would be an expression containing also Symbols

x = symbols('x')
M = 1/10**6 * Matrix(([1, 10*x, 100], [1000, 10000*x, 100000]))

The preferred output is

In [3]: M
Out[3]: 
Matrix([
[ 0.000, 0.000*x, 0.000],
[ 0.001, 0.010*x, 0.100]])
Asked By: Markus

||

Answers:

To round every number in an expression, use the following function

def round_expr(expr, num_digits):
    return expr.xreplace({n : round(n, num_digits) for n in expr.atoms(Number)})

It can be applied to any SymPy expression, including matrices.

x = symbols('x')
M = 1/10**6 * Matrix(([1, 10*x, 100], [1000, 10000*x, 100000]))
round_expr(M, 3)

yields

Matrix([
[  0.0,      0, 0.0],
[0.001, 0.01*x, 0.1]])

For numeric matrices, the following is simpler and preserves trailing zeros:

>>> M.applyfunc(lambda x: Symbol('{:.3f}'.format(x)))
Matrix([
[0.000, 0.000, 0.000],
[0.001, 0.010, 0.100]])

Here, applyfunc applies the given function to each entry of M. A natural thing to try would be lambda x: '{:.3f}'.format(x) but SymPy matrices aren’t really meant to hold strings: the strings get parsed back into numbers and the trailing zeros get dropped, resulting in

Matrix([
[  0.0,  0.0, 0.0],
[0.001, 0.01, 0.1]])

So I wrap each string in Symbol, thus making a new symbol out of it. Symbols can appear in matrices, and they printed as their names, in this case the names are “0.000” and so on.

Answered By: user6655984

A little bit old, but I used some time to find this today and needed some adjustment. My solution to the exact same question is as a previous answer (user6655984) but with evalf():

def printM(expr, num_digits):
    return expr.xreplace({n.evalf() : round(n, num_digits) for n in expr.atoms(Number)})

Then e.g. sqrt(13) is still sqrt(13) and not a numeric value. (Otherwise it is converted to a numeric value after the round and thus having more digits than num_digits. At least in Sympy 1.3 at Python 3.7.1)

Answered By: Space47

I needed to improve these 2 solutions (Space47 already improved the accepted solution) because my numbers were very big, and Sympy just send “123456789.123 x”

def printM(expr, num_digits):
     return expr.xreplace({n.evalf() : n if type(n)==int else Float(n, num_digits) for n in expr.atoms(Number)}) 

so “123456789.123 x” will be displayed 1.23 * 10^8
and the function does NOT change representation of integers (previously, “1” was replaced by “1.0”)

Answered By: 2diabolos.com

I had some code with many instructions of the form print('some string', some_sympy_expression).
So I used the function by 2diabolos.com (named as round_expr), defined function below, and replaced every print with print_my.

def print_my(*args, **kwargs):
    end_my = kwargs['end'] if 'end' in kwargs else 'n'
    sep_my = kwargs['sep'] if 'sep' in kwargs else ' '
    for arg in args:
        if (type(arg)==str):
            print(arg, end=sep_my)
        else:
            print(round_expr(arg, 3), end=sep_my)
    print(end=end_my)
    return

I didn’t take the time to check if there are other use cases that require some modification for this to work, or even others which cannot be made to work with this approach.

Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.