怕老婆的鸡蛋 · 常用字体包(百度网盘链接在文末)_字体打包下 ...· 3 月前 · |
聪明的风衣 · Solved: help "An ...· 3 月前 · |
直爽的哑铃 · 数据加密 :: ShardingSphere· 7 月前 · |
体贴的太阳 · 如何解读《咒怨》这部电影?这部电影单纯是为了 ...· 1 年前 · |
安静的饭盒 · matlab:高斯函数对图像的空域滤波和频域 ...· 1 年前 · |
This page outlines some of the best practices for users of SymPy. The best practices here will help avoid some common bugs and pitfalls that can occur when using SymPy.
This page primarily focuses on best practices that apply generally to all parts of SymPy. Best practices that are specific to certain SymPy submodules or functions are outlined in the documentation for those specific functions.
Define symbols with
symbols()
or
Symbol()
.
The
symbols()
function is the most convenient way to create symbols. It
supports creating one or more symbols at once:
>>> from sympy import symbols
>>> x = symbols('x')
>>> a, b, c = symbols('a b c')
Additionally, it supports adding assumptions to symbols
>>> i, j, k = symbols('i j k', integer=True)
and defining Function
objects:
>>> from sympy import Function
>>> f, g, h = symbols('f g h', cls=Function)
It also supports shorthands for defining many numbered symbols at once:
>>> symbols('x:10')
(x0, x1, x2, x3, x4, x5, x6, x7, x8, x9)
The Symbol()
constructor may also be used directly. Unlike symbols()
,
Symbol()
always creates one symbol. It is the best option if you want to
make a symbol with unusual characters in its name or if you are creating
symbols programmatically.
>>> from sympy import Symbol
>>> x_y = Symbol('x y') # This creates a single symbol named 'x y'
The var()
function should be avoided, except when working
interactively. It works like the symbols()
function, except it
automatically injects symbol names into the calling namespace. This function
is designed solely for interactive typing convenience and is not recommended
for programmatic use.
Do not use sympify()
or S()
to create symbols. This may appear to work:
>>> from sympy import S
>>> x = S("x") # DO NOT DO THIS
However, S()
/sympify()
are not designed to create symbols. They are
designed to parse entire expressions. This method fails if the input string
is not valid Python. It also fails if the string parses to a larger
expression:
>>> # These both fail
>>> x = S("0x")
Traceback (most recent call last):
SyntaxError: invalid syntax (<string>, line 1)
>>> x = S("x+")
Traceback (most recent call last):
SyntaxError: invalid syntax (<string>, line 1)
Any Python string can be used as a valid Symbol name.
Furthermore, all the same issues described in the
Avoid String Inputs section below apply here.
Add assumptions to symbols when they are known.
Assumptions can be added by passing the relevant
keywords to symbols()
. The most common assumptions are real=True
,
positive=True
(or nonnegative=True
), and integer=True
.
Assumptions are never required, but it is always recommended to include them
if they are known because it will allow certain operations to simplify. If
no assumptions are provided, symbols are assumed to be general complex
numbers, and simplifications will not be made unless they are true for all
complex numbers.
For example:
>>> from sympy import integrate, exp, oo
>>> a = symbols('a') # no assumptions
>>> integrate(exp(-a*x), (x, 0, oo))
Piecewise((1/a, Abs(arg(a)) < pi/2), (Integral(exp(-a*x), (x, 0, oo)), True))
>>> a = symbols('a', positive=True)
>>> integrate(exp(-a*x), (x, 0, oo))
Here, \(\int_0^\infty e^{-ax}\,dx\) gives a piecewise result when a
is defined with no assumptions, because the integral only converges when a
is positive. Setting a
to be positive removes this piecewise.
When you do use assumptions, the best practice is to always use the same
assumptions for each symbol name. SymPy allows the same symbol name to be
defined with different assumptions, but these symbols will be considered
unequal to each other:
>>> z1 = symbols('z')
>>> z2 = symbols('z', positive=True)
>>> z1 == z2
False
>>> z1 + z2
z + z
See also Avoid String Inputs and
Don’t Hardcode Symbol Names in Python Functions for related best practices
around defining symbols.
Avoid String Inputs¶
Don’t use strings as input to functions. Rather, create the objects
symbolically using Symbols and the appropriate SymPy functions, and manipulate
them.
Don’t
>>> from sympy import expand
>>> expand("(x**2 + x)/x")
x + 1
>>> from sympy import symbols
>>> x = symbols('x')
>>> expand((x**2 + x)/x)
x + 1
It’s always best to create expressions explicitly using Python operators, but
sometimes you really do start with a string input, like if you accept an
expression from the user. If you do have a string that you are starting with,
you should parse it explicitly with
parse_expr()
. It is best to parse
all strings early and only use symbolic manipulation from there on.
>>> from sympy import parse_expr
>>> string_input = "(x**2 + x)/x"
>>> expr = parse_expr(string_input)
>>> expand(expr)
x + 1
Reason
There are many disadvantages to using strings as input to SymPy functions:
It is unpythonic and makes code harder to read. See the Zen of
Python “explicit is better than implicit”.
Support for string inputs in general SymPy functions is mostly accidental.
It happens because these functions call sympify()
on their inputs in
order to convert things like Python int
s into SymPy Integer
s. However,
sympify()
also parses strings into SymPy expressions, unless the
strict=True
flag is used. Automatic parsing of strings for general SymPy
functions (other than sympify()
or parse_expr()
) may go away in
a future version of SymPy.
Typos in symbol or function names can go unnoticed. This is because all
undefined names in the string will be automatically parsed into Symbols or
Functions. If the input has a typo, the string will still parse correctly,
but the output will not be what was expected. For example
>>> from sympy import expand_trig
>>> expand_trig("sine(x + y)")
sine(x + y)
Compare this to the explicit error you get when not using strings:
>>> from sympy import sin, symbols
>>> x, y = symbols('x y')
>>> expand_trig(sine(x + y)) # The typo is caught by a NameError
Traceback (most recent call last):
NameError: name 'sine' is not defined
>>> expand_trig(sin(x + y))
sin(x)*cos(y) + sin(y)*cos(x)
In the first example, sine
, a typo for sin
, is parsed into
Function("sine")
, and it appears that expand_trig
cannot handle it. In the
second case, we immediately get an error from the undefined name sine
, and
fixing our typo, we see that expand_trig
can indeed do what we want.
The biggest gotcha when using string inputs comes from using assumptions. In
SymPy, if two symbols have the same name but different assumptions, they are
considered unequal:
>>> z1 = symbols('z')
>>> z2 = symbols('z', positive=True)
>>> z1 == z2
False
>>> z1 + z2
z + z
It is generally recommended to avoid doing this, as it can lead to confusing
expressions like the one above (see Defining Symbols
above).
However, string inputs will always create symbols without assumptions. So
if you have a symbol with an assumption and later try to use the string
version of it, you will end up with confusing results.
>>> from sympy import diff
>>> z = symbols('z', positive=True)
>>> diff('z**2', z)
The answer here is apparently wrong, but what happened is that the z
in
"z**2"
parsed to Symbol('z')
with no assumptions, which SymPy considers
to be a different symbol from z = Symbol('z', positive=True)
, which is
used as the second argument to diff()
. So as far as diff
is concerned,
the expression is constant and the result is 0.
This sort of thing is particularly bad because it generally doesn’t lead to
any errors. It will just silently give the “wrong” answer because SymPy will
be treating symbols that you thought were the same as different. The
situation is avoided by not using string inputs.
If you are parsing strings, and you want some of the symbols in it to have
certain assumptions, you should create those symbols and pass them to the
dictionary to parse_expr()
. For example:
Don’t
>>> a, b, c = symbols('a b c', real=True)
>>> # a, b, and c in expr are different symbols without assumptions
>>> expr = parse_expr('a**2 + b - c')
>>> expr.subs({a: 1, b: 1, c: 1}) # The substitution (apparently) doesn't work
a**2 + b - c
>>> # a, b, and c are the same as the a, b, c with real=True defined above
>>> expr = parse_expr('a**2 + b - c', {'a': a, 'b': b, 'c': c})
>>> expr.subs({a: 1, b: 1, c: 1})
Many SymPy operations are defined as methods, not functions, that is, they
are called like sympy_obj.method_name()
. These methods won’t work on
strings, since they are not yet SymPy objects. For example:
>>> "x + 1".subs("x", "y")
Traceback (most recent call last):
AttributeError: 'str' object has no attribute 'subs'
Contrasted with:
>>> x, y = symbols('x y')
>>> (x + 1).subs(x, y)
y + 1
Symbol names can contain any character, including things that aren’t
valid Python. But if you use strings as input, it is impossible to use such
symbols. For example
>>> from sympy import solve
>>> solve('x_{2} - 1')
ValueError: Error from parse_expr with transformed code: "Symbol ('x_' ){Integer (2 )}-Integer (1 )"
SyntaxError: invalid syntax (<string>, line 1)
This doesn’t work because x_{2}
is not valid Python. But it is perfectly
possible to use this as a Symbol name:
>>> x2 = symbols('x_{2}')
>>> solve(x2 - 1, x2)
Actually, the above is the best case scenario, where you get an error. It is
also possible you might get something unexpected:
>>> solve('x^1_2 - 1')
[-1, 1, -I, I, -1/2 - sqrt(3)*I/2, -1/2 + sqrt(3)*I/2, 1/2 - sqrt(3)*I/2, 1/2 + sqrt(3)*I/2, -sqrt(3)/2 - I/2, -sqrt(3)/2 + I/2, sqrt(3)/2 - I/2, sqrt(3)/2 + I/2]
What happened here is that instead of parsing x^1_2
as \(x^1_2\), it is
parsed as x**12
(^
is converted to **
and _
is ignored in
numeric literals in Python).
If we instead create a Symbol, the actual contents of the symbol name are
ignored. It is always represented as a single symbol.
>>> x12 = symbols('x^1_2')
>>> solve(x12 - 1, x12)
If you use strings, syntax errors won’t be caught until the line is run. If
you build up the expressions, syntax errors will be caught immediately by
before any of it runs.
Syntax highlighting in code editors doesn’t typically recognize and
color-code the content of strings, whereas it can recognize Python
expressions.
Avoid Manipulating Expressions as Strings¶
If you find yourself doing a lot of string or regular expression manipulations
on symbolic expressions, this is generally a sign that you are using SymPy
incorrectly. It’s better to build up expressions directly with operators like
+
, -
, *
, and /
and SymPy’s various functions and methods. String-based
manipulations can introduce errors, grow complex quickly, and lose the
benefits of symbolic expression structures.
The reason for this is that there is no notion of a symbolic expression in a
string. To Python, "(x + y)/z"
is no different from "/x+)(y z "
, which is
the same string with the characters in another order. To contrast, a SymPy
expression actually knows about what type of mathematical object it
represents. SymPy has many methods and functions for building and manipulating
expressions, and they all operate on SymPy objects, not strings.
For example
Don’t
>>> expression_str = '+'.join([f'{i}*x_{i}' for i in range(10)])
>>> expr = parse_expr(expression_str)
x_1 + 2*x_2 + 3*x_3 + 4*x_4 + 5*x_5 + 6*x_6 + 7*x_7 + 8*x_8 + 9*x_9
>>> from sympy import Add, Symbol
>>> expr = Add(*[i*Symbol(f'x_{i}') for i in range(10)])
x_1 + 2*x_2 + 3*x_3 + 4*x_4 + 5*x_5 + 6*x_6 + 7*x_7 + 8*x_8 + 9*x_9
See also the previous section on avoiding string inputs to
functions.
Exact Rational Numbers vs. Floats¶
If a number is known to be exactly equal to some quantity, avoid defining it
as a floating-point number.
For example,
Don’t
>>> expression = x**2 + 0.5*x + 1
>>> from sympy import Rational
>>> expression = x**2 + Rational(1, 2)*x + 1
>>> expression = x**2 + x/2 + 1 # Equivalently
However, this isn’t to say that you should never use floating-point numbers in
SymPy, only that if a more exact value is known it should be preferred. SymPy
does support arbitrary precision floating-point
numbers, but some operations may not perform as
well with them.
This also applies to non-rational numbers which can be represented exactly. For
example, one should avoid using math.pi
and prefer sympy.pi
, since the
former is a numerical approximation to \(\pi\) and the latter is exactly \(\pi\)
(see also Separate Symbolic and Numeric Code below; in
general, one should avoid importing math
when using SymPy).
Don’t
>>> import math
>>> import sympy
>>> math.pi
3.141592653589793
>>> sympy.sin(math.pi)
1.22464679914735e-16
>>> sympy.pi
>>> sympy.sin(sympy.pi)
Here sympy.sin(math.pi)
is not exactly 0, because math.pi
is not exactly \(\pi\).
One should also take care to avoid writing integer/integer
where both
integers are explicit integers. This is because Python will evaluate this to a
floating-point value before SymPy is able to parse it.
Don’t
>>> x + 2/7 # The exact value of 2/7 is lost
x + 0.2857142857142857
In this case, use Rational
to create a rational number, or use
S()
shorthand if you want to save on typing.
>>> from sympy import Rational, S
>>> x + Rational(2, 7)
x + 2/7
>>> x + S(2)/7 # Equivalently
x + 2/7
Reason
Exact values, if they are known, should be preferred over floats for the
following reasons:
An exact symbolic value can often be symbolically simplified or manipulated.
A float represents an approximation to an exact real number, and therefore
cannot be simplified exactly. For example, in the above example,
sin(math.pi)
does not produce 0
because math.pi
is not exactly \(\pi\).
It is just a floating-point number that approximates \(\pi\) to 15 digits
(effectively, a close rational approximation to \(\pi\), but not exactly
\(\pi\)).
Some algorithms will not be able to compute a result if there are
floating-point values, but can if the values are rational numbers. This is
because rational numbers have properties that make it easier for these
algorithms to work with them. For instance, with floats, one can have a
situation where a number should be 0, but due to approximation errors, does
not equal exactly 0.
A particularly notable example of this is with floating-point exponents. For
example,
>>> from sympy import factor
>>> factor(x**2.0 - 1)
x**2.0 - 1
>>> factor(x**2 - 1)
(x - 1)*(x + 1)
SymPy Floats have the same loss of significance cancellation issues that can
occur from using finite precision floating-point approximations:
>>> from sympy import expand
>>> expand((x + 1.0)*(x - 1e-16)) # the coefficient of x should be slightly less than 1
x**2 + 1.0*x - 1.0e-16
>>> expand((x + 1)*(x - Rational('1e-16'))) # Using rational numbers gives the coefficient of x exactly
x**2 + 9999999999999999*x/10000000000000000 - 1/10000000000000000
It is possible to avoid these issues in SymPy in many cases by making
careful use of evalf
with its ability to evaluate in arbitrary precision.
This typically involves either computing an expression with symbolic values
and substituting them later with expr.evalf(subs=...)
, or by starting with
Float
values with a precision higher than the default of 15 digits:
>>> from sympy import Float
>>> expand((x + 1.0)*(x - Float('1e-16', 20)))
x**2 + 0.9999999999999999*x - 1.0e-16
A Float
number can be converted to its exact rational equivalent by passing
it to Rational
. Alternatively, you can use nsimplify
to find the nicest
rational approximation. This can sometimes reproduce the number that was
intended if the number is supposed to be rational (although again, it’s best
to just start with rational numbers in the first place, if you can):
>>> from sympy import nsimplify
>>> Rational(0.7)
3152519739159347/4503599627370496
>>> nsimplify(0.7)
Avoid simplify()
¶
simplify()
(not to be confused with sympify()
) is designed as
a general purpose heuristic. It tries various simplification algorithms on the
input expression and returns the result that seems the “simplest” based on
some metric.
simplify()
is perfectly fine for interactive use, where you just want SymPy
to do whatever it can to an expression. However, in programmatic usage, it’s
better to avoid simplify()
and use more targeted simplification
functions instead (e.g., cancel()
, expand()
,
or collect()
).
There are a few reasons why this is generally preferred:
Due to its heuristical nature, simplify()
can potentially be slow, since
it tries a lot of different approaches to try to find the best
simplification.
There are no guarantees about what form an expression will have after being
passed through simplify()
. It may actually end up “less simple” by
whatever metric you were hoping for. To contrast, targeted simplification
functions are very specific about what behaviors they have and what they
guarantee about the output. For example,
factor()
will always factor a polynomial into irreducible factors.
cancel()
will always convert a rational function into the form
\(p/q\) where \(p\) and \(q\) are expanded polynomials with no common factors.
The documentation for each function describes exactly what behavior it will
have on the input expression.
A targeted simplification will not do something unexpected if the expression
contains an unexpected form, or an unexpected subexpression. This is
especially the case if simplification functions are applied with deep=False
to only apply the simplification to the top-level expression.
Some other simplification functions are heuristical in nature, and care should
be taken with them as well. For example, the trigsimp()
function is a
heuristic targeted to trigonometric functions, but the routines in the
sympy.simplify.fu
submodule allow applying specific trigonometric
identities.
The simplify section of the tutorial and the
simplify module reference list the various
targeted simplification functions.
In some cases, you may know exactly what simplification operations you wish to
apply to an expression, but there may not be an exact set of simplification
functions that do them. When this happens, you can create your own targeted
simplification using replace()
, or in general, manually using
advanced expression manipulation.
Don’t Hardcode Symbol Names in Python Functions¶
Instead of hard-coding Symbol
names inside of a function
definition, make the symbols a parameter to the function.
For example, consider a function theta_operator
that computes the theta
operator \(\theta =
zD_z\):
Don’t