Skip to content

Commit

Permalink
fix gmpy2 support (#1026)
Browse files Browse the repository at this point in the history
  • Loading branch information
maximlt authored Feb 16, 2025
1 parent aa3315a commit facb9e5
Show file tree
Hide file tree
Showing 6 changed files with 39 additions and 37 deletions.
6 changes: 5 additions & 1 deletion numbergen/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,11 @@ def _rational(self, val):
numer, denom = val, 1
elif isinstance(val, fractions.Fraction):
numer, denom = val.numerator, val.denominator
elif hasattr(val, 'numerator') and hasattr(val, 'denominator'):
# gmpy2 mpq objects have these attributes
numer, denom = val.numerator, val.denominator
elif hasattr(val, 'numer'):
# I think this branch supports gmpy (i.e. not gmpy2)
(numer, denom) = (int(val.numer()), int(val.denom()))
else:
param.main.param.log(param.WARNING, "Casting type '%s' to Fraction.fraction"
Expand Down Expand Up @@ -276,7 +280,7 @@ def __call__(self, *vals):
architecture-independent 32-bit integer hash.
"""
# Convert inputs to (numer, denom) pairs with integers
# becoming (int, 1) pairs to match gmpy.mpqs for int values.
# becoming (int, 1) pairs to match gmpy2.mpqs for int values.
pairs = [self._rational(val) for val in vals]
# Unpack pairs and fill struct with ints to update md5 hash
ints = [el for pair in pairs for el in pair]
Expand Down
2 changes: 1 addition & 1 deletion param/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,7 @@ def _deserialize_from_path(ext_to_routine, path, type_name):
def _is_number(obj):
if isinstance(obj, numbers.Number): return True
# The extra check is for classes that behave like numbers, such as those
# found in numpy, gmpy, etc.
# found in numpy, gmpy2, etc.
elif (hasattr(obj, '__int__') and hasattr(obj, '__add__')): return True
# This is for older versions of gmpy
elif hasattr(obj, 'qdiv'): return True
Expand Down
8 changes: 4 additions & 4 deletions param/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ class Infinity:
"""
An instance of this class represents an infinite value. Unlike
Python's float('inf') value, this object can be safely compared
with gmpy numeric types across different gmpy versions.
with gmpy2 numeric types across different gmpy2 versions.
All operators on Infinity() return Infinity(), apart from the
comparison and equality operators. Equality works by checking
Expand Down Expand Up @@ -322,11 +322,11 @@ class Time(Parameterized):
times in decimal notation, but very slow and needs to be
installed separately.
- gmpy.mpq: Allows a natural representation of times in
- gmpy2.mpq: Allows a natural representation of times in
decimal notation, and very fast because it uses the GNU
Multi-Precision library, but needs to be installed
separately and depends on a non-Python library. gmpy.mpq
is gmpy's rational type.
separately and depends on a non-Python library. gmpy2.mpq
is gmpy2's rational type.
""")

timestep = Parameter(default=1.0,doc="""
Expand Down
2 changes: 0 additions & 2 deletions pixi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,6 @@ pandas = "*"
pyarrow = "*"
pytables = "*"
xlrd = "*"

[feature.test.target.linux.dependencies]
gmpy2 = "*"

[feature.test-pypy.dependencies]
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ tests-full = [
"pandas",
"ipython",
"jsonschema",
"gmpy",
"gmpy2",
"cloudpickle",
"nest_asyncio",
]
Expand Down
56 changes: 28 additions & 28 deletions tests/testtimedependent.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@
import numbergen

try:
import gmpy
import gmpy2
except ModuleNotFoundError:
import os
if os.getenv('PARAM_TEST_GMPY','0') == '1':
raise ImportError("PARAM_TEST_GMPY=1 but gmpy not available.")
raise ImportError("PARAM_TEST_GMPY=1 but gmpy2 not available.")
else:
gmpy = None
gmpy2 = None

from .utils import warnings_as_excepts

Expand Down Expand Up @@ -105,31 +105,31 @@ def test_time_integration(self):
self.assertEqual(t(), 22)
self.assertEqual(time(), 10)

@pytest.mark.skipif(gmpy is None, reason="gmpy is not installed")
@pytest.mark.skipif(gmpy2 is None, reason="gmpy2 is not installed")
def test_time_init_gmpy(self):
t = param.Time(time_type=gmpy.mpq)
self.assertEqual(t(), gmpy.mpq(0))
t.advance(gmpy.mpq(0.25))
self.assertEqual(t(), gmpy.mpq(1,4))
t = param.Time(time_type=gmpy2.mpq)
self.assertEqual(t(), gmpy2.mpq(0))
t.advance(gmpy2.mpq(0.25))
self.assertEqual(t(), gmpy2.mpq(1,4))

@pytest.mark.skipif(gmpy is None, reason="gmpy is not installed")
@pytest.mark.skipif(gmpy2 is None, reason="gmpy2 is not installed")
def test_time_init_gmpy_advanced(self):
t = param.Time(time_type=gmpy.mpq,
timestep=gmpy.mpq(0.25),
t = param.Time(time_type=gmpy2.mpq,
timestep=gmpy2.mpq(0.25),
until=1.5)
self.assertEqual(t(), gmpy.mpq(0,1))
self.assertEqual(t(), gmpy2.mpq(0,1))
t(0.5)
self.assertEqual(t(), gmpy.mpq(1,2))
self.assertEqual(t(), gmpy2.mpq(1,2))
with t:
t.advance(0.25)
self.assertEqual(t(), gmpy.mpq(3,4))
self.assertEqual(t(), gmpy.mpq(1,2))
self.assertEqual(t(), gmpy2.mpq(3,4))
self.assertEqual(t(), gmpy2.mpq(1,2))
tvals = [tval for tval in t]
self.assertEqual(tvals, [gmpy.mpq(1,2),
gmpy.mpq(3,4),
gmpy.mpq(1,1),
gmpy.mpq(5,4),
gmpy.mpq(3,2)])
self.assertEqual(tvals, [gmpy2.mpq(1,2),
gmpy2.mpq(3,4),
gmpy2.mpq(1,1),
gmpy2.mpq(5,4),
gmpy2.mpq(3,2)])


class TestTimeDependentDynamic(unittest.TestCase):
Expand Down Expand Up @@ -298,31 +298,31 @@ def test_time_hashing_rationals(self):
self.assertEqual(hashfn(pi), hashfn(fractions.Fraction(pi)))


@pytest.mark.skipif(gmpy is None, reason="gmpy is not installed")
@pytest.mark.skipif(gmpy2 is None, reason="gmpy2 is not installed")
def test_time_hashing_integers_gmpy(self):
"""
Check that hashes for gmpy values at the integers also matches
Check that hashes for gmpy2 values at the integers also matches
those of ints, fractions and strings.
"""
hashfn = numbergen.Hash("test", input_count=1)
hash_1 = hashfn(1)
hash_42 = hashfn(42)

self.assertEqual(hash_1, hashfn(gmpy.mpq(1)))
self.assertEqual(hash_1, hashfn(gmpy2.mpq(1)))
self.assertEqual(hash_1, hashfn(1))

self.assertEqual(hash_42, hashfn(gmpy.mpq(42)))
self.assertEqual(hash_42, hashfn(gmpy2.mpq(42)))
self.assertEqual(hash_42, hashfn(42))

@pytest.mark.skipif(gmpy is None, reason="gmpy is not installed")
@pytest.mark.skipif(gmpy2 is None, reason="gmpy2 is not installed")
def test_time_hashing_rationals_gmpy(self):
"""
Check that hashes of fractions and gmpy mpqs match for some
Check that hashes of fractions and gmpy2 mpqs match for some
reasonable rational numbers.
"""
pi = "3.141592"
hashfn = numbergen.Hash("test", input_count=1)
with warnings_as_excepts(match="Casting type 'float' to Fraction.fraction"):
self.assertEqual(hashfn(0.5), hashfn(gmpy.mpq(0.5)))
self.assertEqual(hashfn(0.5), hashfn(gmpy2.mpq(0.5)))
with warnings_as_excepts(match="Casting type 'str' to Fraction.fraction"):
self.assertEqual(hashfn(pi), hashfn(gmpy.mpq(3.141592)))
self.assertEqual(hashfn(pi), hashfn(gmpy2.mpq(3.141592)))

0 comments on commit facb9e5

Please sign in to comment.