def check_valid_minmax(prefix, val, info_obj): name = f"{prefix}_value" check_valid_bound(val, name) check_argument( val >= info_obj.min, f"dtype={dtype} requires {name}={val} to be at least {info_obj.min}", ) check_argument( val <= info_obj.max, f"dtype={dtype} requires {name}={val} to be at most {info_obj.max}", )
def check_valid_sizes(category: str, sizes: Sequence[int], valid_sizes: Sequence[int]) -> None: check_argument(len(sizes) > 0, "No sizes passed") invalid_sizes = [s for s in sizes if s not in valid_sizes] f_valid_sizes = ", ".join(str(s) for s in valid_sizes) f_invalid_sizes = ", ".join(str(s) for s in invalid_sizes) check_argument( len(invalid_sizes) == 0, f"The following sizes are not valid for {category} dtypes: " f"{f_invalid_sizes} (valid sizes: {f_valid_sizes})", )
def indices( shape: Shape, *, min_dims: int = 0, max_dims: Optional[int] = None, allow_ellipsis: bool = True, ) -> st.SearchStrategy[BasicIndex]: """Return a strategy for :xp-ref:`valid indices <indexing.html>` of arrays with the specified shape, which may include dimensions of size zero. It generates tuples containing some mix of integers, :obj:`python:slice` objects, and ``...`` (an ``Ellipsis``). When a length-one tuple would be generated, this strategy may instead return the element which will index the first axis, e.g. ``5`` instead of ``(5,)``. * ``shape`` is the shape of the array that will be indexed, as a tuple of integers >= 0. This must be at least two-dimensional for a tuple to be a valid index; for one-dimensional arrays use :func:`~hypothesis.strategies.slices` instead. * ``min_dims`` is the minimum dimensionality of the resulting array from use of the generated index. * ``max_dims`` is the the maximum dimensionality of the resulting array, defaulting to ``len(shape)``. * ``allow_ellipsis`` specifies whether ``...`` is allowed in the index. """ check_type(tuple, shape, "shape") check_argument( all(isinstance(x, int) and x >= 0 for x in shape), f"shape={shape!r}, but all dimensions must be non-negative integers.", ) check_type(bool, allow_ellipsis, "allow_ellipsis") check_type(int, min_dims, "min_dims") check_argument( min_dims <= len(shape), f"min_dims={min_dims} is larger than len(shape)={len(shape)}, " "but it is impossible for an indexing operation to add dimensions.", ) check_valid_dims(min_dims, "min_dims") if max_dims is None: max_dims = min(len(shape), NDIM_MAX) check_type(int, max_dims, "max_dims") assert isinstance(max_dims, int) check_argument( max_dims <= len(shape), f"max_dims={max_dims} is larger than len(shape)={len(shape)}, " "but it is impossible for an indexing operation to add dimensions.", ) check_valid_dims(max_dims, "max_dims") order_check("dims", 0, min_dims, max_dims) return BasicIndexStrategy( shape, min_dims=min_dims, max_dims=max_dims, allow_ellipsis=allow_ellipsis, allow_newaxis=False, allow_fewer_indices_than_dims=False, )
def dtype_factory(kind, sizes, valid_sizes, endianness): # Utility function, shared logic for most integer and string types valid_endian = ("?", "<", "=", ">") check_argument( endianness in valid_endian, "Unknown endianness: was {}, must be in {}", endianness, valid_endian, ) if valid_sizes is not None: if isinstance(sizes, int): sizes = (sizes, ) check_argument(sizes, "Dtype must have at least one possible size.") check_argument( all(s in valid_sizes for s in sizes), "Invalid sizes: was {} must be an item or sequence in {}", sizes, valid_sizes, ) if all(isinstance(s, int) for s in sizes): sizes = sorted({s // 8 for s in sizes}) strat = st.sampled_from(sizes) if "{}" not in kind: kind += "{}" if endianness == "?": return strat.map(("<" + kind).format) | strat.map((">" + kind).format) return strat.map((endianness + kind).format)
def validate_time_slice(max_period, min_period): check_argument( max_period in TIME_RESOLUTIONS, "max_period {} must be a valid resolution in {}", max_period, TIME_RESOLUTIONS, ) check_argument( min_period in TIME_RESOLUTIONS, "min_period {} must be a valid resolution in {}", min_period, TIME_RESOLUTIONS, ) start = TIME_RESOLUTIONS.index(max_period) end = TIME_RESOLUTIONS.index(min_period) + 1 check_argument( start < end, "max_period {} must be earlier in sequence {} than min_period {}", max_period, TIME_RESOLUTIONS, min_period, ) return TIME_RESOLUTIONS[start:end]
def _arrays( xp: Any, dtype: Union[DataType, str, st.SearchStrategy[DataType], st.SearchStrategy[str]], shape: Union[int, Shape, st.SearchStrategy[Shape]], *, elements: Optional[Union[Mapping[str, Any], st.SearchStrategy]] = None, fill: Optional[st.SearchStrategy[Any]] = None, unique: bool = False, ) -> st.SearchStrategy: """Returns a strategy for :xp-ref:`arrays <array_object.html>`. * ``dtype`` may be a :xp-ref:`valid dtype <data_types.html>` object or name, or a strategy that generates such values. * ``shape`` may be an integer >= 0, a tuple of such integers, or a strategy that generates such values. * ``elements`` is a strategy for values to put in the array. If ``None`` then a suitable value will be inferred based on the dtype, which may give any legal value (including e.g. NaN for floats). If a mapping, it will be passed as ``**kwargs`` to :func:`from_dtype()` when inferring based on the dtype. * ``fill`` is a strategy that may be used to generate a single background value for the array. If ``None``, a suitable default will be inferred based on the other arguments. If set to :func:`~hypothesis.strategies.nothing` then filling behaviour will be disabled entirely and every element will be generated independently. * ``unique`` specifies if the elements of the array should all be distinct from one another; if fill is also set, the only valid values for fill to return are NaN values. Arrays of specified ``dtype`` and ``shape`` are generated for example like this: .. code-block:: pycon >>> from numpy import array_api as xp >>> xps.arrays(xp, xp.int8, (2, 3)).example() Array([[-8, 6, 3], [-6, 4, 6]], dtype=int8) Specifying element boundaries by a :obj:`python:dict` of the kwargs to pass to :func:`from_dtype` will ensure ``dtype`` bounds will be respected. .. code-block:: pycon >>> xps.arrays(xp, xp.int8, 3, elements={"min_value": 10}).example() Array([125, 13, 79], dtype=int8) Refer to :doc:`What you can generate and how <data>` for passing your own elements strategy. .. code-block:: pycon >>> xps.arrays(xp, xp.float32, 3, elements=floats(0, 1, width=32)).example() Array([ 0.88974794, 0.77387938, 0.1977879 ], dtype=float32) Array values are generated in two parts: 1. A single value is drawn from the fill strategy and is used to create a filled array. 2. Some subset of the coordinates of the array are populated with a value drawn from the elements strategy (or its inferred form). You can set ``fill`` to :func:`~hypothesis.strategies.nothing` if you want to disable this behaviour and draw a value for every element. By default ``arrays`` will attempt to infer the correct fill behaviour: if ``unique`` is also ``True``, no filling will occur. Otherwise, if it looks safe to reuse the values of elements across multiple coordinates (this will be the case for any inferred strategy, and for most of the builtins, but is not the case for mutable values or strategies built with flatmap, map, composite, etc.) then it will use the elements strategy as the fill, else it will default to having no fill. Having a fill helps Hypothesis craft high quality examples, but its main importance is when the array generated is large: Hypothesis is primarily designed around testing small examples. If you have arrays with hundreds or more elements, having a fill value is essential if you want your tests to run in reasonable time. """ check_xp_attributes(xp, [ "finfo", "asarray", "zeros", "full", "all", "isnan", "isfinite", "reshape" ]) if isinstance(dtype, st.SearchStrategy): return dtype.flatmap(lambda d: _arrays( xp, d, shape, elements=elements, fill=fill, unique=unique)) elif isinstance(dtype, str): dtype = dtype_from_name(xp, dtype) if isinstance(shape, st.SearchStrategy): return shape.flatmap(lambda s: _arrays( xp, dtype, s, elements=elements, fill=fill, unique=unique)) elif isinstance(shape, int): shape = (shape, ) elif not isinstance(shape, tuple): raise InvalidArgument( f"shape={shape} is not a valid shape or strategy") check_argument( all(isinstance(x, int) and x >= 0 for x in shape), f"shape={shape!r}, but all dimensions must be non-negative integers.", ) if elements is None: elements = _from_dtype(xp, dtype) elif isinstance(elements, Mapping): elements = _from_dtype(xp, dtype, **elements) check_strategy(elements, "elements") if fill is None: assert isinstance(elements, st.SearchStrategy) # for mypy if unique or not elements.has_reusable_values: fill = st.nothing() else: fill = elements check_strategy(fill, "fill") return ArrayStrategy(xp, elements, dtype, shape, fill, unique)
def integer_array_indices( shape: Shape, *, result_shape: st.SearchStrategy[Shape] = array_shapes(), dtype: np.dtype = "int", ) -> st.SearchStrategy[Tuple[np.ndarray, ...]]: """Return a search strategy for tuples of integer-arrays that, when used to index into an array of shape ``shape``, given an array whose shape was drawn from ``result_shape``. Examples from this strategy shrink towards the tuple of index-arrays:: len(shape) * (np.zeros(drawn_result_shape, dtype), ) * ``shape`` a tuple of integers that indicates the shape of the array, whose indices are being generated. * ``result_shape`` a strategy for generating tuples of integers, which describe the shape of the resulting index arrays. The default is :func:`~hypothesis.extra.numpy.array_shapes`. The shape drawn from this strategy determines the shape of the array that will be produced when the corresponding example from ``integer_array_indices`` is used as an index. * ``dtype`` the integer data type of the generated index-arrays. Negative integer indices can be generated if a signed integer type is specified. Recall that an array can be indexed using a tuple of integer-arrays to access its members in an arbitrary order, producing an array with an arbitrary shape. For example: .. code-block:: pycon >>> from numpy import array >>> x = array([-0, -1, -2, -3, -4]) >>> ind = (array([[4, 0], [0, 1]]),) # a tuple containing a 2D integer-array >>> x[ind] # the resulting array is commensurate with the indexing array(s) array([[-4, 0], [0, -1]]) Note that this strategy does not accommodate all variations of so-called 'advanced indexing', as prescribed by NumPy's nomenclature. Combinations of basic and advanced indexes are too complex to usefully define in a standard strategy; we leave application-specific strategies to the user. Advanced-boolean indexing can be defined as ``arrays(shape=..., dtype=bool)``, and is similarly left to the user. """ check_type(tuple, shape, "shape") check_argument( shape and all(isinstance(x, int) and x > 0 for x in shape), f"shape={shape!r} must be a non-empty tuple of integers > 0", ) check_strategy(result_shape, "result_shape") check_argument(np.issubdtype(dtype, np.integer), f"dtype={dtype!r} must be an integer dtype") signed = np.issubdtype(dtype, np.signedinteger) def array_for(index_shape, size): return arrays( dtype=dtype, shape=index_shape, elements=st.integers(-size if signed else 0, size - 1), ) return result_shape.flatmap(lambda index_shape: st.tuples(*( array_for(index_shape, size) for size in shape)))
def basic_indices( shape: Shape, *, min_dims: int = 0, max_dims: Optional[int] = None, allow_newaxis: bool = False, allow_ellipsis: bool = True, ) -> st.SearchStrategy[BasicIndex]: """Return a strategy for :doc:`basic indexes <numpy:reference/arrays.indexing>` of arrays with the specified shape, which may include dimensions of size zero. It generates tuples containing some mix of integers, :obj:`python:slice` objects, ``...`` (an ``Ellipsis``), and ``None``. When a length-one tuple would be generated, this strategy may instead return the element which will index the first axis, e.g. ``5`` instead of ``(5,)``. * ``shape`` is the shape of the array that will be indexed, as a tuple of positive integers. This must be at least two-dimensional for a tuple to be a valid index; for one-dimensional arrays use :func:`~hypothesis.strategies.slices` instead. * ``min_dims`` is the minimum dimensionality of the resulting array from use of the generated index. When ``min_dims == 0``, scalars and zero-dimensional arrays are both allowed. * ``max_dims`` is the the maximum dimensionality of the resulting array, defaulting to ``len(shape) if not allow_newaxis else max(len(shape), min_dims) + 2``. * ``allow_newaxis`` specifies whether ``None`` is allowed in the index. * ``allow_ellipsis`` specifies whether ``...`` is allowed in the index. """ # Arguments to exclude scalars, zero-dim arrays, and dims of size zero were # all considered and rejected. We want users to explicitly consider those # cases if they're dealing in general indexers, and while it's fiddly we can # back-compatibly add them later (hence using kwonlyargs). check_type(tuple, shape, "shape") check_argument( all(isinstance(x, int) and x >= 0 for x in shape), f"shape={shape!r}, but all dimensions must be non-negative integers.", ) check_type(bool, allow_ellipsis, "allow_ellipsis") check_type(bool, allow_newaxis, "allow_newaxis") check_type(int, min_dims, "min_dims") if min_dims > len(shape) and not allow_newaxis: note_deprecation( f"min_dims={min_dims} is larger than len(shape)={len(shape)}, " "but allow_newaxis=False makes it impossible for an indexing " "operation to add dimensions.", since="2021-09-15", has_codemod=False, ) check_valid_dims(min_dims, "min_dims") if max_dims is None: if allow_newaxis: max_dims = min(max(len(shape), min_dims) + 2, NDIM_MAX) else: max_dims = min(len(shape), NDIM_MAX) else: check_type(int, max_dims, "max_dims") if max_dims > len(shape) and not allow_newaxis: note_deprecation( f"max_dims={max_dims} is larger than len(shape)={len(shape)}, " "but allow_newaxis=False makes it impossible for an indexing " "operation to add dimensions.", since="2021-09-15", has_codemod=False, ) check_valid_dims(max_dims, "max_dims") order_check("dims", 0, min_dims, max_dims) return BasicIndexStrategy( shape, min_dims=min_dims, max_dims=max_dims, allow_ellipsis=allow_ellipsis, allow_newaxis=allow_newaxis, allow_fewer_indices_than_dims=True, )
def arrays( dtype: Any, shape: Union[int, st.SearchStrategy[int], Shape, st.SearchStrategy[Shape]], *, elements: Optional[Union[st.SearchStrategy, Mapping[str, Any]]] = None, fill: Optional[st.SearchStrategy[Any]] = None, unique: bool = False, ) -> st.SearchStrategy[np.ndarray]: r"""Returns a strategy for generating :class:`numpy:numpy.ndarray`\ s. * ``dtype`` may be any valid input to :class:`~numpy:numpy.dtype` (this includes :class:`~numpy:numpy.dtype` objects), or a strategy that generates such values. * ``shape`` may be an integer >= 0, a tuple of such integers, or a strategy that generates such values. * ``elements`` is a strategy for generating values to put in the array. If it is None a suitable value will be inferred based on the dtype, which may give any legal value (including eg ``NaN`` for floats). If a mapping, it will be passed as ``**kwargs`` to ``from_dtype()`` * ``fill`` is a strategy that may be used to generate a single background value for the array. If None, a suitable default will be inferred based on the other arguments. If set to :func:`~hypothesis.strategies.nothing` then filling behaviour will be disabled entirely and every element will be generated independently. * ``unique`` specifies if the elements of the array should all be distinct from one another. Note that in this case multiple NaN values may still be allowed. If fill is also set, the only valid values for it to return are NaN values (anything for which :obj:`numpy:numpy.isnan` returns True. So e.g. for complex numbers (nan+1j) is also a valid fill). Note that if unique is set to True the generated values must be hashable. Arrays of specified ``dtype`` and ``shape`` are generated for example like this: .. code-block:: pycon >>> import numpy as np >>> arrays(np.int8, (2, 3)).example() array([[-8, 6, 3], [-6, 4, 6]], dtype=int8) - See :doc:`What you can generate and how <data>`. .. code-block:: pycon >>> import numpy as np >>> from hypothesis.strategies import floats >>> arrays(np.float, 3, elements=floats(0, 1)).example() array([ 0.88974794, 0.77387938, 0.1977879 ]) Array values are generated in two parts: 1. Some subset of the coordinates of the array are populated with a value drawn from the elements strategy (or its inferred form). 2. If any coordinates were not assigned in the previous step, a single value is drawn from the fill strategy and is assigned to all remaining places. You can set fill to :func:`~hypothesis.strategies.nothing` if you want to disable this behaviour and draw a value for every element. If fill is set to None then it will attempt to infer the correct behaviour automatically: If unique is True, no filling will occur by default. Otherwise, if it looks safe to reuse the values of elements across multiple coordinates (this will be the case for any inferred strategy, and for most of the builtins, but is not the case for mutable values or strategies built with flatmap, map, composite, etc) then it will use the elements strategy as the fill, else it will default to having no fill. Having a fill helps Hypothesis craft high quality examples, but its main importance is when the array generated is large: Hypothesis is primarily designed around testing small examples. If you have arrays with hundreds or more elements, having a fill value is essential if you want your tests to run in reasonable time. """ # We support passing strategies as arguments for convenience, or at least # for legacy reasons, but don't want to pay the perf cost of a composite # strategy (i.e. repeated argument handling and validation) when it's not # needed. So we get the best of both worlds by recursing with flatmap, # but only when it's actually needed. if isinstance(dtype, st.SearchStrategy): return dtype.flatmap(lambda d: arrays( d, shape, elements=elements, fill=fill, unique=unique)) if isinstance(shape, st.SearchStrategy): return shape.flatmap(lambda s: arrays( dtype, s, elements=elements, fill=fill, unique=unique)) # From here on, we're only dealing with values and it's relatively simple. dtype = np.dtype(dtype) if elements is None or isinstance(elements, Mapping): if dtype.kind in ("m", "M") and "[" not in dtype.str: # For datetime and timedelta dtypes, we have a tricky situation - # because they *may or may not* specify a unit as part of the dtype. # If not, we flatmap over the various resolutions so that array # elements have consistent units but units may vary between arrays. return (st.sampled_from(TIME_RESOLUTIONS).map( (dtype.str + "[{}]").format).flatmap(lambda d: arrays( d, shape=shape, fill=fill, unique=unique))) elements = from_dtype(dtype, **(elements or {})) check_strategy(elements, "elements") if isinstance(shape, int): shape = (shape, ) shape = tuple(shape) check_argument( all(isinstance(s, int) for s in shape), "Array shape must be integer in each dimension, provided shape was {}", shape, ) fill = fill_for(elements=elements, unique=unique, fill=fill) return ArrayStrategy(elements, shape, dtype, fill, unique)