-
-
Notifications
You must be signed in to change notification settings - Fork 60
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Arithmetic refactor #766
Arithmetic refactor #766
Conversation
} | ||
|
||
|
||
# Here I used the convention of calling eval_* to functions that can produce a new expression, or None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rocky, I leave this long comment here about the eval<-> apply semantics. Maybe we want to go over this again before merging this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I looked at this and there is information here that I a do not believe is elsewhere. I think it should be elsewhere but I don't know right now where that elsewhere is.
# Handled already | ||
# "Gudermannian[Infinity]": "Pi/2", | ||
"Gudermannian[DirectedInfinity[1]]": "Pi/2", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Notice that the rule requires to use DirectedInfinity[1]
instead of Infinity
. This is due to Infinity
is evaluated to DirectedInfinity[1]
before looking at the Gudermannian
set of rules.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok - Thanks for the explanation. I'd like to understand this more though. What is it that turns Infinity
into DirectedInfinity[1]
? Is this SymPy doing this?
As you say below, WMA turns Infinity
to DirectedInfinity[1]
. I wonder if we should be doing the same thing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should if we want to get the same result if we apply the same rules as written for WMA.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. If this is simple for now to do, then let's do. Or we can write DirectedInfinity[1]
in the rules and come back to this some other time. Your choice - assuming this is a simple fix.
Thanks for looking at and untangling this. With regards to:
I would like to understand this in more detail. Is this documented somewhere? (I am currently looking myself). What exactly are the differences? |
With respect to
I would like more details here too. Obviously we want to reduce this, but generally the ideal flow is convert into SymPy as much as possible and convert back to Mathics as late or as little as possible. In fact this idea is true for other things as well, like numpy arrays, constant literals that map into Python, etc. See below for revision to this |
mathics/eval/arithmetic.py
Outdated
if exp.is_zero: | ||
return Integer1 | ||
if isinstance(exp, (Integer, Real, Rational)): | ||
sign = eval_sign(base) or Expression(SymbolSign, base) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My Editor tells and flake8 tell me SymbolSign is not defined.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for catching this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have to add more tests for these routines, but at this point, I need a little bit of feedback (not necessarily right now, but before continuing with this).
mathics/eval/arithmetic.py
Outdated
if isinstance(exp, (Integer, Real, Rational)): | ||
return Integer1 | ||
if isinstance(exp, Complex): | ||
return Expression(SymbolExp, exp.imag) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My editor and flake8 tell me that SymbolExp is not defined.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With respect to
conversions back and forward to SymPy.
I would like more details here too. Obviously we want to reduce this, but generally the ideal flow is convert into SymPy as much as possible and convert back to Mathics as late or as little as possible. In fact this idea is true for other things as well, like numpy arrays, constant literals that map into Python, etc.
Suppose you are going to evaluate
3^(1/2) + 5 * 3^(-1/2) + 3 * Pi^2
1/2
is converted to SymPy twice back and forward, to see that the expression didn't change, so 1/2
is kept. Then we have the evaluation of 3^(-1/2)
and 3^(-1/2)
where 3
and 1/2
, and 3^(1/2)
are converted again back and forward, to again get the same result. And again in the multiplication by 5, and again in the addition, to get at the end the very same expression.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of this can be addressed by having a special form of Expression that is known to have a SymPy equivalent and that is saved. So 3, 1/2, 3^(1/2) are all Expressions that have SymPy expr object equivalents.
We will probably do the same for literals that have exact Python correspondences, and numerous other things.
If this is the only thing that being addressed, please don't do it this way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem is that for certain expressions, the replacement rules in WMA and SymPy are different, which makes that new rules written in WL fails to be applied, like in the case of DirectedInfinity. Actually, I did all of this because it was not possible to fix these rules without disentangling all the arithmetic module.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think here we need more knowledgeable conversion routines between the Mathics and SymPy representations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guder
That part of the PR are in this branch: https://github.com/Mathics3/mathics-core/tree/tests_from_arithmetic_refactor
However, the new tests do not pass without the other changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A number of them do, so those could go in. The ones that don't can be commented out to remind us to address various issues.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mmatera if you would like me to make a stab and extracting the non-controversial parts, I'll do.
mathics/eval/arithmetic.py
Outdated
RealOne = Real(1.0) | ||
|
||
|
||
# Here we store numerical constants with their approximate |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This smells like a wrong thing to do. I see it is used in test_arithmetic_expr
and that is new too, so that is probably not advisable either. What is the history behind these?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In a couple of places in the code, I need to multiply by that constant. The same with RationalMOneHalf
. It is just to about avoiding calling the constructor to get the instance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't mean RealOne, I was referring to SymolE, SymbolEulerGamma, and SymbolPi.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW Computing RealOne once and using it many times is fine. And if we find that other modules tend to need RealOne, we can put it in a more global place.
The first difference that I noticed is that SymPy rationalizes fractions. For example, |
return result | ||
|
||
|
||
def distribute_factor(expr: BaseElement, factor: BaseElement) -> BaseElement: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not seeing distribute_factor
or distribute_powers
used anywhere. Where is this used?
This was said above but I think it applies here as well. The real issue is about whether there is loss of information in either representation. If not, then I would first try to get this by doing a better conversion. In a sense this is also related to the Form stuff as well. It may turn out that this is too tricky, but I strongly encourage starting out with that first (and don't ask me for the reasons why unless you are prepared for the consequences of the answer). Also, please note that in a Expression that saves the sympy representation, there is no conversion smarts per se. The system has its representation in its nice form and the Sympy expression has its representation in its nice form whether or not we had smarts to know that this is how the two correspond. |
So based on what has followed, I have to back off here, but only a little. You know how we have this overly complex caching mechanism for Expressions? Well, we might have something like that for Expressions with sympy representations. However the invalidation/update is done only when a rewrite rule kicks in. |
Let me put this in a different way. I am pretty sure that your idea of implementing specialized expressions that stores the SymPy representation would provide a better implementation over what I have write here. However, even with that implementation, I think that it would be better to split the evaluation of arithmetic expressions in a part that could depend on the context (provided by the Evaluation object) and a part that is independent of the context. This is what this or is about. Then you could replace part of the implementation with proper calls to SymPy, and use a smarter class hierarchy, but still I think we want to have a way to do arithmetic without passing throw the standard evaluation process. |
I totally agree. And then this PR can be much much smaller and simpler. Please keep the new tests too - I like those as well! The other change for now that makes sense is changing the rule with Doing both of these better is something we should keep in mind and do when the opportunity presents itself. |
This would be the simplest part of #766, including tests and changes in `Infinity` and `Precision`. Some of the new tests are commented / marked as xfail because require the other changes.
f5ab2cc
to
11eb5ff
Compare
11eb5ff
to
f5ab2cc
Compare
@rocky, I was trying to merge this with the current state of master, but I found a lot of conflicts. My plan is to take the tests added here, and move the changes to another new branch, and then, take the part of the code needed to pass the tests. It will take a while. |
f5ab2cc
to
10c9cbd
Compare
I completed the rebase, but for some reason, now the performance is lower than in master. Also, I found some extra bugs in non directly related code. I am going to mark them, then propose fixing for them, and then continue over this. |
10c9cbd
to
c982e6e
Compare
Small PRs are always appreciated over large ones like this. |
This came up during the #766 rebase: `PrecisionForm` didn't have a `Builtin` symbol, and the `Makeboxes` rule didn't take into account the `form` parameter. This PR fixes these two issues.
@rocky, yes, I am slowly splitting this up. |
This is based on one part of #766 related to how DirectedInfinity is handled. To simplify this, I remove part of the `eval_Times` code handling this kind of expression and replace it with upvalues rules. Also, some of the new pytests in #766 were included here. --------- Co-authored-by: rocky <[email protected]> Co-authored-by: R. Bernstein <[email protected]>
2f8c02e
to
bd3bcc2
Compare
757011f
to
7b6f650
Compare
…ving unused imports. adding symbols to mathics.core.systemsymbols
7b6f650
to
2481118
Compare
e8e0de7
to
a0b5310
Compare
2d2a37a
to
c32daa2
Compare
Well, it took a lot of time, but I think this is now ready for revision. The idea is to move the code associate to the evaluation of arithmetic expressions, especially those expressions composed mainly of numbers and arithmetic operations, to a dedicated module in
mathics.core.eval
.There are several motivations to do this. In the first place, the code in master is quite involved, and not very well documented. Also, implies conversions back and forward to SymPy. Moreover, the way in which sympy evals arithmetic expressions is not fully compatible with the expected behavior in WMA. Then, more conversions and hacking over the returned code were needed.
With the code in this branch, I hope the logic is a little bit clearer, and several useless conversions are avoided. Also, the issue with
DirectedInfinity
that prevents the application of some rules associated withGudermannian
and other complex functions should be fixed now.