In SymPy, we have two ways to get the mathematical property of an object : 
the old assumptions system and the new assumptions system.
With old assumptions system, the property is accessed by `.is_[...]` 
attribute. Here, property of the object is intrinsic.
```
In [1]: x = Symbol('x', positive=True)
In [2]: x.is_positive
Out[2]: True
```
With new assumption system, the property is accessed by assumption 
predicate and `ask()` function. Here, property of the object can be 
extrinsically assumed.
```
In [1]: x = Symbol('x')
In [2]: ask(Q.integer(x), Q.even(x))
Out[2]: True
```

Old assumption system is used in evaluation logic because it is faster. In 
refinement logic where user can pass local assumptions, new assumption 
system is used.
This results the duplication in two methods. For example, evaluation logic 
of `Abs` is vaguely like this:
```
def eval_Abs(x):
    if x.is_nonnegative:
        return x
    ...
```
And refinement logic of `Abs` is like this:
```
def eval_Abs(x, assumptions=True):
    if ask(Q.nonnegative(x), assumptions):
        return x
    ...
```

As you can see, two codes are mostly identical and the only difference is 
old assumptions vs new assumptions. By introducing a function which can 
switch between two behaviors, we can remove this duplication.

The first suggestion is to pass a getter function.
```
def eval_Abs(x, getter):
    if getter(x, 'nonnegative'):
        return x
    ...
```
Here, we can pass `lambda x, key : getattr(x, "is_%s" % key)` to getter for 
evaluation, and `lambda x, key : ask(getattr(Q, key)(x), assumptions)` for 
refinement.
Pros :
- Easy to implement
Cons:
- `getattr(x, "is_%s" % "positive")` is slower than `x.is_positive`.
- `getter(x, '...')` everywhere looks ugly.

The second suggestion is proposed by oscarbenjamin in 
https://github.com/sympy/sympy/pull/21203#issuecomment-810134012.
If I understood correctly, the idea is to introduce `AssumptionsWrapper` 
class like this:
```
class AssumptionsWrapper(Basic):
    def __new__(cls, expr, assumptions=True):
        if assumptions == True:
            return expr
        obj = super().__new__(cls, expr, assumptions)
        obj.expr = expr
        obj.assumptions = assumptions
        return obj
    def _eval_is_positive(self):
        return ask(Q.positive(self.expr), self.assumptions)
```
And `eval_Abs` will be like this:
```
def eval_Abs(x, assumptions=True):
    _x = AssumptionsWrapper(x, assumptions)
    if _x.is_positive:
        return x
    ...
```
Pros :
- No slowdown for retrieving `.is_positive` attribute with old assumptions
Cons:
- Generating wrapper objects is slow.
- Having wrapped objects everywhere makes the code messy

And here is the third suggestion, which is to modify `ManagedProperties` 
(metaclass for `Basic`) to automatically define `check_[...]` methods for 
every keys.
The design will be like this:
```
...
    # This method will be automatically generated by metaclass
    def check_positive(self, assumptions=True):
        if assumptions is True:
            return self.is_positive
        else:
            return ask(Q.positive(self), assumptions)
```
And `eval_Abs` will be:
```
def eval_Abs(x, assumptions=True):
    if x.check_nonnegative(assumptions):
        return x
    ...
```
Pros:
- Easy to implement. I tried this, and it required only <20 more lines in 
core/assumptions file
- Fastest
- Cleaner code
Cons:
- Basic will be cluttered up with more methods

-- 
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/63e8baab-e299-4c15-a4c5-bc862fe406e3n%40googlegroups.com.

Reply via email to