Changed the function called by sqrt from mpmath to math#25111
Changed the function called by sqrt from mpmath to math#25111oscarbenjamin merged 4 commits intosympy:masterfrom
Conversation
`sympy.external.gmpy.sqrt` used to use mpmath when gmpy was not used, but `math.isqrt` has been implemented since Python 3.8, so this is now used. Also, `sympy.core.power.isqrt` now calls `sympy.external.gmpy.sqrt`.
|
✅ Hi, I am the SymPy bot. I'm here to help you write a release notes entry. Please read the guide on how to write release notes.
Click here to see the pull request description that was parsed. |
|
Benchmark results from GitHub Actions Lower numbers are good, higher numbers are bad. A ratio less than 1 Significantly changed benchmark results (PR vs master) Significantly changed benchmark results (master vs previous release) before after ratio
[2c3de5f4] [e0235b4b]
- 83.4±1ms 55.4±0.2ms 0.66 integrate.TimeIntegrationRisch02.time_doit(10)
- 82.5±1ms 54.7±0.2ms 0.66 integrate.TimeIntegrationRisch02.time_doit_risch(10)
Full benchmark results can be found as artifacts in GitHub Actions |
|
Have you run any timings to see how It is not necessarily faster so we should check: #17038 (comment) It is important to test with both small examples but also extremely large examples and to test both square and non-square numbers. |
|
The following three functions are compared in time. from math import sqrt, isqrt
from sympy.core.power import integer_nthroot
import mpmath.libmp as mlib
def isqrt_sympy(n):
n = int(n)
if n < 4503599761588224:
s = int(sqrt(n))
if 0 <= n - s*s <= 2*s:
return s
return integer_nthroot(n, 2)[0]
def isqrt_mpmath(n):
return mlib.isqrt(n)
def isqrt_math(n):
return isqrt(n)import time
import matplotlib.pyplot as plt
import seaborn as sns
sympy_time = []
mpmath_time = []
math_time = []
r = list(range(1, 300))
times = 100_000
micro = 1_000_000
for k in r:
n = 2**k
start = time.perf_counter()
for _ in range(times):
isqrt_sympy(n)
sympy_time.append((time.perf_counter() - start) / times * micro)
start = time.perf_counter()
for _ in range(times):
isqrt_mpmath(n)
mpmath_time.append((time.perf_counter() - start) / times * micro)
start = time.perf_counter()
for _ in range(times):
isqrt_math(n)
math_time.append((time.perf_counter() - start) / times * micro)
sns.lineplot(x=r, y=sympy_time, label='sympy')
sns.lineplot(x=r, y=mpmath_time, label='mpmath')
sns.lineplot(x=r, y=math_time, label='math')
plt.xlabel('log_2 n')
plt.ylabel('elapsed time [micro sec]')
plt.legend()
plt.savefig('001.png')This result seems to indicate that |
|
I would be wary about only timing with powers of 2. Computing the square root of a power of 2 means that you have a number with a binary representation like
I see the same. I expect it is because the algorithm is different as highlighted by @mdickinson in #17038 (comment):
For now it looks like we can do: def isqrt_math_mpmath(n):
if n.bit_length() < 55_000:
return isqrt(n)
else:
return isqrt_mpmath(n)Actually though it would be better to add that logic to mpmath. I think that SymPy should use mpmath's |
|
Out of curiosity, what version of Python were the timings run with? It could be interesting to try with Python 3.12, where integer division is no longer asymptotically quadratic. |
|
@oscarbenjamin Very interesting; thank you for doing the testing. I've been meaning to retest the performance of That gmpy2 massively outperforms |
They look asymptotically similar though which is good. Which operations exactly have changed in 3.12? I can't see the |
|
I'm trying to revert gmpy.py to an previous version, do I need to cast |
A Python (yes, really!) fast path was added for int<->decimal string and int<->Decimal conversions, as well as divmod.
It's here. I don't think it got moved. |
Okay so no FFT multiplication yet? I'm sure I saw an implementation of that somewhere. Presumably then isqrt became faster because it uses divmod. Is it fair to say that the mpmath implementation is largely a workaround for poor asymptotics of divmod? Or is it something that could also be improved by taking advantage of faster divmod (I haven't looked at how it actually works)? |
We would only use it if gmpy2 is not installed in which case mpmath would be using plain int as well. Although I suppose it is also possible that someone could set |
|
Okay, the cast to int will remain. |
Right. Maybe someday.
Yes - for large inputs, the I haven't looked at the mpmath implementation, so I don't have a basis for comparison. |
|
Looks good, merging. |


sympy.external.gmpy.sqrtused to use mpmath when gmpy was not used, butmath.isqrthas been implemented since Python 3.8, so this is now used. Also,sympy.core.power.isqrtnow callssympy.external.gmpy.sqrt.References to other Issues or PRs
#25067
Brief description of what is fixed or changed
Other comments
Release Notes
NO ENTRY