On Wed, 3 Mar 2021 at 18:58, JSS95 <[email protected]> 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

-- 
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 [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/sympy/CAHVvXxTSUyVgd6kh0fkRTv6xbeV%2BsbxeNHJ_iyGkHrgLOGqc7g%40mail.gmail.com.

Reply via email to