Ideas for the new assumptions system

8 views
Skip to first unread message

Fabian Seoane

unread,
Jan 23, 2009, 6:38:49 AM1/23/09
to sy...@googlegroups.com

In this email, I'll expose some design ideas for the new assumptions system. It would be great if I get some feedback and we agree on a clear design that we can implement.

---------

The basic idea is to decouple the assumptions from the model, so that new assumptions can be created without having to add new code to the model class and assumptions can be shared across classes. For this, Basic and Assumptions class follow a model/manager pattern somewhat based on Django's model for queries [1].

Concrete assumptions are defined by inheriting from the abstract class Assumptions and overriding the methods eval_Mul, eval_Pow, etc. eval_CLASS will be called when an instance of CLASS is queried about this assumption. This allows to have all code responsible for one assumption under one class and not scattered across many.

The bridge between Basic and Assumptions is made via a manager. Each instance of Basic will have a field assumptions, an instance of AssumptionsManager

class Basic(object):
    assumptions = AssumptionsManager()

which keeps track of the object's assumptions. This is the responsible for adding, removing Assumptions as well as translating a query about the assumptions of an object to the appropriate Assumptions.eval_* method.

                          | Class diagram | (see attached image)

Decoupling assumptions from the object translates in flexibility and extensibility: users can define their own assumptions without having to mess with core classes. Also, assumptions can be made as complex as needed without making core classes grow huge.


Examples
========

Query
--------

>>> from sympy.assumptions import IsPositive
>>> x = Symbol('x')
>>> x.assumptions.add(IsPositive)
>>> x.assumptions.query(IsPositive)

x is an atom, so the manager for x then will look at it's assumptions, check that IsPositive is present and return True. If the expression is not trivial, like in the following case,

>> (x+1).assumptions.query(IsPositive)

the assumption's manager for (1+x) will call IsPositive.eval_Mul, which will look at the args, detect that x has the assumption IsPositive and return True


Extensibility
--------------

You just inherit from Assumptions, and implement as many eval_* methods as you need. Then you can do

>>> from myassumptions import IsNotZero
>>> x.assumptions.add(IsNotZero)
>>> (x/2).assumptions.query(IsNotZero)

which will return True if you properly defined IsNotZero.eval_Mul().


Implicit equations as assumptions
=================================

Sometimes we need to express a relationship in terms of an implicit equation. For this, we add an equation as a special type of assumption

>>> n = Symbol('n', assumptions=IsInteger)
>>> m = Symbol('m', assumptions=IsInteger)
>>> n.assumptions.add(Eq(gcd(m, n) - 1))
>>> gcd(m, n)
1

AssumptionsManager then will recognize this and create the appropriate instance of assumptions.



Shortcuts
=========

Assumptions can also be added to the constructor:
>>> x = Symbol('x', assumptions=[Assumption1, Assumption2, etc])

also we could possibly add support for python's with keyword (>= python2.5), so that the following are
equivalent (this is an idea from fredrik.johansson, see [3] for more info)

>>> x.assumptions.add(IsPositive)
>>> y.assumptions.add(IsPositive)
>>> x*y > 0
True
>>> x.assumptions.remove(IsPositive)
>>> y.assumptions.remove(IsPositive)

>>> with Gt(y, 0), Gt(x, 0):
...     (x*y) > 0
...
True

which is obviously much less verbose


Comparison with Mathematica's assumption system
================================================

What would the examples from mathematica's web page [2] look like ?

M = Mathematica
S = Sympy

M: Simplify[1/Sqrt[x] - Sqrt[1/x], x > 0]
S: x = Symbol('x', assumptions=IsPositive)
   symplify(1/sqrt(x) - sqrt(1/x))

M: FunctionExpand[Log[x y], x > 0 && y > 0]
S: x, y = Symbol('x', assumptions=IsPositive), Symbol('y', assumptions=IsPositive)
  log(x*y).expand()

M: Simplify[Sin[n Pi], n \[Element] Integers]
S: n = Symbol('n', assumptions=IsInteger)
   simplify(sin(n*pi))

M: FunctionExpand[
    EulerPhi[m n], {m, n} \[Element] Integers && GCD[m, n] == 1]
S: n = Symbol('n', assumptions=IsInteger)
   m = Symbol('m', assumptions=IsInteger)
   n.assumptions.add(Eq(gcd(m, n) - 1))
   euler_phi(m, n)


[1]: http://docs.djangoproject.com/en/dev/topics/db/managers/
[2]: http://reference.wolfram.com/mathematica/tutorial/UsingAssumptions.html
[3]: http://code.google.com/p/sympy/issues/detail?id=1047
--
Fabian, http://fseoane.net/blog/

ClassDiagram1.png

Ondrej Certik

unread,
Jan 24, 2009, 2:46:31 PM1/24/09
to sy...@googlegroups.com
Hi Fabian!

On Fri, Jan 23, 2009 at 3:38 AM, Fabian Seoane <fabian...@gmail.com> wrote:
>
> In this email, I'll expose some design ideas for the new assumptions system.
> It would be great if I get some feedback and we agree on a clear design that
> we can implement.

First of all, many thanks for all the work you have done lately, it's
really helpful and also thanks for pushing these assumptions forward.

I have also concentrated on this lately, here is what I think:

The main problem I can see with your model is, that compared to
Mathematica below, it is a more complex, compare the one liner in
mathematica and 2 or more lines in sympy:

> Comparison with Mathematica's assumption system
> ================================================
>
> What would the examples from mathematica's web page [2] look like ?
>
> M = Mathematica
> S = Sympy
>
> M: Simplify[1/Sqrt[x] - Sqrt[1/x], x > 0]
> S: x = Symbol('x', assumptions=IsPositive)
> symplify(1/sqrt(x) - sqrt(1/x))
>
> M: FunctionExpand[Log[x y], x > 0 && y > 0]
> S: x, y = Symbol('x', assumptions=IsPositive), Symbol('y',
> assumptions=IsPositive)
> log(x*y).expand()
>
> M: Simplify[Sin[n Pi], n \[Element] Integers]
> S: n = Symbol('n', assumptions=IsInteger)
> simplify(sin(n*pi))
>
> M: FunctionExpand[
> EulerPhi[m n], {m, n} \[Element] Integers && GCD[m, n] == 1]
> S: n = Symbol('n', assumptions=IsInteger)
> m = Symbol('m', assumptions=IsInteger)
> n.assumptions.add(Eq(gcd(m, n) - 1))
> euler_phi(m, n)


Also I don't like that the assumptions are assigned to the symbols
directly. See also the issue #1047 for some arguments against it.
For example, if that is possible, I'd like to have the core really
simple, so that we can easily hook our Cython core into sympy.
However, thinking about it, your model can in fact be also transformed
into my idea how things could work, let me show it on the examples
above first:

M ... Mathematica
S1 .... SymPy, your approach
S2 .... SymPy, my approach

M: Simplify[1/Sqrt[x] - Sqrt[1/x], x > 0]

S1: x = Symbol('x', assumptions=IsPositive)
simplify(1/sqrt(x) - sqrt(1/x))
S2: simplify(1/sqrt(x) - sqrt(1/x), Assumptions(x>0))

M: FunctionExpand[Log[x y], x > 0 && y > 0]

S1: x, y = Symbol('x', assumptions=IsPositive), Symbol('y',
assumptions=IsPositive)
log(x*y).expand()
S2: log(x*y).expand(Assumptions([x>0, y>0]))

M: Simplify[Sin[n Pi], n \[Element] Integers]

S1: n = Symbol('n', assumptions=IsInteger)
simplify(sin(n*pi))
S2: simplify(sin(n*pi), Assumptions(Element(n, Integer)))

# we can talk about the syntax of Element(n, Integer)

M: FunctionExpand[
EulerPhi[m n], {m, n} \[Element] Integers && GCD[m, n] == 1]

S1: n = Symbol('n', assumptions=IsInteger)


m = Symbol('m', assumptions=IsInteger)
n.assumptions.add(Eq(gcd(m, n) - 1))
euler_phi(m, n)

S2: euler_phi(m, n).expand(Assumptions([Element(n, Integer),
Element(m, Integer), Eq(gcd(m, n) - 1)]))

# again we can talk about the syntax of Element(n, Integer)


I started to implement that as well. Pull from:

http://github.com/certik/sympy/tree/assume

And use like:

In [1]: from sympy import var, Assumptions, sqrt

In [3]: var("x y z")
Out[3]: (x, y, z)

In [5]: e = abs(x)

In [6]: print e
abs(x)

In [7]: print e.refine(Assumptions(x>0))
x

In [8]: print e.refine(Assumptions(x<0))
-x


What do you think?

Ondrej

Fabian Seoane

unread,
Jan 24, 2009, 5:44:43 PM1/24/09
to sy...@googlegroups.com

yeah, I too think now that assumptions should not be assigned to symbols.

taken from test_assumptions.py:

 x = Symbol('x',real=True, integer=True)
assert x.is_real == True

How could this be translated into your assumption system?


x = Symbol('x')
assert IsElement(x, Real, assumptions=Element(x, Real))

 



--
Fabian, http://fseoane.net/blog/

Ondrej Certik

unread,
Jan 24, 2009, 5:56:39 PM1/24/09
to sy...@googlegroups.com
>>
>> Also I don't like that the assumptions are assigned to the symbols
>> directly. See also the issue #1047 for some arguments against it.
>
> yeah, I too think now that assumptions should not be assigned to symbols.
>
> taken from test_assumptions.py:
>
> x = Symbol('x',real=True, integer=True)
> assert x.is_real == True
>
> How could this be translated into your assumption system?
>
> x = Symbol('x')
> assert IsElement(x, Real, assumptions=Element(x, Real))

Good question. I created:

http://wiki.sympy.org/wiki/Assumptions

So far I added your suggested solution to it. How would you write the
following in your system:

e = abs(x)
print e.refine(Assumptions(x>0))
print e.refine(Assumptions(x<0))

(feel free to edit the wiki)

Ondrej

Fabian Seoane

unread,
Jan 24, 2009, 7:19:20 PM1/24/09
to sy...@googlegroups.com
On Sat, Jan 24, 2009 at 11:56 PM, Ondrej Certik <ond...@certik.cz> wrote:

>>
>> Also I don't like that the assumptions are assigned to the symbols
>> directly. See also the issue #1047 for some arguments against it.
>
> yeah, I too think now that assumptions should not be assigned to symbols.
>
> taken from test_assumptions.py:
>
>  x = Symbol('x',real=True, integer=True)
> assert x.is_real == True
>
> How could this be translated into your assumption system?
>
> x = Symbol('x')
> assert IsElement(x, Real, assumptions=Element(x, Real))

Good question. I created:

http://wiki.sympy.org/wiki/Assumptions

Cool. So far I prefer your approach, I just wonder how our tests would look like on this assumption system. I pulled from your repo, and I am willing to help once we have clear the api that we want
 


So far I added your suggested solution to it. How would you write the
following in your system:

e = abs(x)
 print e.refine(Assumptions(x>0))
 print e.refine(Assumptions(x<0))

(feel free to edit the wiki)

Ondrej





--
Fabian, http://fseoane.net/blog/

Ondrej Certik

unread,
Jan 24, 2009, 7:33:51 PM1/24/09
to sy...@googlegroups.com
>> Good question. I created:
>>
>> http://wiki.sympy.org/wiki/Assumptions
>
> Cool. So far I prefer your approach, I just wonder how our tests would look
> like on this assumption system. I pulled from your repo, and I am willing to
> help once we have clear the api that we want

I agree. Could you please fill in the last two cases for S1 and M?

Ondrej

Vinzent Steinberg

unread,
Jan 25, 2009, 6:15:06 AM1/25/09
to sympy
I prefer Ondrej's syntax. What about assuming(x>0) instead of
Assumptions(x>0)?

I think it could be combined with Fabian's approach to allow user-
defined assumptions.

We could try to abuse the generator syntax to allow

e = (sin(x) for x in R)

or similar.

Sometimes it's probably better to store assumptions in symbols (for
instance when x is real all the time).

Vinzent

Ondrej Certik

unread,
Jan 25, 2009, 1:12:27 PM1/25/09
to sy...@googlegroups.com
On Sun, Jan 25, 2009 at 3:15 AM, Vinzent Steinberg
<vinzent....@googlemail.com> wrote:
>
> I prefer Ondrej's syntax. What about assuming(x>0) instead of
> Assumptions(x>0)?

or just assume(x>0), and it will return an Assumptions() instance. Or
maybe assuming() to return an instance, and assume() to set global
assumptions.

>
> I think it could be combined with Fabian's approach to allow user-
> defined assumptions.
>
> We could try to abuse the generator syntax to allow
>
> e = (sin(x) for x in R)
>
> or similar.
>
> Sometimes it's probably better to store assumptions in symbols (for
> instance when x is real all the time).

I think that could be something like a global assumption. But it's
true that I am still not convinced we need global assumptions.

Ondrej

nico

unread,
Jan 26, 2009, 5:34:24 PM1/26/09
to sympy

> I think that could be something like a global assumption. But it's
> true that I am still not convinced we need global assumptions.

I agree with Vinzent about global assumptions.
If x refers to a real in a whole session, I think it would be really
fastidious to use 'assume(x in R)' or something like that in every
calculation.

Ondrej Certik

unread,
Jan 26, 2009, 6:53:50 PM1/26/09
to sy...@googlegroups.com
On Mon, Jan 26, 2009 at 2:34 PM, nico <nicolas....@gmail.com> wrote:
>
>
>> I think that could be something like a global assumption. But it's
>> true that I am still not convinced we need global assumptions.
>
> I agree with Vinzent about global assumptions.

Thanks for joining the discussion.

> If x refers to a real in a whole session, I think it would be really
> fastidious to use 'assume(x in R)' or something like that in every
> calculation.

That's right, but once we start to have anything global, it means that
you can never be sure what happens if you write abs(x**2) anymore,
becuase you simply don't know what global assumptions the user has.

If, on the other hand, we don't have global assumptions, I don't think
you need to use 'assume(x in R)' at every step. All you have to do is
to refine the final answer, e.g. one command.

Technically, I have nothing against global assumptions as long as they
are made explicit, e.g. if you call

e.refine()

it would consult the global assumptions and refine the answer, but if
you type abs(x**2), I think it can be quite dangerous --- but I know
that a lot of other systems do that, so we might do that as well. In
anycase, as a first step, we should implement local assumptions using
refine() and when we get this working correctly, we may think if we
want to do this globally and automagically.


Ondrej

Fabian Seoane

unread,
Jan 27, 2009, 6:23:50 AM1/27/09
to sy...@googlegroups.com
On Tue, Jan 27, 2009 at 12:53 AM, Ondrej Certik <ond...@certik.cz> wrote:

On Mon, Jan 26, 2009 at 2:34 PM, nico <nicolas....@gmail.com> wrote:
>
>
>> I think that could be something like a global assumption. But it's
>> true that I am still not convinced we need global assumptions.
>
> I agree with Vinzent about global assumptions.

Thanks for joining the discussion.

> If x refers to a real in a whole session, I think it would be really
> fastidious to use 'assume(x in R)' or something like that in every
> calculation.

That's right, but once we start to have anything global, it means that
you can never be sure what happens if you write abs(x**2) anymore,
becuase you simply don't know what global assumptions the user has.

If, on the other hand, we don't have global assumptions, I don't think
you need to use 'assume(x in R)' at every step. All you have to do is
to refine the final answer, e.g. one command. 


Technically, I have nothing against global assumptions as long as they
are made explicit, e.g. if you call

I think fredrik's idea to use the with statement offers a nice alternative to global assumptions:

with Assume(x>0):
    abs(x).refine() #although i preffer refine(abs(x))
    something
    bla bla bla


Actually there is no such thing as global assumptions, but this prevents you from typing Assume(x>0) on every step

 


e.refine()

it would consult the global assumptions and refine the answer, but if
you type abs(x**2), I think it can be quite dangerous --- but I know
that a lot of other systems do that, so we might do that as well. In
anycase, as a first step, we should implement local assumptions using
refine() and when we get this working correctly, we may think if we
want to do this globally and automagically.


Ondrej





--
Fabian, http://fseoane.net/blog/

Vinzent Steinberg

unread,
Jan 27, 2009, 1:37:30 PM1/27/09
to sympy
I'm very fine with that (it might introduce unecessary nesting
though). With global assumptions I meant the current behaviour to
store the assumptions in the symbol/expression.

> If, on the other hand, we don't have global assumptions, I don't think
> you need to use 'assume(x in R)' at every step. All you have to do is
> to refine the final answer, e.g. one command.

Mathematically the first step is to define the symbols (n in N etc.).
Moving this to the last step seems counter-intuitive to me.

Vinzent

On 27 Jan., 12:23, Fabian Seoane <fabian.seo...@gmail.com> wrote:
> On Tue, Jan 27, 2009 at 12:53 AM, Ondrej Certik <ond...@certik.cz> wrote:
>

Ondrej Certik

unread,
Jan 27, 2009, 2:24:56 PM1/27/09
to sy...@googlegroups.com
>> If, on the other hand, we don't have global assumptions, I don't think
>> you need to use 'assume(x in R)' at every step. All you have to do is
>> to refine the final answer, e.g. one command.
>
> Mathematically the first step is to define the symbols (n in N etc.).
> Moving this to the last step seems counter-intuitive to me.

That's interesting, because at least for me it is actually very
intuitive to just write the equation and only then worry about
domains/assumptions, if that is needed.

Anyway, I think we can have both ways.

Ondrej

nico

unread,
Jan 27, 2009, 5:51:12 PM1/27/09
to sympy
Thanks for your welcome. :)

Again, I agree with Vinzent.

Sometimes, it's ok to work in, say, C, and then, finally, select only
real solutions.
It's typically the case when you search for roots of polynomials.

But there're other times you simply can't do that. For example,
solving inequalities in C, like 2x+7<3x+5, and then restricting to R
has no meaning at all.

In any case, I find also more intuitive to start with assumptions,
before solving (probably because I always teach to my pupils to use
quantifiers to indicate where you work, before starting to
calculate...).

Brian Granger

unread,
Jan 27, 2009, 7:02:45 PM1/27/09
to sy...@googlegroups.com
>> Mathematically the first step is to define the symbols (n in N etc.).
>> Moving this to the last step seems counter-intuitive to me.
>
> That's interesting, because at least for me it is actually very
> intuitive to just write the equation and only then worry about
> domains/assumptions, if that is needed.

But you are a physicist ;-) (so am I)

In some ways I agree, but the typical mathematics textbook does tend
to start like this...

Let x, y and z be elements of ..... and assume that x>0. Then if, ....

I haven't been following the discussion to closely, but here is my gut feelings.

1. Avoid anything global if at all possible. What if I want to use
my sympy code with yours, but we need different global assumptions.
Ahhhhh!

2. The with statement actually gives a very nice way of expressing an
assumption that should be applied to a set of expressions. Nice idea!
+1

Cheers,

Brian

nico

unread,
Jan 28, 2009, 5:18:22 AM1/28/09
to sympy
Brian Granger :
> 2. The with statement actually gives a very nice way of expressing an
> assumption that should be applied to a set of expressions.  Nice idea!
> +1

It's really nice in a script or a program indeed (if python version is
2.5 or above).

However, in a an interactive session, it is not always so nice, since
you typically write a calculus, then read the output, and depending on
the output, decide to write an other calculus.
Using a "with" bloc in this case is not really pleasant.



Ondrej Certik:
>> Also I don't like that the assumptions are assigned to the symbols
>> directly. See also the issue #1047 for some arguments against it.
Sorry, but I did'nt see any argument against it in issue #1047. Did
you mean another issue ?


Reply all
Reply to author
Forward
0 new messages