Repository: mpmath/mpmath
Pull request #769 (2024-03-30 11:35:06)

v1.4.0a1
Lets include some compatibility wrappers for sympy and co, then do alpha release.

oscarbenjamin: Thanks for doing this. These changes look good.

Actually right now the best thing would just be for mpmath to hold off on making any prereleases though. Right now things seem to be working and there is a risk that further changes might cause further problems. I don't know what strange things people are doing downstream with packaging tools...

I would like to get a sympy release out with an upper cap on mpmath: https://github.com/sympy/sympy/pull/26413 https://github.com/sympy/sympy/issues/26273

Then it should be possible for mpmath to put out prereleases without anything breaking.
skirpichev: > right now the best thing would just be for mpmath to hold off on making any prereleases though

Ok.  Though, I think changes aren't too big:
* 22859b0 Merge pull request #711 from skirpichev/hex
* afb829b Merge pull request #759 from skirpichev/misc
* d294c04 Merge pull request #761 from skirpichev/pickling
* 3512b74 Merge pull request #763 from skirpichev/misc
* 4c1b435 Merge pull request #767 from skirpichev/misc
* aa1c0b9 Merge pull request #770 from ChefAharoni/patch-1


> Then it should be possible for mpmath to put out prereleases without anything breaking.

Unless someone will install sympy pre-release...
oscarbenjamin: > > Then it should be possible for mpmath to put out prereleases without anything breaking.
> 
> Unless someone will install sympy pre-release...

I think we just need to ensure that each release of sympy whether it is a full release or a prerelease has an upper cap on mpmath and is fully tested with that capped version.

I want to cap `mpmath < 1.4` so that a mpmath 1.3.1 release would still be picked up in case there is a need to put out 1.3.1 e.g. for Python 3.13 support or something like that.
skirpichev: > we just need to ensure that each release of sympy whether it is a full release or a prerelease has an upper cap on mpmath

Limiting upper versions does make sense for stable releases.  But for pre-releases - I don't think so, unless you have **many** deps.  People should install pre-releases only intentionally and with understanding of consequences.
oscarbenjamin: > People should install pre-releases only intentionally and with understanding of consequences.

The purpose of installing the prerelease is to test in advance what can be expected to happen once the final release is released so there should not be any difference in behaviour. I think that a pre-release of e.g. sympy should have version constraints that match what the future final release would be expected to have. For example if I `pip install sympy==1.13.0rc1` then I would hope that this gets the same version of mpmath that `pip install sympy==1.13.0` would be expected to get if there are no changes between rc1 and final.
skirpichev: > I think that a pre-release of e.g. sympy should have version constraints that match what the future final release would be expected to have.

That depends, e.g. clearly make sense for late rc-releases.  But alpha- and beta-releases - also pre-releases c.f. pypi point of view.
oscarbenjamin: I would say that the alpha/beta releases are where you bump the dependency constraints.

If someone wants to use `pip install --pre` then they can install prereleases of everything transitively and deal with the bleeding edge consequences. If they just track prereleases of sympy though then the bumped version constraints would be part of what they are tracking.
skirpichev: Ok, I did tests for mpq in domains - that works too.  The only missing thing from https://github.com/sympy/sympy/pull/25290/commits/eef227fbac2c3542758379277727d9aafc7229fd is ``from_str()`` changes.

It's support for underscores, 9585bb9.  Obviously, it's a compatibility break.  I think it's a good idea to support this, but we could implement that differently.  The current code uses gmpy2 approach: just filter out underscores.  This accepts valid python strings, but also much more.  Instead, we could choose to be strictly python-compatible.  Let me know if it worth to reopen #613 for discussion.  But see also aleaxit/gmpy#210.


oscarbenjamin: > What do you think?

I think that sympy reaches into mpmath's internals too much and it is inevitable that if mpmath is actively developed then future versions of mpmath are not going to be compatible with current versions of sympy. We can coordinate this to keep sympy up to date with latest mpmath changes but we need sympy releases to have a version cap like `mpmath < 1.4`.

The first thing to consider though before deprecating or removing things is what will ultimately be the public API that should be used instead. Currently the awkward thing about mpmath's documented API is that it is all based on contexts and the idea of a global precision. What is nice about the libmp functions is that they are pure functions that take an explicit precision argument.
skirpichev: > We can coordinate this to keep sympy up to date with latest mpmath changes

As a first step for the sympy master, I suggest something like https://github.com/diofant/diofant/pull/1361/commits/1dd72fbf4bd9c2cf28fa3d7de850fd3f82359dc5: all required libmp's imports (including the ``giant_steps()``) could be imported from the ``mpmath.libmp`` namespace.  (``from mpmath.libmp import foo, bar`` or ``import mpmath.libmp as libmp``)

> that it is all based on contexts and the idea of a global precision.

And contexts serve both to hold some attributes (like precision or rounding mode) and as namespaces for functions (namespaces, where we actually have same set of mathematical functions for all contexts).

> What is nice about the libmp functions is that they are pure functions that take an explicit precision argument.

Yet the namespace of libmp is huge:
```pycon
>>> len(libmp_names)  # not prefixed by '_'
320
```
While e.g. SymPy (v1.12) is using:
```
>>> len(sympy_names)  # from above script, maybe I miss something
78
```

Not all functions have common naming pattern (e.g. ``mpf_foo``), common arguments convention (say, function arguments, precision, optional rounding mode), have different defaults for prec/rnd, etc...

I think we could start with something, that fits SymPy's needs, with few functions being deprecated (e.g. to/from_pickable() or bitcount()) and treat the rest of libmp as private stuff.

skirpichev: Ok, let me know when I can release a new alpha.
oscarbenjamin: The latest sympy prerelease (`sympy 1.12.1rc1`) pins mpmath < 1.4.0 so it should be fine. Go ahead and put out prereleases if you want to get them out.

Thanks.

Patch:
diff --git a/mpmath/__init__.py b/mpmath/__init__.py
index c8224e6..c036c13 100644
--- a/mpmath/__init__.py
+++ b/mpmath/__init__.py
@@ -13,6 +13,10 @@
 from .ctx_mp import MPContext
 from .ctx_iv import MPIntervalContext
 
+# deprecated modules
+from . import rational
+from . import math2
+
 fp = FPContext()
 mp = MPContext()
 iv = MPIntervalContext()
@@ -452,4 +456,4 @@ def _helper(self, *args, prop=''):
 
 
 sys.modules[__name__].__class__ = _MPMathModule
-del functools, sys, types
+del functools, sys, types, _MPMathModule
diff --git a/mpmath/libmp/__init__.py b/mpmath/libmp/__init__.py
index 56a8e34..e075206 100644
--- a/mpmath/libmp/__init__.py
+++ b/mpmath/libmp/__init__.py
@@ -44,17 +44,18 @@
                      mpf_expj, mpf_expjpi)
 from .libmpf import (ComplexResult, dps_to_prec, fhalf, finf, fnan, fninf,
                      fnone, fnzero, fone, from_Decimal, from_float, from_int,
-                     from_man_exp, from_npfloat, from_rational, from_str, ften,
-                     ftwo, fzero, mpf_abs, mpf_add, mpf_ceil, mpf_cmp, mpf_div,
-                     mpf_eq, mpf_floor, mpf_frac, mpf_frexp, mpf_ge, mpf_gt,
-                     mpf_hash, mpf_hypot, mpf_le, mpf_lt, mpf_mod, mpf_mul,
-                     mpf_mul_int, mpf_neg, mpf_nint, mpf_perturb, mpf_pos,
-                     mpf_pow_int, mpf_rand, mpf_rdiv_int, mpf_shift, mpf_sign,
-                     mpf_sqrt, mpf_sub, mpf_sum, normalize, prec_to_dps,
-                     repr_dps, round_ceiling, round_down, round_floor,
-                     round_int, round_nearest, round_up, str_to_man_exp,
-                     to_digits_exp, to_fixed, to_float, to_int, to_man_exp,
-                     to_rational, to_str)
+                     from_man_exp, from_npfloat, from_pickable, from_rational,
+                     from_str, ften, ftwo, fzero, mpf_abs, mpf_add, mpf_ceil,
+                     mpf_cmp, mpf_div, mpf_eq, mpf_floor, mpf_frac, mpf_frexp,
+                     mpf_ge, mpf_gt, mpf_hash, mpf_hypot, mpf_le, mpf_lt,
+                     mpf_mod, mpf_mul, mpf_mul_int, mpf_neg, mpf_nint,
+                     mpf_perturb, mpf_pos, mpf_pow_int, mpf_rand, mpf_rdiv_int,
+                     mpf_shift, mpf_sign, mpf_sqrt, mpf_sub, mpf_sum,
+                     normalize, prec_to_dps, repr_dps, round_ceiling,
+                     round_down, round_floor, round_int, round_nearest,
+                     round_up, str_to_man_exp, to_digits_exp, to_fixed,
+                     to_float, to_int, to_man_exp, to_pickable, to_rational,
+                     to_str)
 from .libmpi import (mpci_abs, mpci_add, mpci_cos, mpci_div, mpci_exp,
                      mpci_factorial, mpci_gamma, mpci_log, mpci_loggamma,
                      mpci_mul, mpci_neg, mpci_pos, mpci_pow, mpci_rgamma,
diff --git a/mpmath/libmp/libmpf.py b/mpmath/libmp/libmpf.py
index 2d3faf9..94501f0 100644
--- a/mpmath/libmp/libmpf.py
+++ b/mpmath/libmp/libmpf.py
@@ -16,6 +16,18 @@
                          stddigits, trailtable)
 
 
+def to_pickable(x):
+    warnings.warn("to_pickable helper function is deprecated",
+                  DeprecationWarning)
+    return x
+
+
+def from_pickable(x):
+    warnings.warn("from_pickable helper function is deprecated",
+                  DeprecationWarning)
+    return x
+
+
 class ComplexResult(ValueError):
     pass
 
diff --git a/mpmath/math2.py b/mpmath/math2.py
new file mode 100644
index 0000000..80e3d4c
--- /dev/null
+++ b/mpmath/math2.py
@@ -0,0 +1,7 @@
+import warnings
+
+def __getattr__(name):
+    warnings.warn("the math2 module is deprecated, use libfp instead",
+                  DeprecationWarning)
+    from . import libfp
+    return getattr(libfp, name)
diff --git a/mpmath/rational.py b/mpmath/rational.py
new file mode 100644
index 0000000..b4ba024
--- /dev/null
+++ b/mpmath/rational.py
@@ -0,0 +1,11 @@
+import warnings
+
+def __getattr__(name):
+    warnings.warn("the rational private module is deprecated",
+                  DeprecationWarning)
+    if name == 'mpq':
+        from .libmp.backend import Fraction
+        class mpq(Fraction):
+            _mpq_ = property(Fraction.as_integer_ratio)
+        return mpq
+    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


Test patch:
diff --git a/conftest.py b/conftest.py
index 2b58f61..3163aaf 100644
--- a/conftest.py
+++ b/conftest.py
@@ -6,6 +6,10 @@
 import mpmath
 
 
+collect_ignore = ['mpmath/__init__.py',
+                  'mpmath/rational.py', 'mpmath/math2.py']
+
+
 def pytest_report_header(config):
     print("mpmath backend: %s" % mpmath.libmp.backend.BACKEND)
     print("mpmath mp class: %s" % repr(mpmath.mp))
diff --git a/mpmath/tests/test_basic_ops.py b/mpmath/tests/test_basic_ops.py
index d66764e..121de90 100644
--- a/mpmath/tests/test_basic_ops.py
+++ b/mpmath/tests/test_basic_ops.py
@@ -4,13 +4,14 @@
 
 import pytest
 
+import mpmath
 from mpmath import (ceil, fadd, fdiv, floor, fmul, fneg, fp, frac, fsub, inf,
                     isinf, isint, isnan, isnormal, iv, monitor, mp, mpc, mpf,
                     mpi, nan, ninf, nint, nint_distance, pi, workprec)
 from mpmath.libmp import (MPQ, finf, fnan, fninf, fnone, fone, from_float,
-                          from_int, from_str, mpf_add, mpf_mul, mpf_sub,
-                          round_down, round_nearest, round_up, to_int,
-                          to_man_exp)
+                          from_int, from_pickable, from_str, mpf_add, mpf_mul,
+                          mpf_sub, round_down, round_nearest, round_up, to_int,
+                          to_man_exp, to_pickable)
 
 
 def test_type_compare():
@@ -540,6 +541,24 @@ def test_to_man_exp_deprecation():
     with pytest.deprecated_call():
         to_man_exp(fnone)
 
+def test_rational_deprecation():
+    with pytest.deprecated_call():
+        assert mpmath.rational.mpq(1, 2) == MPQ(1, 2)
+
+
+def test_math2_deprecation():
+    with pytest.deprecated_call():
+        assert mpmath.math2.log == mpmath.libfp.log
+
+
+def test_to_from_pickable():
+    x = mpf(1.2)._mpf_
+    with pytest.deprecated_call():
+        assert to_pickable(x) == x
+    with pytest.deprecated_call():
+        assert from_pickable(x) == x
+
+
 def test_rand_precision():
     """
     Test precision of rand()