Mastering Assumptions in Python's SymPy: A Comprehensive Guide
Written on
Chapter 1: Introduction to Assumptions in SymPy
In the realm of computer algebra systems (CAS), you may encounter instances where the system cannot simplify or manipulate an expression that seems straightforward to you. This often occurs due to an implicit assumption regarding the symbols in the expression. The CAS cannot infer these assumptions unless explicitly informed. This article delves into how to utilize assumptions on symbols or their relationships in Python's SymPy library. There are primarily two types of assumptions: "a priori" and "a posteriori." Each type has its advantages and disadvantages, depending on your objectives. Generally, a posteriori assumptions are more versatile, allowing us to express conditions about both individual symbols and their interrelations.
Section 1.1: A Priori Assumptions
Let's examine a basic expression in SymPy. Consider the symbol π₯. The reason we cannot simply use π₯ is that SymPy lacks context regarding what you intendβwhether it represents a real number, a complex one, or could be either. Without this clarity, SymPy cannot perform simplifications, as it might lead to mathematical inaccuracies.
SymPy provides a function called ask to check what assumptions are made about a symbol. To verify if SymPy considers π₯ to be a real number, you would execute the following command:
ask(Q.real(x))
The result is no response, indicating that SymPy does not have any explicit assumptions about π₯. Consequently, it also cannot determine if π₯ is positive:
ask(Q.positive(x))
Again, there's no output since SymPy does not presume that π₯ is a non-negative real number, preventing it from simplifying sqrt(x**2) to x.
However, you can clarify your assumptions when defining symbols. For instance, to assert that π₯ is a real number, you would define it as follows:
x = Symbol('x', real=True)
This is what I refer to as "a priori" assumptions, since the assumptions are declared while defining the symbols prior to their usage.
After this, querying whether π₯ is real would yield a positive response:
ask(Q.real(x))
If we further specify that π₯ is nonnegative:
x = Symbol('x', real=True, positive=True)
We can immediately see the simplification to π₯. When we inquire about its properties, we find that defining π₯ as nonnegative also inherently categorizes it as real. However, it is essential to note that we still lack information on whether it is strictly positive since nonnegative could mean it might equal zero:
ask(Q.positive(x))
Still no response, which is expected.
To explore the available assumption keys, simply type Q. and hit the TAB key in your Jupyter notebook to trigger autocompletion.
Section 1.2: A Posteriori Assumptions
While establishing assumptions during symbol definitions is a useful starting point, it often proves insufficient in numerous scenarios. Frequently, you may need to treat different cases for a given symbol within a calculation, necessitating temporary assumptions. In such cases, a posteriori assumptions are ideal. Define your symbols without assumptions, like so:
n = Symbol('n')
Then, specify the assumption afterward. For instance, if you wish to simplify the expression to π₯, typically handled by the simplify function, you should instead employ the refine function:
refine(sqrt(x**2), Q.positive(x))
In another example, consider an expression that results from some computation:
result = n + 2
Without an assumption regarding π, standard simplification won't be effective. Utilizing refine, however, allows you to differentiate between π being odd or even:
refine(result, Q.even(n))
This assumption is temporary; once the refine function executes, the condition regarding π being odd is no longer applicable:
ask(Q.odd(n))
This yields no response, indicating that the assumption is inactive.
A notable type of assumption involves the relationship between two different symbols. For example, when calculating an integral, SymPy can readily solve:
integrate(sin(m), (m, 0, pi))
The outcome depends on how π relates to π. If π β βπ, the result is zero; otherwise, it equals 2π. To articulate this assumption, we can use Q.ne or Q.eq:
assume(m != -n)
Conversely, assuming π = βπ can be expressed as follows:
assume(m == -n)
Similar relational assumptions can be made using Q.le, Q.lt, Q.ge, and Q.gt for the relations β€, <, β₯, and >, respectively.
Finally, to combine assumptions, simply utilize the logical AND operator. For example, consider this expression:
refine(expr, Q.positive(x) & Q.odd(n))
This concludes the essential concepts regarding assumptions for most common scenarios. Thank you for reading!
P.S. Below is a comprehensive list of all the available assumption keys:
['algebraic', 'antihermitian', 'commutative', 'complex',
'complex_elements', 'composite', 'diagonal', 'eq',
'even', 'extended_negative', 'extended_nonnegative',
'extended_nonpositive', 'extended_nonzero', 'extended_positive',
'extended_real', 'finite', 'fullrank', 'ge', 'gt',
'hermitian', 'imaginary', 'infinite', 'integer',
'integer_elements', 'invertible', 'irrational', 'is_true',
'le', 'lower_triangular', 'lt', 'ne', 'negative',
'negative_infinite', 'nonnegative', 'nonpositive',
'nonzero', 'normal', 'odd', 'orthogonal', 'positive',
'positive_definite', 'positive_infinite', 'prime',
'rational', 'real', 'real_elements', 'singular',
'square', 'symmetric', 'transcendental', 'triangular',
'unit_triangular', 'unitary', 'upper_triangular', 'zero']
Chapter 2: SymPy Video Tutorials
Explore the essentials of SymPy in this beginner-friendly tutorial, focusing on symbolic mathematics with Python.
Delve deeper into symbolic math with this advanced discussion, showcasing practical applications of SymPy in Python.