Proposition for the new relation system using predicate

157 views
Skip to first unread message

JSS95

unread,
Mar 3, 2021, 1:58:19 PM3/3/21
to sympy
SymPy had implemented the binary relation as `Relational` class in core/relation module. In this document, I propose a new system which implements general binary relation as `Predicate` which is defined in assumptions module.

Since predicates are registered on `Q` such as `Q.positive`, I will call the relational predicate as `Q.eq`, `Q.gt`, etc in this document. Please note that registering the predicate on `Q` is not mandatory, and these predicates can have different name when SymPy 1.8 is released. After a few releases, relation predicates will completely replace relational classes so that `Eq`, `Gt`, etc return `Q.eq`, `Q.gt`, etc.

1. Why do we need relation predicates?

There are two design shifts that need to be done across the SymPy codebase. First, we need to avoid automatic evaluation, which means `Add(x, x)` should return `Add` instance rather than `2*x`. This is because evaluating the non-evaluated expression can always be easily done, but doing it vice versa is very difficult. Matrices module adopts this idea. With matrix `A`, `A + A` is evaluated to `2*A` but `Add(A, A)` is not evaluated - and we need to make every object behave like this. Second, functions should be object. This allows the function itself to be dealt as a separate entity.

`Predicate` in assumption module is a good example for these designs. Predicate like `Q.positive` is an object, and `Q.positive(x)` returns `AppliedPredicate` instance. Unlike `Eq(x, x)` which automatically evaluates to `true`, `Q.eq(x, x)` is not evaluated unless passed to `ask()` or `refine()` functions. `Q.positive` itself can be an argument of other SymPy class to form a tree structure. Most importantly, `Predicate` supports fact management system and sat solver. With relation predicate, we can make `ask(Q.extended_real(x), Q.gt(x, y))` return `True` by adding this rule to known facts. Relational classes cannot support this properly. In fact, relational classes cannot even be proposed nor assumed without being wrapped by `Q.is_true` predicate like `Q.is_true(Eq(x, y))`. Using relation predicate instead of relational class gets rid of this nuisance.

2. Evaluation of relation predicates

Like any other applied predicate, `Q.eq(x, y)` is never evaluated by construction or simplification. In other words, `Q.eq(x, x)`, `Q.eq(x, x).doit()` or `Q.eq(x, x).simplify()` does not return `true`. Instead, `ask(Q.eq(x, x))` returns `True` and `refine(Q.eq(x, x))` returns `S.true`.

After relation predicates replace relational classes (so that `x > 1` returns `Q.gt(x, 1)`), using infix relational operator will return evaluated result. This is to follow the evaluation logic of matrix module where `A + A` returns `2*A`.

3. Symbolic manipulation of relation predicates

Since `Q.eq(x, y)` is never automatically evaluated, we can safely introduce symbolic manipulation of the relation.

`Q.eq(x, y)` is strictly boolean and does not have direct symbolic manipulation methods. For example `Eq(x, y).expand()` returns `Eq(x.expand(), y.expand())`, but `Q.eq(x, y).expand()` is not allowed.

Instead, `Q.eq(x, y)` will have proxy object as @oscarbenjamin suggested for 'Equation' in this comment, which can be called by `.apply` property. `Q.eq(x, y).apply.expand()` will return `Q.eq(x.expand(), y.expand())`.

Also, `Q.eq(x, y)` will be able to support algebra between the relational as @sylee957 shared in this comment. `Q.eq(x, 1) + Q.eq(y, 1)` will return `AddSides(Q.eq(x, 1), Q.eq(y, 1), evaluate=True)`, which in turn will be evaluated to `Q.eq(x + y, 2)`. `Q.gt(x, 2) * -1` will return `Q.lt(-x, -2)`, and `Q.gt(x, 2) * y` will return `MultiplySides(Q.gt(x, 2), y)` if the sign of `y` cannot be determined.

This way, `Q.eq` can make `Equation` no longer be required to be implemented. However this idea can be discarded if it does not get approved.

4. Compatibility with Eq around codebase

Every codebase will be modified so that `Q.eq` can be used instead of `Eq` everywhere. For example `Piecewise((1, Q.eq(x, 0)), (0, True))` will be supported, and `solveset(Q.eq(x**2, 1))` will return `{-1, 1}`.

5. Migration from Eq to Q.eq

`Eq` and other relational will be retained only for backwards compatibility. Documentation for `Q.eq` will be thoroughly made and users will be informed to use `Q.eq` instead of `Eq`. After a few release, `Q.eq` will be renamed to `Eq`.

6. Final remark

The general idea had been discussed in PR 20723 but the detailed design has been continuously shifted as I tried to implement this. I would like to have a final verdict here before moving on.

Oscar Benjamin

unread,
Mar 3, 2021, 3:46:05 PM3/3/21
to sympy
On Wed, 3 Mar 2021 at 18:58, JSS95 <jeeso...@snu.ac.kr> wrote:
>
> 1. Why do we need relation predicates?
>
> There are two design shifts that need to be done across the SymPy codebase. First, we need to avoid automatic evaluation, which means `Add(x, x)` should return `Add` instance rather than `2*x`. This is because evaluating the non-evaluated expression can always be easily done, but doing it vice versa is very difficult. Matrices module adopts this idea. With matrix `A`, `A + A` is evaluated to `2*A` but `Add(A, A)` is not evaluated - and we need to make every object behave like this. Second, functions should be object. This allows the function itself to be dealt as a separate entity.
>
> `Predicate` in assumption module is a good example for these designs.

Making a "good example" is a weak case for breaking backwards
compatibility though. The reason Add(A, A) has not yet been changed to
not evaluate is because:
1) It is impossible to do without breaking compatibility.
2) More work is needed in other places to make the "don't evaluate
automatically" behaviour actually usable.

> Predicate like `Q.positive` is an object, and `Q.positive(x)` returns `AppliedPredicate` instance. Unlike `Eq(x, x)` which automatically evaluates to `true`, `Q.eq(x, x)` is not evaluated unless passed to `ask()` or `refine()` functions. `Q.positive` itself can be an argument of other SymPy class to form a tree structure. Most importantly, `Predicate` supports fact management system and sat solver. With relation predicate, we can make `ask(Q.extended_real(x), Q.gt(x, y))` return `True` by adding this rule to known facts. Relational classes cannot support this properly. In fact, relational classes cannot even be proposed nor assumed without being wrapped by `Q.is_true` predicate like `Q.is_true(Eq(x, y))`. Using relation predicate instead of relational class gets rid of this nuisance.

Functions like `ask` could wrap with Q.is_true automatically though.
That would be an easy problem to fix without new classes.

> 2. Evaluation of relation predicates
>
> Like any other applied predicate, `Q.eq(x, y)` is never evaluated by construction or simplification. In other words, `Q.eq(x, x)`, `Q.eq(x, x).doit()` or `Q.eq(x, x).simplify()` does not return `true`. Instead, `ask(Q.eq(x, x))` returns `True` and `refine(Q.eq(x, x))` returns `S.true`.
>
> After relation predicates replace relational classes (so that `x > 1` returns `Q.gt(x, 1)`), using infix relational operator will return evaluated result. This is to follow the evaluation logic of matrix module where `A + A` returns `2*A`.

This does seem like a good design to me.

> 3. Symbolic manipulation of relation predicates
>
> Since `Q.eq(x, y)` is never automatically evaluated, we can safely introduce symbolic manipulation of the relation.
>
> `Q.eq(x, y)` is strictly boolean and does not have direct symbolic manipulation methods. For example `Eq(x, y).expand()` returns `Eq(x.expand(), y.expand())`, but `Q.eq(x, y).expand()` is not allowed.
>
> Instead, `Q.eq(x, y)` will have proxy object as @oscarbenjamin suggested for 'Equation' in this comment, which can be called by `.apply` property. `Q.eq(x, y).apply.expand()` will return `Q.eq(x.expand(), y.expand())`.
>
> Also, `Q.eq(x, y)` will be able to support algebra between the relational as @sylee957 shared in this comment. `Q.eq(x, 1) + Q.eq(y, 1)` will return `AddSides(Q.eq(x, 1), Q.eq(y, 1), evaluate=True)`, which in turn will be evaluated to `Q.eq(x + y, 2)`. `Q.gt(x, 2) * -1` will return `Q.lt(-x, -2)`, and `Q.gt(x, 2) * y` will return `MultiplySides(Q.gt(x, 2), y)` if the sign of `y` cannot be determined.
>
> This way, `Q.eq` can make `Equation` no longer be required to be implemented. However this idea can be discarded if it does not get approved.

To be clear this particular part of the proposal is something that you
consider as an alternative to the other recent Equation proposal:
https://groups.google.com/g/sympy/c/rSi_I42i35I/m/Gh1NZuwfBwAJ

One reason it is an alternative is because it provides related
functionality although it is not exactly the same. The other reason is
that users are already confused enough about the difference between
x==y and Eq(x, y) so introducing both Equation(x, y) and Q.eq(x, y) as
other kinds of equation types would just be plain confusing.

> 4. Compatibility with Eq around codebase
>
> Every codebase will be modified so that `Q.eq` can be used instead of `Eq` everywhere. For example `Piecewise((1, Q.eq(x, 0)), (0, True))` will be supported, and `solveset(Q.eq(x**2, 1))` will return `{-1, 1}`.
>
> 5. Migration from Eq to Q.eq
>
> `Eq` and other relational will be retained only for backwards compatibility. Documentation for `Q.eq` will be thoroughly made and users will be informed to use `Q.eq` instead of `Eq`. After a few release, `Q.eq` will be renamed to `Eq`.

You need to be more explicit about what this means: this is a
compatibility break for anyone who is currently using Eq. The Eq class
is very widely used and the current expectation is that it should
evaluate so Eq(1, 2) gives false but "renaming" Q.eq to Eq means that
anyone currently using Eq would then find that Eq(1, 2) no longer
works the way it used to.

You are right that if we ever did want to make that kind of change
then we would need to update the documentation as soon as possible.
However updating the documentation helps to prevent new code from
using Eq. The rest of the code that already got written won't read
those docs and would still break when the time comes. There should be
consideration about whether or not it is possible to have a
deprecation and how downstream codebases should prepare for this.
There should also be some effort to consider how much breakage this
would cause. I'm fairly sure it would break a lot of things in sympy
and that some of those things would not be easy to debug - you might
do all that work for sympy but that same impact would apply to
downstream codebases as well.

Firstly though there needs to be a clear rationale for why it is
necessary to break compatibility.

> 6. Final remark
>
> The general idea had been discussed in PR 20723 but the detailed design has been continuously shifted as I tried to implement this. I would like to have a final verdict here before moving on.

Thanks for opening the discussion here. I think it would be better to
try and have the discussion in one place because so far it has been
split over many different PRs on Github and I have lost track of where
everything was going.

I think that the design your suggesting is good and also that it would
have been better if sympy had followed that in the first place.
However we are where we are and it seems like you are proposing to
break backwards compatibility with very minimal rationale.

Your explanation above is reasonable for helping someone like me to
understand what your design is. Probably for most people on this list
it would be easier to understand if you gave clear examples of input
and output and made the significant differences between different
ideas more explicit though.

--
Oscar

Aaron Meurer

unread,
Mar 3, 2021, 4:40:26 PM3/3/21
to sympy
On Wed, Mar 3, 2021 at 11:58 AM JSS95 <jeeso...@snu.ac.kr> wrote:
>
> SymPy had implemented the binary relation as `Relational` class in core/relation module. In this document, I propose a new system which implements general binary relation as `Predicate` which is defined in assumptions module.
>
> Since predicates are registered on `Q` such as `Q.positive`, I will call the relational predicate as `Q.eq`, `Q.gt`, etc in this document. Please note that registering the predicate on `Q` is not mandatory, and these predicates can have different name when SymPy 1.8 is released. After a few releases, relation predicates will completely replace relational classes so that `Eq`, `Gt`, etc return `Q.eq`, `Q.gt`, etc.
>
> 1. Why do we need relation predicates?
>
> There are two design shifts that need to be done across the SymPy codebase. First, we need to avoid automatic evaluation, which means `Add(x, x)` should return `Add` instance rather than `2*x`. This is because evaluating the non-evaluated expression can always be easily done, but doing it vice versa is very difficult. Matrices module adopts this idea. With matrix `A`, `A + A` is evaluated to `2*A` but `Add(A, A)` is not evaluated - and we need to make every object behave like this. Second, functions should be object. This allows the function itself to be dealt as a separate entity.
>
> `Predicate` in assumption module is a good example for these designs. Predicate like `Q.positive` is an object, and `Q.positive(x)` returns `AppliedPredicate` instance. Unlike `Eq(x, x)` which automatically evaluates to `true`, `Q.eq(x, x)` is not evaluated unless passed to `ask()` or `refine()` functions. `Q.positive` itself can be an argument of other SymPy class to form a tree structure. Most importantly, `Predicate` supports fact management system and sat solver. With relation predicate, we can make `ask(Q.extended_real(x), Q.gt(x, y))` return `True` by adding this rule to known facts. Relational classes cannot support this properly. In fact, relational classes cannot even be proposed nor assumed without being wrapped by `Q.is_true` predicate like `Q.is_true(Eq(x, y))`. Using relation predicate instead of relational class gets rid of this nuisance.
>
> 2. Evaluation of relation predicates
>
> Like any other applied predicate, `Q.eq(x, y)` is never evaluated by construction or simplification. In other words, `Q.eq(x, x)`, `Q.eq(x, x).doit()` or `Q.eq(x, x).simplify()` does not return `true`. Instead, `ask(Q.eq(x, x))` returns `True` and `refine(Q.eq(x, x))` returns `S.true`.
>
> After relation predicates replace relational classes (so that `x > 1` returns `Q.gt(x, 1)`), using infix relational operator will return evaluated result. This is to follow the evaluation logic of matrix module where `A + A` returns `2*A`.
>
> 3. Symbolic manipulation of relation predicates
>
> Since `Q.eq(x, y)` is never automatically evaluated, we can safely introduce symbolic manipulation of the relation.
>
> `Q.eq(x, y)` is strictly boolean and does not have direct symbolic manipulation methods. For example `Eq(x, y).expand()` returns `Eq(x.expand(), y.expand())`, but `Q.eq(x, y).expand()` is not allowed.
>.
> Instead, `Q.eq(x, y)` will have proxy object as @oscarbenjamin suggested for 'Equation' in this comment, which can be called by `.apply` property. `Q.eq(x, y).apply.expand()` will return `Q.eq(x.expand(), y.expand())`.
>
> Also, `Q.eq(x, y)` will be able to support algebra between the relational as @sylee957 shared in this comment. `Q.eq(x, 1) + Q.eq(y, 1)` will return `AddSides(Q.eq(x, 1), Q.eq(y, 1), evaluate=True)`, which in turn will be evaluated to `Q.eq(x + y, 2)`. `Q.gt(x, 2) * -1` will return `Q.lt(-x, -2)`, and `Q.gt(x, 2) * y` will return `MultiplySides(Q.gt(x, 2), y)` if the sign of `y` cannot be determined.
>
> This way, `Q.eq` can make `Equation` no longer be required to be implemented. However this idea can be discarded if it does not get approved.

I still think this is a bad idea. Making symbolic and boolean
relationals the same leads to too much type confusion. The evaluation
of relationals to booleans is not the only reason this is problematic.
There's a reason Boolean and Expr are separate objects, and for
example, S.true and S.false are not allowed to be treated as numbers
(S.true + 1 gives an error). Treating booleans and expressions
(numbers) as the same thing leads to all sorts of errors.

This is the main issue for this, for reference
https://github.com/sympy/sympy/issues/4986.

I appreciate that it might be confusing to users, but I think we can
mitigate that to some degree with error messages. If Eq and Eqn are
strict about their types, so that Eq only works in boolean contexts
and Eqn only works in expression contexts, then users can
unambiguously tell if they need to use the right object. We can
automatically convert (maybe with a warning) in cases that previously
worked, and also allow either in cases where the exact type doesn't
really matter (like solve(Eq) vs. solve(Eqn)). I expect this won't be
a big deal because Eq() is already mainly only usable as a boolean.
None of the equation methods are really implemented yet, so there
can't be any backwards compatibility break for only implementing them
on Eqn().

>
>
> 4. Compatibility with Eq around codebase
>
> Every codebase will be modified so that `Q.eq` can be used instead of `Eq` everywhere. For example `Piecewise((1, Q.eq(x, 0)), (0, True))` will be supported, and `solveset(Q.eq(x**2, 1))` will return `{-1, 1}`.
>
> 5. Migration from Eq to Q.eq
>
> `Eq` and other relational will be retained only for backwards compatibility. Documentation for `Q.eq` will be thoroughly made and users will be informed to use `Q.eq` instead of `Eq`. After a few release, `Q.eq` will be renamed to `Eq`.

I still maintain that adding a new *name*, Q.eq, is unnecessary and
confusing. A new name is appropriate for a new object (like Equation),
but what you are suggesting is to replace Eq with Q.eq. So why not
just change Eq to be what you are proposing Q.eq to be? I think we can
remove the automatic evaluation from Eq. It would be less of a
backwards compatibility break than renaming the name (you could argue
it isn't even technically a break, since nothing guarantees when or if
Eq() will evaluate to a boolean). Evaluation to a boolean should be
replaced with ask(Eq(x, y)), which will also work in old versions
where Eq evaluates, so it can be done in a forward compatible way in
downstream code.

And to reiterate a point I made on one of your pull requests: "As an
aside, I never realised there were so many ways to spell '='.
'Equation', 'equality', 'equal', 'equals', 'equivalent', 'eq', ....
And at the rate things are going, we are going to have a separate
class in SymPy for each one, each with its own distinct semantic
meaning. This is very confusing for users, so I would like to avoid
it."

The way I could see this being a good idea is if we want to convert Eq
to instead be the symbolic equation class and make Q.eq the boolean
variant. But as Oscar pointed out on one of the issues, this would
break a lot of code that currently assumes Eq is a boolean (it's often
used inside of boolean expressions, like in Piecewise). As I noted
above, Eq() is currently an amalgamation of boolean and symbolic
relational, but it currently has more implemented as a boolean, so it
is more often used as that.

Aaron Meurer

>
> 6. Final remark
>
> The general idea had been discussed in PR 20723 but the detailed design has been continuously shifted as I tried to implement this. I would like to have a final verdict here before moving on.
>
> --
> You received this message because you are subscribed to the Google Groups "sympy" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to sympy+un...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/sympy/c7d3f7c1-f24a-43ac-aa05-06fa31cb33e0n%40googlegroups.com.

Oscar Benjamin

unread,
Mar 3, 2021, 6:39:08 PM3/3/21
to sympy
On Wed, 3 Mar 2021 at 21:40, Aaron Meurer <asme...@gmail.com> wrote:
>
> On Wed, Mar 3, 2021 at 11:58 AM JSS95 <jeeso...@snu.ac.kr> wrote:
> >
> > Also, `Q.eq(x, y)` will be able to support algebra between the relational as @sylee957 shared in this comment. `Q.eq(x, 1) + Q.eq(y, 1)` will return `AddSides(Q.eq(x, 1), Q.eq(y, 1), evaluate=True)`, which in turn will be evaluated to `Q.eq(x + y, 2)`. `Q.gt(x, 2) * -1` will return `Q.lt(-x, -2)`, and `Q.gt(x, 2) * y` will return `MultiplySides(Q.gt(x, 2), y)` if the sign of `y` cannot be determined.
> >
> > This way, `Q.eq` can make `Equation` no longer be required to be implemented. However this idea can be discarded if it does not get approved.
>
> I still think this is a bad idea. Making symbolic and boolean
> relationals the same leads to too much type confusion. The evaluation
> of relationals to booleans is not the only reason this is problematic.
> There's a reason Boolean and Expr are separate objects, and for
> example, S.true and S.false are not allowed to be treated as numbers
> (S.true + 1 gives an error). Treating booleans and expressions
> (numbers) as the same thing leads to all sorts of errors.
>
> This is the main issue for this, for reference
> https://github.com/sympy/sympy/issues/4986.
>
> I appreciate that it might be confusing to users, but I think we can
> mitigate that to some degree with error messages. If Eq and Eqn are
> strict about their types, so that Eq only works in boolean contexts
> and Eqn only works in expression contexts, then users can
> unambiguously tell if they need to use the right object.

We need to be very careful to define what "in expression contexts"
means. Personally I see a big difference between allowing the use of
operators like + and * and allowing in Eq to be used as one of the
terms in a symbolic Add or Mul expression.

To be clear the first means saying:

Given an equation object eq=Eq(x, y), the Python code 2*eq will
give Eq(2*x, 2*y).

To me that does not imply that there is any mathematical meaning to
multiplication between an equation and an integer. It just means that
we can use Python's infix arithmetic operators for convenient
manipulations of equations. In Python you can also add lists like [1,
2] + [3]. That does not imply that we should allow the list [1, 2] to
be substituted for x in a symbolic expression like x**2 + 1.

On the other hand in sympy 1.4 the Relational class which is a
superclass of Eq was actually a subclass of Expr meaning that it could
be used as part of any symbolic expression in any place where an
expression ought to be e.g.:

In [4]: Eq(x, 2)**(x > 1)
Out[4]:
x > 1
(x = 2)

To me that is just a gibberish expression object and should not be
allowed under any variant of an equation/inequality object (I removed
this behaviour in sympy 1.5 I think). I would not propose that any
kind of equation object could appear in place of an expression as part
of any *symbolic* expression. That is very different from allowing
some operations that can conveniently manipulate equation objects
while still keeping them entirely separate from symbolic expressions.

> > 4. Compatibility with Eq around codebase
> >
> > Every codebase will be modified so that `Q.eq` can be used instead of `Eq` everywhere. For example `Piecewise((1, Q.eq(x, 0)), (0, True))` will be supported, and `solveset(Q.eq(x**2, 1))` will return `{-1, 1}`.
> >
> > 5. Migration from Eq to Q.eq
> >
> > `Eq` and other relational will be retained only for backwards compatibility. Documentation for `Q.eq` will be thoroughly made and users will be informed to use `Q.eq` instead of `Eq`. After a few release, `Q.eq` will be renamed to `Eq`.
>
> I still maintain that adding a new *name*, Q.eq, is unnecessary and
> confusing. A new name is appropriate for a new object (like Equation),
> but what you are suggesting is to replace Eq with Q.eq. So why not
> just change Eq to be what you are proposing Q.eq to be? I think we can
> remove the automatic evaluation from Eq. It would be less of a
> backwards compatibility break than renaming the name (you could argue
> it isn't even technically a break, since nothing guarantees when or if
> Eq() will evaluate to a boolean).

There are different perspectives on this. It's also possible to argue
that *any* change to anything is a compatibility break!

I think in this case that this is not only a change in behaviour but
also clearly departing from the documented purpose of the Eq class.
These changes contradict most of the opening section of the docstring
for Eq:
"""
Represents that two objects are equal. If they can be easily shown to
be definitively equal (or unequal), this will reduce to True (or
False). Otherwise, the relation is maintained as an unevaluated
Equality object. Use the simplify function on this object for more
nontrivial evaluation of the equality relation.

As usual, the keyword argument evaluate=False can be used to prevent
any evaluation.
"""
https://docs.sympy.org/latest/modules/core.html#sympy.core.relational.Equality

I do think that a clear rationale is needed before any significant
backwards compatible change and these specific points need to be
addressed:

1. Why is an incompatible change needed?
2. How would the incompatibility cause downstream user/library code to break?
3. Does it seem like much code would break?
4. Are there ways to mitigate that breakage?
5. How would someone change code to be compatible with sympy both
before and after the break?

> And to reiterate a point I made on one of your pull requests: "As an
> aside, I never realised there were so many ways to spell '='.
> 'Equation', 'equality', 'equal', 'equals', 'equivalent', 'eq', ....
> And at the rate things are going, we are going to have a separate
> class in SymPy for each one, each with its own distinct semantic
> meaning. This is very confusing for users, so I would like to avoid
> it."
>
> The way I could see this being a good idea is if we want to convert Eq
> to instead be the symbolic equation class and make Q.eq the boolean
> variant. But as Oscar pointed out on one of the issues, this would
> break a lot of code that currently assumes Eq is a boolean (it's often
> used inside of boolean expressions, like in Piecewise).

I don't think that Piecewise is a problem because it can just be made
to call ask(cond) or bool(cond). It knows the condition is supposed to
be boolean so it can do that explicitly.

The problem is more code that tries to use Eq directly like:

if Eq(x, y) is S.true:
# do something

For example right now all of the logic in the FiniteSet class is built
around using this as a way of sometimes being able to tell when two
objects are equal. I have no idea how much this is used outside of the
sympy codebase though.


--
OScar

Aaron Meurer

unread,
Mar 3, 2021, 6:56:31 PM3/3/21
to sympy
That's a fair point. Maybe it's wrong to say that Eqn should be an
Expr. But certainly it should not be a boolean.
Making Eq always unevaluated is a breakage in the sense that anything
that relies on it returning True or False for trivial cases will stop
working. But any code that does that should already be handling the
unevaluated case, because there are no guarantees about it when it can
evaluate, and in general, it can't. So user code, if correct, won't
break in the sense that it gets unexpected input, only in the sense
that it might stop evaluating for cases that it did before. Also the
fix would be to use ask() or doit() (whatever we decide is the best),
and should be difficult to apply. We could even do a deprecation where
make __bool__ continue to evaluate.

>
> > And to reiterate a point I made on one of your pull requests: "As an
> > aside, I never realised there were so many ways to spell '='.
> > 'Equation', 'equality', 'equal', 'equals', 'equivalent', 'eq', ....
> > And at the rate things are going, we are going to have a separate
> > class in SymPy for each one, each with its own distinct semantic
> > meaning. This is very confusing for users, so I would like to avoid
> > it."
> >
> > The way I could see this being a good idea is if we want to convert Eq
> > to instead be the symbolic equation class and make Q.eq the boolean
> > variant. But as Oscar pointed out on one of the issues, this would
> > break a lot of code that currently assumes Eq is a boolean (it's often
> > used inside of boolean expressions, like in Piecewise).
>
> I don't think that Piecewise is a problem because it can just be made
> to call ask(cond) or bool(cond). It knows the condition is supposed to
> be boolean so it can do that explicitly.

My point is that the equation class should not be usable as a boolean.
ask(Equation) should give an error. So that's problematic if we want
to make Eq the equation class and use something new for boolean
equations. Any existing code that does stuff like And(Eq(x, y), ...)
would be wrong if we did that.

>
> The problem is more code that tries to use Eq directly like:
>
> if Eq(x, y) is S.true:
> # do something
>
> For example right now all of the logic in the FiniteSet class is built
> around using this as a way of sometimes being able to tell when two
> objects are equal. I have no idea how much this is used outside of the
> sympy codebase though.

I don't know if people do that with Eq. I didn't find any instances of
it in the SymPy codebase, outside of tests that specifically test that
Eq() returns a boolean.

But that definitely is a pattern with Lt (or at least the operator <).
And we will want whatever we do with Eq to be consistent with the
inequalities, so we should consider that. Although to be sure, <
doesn't have to mean the same thing as Lt. It may actually be easier
because we can make < evaluate but Lt() not. Alternatively, we could
deprecate this pattern, in favor of something like "if ask(a < b) is
True".

Aaron Meurer

>
>
> --
> OScar
>
> --
> You received this message because you are subscribed to the Google Groups "sympy" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to sympy+un...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/sympy/CAHVvXxQhyA14kRTswJ2CSiPsBbMa9GZuvfGraprH%3Dv%3DePOYiKQ%40mail.gmail.com.

JSS95

unread,
Mar 4, 2021, 9:20:23 AM3/4/21
to sympy

I think my original message was misleading. The reason we should use the predicate is not because we merely want to make an example. It's because Q.eq can do what Eq cannot. As far as I know, logical inference in assumption module use classes such as AllArgs in assumptions/sathandlers module. These classes takes predicate as arguments and loosely behave like a composite function. To make relation work with fact system (again, x > y implies that x and y are both extended real numbers), we need relational predicate. But I understand that this still might be weak reason to break the backwards compatibility.

Provided that we do not implement symbolic manipulation, I believe that the only disagreement we have here is  : given that we introduced the relation predicate, do we need to have two different classes or do we need to have one class at the cost of breaking backwards compatibility? If so, then what about this?

1. Eq, Ge, and other variables return relation predicate. No Q.eq.
2. Eq(x, x) does not return True. But unlike predicates which do not evaluated by simplification, Eq(x, x).simplify() evaluates to True with deprecation warning.
3. After deprecation period is over, Eq(x, x).simplify() no longer evaluates to True.

I think this can prevent the compatibility issue for most of downstream codes because the standard way to evaluate the relational has been simplifying. Eq(sin(x)**2 + cos(x)**2, 1) isn't evaluated to True without simplifying, so I believe many codes have simplifying step before expecting a boolean atomic result.

Jisoo Song
2021년 3월 4일 목요일 오전 8시 56분 31초 UTC+9에 asme...@gmail.com님이 작성:

Oscar Benjamin

unread,
Mar 5, 2021, 7:56:09 PM3/5/21
to sympy
On Thu, 4 Mar 2021 at 14:20, JSS95 <jeeso...@snu.ac.kr> wrote:
>
> I believe that the only disagreement we have here is : given that we introduced the relation predicate, do we need to have two different classes or do we need to have one class at the cost of breaking backwards compatibility?

I think that's right. I wouldn't say that it is a "disagreement"
though. I haven't seen compelling arguments for either approach yet.
Rather than disagreeing I am asking for a rationale for any change.

To me there is a clear rationale for the Equation class because it is
not supposed to be a Boolean. That makes it more useful for those
situations where you want to pass an equation around to represent
something that may or may nor be true but that should still have a
clear lhs and rhs. The new Q.eq class you propose here is still a
Boolean but just one that does not evaluate automatically. Placing
this in the assumptions system is odd though because that's a
situation where it really should be fine for the equality to evaluate
in the way that Eq already does.

> If so, then what about this?
>
> 1. Eq, Ge, and other variables return relation predicate. No Q.eq.
> 2. Eq(x, x) does not return True. But unlike predicates which do not evaluated by simplification, Eq(x, x).simplify() evaluates to True with deprecation warning.
> 3. After deprecation period is over, Eq(x, x).simplify() no longer evaluates to True.

We need a rationale for changing anything here. I don't think that any
deprecation warning should be introduced unless it is for something
that will in future become an error.

--
Oscar

JSS95

unread,
Mar 6, 2021, 7:09:23 AM3/6/21
to sympy

>  The new Q.eq class you propose here is still a Boolean but just one that does not evaluate automatically.

Q.eq is not just something that does not evaluate automatically. Again, the reason why I propose Q.eq(...) is because it can do what Eq(..., evaluate=False) cannot - that is, the function itself being the argument of another object.

Known facts such as "x being real number implies x being complex number" is registered in assumptions/ask module as Implies(Q.real, Q.complex). To express "x > y implies both x and y being real number" with this system, we need something like Implies(Q.gt, AllArgs(Q.real)) which is applied to Tuple(x, y). These boldface objects play the role of composite function, which requires boolean functions as objects. We cannot do Implies(Gt, AllArgs(Q.real)) because Gt is a type and thus cannot be the argument of Implies().

> We need a rationale for changing anything here. I don't think that any
> deprecation warning should be introduced unless it is for something
> that will in future become an error.

Okay, I agree. Then I suggest making Q.eq completely internal to assumptions module and letting Eq be converted to Q.eq whenever it is applied to ask() or refine(). No replacement of Eq with Q.eq, and Q.eq will not be exposed to the users.

Jisoo Song
2021년 3월 6일 토요일 오전 9시 56분 9초 UTC+9에 Oscar님이 작성:
Message has been deleted

JSS95

unread,
Mar 12, 2021, 2:15:40 AM3/12/21
to sympy
Aaron, Oscar, please share your idea on this. My recent opinion is:

1. We need relational predicates (Q.eq) to make inference system work with relations. Eq(..., evaluate=False) cannot do this.

2. I will not replace Eq with Q.eq because doing that will break backwards compatibility. Instead, ask() and refine() will convert Eq to Q.eq internally for inference system.

3. Q.eq will not be exposed to the users in order to prevent the confusion. It will be kept inside the assumptions module and will not be used in anywhere else.

4. "Q.eq" is just a tentative naming. Relational predicate may not be registered to "Q".

Jisoo Song

Oscar Benjamin

unread,
Mar 12, 2021, 3:55:24 PM3/12/21
to sympy
On Fri, 12 Mar 2021 at 07:13, JS S <jeeso...@gmail.com> wrote:
>
> Aaron, Oscar, please share your idea on this. My recent opinion is:
>
> 1. We need relational predicates (Q.eq) to make inference system work with relations. Eq(..., evaluate=False) cannot do this.

Why (briefly) is it not possible for Eq to work here?

> 2. I will not replace Eq with Q.eq because doing that will break backwards compatibility. Instead, ask() and refine() will convert Eq to Q.eq internally for inference system.
>
> 3. Q.eq will not be exposed to the users in order to prevent the confusion. It will be kept inside the assumptions module and will not be used in anywhere else.
>
> 4. "Q.eq" is just a tentative naming. Relational predicate may not be registered to "Q".

I think that seems reasonable.


Oscar
Message has been deleted

JSS95

unread,
Mar 13, 2021, 2:01:25 AM3/13/21
to sympy
> Why (briefly) is it not possible for Eq to work here?

That's because inference system in assumptions module requires the boolean function as object, not type.

Known facts such as "x being real number implies x being complex number" is registered in assumptions/ask module as Implies(Q.real, Q.complex). If we want to express "x > y implies both x and y being real number" with this system, we need something like Implies(Q.gt, AllArgs(Q.real)) which is applied to Tuple(x, y). These boldface objects play the role of composite function, which requires boolean functions as objects. We cannot do Implies(Gt, AllArgs(Q.real)) because Gt is a type and thus cannot be the argument of Implies().

This is why we need Q.eq, Q.lt, and other predicates to represent Eq, Lt, etc in assumption system.

Jisoo Song

Oscar Benjamin

unread,
Mar 13, 2021, 6:18:02 AM3/13/21
to sympy
I guess if these are just being used to express the internal rules of
the assumptions system then that's fine. I wonder though if it would
also make sense for Eq to have an internal representation that is like
`Relational(Eq, x, y)` as well.


Oscar

JSS95

unread,
Mar 14, 2021, 9:01:38 PM3/14/21
to sympy
Since Eq is a type, I doubt that `Relational(Eq, x, y)` is possible. Such structure of representation is required only in the assumptions module so I'd say that we don't need to implement that in core right now.

If there is no more objection, I will implement relational predicates following my recent suggestion.

Jisoo Song

Oscar Benjamin

unread,
Mar 15, 2021, 4:43:13 AM3/15/21
to sympy
On Mon, 15 Mar 2021 at 01:01, JSS95 <jeeso...@snu.ac.kr> wrote:
>
> Since Eq is a type, I doubt that `Relational(Eq, x, y)` is possible. Such structure of representation is required only in the assumptions module so I'd say that we don't need to implement that in core right now.

But we could make it so that Eq is an instance just as you are
proposing for Q.eq.

> If there is no more objection, I will implement relational predicates following my recent suggestion.

I'm not objecting.


Oscar
Reply all
Reply to author
Forward
0 new messages