Skip to content
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 width to hypothesis.strategies.complex_numbers() #3559

Merged
merged 19 commits into from
Feb 2, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions hypothesis-python/RELEASE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
RELEASE_TYPE: patch

Adds a ``width`` parameter to :func:`hypothesis.strategies.complex_numbers`, analogously to :func:`hypothesis.strategies.floats`.
Also slightly improves referencing in the documentation.
23 changes: 21 additions & 2 deletions hypothesis-python/src/hypothesis/strategies/_internal/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1630,8 +1630,10 @@ def complex_numbers(
allow_infinity: Optional[bool] = None,
allow_nan: Optional[bool] = None,
allow_subnormal: bool = True,
width: int = 128,
) -> SearchStrategy[complex]:
"""Returns a strategy that generates complex numbers.
"""Returns a strategy that generates :class:`~python:complex`
numbers.

This strategy draws complex numbers with constrained magnitudes.
The ``min_magnitude`` and ``max_magnitude`` parameters should be
Expand All @@ -1649,6 +1651,14 @@ def complex_numbers(
of (around) floating-point epsilon, due to implementation via
the system ``sqrt`` function.

The width argument specifies the maximum number of bits of precision
required to represent the entire generated complex number.
Valid values are 64 or 128, which correspond to the real and imaginary
components having width 32 or 64, respectively.
Passing ``width=64`` will still use the builtin 128-bit
:class:`~python:complex` class, but always for values which can be
exactly represented as two 32-bit floats.

Examples from this strategy shrink by shrinking their real and
imaginary parts, as :func:`~hypothesis.strategies.floats`.

Expand Down Expand Up @@ -1677,15 +1687,24 @@ def complex_numbers(
f"Cannot have allow_nan={allow_nan!r}, min_magnitude={min_magnitude!r} "
f"max_magnitude={max_magnitude!r}"
)

felixdivo marked this conversation as resolved.
Show resolved Hide resolved
check_type(bool, allow_subnormal, "allow_subnormal")
if width not in (64, 128):
felixdivo marked this conversation as resolved.
Show resolved Hide resolved
raise InvalidArgument(
f"width={width!r}, but must be 64 or 128 (other complex dtypes "
"such as complex32 or complex256 are not supported)"
# For numpy, these types would be supported (but not by CPython):
# https://numpy.org/doc/stable/reference/arrays.scalars.html#complex-floating-point-types
)
# The real and imaginary parts get half of the space each
width_for_floats = width // 2
allow_kw = {
"allow_nan": allow_nan,
"allow_infinity": allow_infinity,
# If we have a nonzero normal min_magnitude and draw a zero imaginary part,
# then allow_subnormal=True would be an error with the min_value to the floats()
# strategy for the real part. We therefore replace True with None.
"allow_subnormal": None if allow_subnormal else allow_subnormal,
"width": width_for_floats,
felixdivo marked this conversation as resolved.
Show resolved Hide resolved
}

if min_magnitude == 0 and max_magnitude is None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ def floats(

The width argument specifies the maximum number of bits of precision
required to represent the generated float. Valid values are 16, 32, or 64.
Passing ``width=32`` will still use the builtin 64-bit ``float`` class,
Passing ``width=32`` will still use the builtin 64-bit :class:`~python:float` class,
but always for values which can be exactly represented as a 32-bit float.

The exclude_min and exclude_max argument can be used to generate numbers
Expand Down
9 changes: 9 additions & 0 deletions hypothesis-python/tests/cover/test_direct_strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ def fn_ktest(*fnkwargs):
(ds.floats, {"exclude_max": True}), # because max_value=None
(ds.floats, {"min_value": 1.8, "width": 32}),
(ds.floats, {"max_value": 1.8, "width": 32}),
(ds.complex_numbers, {"min_magnitude": 1.8, "width": 64}),
(ds.complex_numbers, {"max_magnitude": 1.8, "width": 64}),
(ds.fractions, {"min_value": 2, "max_value": 1}),
(ds.fractions, {"min_value": math.nan}),
(ds.fractions, {"max_value": math.nan}),
Expand Down Expand Up @@ -149,6 +151,11 @@ def fn_ktest(*fnkwargs):
(ds.complex_numbers, {"min_magnitude": 3, "max_magnitude": 2}),
(ds.complex_numbers, {"max_magnitude": 2, "allow_infinity": True}),
(ds.complex_numbers, {"max_magnitude": 2, "allow_nan": True}),
(ds.complex_numbers, {"width": None}),
# Common mistakes when misunderstanding width for individual component widths:
(ds.complex_numbers, {"width": 16}),
(ds.complex_numbers, {"width": 32}),
(ds.complex_numbers, {"width": 256}), # Unsupported as of now
(ds.fixed_dictionaries, {"mapping": "fish"}),
(ds.fixed_dictionaries, {"mapping": {1: "fish"}}),
(ds.fixed_dictionaries, {"mapping": {}, "optional": "fish"}),
Expand Down Expand Up @@ -246,6 +253,8 @@ def test_validates_keyword_arguments(fn, kwargs):
(ds.complex_numbers, {"allow_nan": False, "allow_infinity": True}),
(ds.complex_numbers, {"allow_nan": False, "allow_infinity": False}),
(ds.complex_numbers, {"max_magnitude": math.inf, "allow_infinity": True}),
(ds.complex_numbers, {"width": 64}),
(ds.complex_numbers, {"width": 128}),
(ds.sampled_from, {"elements": [1]}),
(ds.sampled_from, {"elements": [1, 2, 3]}),
(ds.fixed_dictionaries, {"mapping": {1: ds.integers()}}),
Expand Down