SymPy has two assumptions systems called (unimaginatively) “old assumptions” and “new assumptions.” They differ in how they manage mathematical attributes.

Old Assumptions

In old assumptions attributes are bound to variables

>>> x = Symbol('x', positive=True)
>>> y = Symbol('y', positive=True)

These are then composed into expressions.

>>> expr = 2*x + y

And we query these expressions directly

>>> expr.is_positive
True

The expression and the attributes are woven into the same object.

New Assumptions

In new assumptions variables and attributes are maintained separately.

>>> x = Symbol('x')
>>> y = Symbol('y')

>>> facts = Q.positive(x) & Q.positive(y)

The construction of mathematical expressions remains unchanged

>>> expr = 2*x + y

But querying now requires two inputs, both the expression and the facts.

>>> ask(Q.positive(expr), facts)
True

The separation of facts from expressions enables rich logical inference but it requires the management of two separate variables, expr and facts, rather than just one, expr. It is difficult to consistently pass the extra variable through all relevant function calls.

Global assumptions

One solution to the management problem is to keep all facts in a globally accessible collection. This removes the need to pass an extra argument between function calls.

This little known feature is already accessible in SymPy

>>> # Setup
>>> global_assumptions.add(Q.positive(x))
>>> global_assumptions.add(Q.positive(y))

>>> # Compute in this context
>>> ask(Q.positive(2*x + y))
True

Unfortunately global variables often cause confusion. We will invariably add an experimental fact to the global collection and then forget to clean up, polluting future computations. In this case we need to always remember to clean up after we’re done.

>>> # Cleanup
>>> global_assumptions.remove(Q.positive(x))
>>> global_assumptions.remove(Q.positive(y))

This cleanup step is both crucial and forgettable. We can not trust ourselves to remember it.

Introducing assuming

Context managers provide the convenience of global variables with side-effect free security. This is accomplished through consistent cleanup.

SymPy now includes, assuming, a context manager for mathematical assumptions. Here is an example

>>> facts = Q.positive(x), Q.positive(y)

>>> with assuming(*facts):
...     ask(Q.positive(2*x + y))
True

All ask calls within this block have global-like access to the knowledge Q.positive(x) and Q.positive(y). These calls may be at top level as in the example above or buried deeply within function calls. This arrangement is convenient because we do not need to pass down facts through all function calls. This knowledge is pervasive like a global variable but contained within the with assuming clause.


blog comments powered by Disqus