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 <https://github.com/sympy/sympy/pull/19479#issuecomment-637859457>, 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 <https://github.com/sympy/sympy/pull/20723#issuecomment-763975854>. `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 <https://github.com/sympy/sympy/pull/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.
