diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 89efc98d..e6225d95 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -46,12 +46,12 @@ repos: - tomli - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.4 + rev: v0.4.5 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/RobertCraigie/pyright-python - rev: v1.1.363 + rev: v1.1.364 hooks: - id: pyright diff --git a/lmo/__init__.py b/lmo/__init__.py index e0de49e3..dac8c63c 100644 --- a/lmo/__init__.py +++ b/lmo/__init__.py @@ -1,31 +1,42 @@ -"""Lmo: Robust statistics with trimmed L-moments and L-comoments.""" -import sys +""" +Robust statistics with trimmed L-moments and L-comoments. +""" + +import sys # noqa: I001 from typing import TYPE_CHECKING, Final from ._lm import ( - l_kurtosis, l_loc, - l_moment, - l_moment_cov, - l_moment_influence, - l_ratio, - l_ratio_influence, - l_ratio_se, l_scale, + l_variation, l_skew, + l_kurtosis, + + l_kurt, + l_moment, + l_ratio, l_stats, + + l_moment_cov, + l_ratio_se, l_stats_se, - l_variation, + + l_moment_influence, + l_ratio_influence, + l_weights, ) from ._lm_co import ( - l_cokurtosis, l_coloc, - l_comoment, - l_coratio, - l_corr, l_coscale, + + l_corr, l_coskew, + l_cokurtosis, + l_cokurt, + + l_comoment, + l_coratio, l_costats, ) from ._meta import get_version as _get_version @@ -60,6 +71,7 @@ 'l_variation', 'l_skew', 'l_kurtosis', + 'l_kurt', 'l_moment', 'l_ratio', @@ -79,6 +91,7 @@ 'l_corr', 'l_coskew', 'l_cokurtosis', + 'l_cokurt', 'l_comoment', 'l_coratio', diff --git a/lmo/_lm.py b/lmo/_lm.py index e28dab8e..f77b38f8 100644 --- a/lmo/_lm.py +++ b/lmo/_lm.py @@ -9,29 +9,28 @@ from . import ostats, pwm_beta from ._utils import ( clean_order, + clean_orders, clean_trim, ensure_axis_at, l_stats_orders, moments_to_ratio, ordered, round0, + sort_maybe, ) from .linalg import ir_pascal, sandwich, sh_legendre, trim_matrix +from .typing import ( + AnyOrder, + AnyOrderND, + np as lnpt, +) from .typing.compat import TypeVar if TYPE_CHECKING: from collections.abc import Callable - from .typing import ( - AnyAWeights, - AnyFWeights, - AnyOrder, - AnyOrderND, - AnyTrim, - LMomentOptions, - np as lnpt, - ) + from .typing import AnyAWeights, AnyFWeights, AnyTrim, LMomentOptions from .typing.compat import Unpack @@ -47,6 +46,7 @@ 'l_variation', 'l_skew', 'l_kurtosis', + 'l_kurt', 'l_moment_cov', 'l_ratio_se', @@ -66,13 +66,17 @@ _Vectorized: TypeAlias = _T_float | npt.NDArray[_T_float] _Floating: TypeAlias = np.floating[Any] -_L_WEIGHTS_CACHE: Final[ - dict[ - # (n, s, t) - tuple[int, int, int] | tuple[int, float, float], - lnpt.Array[tuple[int, int], _Floating], - ] -] = {} +# (dtype.char, n, s, t) +_CacheKey: TypeAlias = ( + tuple[str, int, int, int] + | tuple[str, int, float, float] +) +# `r: _T_order >= 4` +_CacheArray: TypeAlias = lnpt.Array[tuple[_T_order, _T_size], np.longdouble] +_Cache: TypeAlias = dict[_CacheKey, _CacheArray[Any, Any]] + +# depends on `dtype`, `n`, and `trim` +_CACHE: Final[_Cache] = {} def _l_weights_pwm( @@ -87,11 +91,21 @@ def _l_weights_pwm( r0 = r + s + t # `__matmul__` annotations are lacking (`np.matmul` is equivalent to it) - w0 = np.matmul( + wr = np.matmul( sh_legendre(r0, dtype=np.int64 if r0 < 29 else dtype), pwm_beta.weights(r0, n, dtype=dtype), + dtype=dtype, ) - return np.matmul(trim_matrix(r, trim, dtype=dtype), w0) if s or t else w0 + if s or t: + wr = np.matmul(trim_matrix(r, trim, dtype=dtype), wr, dtype=dtype) + + # ensure that the trimmed ends are 0 + if s: + wr[:, :s] = 0 + if t: + wr[:, -t:] = 0 + + return wr def _l_weights_ostat( @@ -125,13 +139,13 @@ def _l_weights_ostat( def l_weights( - r: _T_order, + r_max: _T_order, n: _T_size, /, trim: AnyTrim = 0, *, dtype: _DType[_T_float] = np.float64, - cache: bool = False, + cache: bool | None = None, ) -> lnpt.Array[tuple[_T_order, _T_size], _T_float]: r""" Projection matrix of the first $r$ (T)L-moments for $n$ samples. @@ -170,8 +184,16 @@ def l_weights( observation vector(s) of size $n$), into (an unbiased estimate of) the *generalized trimmed L-moments*, with orders $\le r$. + Args: + r_max: The amount L-moment orders. + n: The number of samples. + trim: A scalar or 2-tuple with the trim orders. Defaults to 0. + dtype: The datatype of the returned weight matrix. + cache: Whether to cache the weights. By default, it's enabled i.f.f + the trim values are integers, and `r_max + sum(trim) < 24`. + Returns: - P_r: 2-D array of shape `(r, n)`. + P_r: 2-D array of shape `(r_max, n)`, readonly if `cache=True` Examples: >>> import lmo @@ -186,55 +208,44 @@ def l_weights( - [J.R.M. Hosking (2007) - Some theory and practical uses of trimmed L-moments](https://doi.org/10.1016/j.jspi.2006.12.002) """ - if r == 0: - return np.empty((r, n), dtype=dtype) - - match clean_trim(trim): - case s, t if s < 0 or t < 0: - msg = f'trim orders must be >=0, got {trim}' - raise ValueError(msg) - case s, t: - pass - case _: # type: ignore [reportUneccessaryComparison] - msg = ( - f'trim must be a tuple with two non-negative ints or floats, ' - f'got {trim!r}' - ) - raise TypeError(msg) - - # manual cache lookup, only if cache=False (for testability) - # e.g. `functools.cache` would be inefficient for e.g. r=3 with cached r=4 - cache_key = n, s, t - if ( - cache - and cache_key in _L_WEIGHTS_CACHE - and (w := _L_WEIGHTS_CACHE[cache_key]).shape[0] <= r - ): - if w.shape[0] < r: - w = w[:r] - - # ignore if r is larger that what's cached - if w.shape[0] == r: - assert w.shape == (r, n) - return w.astype(dtype) - - if r + s + t <= 24 and isinstance(s, int) and isinstance(t, int): - w = _l_weights_pwm(r, n, (s, t), dtype=dtype or np.float64) + if r_max < 0: + msg = f'r must be non-negative, got {r_max}' + raise ValueError(msg) - # ensure that the trimmed ends are 0 - if s: - w[:, :s] = 0 - if t: - w[:, -t:] = 0 - else: - w = _l_weights_ostat(r, n, (s, t), dtype=dtype or np.float64) + dtype = np.dtype(dtype) + sctype = dtype.type - if cache: - # the pyright error here is due to the fact that the first type param - # of `np.ndarray` is invariant (which is incorrect), instead of - # being covariant - _L_WEIGHTS_CACHE[cache_key] = w # pyright: ignore[reportArgumentType] + if r_max == 0: + return np.empty((0, n), dtype=sctype) + s, t = clean_trim(trim) + + if (n_min := r_max + s + t) > n: + msg = f'expected n >= r + s + t, got {n} < {n_min}' + raise ValueError(msg) + + key = dtype.char, n, s, t + if (_w := _CACHE.get(key)) is not None and _w.shape[0] >= r_max: + w = cast(lnpt.Array[tuple[_T_order, _T_size], _T_float], _w) + else: + # when caching, use at least 4 orders, to avoid cache misses + _r_max = 4 if cache and r_max < 4 else r_max + + _cache_default = False + if r_max + s + t <= 24 and isinstance(s, int) and isinstance(t, int): + w = _l_weights_pwm(_r_max, n, trim=(s, t), dtype=sctype) + _cache_default = True + else: + w = _l_weights_ostat(_r_max, n, trim=(s, t), dtype=sctype) + + if cache or cache is None and _cache_default: + w.setflags(write=False) + # be wary of a potential race condition + if key not in _CACHE or w.shape[0] >= _CACHE[key].shape[0]: + _CACHE[key] = w + + if w.shape[0] > r_max: + w = w[:r_max] return w @@ -292,8 +303,8 @@ def l_moment( dtype: _DType[_T_float] = np.float64, fweights: AnyFWeights | None = None, aweights: AnyAWeights | None = None, - sort: lnpt.SortKind | None = None, - cache: bool = False, + sort: lnpt.SortKind | bool = True, + cache: bool | None = None, ) -> _Vectorized[_T_float]: r""" Estimates the generalized trimmed L-moment $\lambda^{(s, t)}_r$ from @@ -348,12 +359,14 @@ def l_moment( All `aweights` must be `>=0`, and the sum must be nonzero. The algorithm is similar to that for weighted quantiles. - sort ('quicksort' | 'heapsort' | 'stable'): + sort (True | False | 'quicksort' | 'heapsort' | 'stable'): Sorting algorithm, see [`numpy.sort`][numpy.sort]. + Set to `False` if the array is already sorted. cache: Set to `True` to speed up future L-moment calculations that have the same number of observations in `a`, equal `trim`, and equal or - smaller `r`. + smaller `r`. By default, it will cache i.f.f. the trim is integral, + and $r + s + t \le 24$. Set to `False` to always disable caching. Returns: l: @@ -403,8 +416,11 @@ def l_moment( x_k = ensure_axis_at(x_k, axis, -1) n = x_k.shape[-1] - _r = np.asarray(r) - r_max = clean_order(np.max(_r)) + if np.isscalar(r): + _r = np.array(clean_order(cast(AnyOrder, r))) + else: + _r = clean_orders(cast(AnyOrderND, r)) + r_min, r_max = np.min(_r), int(np.max(_r)) # TODO @jorenham: nan handling, see: # https://github.com/jorenham/Lmo/issues/70 @@ -420,12 +436,10 @@ def l_moment( l_r = np.inner(l_weights(r_max, n, st, dtype=dtype, cache=cache), x_k) - # we like 0-based indexing; so if P_r starts at r=1, prepend all 1's - # for r=0 (any zeroth moment is defined to be 1) - l_r = np.r_[np.ones((1, *l_r.shape[1:]), dtype=l_r.dtype), l_r] + if r_min > 0: + return l_r.take(_r - 1, 0) - # l[r] fails when r is e.g. a tuple (valid sequence). - return l_r.take(_r, 0) + return np.r_[np.ones((1, *l_r.shape[1:]), l_r.dtype), l_r].take(_r, 0) @overload @@ -910,6 +924,9 @@ def l_kurtosis( return l_ratio(a, 4, 2, trim=trim, axis=axis, dtype=dtype, **kwds) +l_kurt = l_kurtosis + + def l_moment_cov( a: lnpt.AnyArrayFloat, r_max: AnyOrder, @@ -1103,7 +1120,7 @@ def l_moment_influence( /, trim: AnyTrim = 0, *, - sort: lnpt.SortKind | None = 'quicksort', + sort: lnpt.SortKind | bool = True, tol: float = 1e-8, ) -> Callable[[_T_x], _T_x]: r""" @@ -1132,11 +1149,9 @@ def l_moment_influence( _r = clean_order(r) s, t = clean_trim(trim) - x_k = np.array(a, copy=True) - if sort: - x_k.sort(kind=sort) + x_k = np.array(a, copy=bool(sort)) + x_k = sort_maybe(x_k, sort=sort, inplace=True) - x_k = np.sort(np.asarray(a), kind=sort) n = len(x_k) w_k = l_weights(_r, n, (s, t))[-1] @@ -1173,7 +1188,7 @@ def l_ratio_influence( /, trim: AnyTrim = 0, *, - sort: lnpt.SortKind = 'quicksort', + sort: lnpt.SortKind | bool = True, tol: float = 1e-8, ) -> Callable[[_T_x], _T_x]: r""" @@ -1202,12 +1217,12 @@ def l_ratio_influence( """ _r, _s = clean_order(r), clean_order(s, name='s') - _x = np.array(a, copy=True) - _x.sort(kind=sort) + _x = np.array(a, copy=bool(sort)) + _x = sort_maybe(_x, sort=sort, inplace=True) n = len(_x) - eif_r = l_moment_influence(_x, _r, trim, sort=None, tol=0) - eif_k = l_moment_influence(_x, _s, trim, sort=None, tol=0) + eif_r = l_moment_influence(_x, _r, trim, sort=False, tol=0) + eif_k = l_moment_influence(_x, _s, trim, sort=False, tol=0) l_r, l_k = cast( tuple[float, float], diff --git a/lmo/_lm_co.py b/lmo/_lm_co.py index ba91101f..ac1adb97 100644 --- a/lmo/_lm_co.py +++ b/lmo/_lm_co.py @@ -27,6 +27,7 @@ 'l_corr', 'l_coskew', 'l_cokurtosis', + 'l_cokurt', ) @@ -48,9 +49,9 @@ def l_comoment( trim: AnyTrim = 0, *, dtype: _DType[_T_float] = np.float64, - rowvar: bool = True, + rowvar: bool | None = None, sort: lnpt.SortKind | None = None, - cache: bool = False, + cache: bool | None = None, ) -> lnpt.Array[Any, _T_float]: r""" Multivariate extension of [`lmo.l_moment`][lmo.l_moment]. @@ -109,20 +110,22 @@ def l_comoment( Useful for fitting heavy-tailed distributions, such as the Cauchy distribution. rowvar: - If `rowvar` is True (default), then each row (axis 0) represents a + If `True`, then each row (axis 0) represents a variable, with observations in the columns (axis 1). - Otherwise, the relationship is transposed: each column represents + If `False`, the relationship is transposed: each column represents a variable, while the rows contain observations. + If `None` (default), it is determined from the shape of `a`. dtype: Floating type to use in computing the L-moments. Default is [`numpy.float64`][numpy.float64]. - sort ('quick' | 'stable' | 'heap'): + sort ('quicksort' | 'heapsort' | 'stable'): Sorting algorithm, see [`numpy.sort`][numpy.sort]. cache: Set to `True` to speed up future L-moment calculations that have the same number of observations in `a`, equal `trim`, and equal or - smaller `r`. + smaller `r`. By default, it will cache i.f.f. the trim is integral, + and $r + s + t \le 24$. Set to `False` to always disable caching. Returns: L: Array of shape `(*r.shape, m, m)` with r-th L-comoments. @@ -133,29 +136,39 @@ def l_comoment( >>> import lmo, numpy as np >>> rng = np.random.default_rng(12345) - >>> x = rng.multivariate_normal([0, 0], [[6, -3], [-3, 3.5]], 99).T + >>> x = rng.multivariate_normal([0, 0], [[6, -3], [-3, 3.5]], 99) >>> lmo.l_comoment(x, 2) array([[ 1.2766793 , -0.83299947], [-0.71547941, 1.05990727]]) The diagonal contains the univariate L-moments: - >>> lmo.l_moment(x, 2, axis=-1) + >>> lmo.l_moment(x, 2, axis=0) array([1.2766793 , 1.05990727]) + The orientation (`rowvar`) is automatically determined, unless + explicitly specified. + + >>> lmo.l_comoment(x, 2).shape + (2, 2) + >>> lmo.l_comoment(x.T, 2).shape + (2, 2) + >>> lmo.l_comoment(x, 2, rowvar=False).shape + (2, 2) + >>> lmo.l_comoment(x.T, 2, rowvar=True).shape + (2, 2) + References: - [R. Serfling & P. Xiao (2007) - A Contribution to Multivariate L-Moments: L-Comoment Matrices](https://doi.org/10.1016/j.jmva.2007.01.008) """ - x = np.array( - a, - order='C' if rowvar else 'F', - subok=True, - ndmin=2, - ) + x = np.array(a, subok=True, ndmin=2) if x.ndim != 2: msg = f'sample array must be 2-D, got shape {x.shape}' raise ValueError(msg) + + if rowvar is None: + rowvar = x.shape[0] < x.shape[1] if not rowvar: x = x.T m, n = x.shape @@ -165,15 +178,15 @@ def l_comoment( else: _r = clean_orders(cast(AnyOrderND, r)) + if not m: + return np.empty((*np.shape(_r), 0, 0), dtype=dtype) + r_min = int(np.min(_r)) r_max = int(np.max(_r)) if r_min == r_max == 0 and _r.ndim == 0: return np.identity(m, dtype=dtype) - if not m: - return np.empty((*np.shape(_r), 0, 0), dtype=dtype) - # projection/hat matrix of shape (r_max - r_min, n) p_k = l_weights(r_max, n, trim=trim, dtype=dtype, cache=cache) if r_min > 1: @@ -184,7 +197,7 @@ def l_comoment( for j in range(m): # *concomitants* of x[i] w.r.t. x[j] for all i - x_kij = ordered(x, x[j], axis=-1, sort=sort) + x_kij = ordered(x, x[j], axis=-1, sort=sort or True) l_kij[:, :, j] = np.inner(p_k, x_kij) if r_min == 0: @@ -417,3 +430,6 @@ def l_cokurtosis( - [`lmo.l_kurtosis`][lmo.l_kurtosis] """ return l_coratio(a, 4, 2, trim=trim, dtype=dtype, **kwds) + + +l_cokurt = l_cokurtosis diff --git a/lmo/_utils.py b/lmo/_utils.py index 54d2cd06..2fddd6f2 100644 --- a/lmo/_utils.py +++ b/lmo/_utils.py @@ -21,6 +21,7 @@ 'l_stats_orders', 'moments_to_ratio', 'moments_to_stats_cov', + 'sort_maybe', 'ordered', 'plotting_positions', 'round0', @@ -138,6 +139,25 @@ def _sort_like( ) +def sort_maybe( + x: lnpt.Array[_T_shape1, _T_number], + /, + axis: int = -1, + *, + sort: bool | lnpt.SortKind = True, + inplace: bool = False, +) -> lnpt.Array[_T_shape1, _T_number]: + if not sort: + return x + + kind = sort if isinstance(sort, str) else None + + if inplace: + x.sort(axis=axis, kind=kind) + return x + return np.sort(x, axis=axis, kind=kind) + + def ordered( # noqa: C901 x: lnpt.AnyArrayFloat, y: lnpt.AnyArrayFloat | None = None, @@ -147,7 +167,7 @@ def ordered( # noqa: C901 *, fweights: AnyFWeights | None = None, aweights: AnyAWeights | None = None, - sort: lnpt.SortKind | None = None, + sort: lnpt.SortKind | bool = True, ) -> lnpt.Array[lnpt.AtLeast1D, lnpt.Float]: """ Calculate `n = len(x)` order stats of `x`, optionally weighted. @@ -173,8 +193,16 @@ def ordered( # noqa: C901 _z = np.apply_along_axis(np.add, axis, 1j * _x, _y) # apply the ordering - i_kk = np.argsort(_z, axis=axis, kind=sort) - x_kk = _sort_like(_x, i_kk, axis=axis) + if sort or sort is None: # pyright: ignore[reportUnnecessaryComparison] + kind = sort if isinstance(sort, str) else None + i_kk = np.argsort(_z, axis=axis, kind=kind) + x_kk = _sort_like(_x, i_kk, axis=axis) + else: + if axis is None: + i_kk = np.arange(len(_z)) + else: + i_kk = np.mgrid[tuple(slice(0, j) for j in _z.shape)][axis] + x_kk = _x # prepare observation weights w_kk = None diff --git a/lmo/contrib/scipy_stats.py b/lmo/contrib/scipy_stats.py index 6b78d3e5..7fc3f84b 100644 --- a/lmo/contrib/scipy_stats.py +++ b/lmo/contrib/scipy_stats.py @@ -589,6 +589,8 @@ def l_kurtosis( """ return float(self.l_ratio(4, 2, *args, trim=trim, **kwds)) + l_kurt = l_kurtosis + def l_moments_cov( self, r_max: int, @@ -1448,6 +1450,8 @@ def l_skew(self, trim: AnyTrim = 0) -> float: # noqa: D102 def l_kurtosis(self, trim: AnyTrim = 0) -> float: # noqa: D102 return self.dist.l_kurtosis(*self.args, trim=trim, **self.kwds) + l_kurt = l_kurtosis + def l_moments_cov( # noqa: D102 self, r_max: int, diff --git a/lmo/distributions.py b/lmo/distributions.py index 2526ce29..f9b67c1c 100644 --- a/lmo/distributions.py +++ b/lmo/distributions.py @@ -861,6 +861,8 @@ def l_kurtosis(self, trim: AnyTrim | None = None) -> float: """ return float(self.l_ratio(4, 2, trim=trim)) + l_kurt = l_kurtosis + # `rv_continuous` and `rv_frozen` compatibility @property diff --git a/lmo/pwm_beta.py b/lmo/pwm_beta.py index ef606c51..8184a025 100644 --- a/lmo/pwm_beta.py +++ b/lmo/pwm_beta.py @@ -88,7 +88,6 @@ def cov( r: _R, /, axis: None = ..., - *, dtype: _DType[_F] = np.float64, **kwds: Any, ) -> lnpt.Array[tuple[_R, _R], _F]: ... @@ -119,7 +118,7 @@ def cov( r: The amount of orders to evaluate, i.e. $k = 0, \dots, r - 1$. axis: The axis along which to calculate the covariance matrices. dtype: Desired output floating data type. - **kwargs: Additional keywords to pass to `lmo.stats.ordered`. + **kwds: Additional keywords to pass to `lmo.stats.ordered`. Returns: S_b: Variance-covariance matrix/tensor of shape `(r, r)` or (r, r, n) diff --git a/lmo/typing/_core.py b/lmo/typing/_core.py index 9fb26919..453b262e 100644 --- a/lmo/typing/_core.py +++ b/lmo/typing/_core.py @@ -22,8 +22,8 @@ class LMomentOptions(TypedDict, total=False): Use as e.g. `**kwds: Unpack[LMomentOptions]` (on `python<3.11`) or `**kwds: *LMomentOptions` (on `python>=3.11`). """ - sort: lnpt.SortKind - cache: bool + sort: lnpt.SortKind | bool + cache: bool | None fweights: AnyFWeights aweights: AnyAWeights @@ -34,5 +34,5 @@ class LComomentOptions(TypedDict, total=False): `**kwds: *LComomentOptions` (on `python>=3.11`). """ sort: lnpt.SortKind - cache: bool - rowvar: bool + cache: bool | None + rowvar: bool | None diff --git a/poetry.lock b/poetry.lock index 9e4bdf4c..ecb740fe 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "appnope" @@ -618,13 +618,13 @@ test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", [[package]] name = "griffe" -version = "0.45.0" +version = "0.45.1" description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." optional = false python-versions = ">=3.8" files = [ - {file = "griffe-0.45.0-py3-none-any.whl", hash = "sha256:90fe5c90e1b0ca7dd6fee78f9009f4e01b37dbc9ab484a9b2c1578915db1e571"}, - {file = "griffe-0.45.0.tar.gz", hash = "sha256:85cb2868d026ea51c89bdd589ad3ccc94abc5bd8d5d948e3d4450778a2a05b4a"}, + {file = "griffe-0.45.1-py3-none-any.whl", hash = "sha256:12194c10ae07a7f46708741ad78419362cf8e5c883f449c7c48de1686611b853"}, + {file = "griffe-0.45.1.tar.gz", hash = "sha256:84ce9243a9e63c07d55563a735a0d07ef70b46c455616c174010e7fc816f4648"}, ] [package.dependencies] @@ -642,13 +642,13 @@ files = [ [[package]] name = "hypothesis" -version = "6.102.4" +version = "6.102.6" description = "A library for property-based testing" optional = false python-versions = ">=3.8" files = [ - {file = "hypothesis-6.102.4-py3-none-any.whl", hash = "sha256:013df31b04a4daede13756f497e60e451963d86f426395a79f99c5d692919bbd"}, - {file = "hypothesis-6.102.4.tar.gz", hash = "sha256:59b4d144346d5cffb482cc1bafbd21b13ff31608e8c4b3e4630339aee3e87763"}, + {file = "hypothesis-6.102.6-py3-none-any.whl", hash = "sha256:ef281ba8b2626ebade9f463fbe8851ae6ff6ae4a8621a9e54c7c2477a97ccff0"}, + {file = "hypothesis-6.102.6.tar.gz", hash = "sha256:ef5655b4ca349082241ab55f899a34ea6d75cc336a7b07356680909059db1349"}, ] [package.dependencies] @@ -658,10 +658,10 @@ numpy = {version = ">=1.17.3", optional = true, markers = "extra == \"numpy\""} sortedcontainers = ">=2.1.0,<3.0.0" [package.extras] -all = ["backports.zoneinfo (>=0.2.1)", "black (>=19.10b0)", "click (>=7.0)", "crosshair-tool (>=0.0.54)", "django (>=3.2)", "dpcontracts (>=0.4)", "hypothesis-crosshair (>=0.0.2)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.17.3)", "pandas (>=1.1)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2024.1)"] +all = ["backports.zoneinfo (>=0.2.1)", "black (>=19.10b0)", "click (>=7.0)", "crosshair-tool (>=0.0.54)", "django (>=3.2)", "dpcontracts (>=0.4)", "hypothesis-crosshair (>=0.0.4)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.17.3)", "pandas (>=1.1)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2024.1)"] cli = ["black (>=19.10b0)", "click (>=7.0)", "rich (>=9.0.0)"] codemods = ["libcst (>=0.3.16)"] -crosshair = ["crosshair-tool (>=0.0.54)", "hypothesis-crosshair (>=0.0.2)"] +crosshair = ["crosshair-tool (>=0.0.54)", "hypothesis-crosshair (>=0.0.4)"] dateutil = ["python-dateutil (>=1.4)"] django = ["django (>=3.2)"] dpcontracts = ["dpcontracts (>=0.4)"] @@ -829,13 +829,13 @@ files = [ [[package]] name = "jupyter-client" -version = "8.6.1" +version = "8.6.2" description = "Jupyter protocol implementation and client libraries" optional = false python-versions = ">=3.8" files = [ - {file = "jupyter_client-8.6.1-py3-none-any.whl", hash = "sha256:3b7bd22f058434e3b9a7ea4b1500ed47de2713872288c0d511d19926f99b459f"}, - {file = "jupyter_client-8.6.1.tar.gz", hash = "sha256:e842515e2bab8e19186d89fdfea7abd15e39dd581f94e399f00e2af5a1652d3f"}, + {file = "jupyter_client-8.6.2-py3-none-any.whl", hash = "sha256:50cbc5c66fd1b8f65ecb66bc490ab73217993632809b6e505687de18e9dea39f"}, + {file = "jupyter_client-8.6.2.tar.gz", hash = "sha256:2bda14d55ee5ba58552a8c53ae43d215ad9868853489213f37da060ced54d8df"}, ] [package.dependencies] @@ -847,7 +847,7 @@ traitlets = ">=5.3" [package.extras] docs = ["ipykernel", "myst-parser", "pydata-sphinx-theme", "sphinx (>=4)", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] -test = ["coverage", "ipykernel (>=6.14)", "mypy", "paramiko", "pre-commit", "pytest", "pytest-cov", "pytest-jupyter[client] (>=0.4.1)", "pytest-timeout"] +test = ["coverage", "ipykernel (>=6.14)", "mypy", "paramiko", "pre-commit", "pytest (<8.2.0)", "pytest-cov", "pytest-jupyter[client] (>=0.4.1)", "pytest-timeout"] [[package]] name = "jupyter-core" @@ -1242,13 +1242,13 @@ cache = ["platformdirs"] [[package]] name = "mkdocs-material" -version = "9.5.23" +version = "9.5.24" description = "Documentation that simply works" optional = false python-versions = ">=3.8" files = [ - {file = "mkdocs_material-9.5.23-py3-none-any.whl", hash = "sha256:ffd08a5beaef3cd135aceb58ded8b98bbbbf2b70e5b656f6a14a63c917d9b001"}, - {file = "mkdocs_material-9.5.23.tar.gz", hash = "sha256:4627fc3f15de2cba2bde9debc2fd59b9888ef494beabfe67eb352e23d14bf288"}, + {file = "mkdocs_material-9.5.24-py3-none-any.whl", hash = "sha256:e12cd75954c535b61e716f359cf2a5056bf4514889d17161fdebd5df4b0153c6"}, + {file = "mkdocs_material-9.5.24.tar.gz", hash = "sha256:02d5aaba0ee755e707c3ef6e748f9acb7b3011187c0ea766db31af8905078a34"}, ] [package.dependencies] @@ -1326,13 +1326,13 @@ python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] [[package]] name = "mkdocstrings-python" -version = "1.10.2" +version = "1.10.3" description = "A Python handler for mkdocstrings." optional = false python-versions = ">=3.8" files = [ - {file = "mkdocstrings_python-1.10.2-py3-none-any.whl", hash = "sha256:e8e596b37f45c09b67bec253e035fe18988af5bbbbf44e0ccd711742eed750e5"}, - {file = "mkdocstrings_python-1.10.2.tar.gz", hash = "sha256:38a4fd41953defb458a107033440c229c7e9f98f35a24e84d888789c97da5a63"}, + {file = "mkdocstrings_python-1.10.3-py3-none-any.whl", hash = "sha256:11ff6d21d3818fb03af82c3ea6225b1534837e17f790aa5f09626524171f949b"}, + {file = "mkdocstrings_python-1.10.3.tar.gz", hash = "sha256:321cf9c732907ab2b1fedaafa28765eaa089d89320f35f7206d00ea266889d03"}, ] [package.dependencies] @@ -1461,7 +1461,6 @@ optional = false python-versions = ">=3.9" files = [ {file = "pandas-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90c6fca2acf139569e74e8781709dccb6fe25940488755716d1d354d6bc58bce"}, - {file = "pandas-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7adfc142dac335d8c1e0dcbd37eb8617eac386596eb9e1a1b77791cf2498238"}, {file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4abfe0be0d7221be4f12552995e58723c7422c80a659da13ca382697de830c08"}, {file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8635c16bf3d99040fdf3ca3db669a7250ddf49c55dc4aa8fe0ae0fa8d6dcc1f0"}, {file = "pandas-2.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:40ae1dffb3967a52203105a077415a86044a2bea011b5f321c6aa64b379a3f51"}, @@ -1475,14 +1474,12 @@ files = [ {file = "pandas-2.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0cace394b6ea70c01ca1595f839cf193df35d1575986e484ad35c4aeae7266c1"}, {file = "pandas-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:873d13d177501a28b2756375d59816c365e42ed8417b41665f346289adc68d24"}, {file = "pandas-2.2.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9dfde2a0ddef507a631dc9dc4af6a9489d5e2e740e226ad426a05cabfbd7c8ef"}, - {file = "pandas-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b79011ff7a0f4b1d6da6a61aa1aa604fb312d6647de5bad20013682d1429ce"}, {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cb51fe389360f3b5a4d57dbd2848a5f033350336ca3b340d1c53a1fad33bcad"}, {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee3a87076c0756de40b05c5e9a6069c035ba43e8dd71c379e68cab2c20f16ad"}, {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3e374f59e440d4ab45ca2fffde54b81ac3834cf5ae2cdfa69c90bc03bde04d76"}, {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:43498c0bdb43d55cb162cdc8c06fac328ccb5d2eabe3cadeb3529ae6f0517c32"}, {file = "pandas-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:d187d355ecec3629624fccb01d104da7d7f391db0311145817525281e2804d23"}, {file = "pandas-2.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0ca6377b8fca51815f382bd0b697a0814c8bda55115678cbc94c30aacbb6eff2"}, - {file = "pandas-2.2.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9057e6aa78a584bc93a13f0a9bf7e753a5e9770a30b4d758b8d5f2a62a9433cd"}, {file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:001910ad31abc7bf06f49dcc903755d2f7f3a9186c0c040b827e522e9cef0863"}, {file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66b479b0bd07204e37583c191535505410daa8df638fd8e75ae1b383851fe921"}, {file = "pandas-2.2.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a77e9d1c386196879aa5eb712e77461aaee433e54c68cf253053a73b7e49c33a"}, @@ -1842,13 +1839,13 @@ diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pyright" -version = "1.1.363" +version = "1.1.364" description = "Command line wrapper for pyright" optional = false python-versions = ">=3.7" files = [ - {file = "pyright-1.1.363-py3-none-any.whl", hash = "sha256:d3b8d73c8d230e26cc3523862f3398032a0c39a00d7bb69dc0f595f8e888fd01"}, - {file = "pyright-1.1.363.tar.gz", hash = "sha256:00a8f0ae0e339473bb0488f8a2a2dcdf574e94a16cd7b4390d49d144714d8db2"}, + {file = "pyright-1.1.364-py3-none-any.whl", hash = "sha256:865f1e02873c5dc7427c95acf53659a118574010e6fb364e27e47ec5c46a9f26"}, + {file = "pyright-1.1.364.tar.gz", hash = "sha256:612a2106a4078ec57efc22b5620729e9bdf4a3c17caba013b534bd33f7d08e5a"}, ] [package.dependencies] @@ -1860,13 +1857,13 @@ dev = ["twine (>=3.4.1)"] [[package]] name = "pytest" -version = "8.2.0" +version = "8.2.1" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.2.0-py3-none-any.whl", hash = "sha256:1733f0620f6cda4095bbf0d9ff8022486e91892245bb9e7d5542c018f612f233"}, - {file = "pytest-8.2.0.tar.gz", hash = "sha256:d507d4482197eac0ba2bae2e9babf0672eb333017bcedaa5fb1a3d42c1174b3f"}, + {file = "pytest-8.2.1-py3-none-any.whl", hash = "sha256:faccc5d332b8c3719f40283d0d44aa5cf101cec36f88cde9ed8f2bc0538612b1"}, + {file = "pytest-8.2.1.tar.gz", hash = "sha256:5046e5b46d8e4cac199c373041f26be56fdb81eb4e67dc11d4e10811fc3408fd"}, ] [package.dependencies] @@ -1986,7 +1983,6 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -2246,62 +2242,62 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "ruff" -version = "0.4.4" +version = "0.4.5" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.4.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:29d44ef5bb6a08e235c8249294fa8d431adc1426bfda99ed493119e6f9ea1bf6"}, - {file = "ruff-0.4.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c4efe62b5bbb24178c950732ddd40712b878a9b96b1d02b0ff0b08a090cbd891"}, - {file = "ruff-0.4.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c8e2f1e8fc12d07ab521a9005d68a969e167b589cbcaee354cb61e9d9de9c15"}, - {file = "ruff-0.4.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:60ed88b636a463214905c002fa3eaab19795679ed55529f91e488db3fe8976ab"}, - {file = "ruff-0.4.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b90fc5e170fc71c712cc4d9ab0e24ea505c6a9e4ebf346787a67e691dfb72e85"}, - {file = "ruff-0.4.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:8e7e6ebc10ef16dcdc77fd5557ee60647512b400e4a60bdc4849468f076f6eef"}, - {file = "ruff-0.4.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9ddb2c494fb79fc208cd15ffe08f32b7682519e067413dbaf5f4b01a6087bcd"}, - {file = "ruff-0.4.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c51c928a14f9f0a871082603e25a1588059b7e08a920f2f9fa7157b5bf08cfe9"}, - {file = "ruff-0.4.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5eb0a4bfd6400b7d07c09a7725e1a98c3b838be557fee229ac0f84d9aa49c36"}, - {file = "ruff-0.4.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b1867ee9bf3acc21778dcb293db504692eda5f7a11a6e6cc40890182a9f9e595"}, - {file = "ruff-0.4.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1aecced1269481ef2894cc495647392a34b0bf3e28ff53ed95a385b13aa45768"}, - {file = "ruff-0.4.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9da73eb616b3241a307b837f32756dc20a0b07e2bcb694fec73699c93d04a69e"}, - {file = "ruff-0.4.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:958b4ea5589706a81065e2a776237de2ecc3e763342e5cc8e02a4a4d8a5e6f95"}, - {file = "ruff-0.4.4-py3-none-win32.whl", hash = "sha256:cb53473849f011bca6e754f2cdf47cafc9c4f4ff4570003a0dad0b9b6890e876"}, - {file = "ruff-0.4.4-py3-none-win_amd64.whl", hash = "sha256:424e5b72597482543b684c11def82669cc6b395aa8cc69acc1858b5ef3e5daae"}, - {file = "ruff-0.4.4-py3-none-win_arm64.whl", hash = "sha256:39df0537b47d3b597293edbb95baf54ff5b49589eb7ff41926d8243caa995ea6"}, - {file = "ruff-0.4.4.tar.gz", hash = "sha256:f87ea42d5cdebdc6a69761a9d0bc83ae9b3b30d0ad78952005ba6568d6c022af"}, + {file = "ruff-0.4.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8f58e615dec58b1a6b291769b559e12fdffb53cc4187160a2fc83250eaf54e96"}, + {file = "ruff-0.4.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:84dd157474e16e3a82745d2afa1016c17d27cb5d52b12e3d45d418bcc6d49264"}, + {file = "ruff-0.4.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25f483ad9d50b00e7fd577f6d0305aa18494c6af139bce7319c68a17180087f4"}, + {file = "ruff-0.4.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:63fde3bf6f3ad4e990357af1d30e8ba2730860a954ea9282c95fc0846f5f64af"}, + {file = "ruff-0.4.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78e3ba4620dee27f76bbcad97067766026c918ba0f2d035c2fc25cbdd04d9c97"}, + {file = "ruff-0.4.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:441dab55c568e38d02bbda68a926a3d0b54f5510095c9de7f95e47a39e0168aa"}, + {file = "ruff-0.4.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1169e47e9c4136c997f08f9857ae889d614c5035d87d38fda9b44b4338909cdf"}, + {file = "ruff-0.4.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:755ac9ac2598a941512fc36a9070a13c88d72ff874a9781493eb237ab02d75df"}, + {file = "ruff-0.4.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4b02a65985be2b34b170025a8b92449088ce61e33e69956ce4d316c0fe7cce0"}, + {file = "ruff-0.4.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:75a426506a183d9201e7e5664de3f6b414ad3850d7625764106f7b6d0486f0a1"}, + {file = "ruff-0.4.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:6e1b139b45e2911419044237d90b60e472f57285950e1492c757dfc88259bb06"}, + {file = "ruff-0.4.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a6f29a8221d2e3d85ff0c7b4371c0e37b39c87732c969b4d90f3dad2e721c5b1"}, + {file = "ruff-0.4.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d6ef817124d72b54cc923f3444828ba24fa45c3164bc9e8f1813db2f3d3a8a11"}, + {file = "ruff-0.4.5-py3-none-win32.whl", hash = "sha256:aed8166c18b1a169a5d3ec28a49b43340949e400665555b51ee06f22813ef062"}, + {file = "ruff-0.4.5-py3-none-win_amd64.whl", hash = "sha256:b0b03c619d2b4350b4a27e34fd2ac64d0dabe1afbf43de57d0f9d8a05ecffa45"}, + {file = "ruff-0.4.5-py3-none-win_arm64.whl", hash = "sha256:9d15de3425f53161b3f5a5658d4522e4eee5ea002bf2ac7aa380743dd9ad5fba"}, + {file = "ruff-0.4.5.tar.gz", hash = "sha256:286eabd47e7d4d521d199cab84deca135557e6d1e0f0d01c29e757c3cb151b54"}, ] [[package]] name = "scipy" -version = "1.13.0" +version = "1.13.1" description = "Fundamental algorithms for scientific computing in Python" optional = false python-versions = ">=3.9" files = [ - {file = "scipy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba419578ab343a4e0a77c0ef82f088238a93eef141b2b8017e46149776dfad4d"}, - {file = "scipy-1.13.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:22789b56a999265431c417d462e5b7f2b487e831ca7bef5edeb56efe4c93f86e"}, - {file = "scipy-1.13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05f1432ba070e90d42d7fd836462c50bf98bd08bed0aa616c359eed8a04e3922"}, - {file = "scipy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8434f6f3fa49f631fae84afee424e2483289dfc30a47755b4b4e6b07b2633a4"}, - {file = "scipy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:dcbb9ea49b0167de4167c40eeee6e167caeef11effb0670b554d10b1e693a8b9"}, - {file = "scipy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:1d2f7bb14c178f8b13ebae93f67e42b0a6b0fc50eba1cd8021c9b6e08e8fb1cd"}, - {file = "scipy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fbcf8abaf5aa2dc8d6400566c1a727aed338b5fe880cde64907596a89d576fa"}, - {file = "scipy-1.13.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:5e4a756355522eb60fcd61f8372ac2549073c8788f6114449b37e9e8104f15a5"}, - {file = "scipy-1.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5acd8e1dbd8dbe38d0004b1497019b2dbbc3d70691e65d69615f8a7292865d7"}, - {file = "scipy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ff7dad5d24a8045d836671e082a490848e8639cabb3dbdacb29f943a678683d"}, - {file = "scipy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4dca18c3ffee287ddd3bc8f1dabaf45f5305c5afc9f8ab9cbfab855e70b2df5c"}, - {file = "scipy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:a2f471de4d01200718b2b8927f7d76b5d9bde18047ea0fa8bd15c5ba3f26a1d6"}, - {file = "scipy-1.13.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d0de696f589681c2802f9090fff730c218f7c51ff49bf252b6a97ec4a5d19e8b"}, - {file = "scipy-1.13.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:b2a3ff461ec4756b7e8e42e1c681077349a038f0686132d623fa404c0bee2551"}, - {file = "scipy-1.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf9fe63e7a4bf01d3645b13ff2aa6dea023d38993f42aaac81a18b1bda7a82a"}, - {file = "scipy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e7626dfd91cdea5714f343ce1176b6c4745155d234f1033584154f60ef1ff42"}, - {file = "scipy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:109d391d720fcebf2fbe008621952b08e52907cf4c8c7efc7376822151820820"}, - {file = "scipy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:8930ae3ea371d6b91c203b1032b9600d69c568e537b7988a3073dfe4d4774f21"}, - {file = "scipy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5407708195cb38d70fd2d6bb04b1b9dd5c92297d86e9f9daae1576bd9e06f602"}, - {file = "scipy-1.13.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:ac38c4c92951ac0f729c4c48c9e13eb3675d9986cc0c83943784d7390d540c78"}, - {file = "scipy-1.13.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09c74543c4fbeb67af6ce457f6a6a28e5d3739a87f62412e4a16e46f164f0ae5"}, - {file = "scipy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28e286bf9ac422d6beb559bc61312c348ca9b0f0dae0d7c5afde7f722d6ea13d"}, - {file = "scipy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:33fde20efc380bd23a78a4d26d59fc8704e9b5fd9b08841693eb46716ba13d86"}, - {file = "scipy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:45c08bec71d3546d606989ba6e7daa6f0992918171e2a6f7fbedfa7361c2de1e"}, - {file = "scipy-1.13.0.tar.gz", hash = "sha256:58569af537ea29d3f78e5abd18398459f195546bb3be23d16677fb26616cc11e"}, + {file = "scipy-1.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:20335853b85e9a49ff7572ab453794298bcf0354d8068c5f6775a0eabf350aca"}, + {file = "scipy-1.13.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d605e9c23906d1994f55ace80e0125c587f96c020037ea6aa98d01b4bd2e222f"}, + {file = "scipy-1.13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfa31f1def5c819b19ecc3a8b52d28ffdcc7ed52bb20c9a7589669dd3c250989"}, + {file = "scipy-1.13.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26264b282b9da0952a024ae34710c2aff7d27480ee91a2e82b7b7073c24722f"}, + {file = "scipy-1.13.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eccfa1906eacc02de42d70ef4aecea45415f5be17e72b61bafcfd329bdc52e94"}, + {file = "scipy-1.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:2831f0dc9c5ea9edd6e51e6e769b655f08ec6db6e2e10f86ef39bd32eb11da54"}, + {file = "scipy-1.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:27e52b09c0d3a1d5b63e1105f24177e544a222b43611aaf5bc44d4a0979e32f9"}, + {file = "scipy-1.13.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:54f430b00f0133e2224c3ba42b805bfd0086fe488835effa33fa291561932326"}, + {file = "scipy-1.13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e89369d27f9e7b0884ae559a3a956e77c02114cc60a6058b4e5011572eea9299"}, + {file = "scipy-1.13.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a78b4b3345f1b6f68a763c6e25c0c9a23a9fd0f39f5f3d200efe8feda560a5fa"}, + {file = "scipy-1.13.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45484bee6d65633752c490404513b9ef02475b4284c4cfab0ef946def50b3f59"}, + {file = "scipy-1.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:5713f62f781eebd8d597eb3f88b8bf9274e79eeabf63afb4a737abc6c84ad37b"}, + {file = "scipy-1.13.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5d72782f39716b2b3509cd7c33cdc08c96f2f4d2b06d51e52fb45a19ca0c86a1"}, + {file = "scipy-1.13.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:017367484ce5498445aade74b1d5ab377acdc65e27095155e448c88497755a5d"}, + {file = "scipy-1.13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:949ae67db5fa78a86e8fa644b9a6b07252f449dcf74247108c50e1d20d2b4627"}, + {file = "scipy-1.13.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de3ade0e53bc1f21358aa74ff4830235d716211d7d077e340c7349bc3542e884"}, + {file = "scipy-1.13.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2ac65fb503dad64218c228e2dc2d0a0193f7904747db43014645ae139c8fad16"}, + {file = "scipy-1.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:cdd7dacfb95fea358916410ec61bbc20440f7860333aee6d882bb8046264e949"}, + {file = "scipy-1.13.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:436bbb42a94a8aeef855d755ce5a465479c721e9d684de76bf61a62e7c2b81d5"}, + {file = "scipy-1.13.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:8335549ebbca860c52bf3d02f80784e91a004b71b059e3eea9678ba994796a24"}, + {file = "scipy-1.13.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d533654b7d221a6a97304ab63c41c96473ff04459e404b83275b60aa8f4b7004"}, + {file = "scipy-1.13.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637e98dcf185ba7f8e663e122ebf908c4702420477ae52a04f9908707456ba4d"}, + {file = "scipy-1.13.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a014c2b3697bde71724244f63de2476925596c24285c7a637364761f8710891c"}, + {file = "scipy-1.13.1-cp39-cp39-win_amd64.whl", hash = "sha256:392e4ec766654852c25ebad4f64e4e584cf19820b980bc04960bca0b0cd6eaa2"}, + {file = "scipy-1.13.1.tar.gz", hash = "sha256:095a87a0312b08dfd6a6155cbbd310a8c51800fc931b8c0b84003014b874ed3c"}, ] [package.dependencies] @@ -2314,19 +2310,18 @@ test = ["array-api-strict", "asv", "gmpy2", "hypothesis (>=6.30)", "mpmath", "po [[package]] name = "setuptools" -version = "69.5.1" +version = "70.0.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"}, - {file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"}, + {file = "setuptools-70.0.0-py3-none-any.whl", hash = "sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4"}, + {file = "setuptools-70.0.0.tar.gz", hash = "sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "six" @@ -2498,40 +2493,43 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [[package]] name = "watchdog" -version = "4.0.0" +version = "4.0.1" description = "Filesystem events monitoring" optional = false python-versions = ">=3.8" files = [ - {file = "watchdog-4.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:39cb34b1f1afbf23e9562501673e7146777efe95da24fab5707b88f7fb11649b"}, - {file = "watchdog-4.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c522392acc5e962bcac3b22b9592493ffd06d1fc5d755954e6be9f4990de932b"}, - {file = "watchdog-4.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6c47bdd680009b11c9ac382163e05ca43baf4127954c5f6d0250e7d772d2b80c"}, - {file = "watchdog-4.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8350d4055505412a426b6ad8c521bc7d367d1637a762c70fdd93a3a0d595990b"}, - {file = "watchdog-4.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c17d98799f32e3f55f181f19dd2021d762eb38fdd381b4a748b9f5a36738e935"}, - {file = "watchdog-4.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4986db5e8880b0e6b7cd52ba36255d4793bf5cdc95bd6264806c233173b1ec0b"}, - {file = "watchdog-4.0.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:11e12fafb13372e18ca1bbf12d50f593e7280646687463dd47730fd4f4d5d257"}, - {file = "watchdog-4.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5369136a6474678e02426bd984466343924d1df8e2fd94a9b443cb7e3aa20d19"}, - {file = "watchdog-4.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76ad8484379695f3fe46228962017a7e1337e9acadafed67eb20aabb175df98b"}, - {file = "watchdog-4.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:45cc09cc4c3b43fb10b59ef4d07318d9a3ecdbff03abd2e36e77b6dd9f9a5c85"}, - {file = "watchdog-4.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eed82cdf79cd7f0232e2fdc1ad05b06a5e102a43e331f7d041e5f0e0a34a51c4"}, - {file = "watchdog-4.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba30a896166f0fee83183cec913298151b73164160d965af2e93a20bbd2ab605"}, - {file = "watchdog-4.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d18d7f18a47de6863cd480734613502904611730f8def45fc52a5d97503e5101"}, - {file = "watchdog-4.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2895bf0518361a9728773083908801a376743bcc37dfa252b801af8fd281b1ca"}, - {file = "watchdog-4.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87e9df830022488e235dd601478c15ad73a0389628588ba0b028cb74eb72fed8"}, - {file = "watchdog-4.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6e949a8a94186bced05b6508faa61b7adacc911115664ccb1923b9ad1f1ccf7b"}, - {file = "watchdog-4.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6a4db54edea37d1058b08947c789a2354ee02972ed5d1e0dca9b0b820f4c7f92"}, - {file = "watchdog-4.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d31481ccf4694a8416b681544c23bd271f5a123162ab603c7d7d2dd7dd901a07"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8fec441f5adcf81dd240a5fe78e3d83767999771630b5ddfc5867827a34fa3d3"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:6a9c71a0b02985b4b0b6d14b875a6c86ddea2fdbebd0c9a720a806a8bbffc69f"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:557ba04c816d23ce98a06e70af6abaa0485f6d94994ec78a42b05d1c03dcbd50"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:d0f9bd1fd919134d459d8abf954f63886745f4660ef66480b9d753a7c9d40927"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:f9b2fdca47dc855516b2d66eef3c39f2672cbf7e7a42e7e67ad2cbfcd6ba107d"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:73c7a935e62033bd5e8f0da33a4dcb763da2361921a69a5a95aaf6c93aa03a87"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6a80d5cae8c265842c7419c560b9961561556c4361b297b4c431903f8c33b269"}, - {file = "watchdog-4.0.0-py3-none-win32.whl", hash = "sha256:8f9a542c979df62098ae9c58b19e03ad3df1c9d8c6895d96c0d51da17b243b1c"}, - {file = "watchdog-4.0.0-py3-none-win_amd64.whl", hash = "sha256:f970663fa4f7e80401a7b0cbeec00fa801bf0287d93d48368fc3e6fa32716245"}, - {file = "watchdog-4.0.0-py3-none-win_ia64.whl", hash = "sha256:9a03e16e55465177d416699331b0f3564138f1807ecc5f2de9d55d8f188d08c7"}, - {file = "watchdog-4.0.0.tar.gz", hash = "sha256:e3e7065cbdabe6183ab82199d7a4f6b3ba0a438c5a512a68559846ccb76a78ec"}, + {file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:da2dfdaa8006eb6a71051795856bedd97e5b03e57da96f98e375682c48850645"}, + {file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e93f451f2dfa433d97765ca2634628b789b49ba8b504fdde5837cdcf25fdb53b"}, + {file = "watchdog-4.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ef0107bbb6a55f5be727cfc2ef945d5676b97bffb8425650dadbb184be9f9a2b"}, + {file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:17e32f147d8bf9657e0922c0940bcde863b894cd871dbb694beb6704cfbd2fb5"}, + {file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:03e70d2df2258fb6cb0e95bbdbe06c16e608af94a3ffbd2b90c3f1e83eb10767"}, + {file = "watchdog-4.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:123587af84260c991dc5f62a6e7ef3d1c57dfddc99faacee508c71d287248459"}, + {file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:093b23e6906a8b97051191a4a0c73a77ecc958121d42346274c6af6520dec175"}, + {file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:611be3904f9843f0529c35a3ff3fd617449463cb4b73b1633950b3d97fa4bfb7"}, + {file = "watchdog-4.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:62c613ad689ddcb11707f030e722fa929f322ef7e4f18f5335d2b73c61a85c28"}, + {file = "watchdog-4.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d4925e4bf7b9bddd1c3de13c9b8a2cdb89a468f640e66fbfabaf735bd85b3e35"}, + {file = "watchdog-4.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cad0bbd66cd59fc474b4a4376bc5ac3fc698723510cbb64091c2a793b18654db"}, + {file = "watchdog-4.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a3c2c317a8fb53e5b3d25790553796105501a235343f5d2bf23bb8649c2c8709"}, + {file = "watchdog-4.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c9904904b6564d4ee8a1ed820db76185a3c96e05560c776c79a6ce5ab71888ba"}, + {file = "watchdog-4.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:667f3c579e813fcbad1b784db7a1aaa96524bed53437e119f6a2f5de4db04235"}, + {file = "watchdog-4.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d10a681c9a1d5a77e75c48a3b8e1a9f2ae2928eda463e8d33660437705659682"}, + {file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0144c0ea9997b92615af1d94afc0c217e07ce2c14912c7b1a5731776329fcfc7"}, + {file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:998d2be6976a0ee3a81fb8e2777900c28641fb5bfbd0c84717d89bca0addcdc5"}, + {file = "watchdog-4.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e7921319fe4430b11278d924ef66d4daa469fafb1da679a2e48c935fa27af193"}, + {file = "watchdog-4.0.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:f0de0f284248ab40188f23380b03b59126d1479cd59940f2a34f8852db710625"}, + {file = "watchdog-4.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bca36be5707e81b9e6ce3208d92d95540d4ca244c006b61511753583c81c70dd"}, + {file = "watchdog-4.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ab998f567ebdf6b1da7dc1e5accfaa7c6992244629c0fdaef062f43249bd8dee"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:dddba7ca1c807045323b6af4ff80f5ddc4d654c8bce8317dde1bd96b128ed253"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_armv7l.whl", hash = "sha256:4513ec234c68b14d4161440e07f995f231be21a09329051e67a2118a7a612d2d"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_i686.whl", hash = "sha256:4107ac5ab936a63952dea2a46a734a23230aa2f6f9db1291bf171dac3ebd53c6"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64.whl", hash = "sha256:6e8c70d2cd745daec2a08734d9f63092b793ad97612470a0ee4cbb8f5f705c57"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:f27279d060e2ab24c0aa98363ff906d2386aa6c4dc2f1a374655d4e02a6c5e5e"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:f8affdf3c0f0466e69f5b3917cdd042f89c8c63aebdb9f7c078996f607cdb0f5"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ac7041b385f04c047fcc2951dc001671dee1b7e0615cde772e84b01fbf68ee84"}, + {file = "watchdog-4.0.1-py3-none-win32.whl", hash = "sha256:206afc3d964f9a233e6ad34618ec60b9837d0582b500b63687e34011e15bb429"}, + {file = "watchdog-4.0.1-py3-none-win_amd64.whl", hash = "sha256:7577b3c43e5909623149f76b099ac49a1a01ca4e167d1785c76eb52fa585745a"}, + {file = "watchdog-4.0.1-py3-none-win_ia64.whl", hash = "sha256:d7b9f5f3299e8dd230880b6c55504a1f69cf1e4316275d1b215ebdd8187ec88d"}, + {file = "watchdog-4.0.1.tar.gz", hash = "sha256:eebaacf674fa25511e8867028d281e602ee6500045b57f43b08778082f7f8b44"}, ] [package.extras] @@ -2568,4 +2566,4 @@ pandas = ["pandas"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.14" -content-hash = "711845b916c770c2006ec8cd5cbf09c4c9ba80a083f635706d0a620b50e9148a" +content-hash = "1588274b645e001ea4b541217a3f80b783139b27472272889292f966f6042240" diff --git a/pyproject.toml b/pyproject.toml index e40d4c8e..304460eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "lmo" -version = "0.13.2" +version = "0.14.0" description = "L-Moments for robust statistics & inference." authors = ["Joren Hammudoglu "] readme = "README.md" @@ -49,15 +49,15 @@ optype = "^0.4.0" codespell = "^2.2.6" hypothesis = {version = "^6.102.4", extras = ["numpy"]} pre-commit = "^3.7.1" -pyright = "^1.1.363" -pytest = "^8.2.0" +pyright = "^1.1.364" +pytest = "^8.2.1" pytest-doctestplus = "^1.2.1" -ruff = "^0.4.4" +ruff = "^0.4.5" tomli = {version = "^2.0.1", python = "<3.11"} [tool.poetry.group.docs.dependencies] mkdocs = "^1.6.0" -mkdocs-material = "^9.5.23" +mkdocs-material = "^9.5.24" mkdocs-include-markdown-plugin = "^6.0.6" mkdocstrings = {version = "^0.25.1", extras = ["python"]} mkdocs-git-revision-date-localized-plugin = "^1.2.5" @@ -87,7 +87,7 @@ numpy = "<=2" optional = true [tool.poetry.group.numpy2.dependencies] numpy = {version = ">=2.0.0rc2", allow-prereleases = true} -scipy = ">=1.13.0" +scipy = ">=1.13.1" [tool.poetry.extras] pandas = ["pandas"] @@ -172,7 +172,6 @@ extend-exclude = [ ] [tool.ruff.lint] -ignore-init-module-imports = true preview = true select = [ "F", # pyflakes diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..3350ec3c --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,25 @@ +# ruff: noqa: SLF001 +# pyright: reportPrivateUsage=false +import contextlib +from collections.abc import Iterator + +import pytest + +from lmo import _lm + + +@contextlib.contextmanager +def tmp_cache() -> Iterator[_lm._Cache]: + cache_tmp: _lm._Cache = {} + cache_old, _lm._CACHE = _lm._CACHE, cache_tmp + try: + yield cache_tmp + finally: + _lm._CACHE = cache_old + + +@pytest.fixture(name='tmp_cache') +def tmp_cache_fixture(): + with tmp_cache() as cache: + assert not cache + yield cache diff --git a/tests/test_weights.py b/tests/test_weights.py index aa0929bd..88f60121 100644 --- a/tests/test_weights.py +++ b/tests/test_weights.py @@ -1,78 +1,166 @@ +import functools +from typing import Any + import numpy as np +import pytest from hypothesis import ( given, strategies as st, ) +from hypothesis.extra import numpy as hnp +from numpy.testing import ( + assert_allclose as _assert_allclose, + assert_array_equal, +) + +from lmo import l_weights +from .conftest import tmp_cache -from lmo._lm import l_weights + +# matches np.allclose +assert_allclose = functools.partial(_assert_allclose, rtol=1e-5, atol=1e-8) -MAX_N = 1 << 10 MAX_R = 8 MAX_T = 4 +MIN_N = MAX_R + MAX_T * 2 + 1 +MAX_N = 1 << 8 + -st_n = st.integers(MAX_R + MAX_T * 2 + 1, MAX_N) +st_n = st.integers(MIN_N, MAX_N) st_r = st.integers(1, MAX_R) -st_t_f = st.floats(0, MAX_T, exclude_min=True) -st_t_i = st.integers(1, MAX_T) -st_t_i0 = st.integers(0, MAX_T) +st_i_eq0 = st.just(0) +st_i_ge0 = st.integers(0, MAX_T) +st_i_gt0 = st.integers(1, MAX_T) + +st_i2_eq0 = st.tuples(st.just(0), st.just(0)) +st_i2_ge0 = st.tuples(st.integers(0, MAX_T), st.integers(0, MAX_T)) +st_i2_gt0 = st.tuples(st.integers(1, MAX_T), st.integers(1, MAX_T)) -st_trim_i = st.tuples(st_t_i, st_t_i) -st_trim_i0 = st.tuples(st_t_i0, st_t_i0) +st_i12_eq0 = st_i_eq0 | st_i2_eq0 +st_i12_ge0 = st_i_ge0 | st_i2_ge0 +st_i12_gt0 = st_i_gt0 | st_i2_gt0 +st_floating = hnp.floating_dtypes(sizes=[32, 64]) -@given(n=st_n, r=st_r, trim0=st.just((0, 0))) -def test_l_weights_alias(n, r, trim0): - w_l = l_weights(r, n) - w_tl = l_weights(r, n, trim0) - assert np.array_equal(w_l, w_tl) +@given(n=st_n, trim=st_i12_eq0) +def test_empty(n: int, trim: int | tuple[int, int]): + w = l_weights(0, n, trim, cache=False) + assert w.shape == (0, n) -@given(n=st_n, r=st_r, trim=st_trim_i0) -def test_l_weights_basic(n, r, trim): - w = l_weights(r, n, trim) +@given(n=st_n, r=st_r, trim=st_i12_eq0) +def test_untrimmed(n: int, r: int, trim: int | tuple[int, int]): + w_l = l_weights(r, n, cache=False) + w_tl = l_weights(r, n, trim, cache=False) + + assert_array_equal(w_l, w_tl) + + +@given(n=st_n, r=st_r, trim=st_i12_ge0) +def test_default(n: int, r: int, trim: int | tuple[int, int]): + w = l_weights(r, n, trim, cache=False) assert w.shape == (r, n) - assert np.all(np.isfinite(n)) + assert np.all(np.isfinite(w)) assert w.dtype.type is np.float64 -# symmetries only apply for symmetric trimming, for obvious reasons -@given(n=st_n, t=st_t_i0) -def test_l_weights_symmetry(n, t): - w = l_weights(MAX_R, n, (t, t)) +@given(n=st_n, r=st_r, trim=st_i12_ge0, dtype=st_floating) +def test_dtype( + n: int, + r: int, + trim: int | tuple[int, int], + dtype: np.dtype[np.floating[Any]], +): + w = l_weights(r, n, trim, dtype=dtype, cache=False) + + assert np.all(np.isfinite(w)) + assert w.dtype.type is dtype.type + + +@given(n=st_n, t=st_i_ge0) +def test_symmetry(n: int, t: int): + w = l_weights(MAX_R, n, (t, t), cache=False) w_evn_lhs, w_evn_rhs = w[::2], w[::2, ::-1] - assert np.allclose(w_evn_lhs, w_evn_rhs) + assert_allclose(w_evn_lhs, w_evn_rhs) w_odd_lhs, w_odd_rhs = w[1::2], w[1::2, ::-1] - assert np.allclose(w_odd_lhs, -w_odd_rhs) + assert_allclose(w_odd_lhs, -w_odd_rhs) def test_l_weights_symmetry_large_even_r(): - w = l_weights(16, MAX_N * 2) + w = l_weights(16, MAX_N * 2, cache=False) w_evn_lhs, w_evn_rhs = w[::2], w[::2, ::-1] - assert np.allclose(w_evn_lhs, w_evn_rhs) + assert_allclose(w_evn_lhs, w_evn_rhs) -@given(n=st_n, r=st_r, trim=st_trim_i) -def test_l_weights_trim(n, r, trim): - w = l_weights(r, n, trim) +@given(n=st_n, r=st_r, trim=st_i2_gt0) +def test_trim(n: int, r: int, trim: tuple[int, int]): + w = l_weights(r, n, trim, cache=False) tl, tr = trim assert tl > 0 assert tr > 0 - assert np.allclose(w[:, :tl], 0) - assert np.allclose(w[:, n - tr :], 0) + assert_allclose(w[:, :tl], 0) + assert_allclose(w[:, n - tr :], 0) -@given(n=st_n, r=st.integers(2, MAX_R), trim=st_trim_i0) -def test_tl_weights_sum(n, r, trim): - w = l_weights(r, n, trim) +@given(n=st_n, r=st.integers(2, MAX_R), trim=st_i12_ge0) +def test_sum(n: int, r: int, trim: int | tuple[int, int]): + w = l_weights(r, n, trim, cache=False) w_sum = w.sum(axis=-1) - assert np.allclose(w_sum, np.eye(r, 1).ravel()) + assert_allclose(w_sum, np.eye(r, 1).ravel()) + + +@given(n=st_n, r=st.integers(4, MAX_R), trim=st_i12_ge0) +def test_uncached(n: int, r: int, trim: int | tuple[int, int]): + with tmp_cache() as cache: + w0 = l_weights(r, n, trim, cache=False) + w1 = l_weights(r, n, trim, cache=False) + + assert not cache + assert w0 is not w1 + assert_array_equal(w0, w1) + + +@given(n=st_n, r=st.integers(4, MAX_R), trim=st_i12_ge0, dtype=st_floating) +def test_cached( + n: int, + r: int, + trim: int | tuple[int, int], + dtype: np.dtype[np.floating[Any]], +): + s, t = trim if isinstance(trim, tuple) else (trim, trim) + cache_key = dtype.char, n, s, t + + with tmp_cache() as cache: + assert not cache + + w0 = l_weights(r, n, trim, dtype=dtype, cache=True) + assert w0.dtype.char == cache_key[0] + assert cache + assert cache_key in cache + w0_cached = cache[cache_key] + + # cached weights should be readonly + w0_orig = w0[0, 0] + with pytest.raises( + ValueError, + match='assignment destination is read-only', + ): + w0[0, 0] = w0_orig + 1 + assert w0[0, 0] == w0_orig + + w1 = l_weights(r, n, trim, dtype=dtype, cache=True) + w1_cached = cache[cache_key] + assert w0_cached is w1_cached + + # this requires `r>=4` and `r == r_cached` + assert w0 is w1