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
Add DomainMatrix for faster rref in solve #18844
Conversation
✅ Hi, I am the SymPy bot (v160). I'm here to help you write a release notes entry. Please read the guide on how to write release notes. Your release notes are in good order. Here is what the release notes will look like:
This will be added to https://github.com/sympy/sympy/wiki/Release-Notes-for-1.7. Note: This comment will be updated with the latest check if you edit the pull request. You need to reload the page to see it. Click here to see the pull request description that was parsed.
Update The release notes on the wiki have been updated. |
23917b9
to
a829783
Compare
This massively increases the speed of solve when solving linear equations that have symbols in e.g.: def dosolve(n):
y = Symbol('y')
syms = symbols('x:%s' % n)
eqs = list(y*randMatrix(n, n+1) * Matrix([syms+(1,)]).T)
sol = solve(eqs, syms)
return eqs, sol, syms That makes a system like In [3]: eqs, sol, syms = dosolve(5)
In [4]: eqs
Out[4]:
[57⋅x₀⋅y + 14⋅x₁⋅y + 30⋅x₂⋅y + 40⋅x₃⋅y + 50⋅x₄⋅y + 80⋅y, 60⋅x₀⋅y + 50⋅x₁⋅y + 49⋅x₂⋅y + 5⋅x₃⋅y + 5⋅x₄⋅y + 71⋅y, 38⋅x₀⋅y + 59⋅x₁⋅y + 31⋅x₂⋅y + 43⋅x₃⋅y + 58⋅x₄
⋅y + 98⋅y, 92⋅x₀⋅y + 5⋅x₁⋅y + 50⋅x₂⋅y + 64⋅x₃⋅y + 47⋅x₄⋅y + 89⋅y, 83⋅x₀⋅y + 49⋅x₁⋅y + 35⋅x₂⋅y + 50⋅x₃⋅y + 96⋅x₄⋅y + 47⋅y]
In [5]: sol
Out[5]:
⎧ 120850754 57073183 -239313614 25717805 -72558796 ⎫
⎨x₀: ─────────, x₁: ────────, x₂: ───────────, x₃: ────────, x₄: ──────────⎬
⎩ 26138981 26138981 26138981 26138981 26138981 ⎭
In [6]: syms
Out[6]: (x₀, x₁, x₂, x₃, x₄) On master we have: In [7]: for n in range(10):
...: print()
...: print(n)
...: %time ok=dosolve(n)
...:
0
CPU times: user 384 µs, sys: 406 µs, total: 790 µs
Wall time: 442 µs
1
CPU times: user 9.63 ms, sys: 1.64 ms, total: 11.3 ms
Wall time: 10.1 ms
2
CPU times: user 35.1 ms, sys: 388 µs, total: 35.5 ms
Wall time: 36.3 ms
3
CPU times: user 85.5 ms, sys: 833 µs, total: 86.4 ms
Wall time: 86.8 ms
4
CPU times: user 164 ms, sys: 1.1 ms, total: 165 ms
Wall time: 166 ms
5
CPU times: user 453 ms, sys: 3.71 ms, total: 456 ms
Wall time: 460 ms
6
CPU times: user 2.25 s, sys: 19.3 ms, total: 2.27 s
Wall time: 2.28 s
7
CPU times: user 6.63 s, sys: 34.5 ms, total: 6.66 s
Wall time: 6.72 s
8
CPU times: user 57.9 s, sys: 426 ms, total: 58.4 s
Wall time: 1min 1s
9
^C (I interrupted because it was too slow) Repeating that with this PR we get In [2]: for n in range(10):
...: print()
...: print(n)
...: %time ok=dosolve(n)
...:
0
CPU times: user 760 µs, sys: 425 µs, total: 1.19 ms
Wall time: 1.11 ms
1
CPU times: user 12.6 ms, sys: 1.87 ms, total: 14.4 ms
Wall time: 13.9 ms
2
CPU times: user 26.4 ms, sys: 917 µs, total: 27.4 ms
Wall time: 27.2 ms
3
CPU times: user 40.5 ms, sys: 440 µs, total: 40.9 ms
Wall time: 41.1 ms
4
CPU times: user 57.8 ms, sys: 422 µs, total: 58.2 ms
Wall time: 58.4 ms
5
CPU times: user 76.5 ms, sys: 652 µs, total: 77.2 ms
Wall time: 77.7 ms
6
CPU times: user 104 ms, sys: 635 µs, total: 105 ms
Wall time: 105 ms
7
CPU times: user 130 ms, sys: 696 µs, total: 130 ms
Wall time: 131 ms
8
CPU times: user 156 ms, sys: 669 µs, total: 157 ms
Wall time: 157 ms
9
CPU times: user 201 ms, sys: 1.04 ms, total: 202 ms
Wall time: 202 ms |
045b4e6
to
f51ebdc
Compare
I think that this approach may start to add a lot of duplicate methods only to operate on a more crude objects. |
I don't think that there will need to be many duplicate methods. From discussions elsewhere I think it was concluded that a high-level matrix class could be built from a low-level one that only implemented a few key methods like The speed improvements are enormous and it also solves the same problems that dotprodsimp doesdoes but in a more natural way. Try the example from #18814 (comment) to see the speed difference. With the PR we have In [12]: eqs, p, y = _mk_eqs(2)
In [13]: %time ok=solve(eqs, *p.c)
CPU times: user 88.2 ms, sys: 697 µs, total: 88.9 ms
Wall time: 90.2 ms
In [14]: eqs, p, y = _mk_eqs(3)
In [15]: %time ok=solve(eqs, *p.c)
CPU times: user 400 ms, sys: 1.55 ms, total: 402 ms
Wall time: 404 ms
In [16]: eqs, p, y = _mk_eqs(4)
In [17]: %time ok=solve(eqs, *p.c)
CPU times: user 1.02 s, sys: 4.96 ms, total: 1.02 s
Wall time: 1.03 s With master we have In [2]: eqs, p, y = _mk_eqs(2)
In [3]: %time ok=solve(eqs, *p.c)
CPU times: user 962 ms, sys: 2.88 ms, total: 965 ms
Wall time: 965 ms
In [4]: eqs, p, y = _mk_eqs(3)
In [5]: %time ok=solve(eqs, *p.c)
CPU times: user 5.42 s, sys: 12 ms, total: 5.44 s
Wall time: 5.45 s
In [6]: eqs, p, y = _mk_eqs(4)
In [7]: %time ok=solve(eqs, *p.c)
CPU times: user 34.5 s, sys: 147 ms, total: 34.6 s
Wall time: 35 s Next steps for this PR are to try and use this in place of |
14b85d0
to
e382280
Compare
Okay, if it is only a few key methods and not intended to be extended heavily from the mass |
0905499
to
64d0a4f
Compare
Codecov Report
@@ Coverage Diff @@
## master #18844 +/- ##
=============================================
+ Coverage 75.662% 75.673% +0.010%
=============================================
Files 658 658
Lines 171003 171211 +208
Branches 40353 40412 +59
=============================================
+ Hits 129385 129561 +176
- Misses 35952 35983 +31
- Partials 5666 5667 +1 |
427d4ba
to
2e4fbab
Compare
This PR is almost ready but I would like to discuss what exactly to do with the new code that is added. Specifically what should be done with the DomainMatrix class? The PR adds a DomainMatrix class in sympy/polys/polymatrix.py along with the PolyMatrix class. The DomainMatrix class is based on the polys domains and uses DomainElements as elements. It gives a much faster rref in a number of cases solving many examples that would otherwise hang. However this PR does not put this as a replacement for Matrix.rref. Instead the The Since The other change here is that the In #18445 there was discussion of adding The question is if Because of the confusion about the relationship between |
In my experiments, I don't think that sympy objects can wrap around non-sympy objects easily. Because there needs to be an another sympy object that wrap around that object, and the problem just recurses down. So in my opinion, I think that everything should be done in the basis assuming I see this approach is also used in So we can have the matrix functions with I also think that this idea is futureproof because even if it can be possible to tie the domain together with Matrix objects, the keyword argument The only cons of this is approach is that
|
The things I find inconvenient in the current implementation of DomainMatrix is that it lacks a lot of convenience methods like multidimensional slicing, submatrix indexing, which often gets handy |
At this stage it is deliberate that DomainMatrix is not a full-featured matrix class. I certainly wouldn't want it to have as wide an API as Matrix does but its role needs to be clarified before adding any other API to it. For the purposes here the class itself is not needed and a list of lists/dicts would be perfectly manageable. The question is where we would want to take this in future. |
I think that it can be provided as an optional API under Polys after it have lots of self-contained features for operating in a lower level of sympy. |
eqs = system | ||
try: | ||
eqs, ring = sympy_eqs_to_ring(eqs, symbols) | ||
except PolynomialError as exc: |
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.
Does this need to recognize ValueError?
File "/home/travis/virtualenv/python3.8.0/lib/python3.8/site-packages/sympy-1.7.dev0-py3.8.egg/sympy/solvers/solveset.py", line 2525, in sympy.solvers.solveset.linsolve
Failed example:
linsolve([x**2 - 1], x)
Expected:
Traceback (most recent call last):
...
NonlinearError:
nonlinear term encountered: x**2
Got:
Traceback (most recent call last):
File "/opt/python/3.8.0/lib/python3.8/doctest.py", line 1328, in __run
exec(compile(example.source, filename, "single",
File "<doctest sympy.solvers.solveset.linsolve[23]>", line 1, in <module>
linsolve([x**2 - 1], x)
File "/home/travis/virtualenv/python3.8.0/lib/python3.8/site-packages/sympy-1.7.dev0-py3.8.egg/sympy/solvers/solveset.py", line 2564, in linsolve
sol = solve_lin_sys(eqs, ring, _raw=False)
File "/home/travis/virtualenv/python3.8.0/lib/python3.8/site-packages/sympy-1.7.dev0-py3.8.egg/sympy/polys/solvers.py", line 73, in solve_lin_sys
eq_coeffs = {ring.gens[monom.index(1)]:coeff for monom, coeff in eq_dict.items()}
File "/home/travis/virtualenv/python3.8.0/lib/python3.8/site-packages/sympy-1.7.dev0-py3.8.egg/sympy/polys/solvers.py", line 73, in <dictcomp>
eq_coeffs = {ring.gens[monom.index(1)]:coeff for monom, coeff in eq_dict.items()}
ValueError: tuple.index(x): x not in tuple
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'm not sure. We have NonlinearError
now which should probably be raised in this case.
Add docs for the solve_lin_sys and related functions
Fix test for eqs_to_matrix
Don't recompute the split after recursively calling solve. Since solve computes the connected components of the system of equations and the pass each back to solve recursively the connected components get recomputed in the second call. This makes sure that the connected components are not recomputed in the recursive call.
Make sure that linsolve raises NonlinearError rather than PolyNonlinearError
Update tests in solvers
ffaf4f3
to
7f470b1
Compare
This PR is now ready for review or merge |
Thanks @sylee957 and thanks to everyone else for reviewing and helping |
Hi, this PR causes output in matrix operations to result in over complicated results and has an effect on the physics mechanics codes. See this issue for an explanation: #23130 This commit causes the regression: f0b7996 The results are mathematically equivalent but the expressions are longer and introduce significantly longer equations in complex multi-body systems (i.e. after passed through KanesMethod/LagrangesMethod). This is an undesired effect. It is a case were the output, even though mathematically the same, shouldn't have changed. |
Is there an option to not use this DomainMatrix underlying implementation? If so, I can disable it for the physics vector/mechanics codes. |
References to other Issues or PRs
This is built on top of #18814 and adds the DomainMatrix class from #18445
Brief description of what is fixed or changed
Other comments
Release Notes