-
-
Notifications
You must be signed in to change notification settings - Fork 55
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
allow conditions being evaluated in Solve #918
base: master
Are you sure you want to change the base?
Conversation
@BlankShrimp thanks for looking at this. Could you please add some examples to the doctests? |
Done. A few cases have been added. |
a0b44a3
to
00d39ee
Compare
mathics/builtin/numbers/calculus.py
Outdated
@@ -2254,8 +2255,7 @@ def eval(self, eqs, vars, evaluation: Evaluation): | |||
elif eq is SymbolFalse: | |||
return ListExpression() | |||
elif not eq.has_form("Equal", 2): | |||
evaluation.message("Solve", "eqf", eqs) | |||
return | |||
sympy_conditions.append(eq.to_sympy()) |
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.
It must be checked that eq is a condition, and has a sympy form.
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 test Solve[{x^2==4 && x < 0 && x > -4},{x}]
fails because here the first argument is treated as a single condition x^2==4 && x < 0 && x > -4
without any equation. Conjunctions should be decomposed to work.
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.
It must be checked that eq is a condition,
It's my bad, I'll try to investigate the way to do this.
and has a sympy form.
I noticed this issue while writing these lines, but I think this problem is out of the scope of this contribution. I have little knowledge over Mathics, so if I said anything wrong please do point out. My argument is that anything that to_sympy() can't translate to a sympy expression shouldn't be considered as valid. In this case, the exception would catch the error and fail the expression.
The test
Solve[{x^2==4 && x < 0 && x > -4},{x}]
fails because here the first argument is treated as a single conditionx^2==4 && x < 0 && x > -4
without any equation. Conjunctions should be decomposed to work.
Agreed. Actually, I noticed this issue too while writing & testing, and put this test case intentionally looking for your opinion. If you could try Solve[a^2 - 3a + 2 == 0 && a > 1, a]
with my code, you will find it works perfectly. I think this is a parsing issue, the parser can't always decompose conjunctions correctly. In fact, I also noticed that the parser can't handle things like <some eqs> && <condition1> || <condition2>
neither, which produces a non-sympy object. In any casaes, I think this problem is also out of the scope of this contribution.
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.
@BlankShrimp the parser can't always decompose conjunctions correctly.
Aha, no doubt I have little knowledge over Mathics. There's nothing wrong with Mathics, it's my job to decompose the conjunction inside solve.eval(). Wait for my code.
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.
Agreed. Actually, I noticed this issue too while writing & testing, and put this test case intentionally looking for your opinion. If you could try
Solve[a^2 - 3a + 2 == 0 && a > 1, a]
with my code, you will find it works perfectly. I think this is a parsing issue, the parser can't always decompose conjunctions correctly. In fact, I also noticed that the parser can't handle things like<some eqs> && <condition1> || <condition2>
neither, which produces a non-sympy object. In any casaes, I think this problem is also out of the scope of this contribution.
@BlankShrimp : Indeed, your example works, but Solve[{x^2==4 && x<0}, x]
(the conjuction inside a list) doesn't. To do it right, the interpretation of the first argument should be done recursively.
This could be a way to do that: replace the loop over eq_list
by
def process_equation(eq):
if eq is SymbolTrue:
pass
elif eq.has_form("And", None):
for sub_eq in eq.elements:
process_equation(sub_eq)
elif eq.has_form("Equal", 2):
left, right = eq.elements
left = left.to_sympy()
right = right.to_sympy()
if left is None or right is None:
return
eq = left - right
eq = sympy.together(eq)
eq = sympy.cancel(eq)
sympy_eqs.append(eq)
numer, denom = eq.as_numer_denom()
sympy_denoms.append(denom)
else:
cond_sympy = eq.to_sympy()
if cond_sympy is None:
evaluation.message("Solve", "eqf", eqs)
raise ValueError(str(eq))
sympy_conditions.append(cond_sympy)
return None
for eq in eq_list:
if eq is SymbolFalse:
return ListExpression()
try:
process_equation(eq)
except ValueError:
return None
With this change, and one on the test on the existence of equations (if not sympy_eqs
-> if not sympy_eqs and sympy_conditions
) all the tests pass.
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.
On first look, it feels like the purpose of the above is to collect sympy conditions. And this feels like something that might come up other places as well, right? Perhaps this should be put inside mathics.core.convert.sympy
then
Small thing: instead of if ... pass
, if ... return None
is more direct.
mathics/builtin/numbers/calculus.py
Outdated
@@ -2269,6 +2269,10 @@ def eval(self, eqs, vars, evaluation: Evaluation): | |||
numer, denom = eq.as_numer_denom() | |||
sympy_denoms.append(denom) | |||
|
|||
if not sympy_eqs: |
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 breaks an existing test: Solve[True,x]
should return {{}}
without warnings. On the other hand,
Solve[{x>5}, x]
produces the message:
Solve::fulldim: The solution set contains a full-dimensional component; use Reduce for complete solution information.
and
Solve[Q, x]
produces
Solve::naqs: Q is not a quantified system of equations and inequalities.
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.
Oh god the warning issue was overlooked, I'll look into it.
@BlankShrimp tests are not passing. Please check the comments. |
test/builtin/calculus/test_solve.py
Outdated
), | ||
( | ||
"Solve[{x^2==4 && x < 0 && x > -4},{x}]", | ||
"{x->-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.
In WMA, the output is "{{x -> -2}}"
test/builtin/calculus/test_solve.py
Outdated
@@ -42,6 +42,16 @@ def test_solve(): | |||
"{x->1.51213}", | |||
"Issue #1235", | |||
), | |||
( | |||
"Solve[{x^2==4 && x < 0},{x}]", | |||
"{x->-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.
In WMA, the output is "{{x -> -2}}"
edf05e5
to
6d3d7c7
Compare
I have been busy recently, so I haven't had a chance to look about or think about this in detail. I will mention some things that you can't really be expected to know. Originally, the builtin-function code (under the In addition to making the code hard to read, this organization was/is slow too. If we ever have a hope of speeding this up, we have to pull out at least evaluation code to make it possible to serialize instructions rather that walk a M-Expression tree. Basically this means functions like Because of the bulk of existing code, we haven't done that fully. So that's why you find these functions still there. The top-level question I have is whether this kind of thing is only used in If it is a more general thing then these functions would go in |
When @BlankShrimp finishes with this work, then I can move the code to eval / convert module. |
I'm currently looking at this. What's the difference between |
In my view, things back in 1.0 were a little haphazard or whimsical and didn't follow rigid thinking. In some cases, terminology was just plain wrong. Most of the time, things were in coded inside the builtin routine either inside a class there or as a module-level method. For Here is the current thinking though.... What goes into the In the current thinking, this is all that should be put in there. Rules for how to Box, or how to format to SVG, LaTeX, or common evaluation routines and their helpers should not be there. It even used to be that unit tests were put in the docstring in a non-standard encoding (not sphinx) format. The organization of the modules inside the builtins follows (roughly) the "Guide "section organization of WMA. Where this gets off is that the WMA hierarchy is more DAG like than tree like. Or in other words a single WMA builtin can appear in more than one Guide section. So here we just have to pick one to place the builtin. As I mentioned before, evaluation methods are there so that one has a chance of writing an interpreter which evaluates a linear sequence of instructions rather than walking an expression tree over possibly repeatedly. The modules in the Some of this is mentioned in https://mathics-development-guide.readthedocs.io/en/latest/extending/code-overview/python-modules.html and other places in that guide, but I see that I probably need to add more information. |
From the discussion in PR #918 (#918 (comment)), I remembered that this was something pendant for a while, so I started moving these modules to follow the new organization of the code.
737178d
to
8e10644
Compare
alternate old plain solve logic with a recursive one. Now Solve is capable with nested logics
1. added one missed line and a few catches 2. replace function type hint with 3.9 compatible style
1. bring domain check back into solve.eval, so that things like Abs() can be evaluated 2. create system symbols such as System`Reals for domain check 3. refactor, moving most logics out of Solve
8e10644
to
0ba1a33
Compare
@@ -5,6 +5,7 @@ | |||
Conversion to SymPy is handled directly in BaseElement descendants. | |||
""" | |||
|
|||
from collections.abc import Iterable |
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 Iterable
class that supports typing (Iterable[sympy.Symbol]
) is in the typing
module. You should also import Dict
, List
and Set
for the following annotations.
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.
typing.Iterable is deprecated. https://docs.python.org/3/library/typing.html#typing.Iterable
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.
You are right. However, as we still support 3.8, we should do a conditional import.
return result | ||
|
||
|
||
def cut_dimension(evaluation, expressions: Union[Expression, list[Expression]], symbols: Iterable[sympy.Symbol]) -> set[sympy.Symbol]: |
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.
List[Expression]
and Set[sympy.Symbol]
(notice the caps)
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.
list[...] is the better choice. https://peps.python.org/pep-0585/
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.
Still, notice that at least until 3.8, you should use the capitalized name (List[...])
SymbolRule, | ||
SymbolSequence, | ||
SymbolSeries, | ||
SymbolSeriesData, | ||
SymbolSimplify, | ||
SymbolUndefined, | ||
) | ||
from mathics.eval.calculus import solve_sympy |
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.
mathics.eval.numbers.calculus.solvers
and the module was not added to the branch.
@@ -664,8 +668,8 @@ def eval(self, f, x, x0, evaluation: Evaluation, options: dict): | |||
|
|||
# Determine the "jacobian"s | |||
if ( | |||
method in ("Newton", "Automatic") | |||
and options["System`Jacobian"] is SymbolAutomatic | |||
method in ("Newton", "Automatic") and |
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 looks like a fight between versions of black / autopep8.
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 Github Actions, we are using the following versions:
'click==8.0.4' 'black==22.3.0' 'isort==5.10.1'
For expressions like Solve[x+1==0 && x<1, x], Mathics3 unexpectedly blocks the ability to handle the conjuncted logics when determine whether the expression is well-formed. This PR unlocks this ability by putting off the check after all eqs were processed, and using sympy.And().subs() to filter solutions using conjuncted logics.