Working With Assumptions in Python’s sympy
When working with any computer algebra system (CAS), you often encounter situations when the CAS cannot simplify or manipulate an expression although the simplification seems all too obvious to you. The reason is usually, that you have an implicit assumption in mind about some of the symbols in the expression. And the CAS cannot know about this assumption unless you tell it about this assumption. This article is about how to use assumptions on symbols or relations between symbols in Python’s computer algebra package sympy . There are basically two kinds of assumptions, what I call “a priori” and “a posteriori” assumptions. Both have their pros and cons, depending on what you are about to do. But see for yourself. The a posteriori are, in general, more powerful, though. They not only allow us to state assumptions about a given symbol but also about the relation between symbols! We will discuss both but let’s start with the simpler ones.
A Priori Assumptions
Consider the following expression in sympy :
Why not just 𝑥? Because sympy doesn’t know what you have in mind when you say 𝑥. Is it a real number or complex? Is it positive or could it be negative? Unless sympy knows more about 𝑥, it cannot do a simplification, because it could be mathematically wrong!
There is a function in
sympy
which allows to ask what assumptions are made on a given symbol. This function is aptly called
ask
. As argument, you pass an assumption key. Assumption keys all start with
Q.
, followed by the type of assumption. To ask whether
sympy
assumes 𝑥 to be a real number, you would write
It gives no answer. So sympy makes no explicit assumption whether a symbol is a real number or something else. It kind of makes sense… Consequently, it doesn’t make an assumption on whether it is positive or not:
No answer again. So since
sympy
doesn’t assume 𝑥x to be a nonnegative real number, it cannot make the simplification from
sqrt(x**2)
to
x
.
But we can explicitly state our knowledge or assumptions when defining symbols. For instance, to state that 𝑥 is a real number, define it like that:
This is what I call “a priori” assumptions because we state the assumptions when defining the symbols and before using them.
Then when we ask whether it is real,
It tells us so. Since we haven’t made an assumption that 𝑥x is nonnegative, the query
still gives no answer. But if we define 𝑥 as
We immediately get the simplification to 𝑥, and when asking about its properties,
we see that defining 𝑥 as nonnegative, also implicitly defined it as real. Note that we still don’t know if it is strictly positive since nonnegative implies that it could be 0 as well:
No answer, as it should be.
There are a lot more assumption keys, and you can get a list of them by simply typing
Q.
followed by the TAB key in your Jupyter notebook. This will trigger the autocompletion and give you a list of available keys. For completeness, at the end of this article, you find a list of all the keys that are available.
A Posteriori Assumptions
Stating assumptions when defining symbols is a good starter, but in many situations, it is not enough. Often, in the course of a calculation, you want to treat different cases for a given symbol. So you want to make an assumption only temporarily. How can you do that? Well, use a posteriori assumptions instead. Define your symbols without assumptions, like
and state the assumption afterwards. For instance, in the expression above, we would like to make
sympy
simplify the expression to 𝑥x. Simplification is normally done with the
simplify
function. But
simplify
doesn't work with assumptions. Instead, use the
refine
function! In the example above, we refine the function using the assumption that 𝑥 is nonnegative:
As another example, consider the expression
which may be the result of some computation. Normal simplification doesn’t work, since we have not made an assumption about what 𝑛 is:
Using refine, instead, we can make a distinction between 𝑛n being odd or even:
This assumption is just temporary. After the
refine
function has been completed, the assumption of 𝑛 being odd, is no longer valid:
No answer is given, so no assumption if 𝑛 is odd is active.
A special kind of assumption, which arises quite often, is to make an assumption not on a single symbol, but on the relation between two different symbols. As an example, consider the integral
sympy can easily solve that integral:
So we have a distinction here depending on how 𝑚 relates to 𝑛. If 𝑚≠-𝑛, the result is zero, otherwise it is 2𝜋. How can we describe this assumption? Use
Q.ne
or
Q.eq
!
Assuming 𝑚≠−𝑛 we can write
On the other hand, assuming 𝑚=−𝑛, we can write
Similar assumptions on the relation between two symbols are
Q.le
,
Q.lt
,
Q.ge
,
Q.gt
for the relations ≤,<,≥, and >, respectively.
Finally, here is how you can
combine
assumptions. Just use the logical
&
operator. For example, consider the expression
That concludes most of what one needs about assumptions in 95% of all cases. Thanks for reading!
P.S. As promised, here is a list of all the 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']