On Wed, Mar 3, 2021 at 11:58 AM JSS95 <[email protected]> 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 [email protected].
> To view this discussion on the web visit 
> https://groups.google.com/d/msgid/sympy/c7d3f7c1-f24a-43ac-aa05-06fa31cb33e0n%40googlegroups.com.

-- 
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/CAKgW%3D6JJU2atiFEEksb4NePGktxb9n%3DKv2Stnmtzue3_rMX8Hw%40mail.gmail.com.

Reply via email to