diff --git a/.github/workflows/on-push.yaml b/.github/workflows/on-push.yaml index 6aa9d6d..72a62fb 100644 --- a/.github/workflows/on-push.yaml +++ b/.github/workflows/on-push.yaml @@ -13,7 +13,7 @@ jobs: timeout-minutes: 30 strategy: matrix: - python-version: ["3.9", "3.10", "pypy-3.9"] + python-version: ["3.9", "3.10", "3.11", "3.12", "pypy-3.9", "pypy-3.10"] steps: - uses: actions/checkout@v2 with: diff --git a/.isort.cfg b/.isort.cfg deleted file mode 100644 index 62a7f45..0000000 --- a/.isort.cfg +++ /dev/null @@ -1,16 +0,0 @@ -# -*- mode: conf -*- -# ====================================================================================== -# Copyright and other protections apply. Please see the accompanying LICENSE file for -# rights and restrictions governing use of this software. All rights not expressly -# waived or licensed are reserved. If that file is missing or appears to be modified -# from its original, then please contact the author before viewing or using this -# software in any capacity. -# ====================================================================================== - -[settings] - -# See -profile = black - -# See -skip_gitignore = true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 26033f6..e647c5c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ repos: hooks: - id: check-useless-excludes - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.6.0 hooks: - id: end-of-file-fixer # See @@ -38,14 +38,14 @@ repos: - id: check-xml - id: check-yaml - repo: https://github.com/psf/black - rev: 23.7.0 + rev: 24.4.2 hooks: - id: black - repo: https://github.com/pycqa/isort - rev: 5.12.0 + rev: 5.13.2 hooks: - id: isort - repo: https://github.com/pycqa/flake8 - rev: 6.0.0 + rev: 7.0.0 hooks: - id: flake8 diff --git a/dyce/evaluation.py b/dyce/evaluation.py index a36e5ae..67b8e81 100644 --- a/dyce/evaluation.py +++ b/dyce/evaluation.py @@ -89,8 +89,7 @@ def __call__( *args: _POrPWithSelectionOrSourceT, limit: Optional[LimitT] = None, **kw: _POrPWithSelectionOrSourceT, - ) -> H: - ... + ) -> H: ... # ---- Data ---------------------------------------------------------------------------- @@ -109,8 +108,7 @@ def __call__( def expandable( *, sentinel: H = _DEFAULT_SENTINEL, -) -> Callable[[_DependentTermT], _ForEachEvaluatorT]: - ... +) -> Callable[[_DependentTermT], _ForEachEvaluatorT]: ... @overload @@ -118,8 +116,7 @@ def expandable( f: _DependentTermT, *, sentinel: H = _DEFAULT_SENTINEL, -) -> _ForEachEvaluatorT: - ... +) -> _ForEachEvaluatorT: ... @experimental diff --git a/dyce/h.py b/dyce/h.py index aa76711..c36c60c 100644 --- a/dyce/h.py +++ b/dyce/h.py @@ -370,6 +370,7 @@ class H(_MappingT): ``` """ + __slots__: Any = ( "_h", "_hash", @@ -502,7 +503,7 @@ def __getitem__(self, key: RealLike) -> int: @beartype def __iter__(self) -> Iterator[RealLike]: - return iter(self._h) + yield from self._h @beartype def __reversed__(self) -> Iterator[RealLike]: @@ -804,7 +805,7 @@ def foreach( """ from dyce import P - def _dependent_term(**roll_kw): + def _dependent_term(**roll_kw) -> HOrOutcomeT: outcome_kw: dict[str, RealLike] = {} for key, roll in roll_kw.items(): @@ -1145,8 +1146,11 @@ def exactly_k_times_in_n( future versions. Computes (in constant time) and returns the number of times *outcome* appears - exactly *k* times among ``#!python n@self``. This is a more efficient - alternative to ``#!python (n@(self.eq(outcome)))[k]``. + exactly *k* times among ``#!python n@self``. This computes the binomial + coefficient as a more efficient alternative to ``#!python + (n@(self.eq(outcome)))[k]``. See [this + video](https://www.khanacademy.org/math/ap-statistics/random-variables-ap/binomial-random-variable/v/generalizing-k-scores-in-n-attempts) + for a pretty clear explanation. ``` python >>> H(6).exactly_k_times_in_n(outcome=5, n=4, k=2) @@ -1157,29 +1161,69 @@ def exactly_k_times_in_n( 8 ``` + + !!! note "Counts, *not* probabilities" + + This computes the *number* of ways to get exactly *k* of *outcome* from *n* + like histograms, which may not expressed in the lowest terms. (See the + [``lowest_terms`` method][dyce.h.H.lowest_terms].) If we want the + *probability* of getting exactly *k* of *outcome* from *n* like histograms, + we can divide by ``#!python h.total ** n``. (See the + [``total`` property][dyce.h.H.total].) + + ``` python + >>> from fractions import Fraction + >>> h = H((2, 3, 3, 4, 4, 5)) + >>> n, k = 3, 2 + >>> (n @ h).total == h.total ** n + True + >>> Fraction( + ... h.exactly_k_times_in_n(outcome=3, n=n, k=k), + ... h.total ** n, + ... ) + Fraction(2, 9) + >>> h_not_lowest_terms = h.accumulate(h) + >>> h == h_not_lowest_terms + True + >>> h_not_lowest_terms + H({2: 2, 3: 4, 4: 4, 5: 2}) + >>> h_not_lowest_terms.exactly_k_times_in_n(outcome=3, n=n, k=k) + 384 + >>> Fraction( + ... h_not_lowest_terms.exactly_k_times_in_n(outcome=3, n=n, k=k), + ... h_not_lowest_terms.total ** n, + ... ) + Fraction(2, 9) + + ``` """ n = as_int(n) k = as_int(k) assert k <= n c_outcome = self.get(outcome, 0) - return comb(n, k) * c_outcome**k * (self.total - c_outcome) ** (n - k) + return ( + # number of ways to choose k things from n things (k <= n) + comb(n, k) + # cumulative counts for the particular outcomes we want + * c_outcome**k + # cumulative counts for all other outcomes + * (self.total - c_outcome) ** (n - k) + ) @overload def explode( self, max_depth: IntegralLike, precision_limit: None = None, - ) -> "H": - ... + ) -> "H": ... @overload def explode( self, max_depth: None, precision_limit: Union[RationalLikeMixedU, RealLike], - ) -> "H": - ... + ) -> "H": ... @overload def explode( @@ -1187,16 +1231,14 @@ def explode( max_depth: None = None, *, precision_limit: Union[RationalLikeMixedU, RealLike], - ) -> "H": - ... + ) -> "H": ... @overload def explode( self, max_depth: None = None, precision_limit: None = None, - ) -> "H": - ... + ) -> "H": ... @deprecated @beartype @@ -1275,8 +1317,9 @@ def order_stat_for_n_at_pos(self, n: SupportsInt, pos: SupportsInt) -> "H": This method should be considered experimental and may change or disappear in future versions. - Computes the probability distribution for each outcome appearing in at *pos* for - *n* histograms. *pos* is a zero-based index. + Computes the probability distribution for each outcome appearing in at *pos* in + rolls of *n* histograms where rolls are ordered least-to-greatest. *pos* is a + zero-based index. ``` python >>> d6avg = H((2, 3, 3, 4, 4, 5)) @@ -1341,8 +1384,7 @@ def substitute( self, expand: _SubstituteExpandCallbackT, coalesce: _SubstituteCoalesceCallbackT = coalesce_replace, - ) -> "H": - ... + ) -> "H": ... @overload def substitute( @@ -1352,8 +1394,7 @@ def substitute( *, max_depth: IntegralLike, precision_limit: None = None, - ) -> "H": - ... + ) -> "H": ... @overload def substitute( @@ -1362,8 +1403,7 @@ def substitute( coalesce: _SubstituteCoalesceCallbackT, max_depth: IntegralLike, precision_limit: None = None, - ) -> "H": - ... + ) -> "H": ... @overload def substitute( @@ -1373,8 +1413,7 @@ def substitute( *, max_depth: None, precision_limit: Union[RationalLikeMixedU, RealLike], - ) -> "H": - ... + ) -> "H": ... @overload def substitute( @@ -1383,8 +1422,7 @@ def substitute( coalesce: _SubstituteCoalesceCallbackT, max_depth: None, precision_limit: Union[RationalLikeMixedU, RealLike], - ) -> "H": - ... + ) -> "H": ... @overload def substitute( @@ -1394,8 +1432,7 @@ def substitute( max_depth: None = None, *, precision_limit: Union[RationalLikeMixedU, RealLike], - ) -> "H": - ... + ) -> "H": ... @overload def substitute( @@ -1404,8 +1441,7 @@ def substitute( coalesce: _SubstituteCoalesceCallbackT = coalesce_replace, max_depth: None = None, precision_limit: None = None, - ) -> "H": - ... + ) -> "H": ... @deprecated @beartype @@ -1543,15 +1579,13 @@ def zero_fill(self, outcomes: Iterable[RealLike]) -> "H": @overload def distribution( self, - ) -> Iterator[tuple[RealLike, Fraction]]: - ... + ) -> Iterator[tuple[RealLike, Fraction]]: ... @overload def distribution( self, rational_t: Callable[[int, int], _T], - ) -> Iterator[tuple[RealLike, _T]]: - ... + ) -> Iterator[tuple[RealLike, _T]]: ... @experimental @beartype @@ -1661,7 +1695,7 @@ def distribution_xy( ``` """ # TODO(posita): See - return tuple( # type: ignore [return-value] + return tuple( zip( *( (outcome, float(probability)) @@ -1899,6 +1933,7 @@ class HableT(Protocol, metaclass=CachingProtocolMeta): World Book Online (WBO) style [pronunciation respelling](https://en.wikipedia.org/wiki/Pronunciation_respelling_for_English#Traditional_respelling_systems). """ + __slots__: Any = () @abstractmethod @@ -1919,6 +1954,7 @@ class HableOpsMixin(HableT): See [``HableT``][dyce.h.HableT] for notes on pronunciation. """ + __slots__: Any = () def __init__(self): @@ -2185,16 +2221,14 @@ def explode( self: HableT, max_depth: IntegralLike, precision_limit: None = None, - ) -> H: - ... + ) -> H: ... @overload def explode( self: HableT, max_depth: None, precision_limit: Union[RationalLikeMixedU, RealLike], - ) -> H: - ... + ) -> H: ... @overload def explode( @@ -2202,16 +2236,14 @@ def explode( max_depth: None = None, *, precision_limit: Union[RationalLikeMixedU, RealLike], - ) -> H: - ... + ) -> H: ... @overload def explode( self: HableT, max_depth: None = None, precision_limit: None = None, - ) -> H: - ... + ) -> H: ... @beartype def explode( @@ -2240,8 +2272,7 @@ def substitute( *, max_depth: IntegralLike, precision_limit: None = None, - ) -> H: - ... + ) -> H: ... @overload def substitute( @@ -2250,8 +2281,7 @@ def substitute( coalesce: _SubstituteCoalesceCallbackT, max_depth: IntegralLike, precision_limit: None = None, - ) -> H: - ... + ) -> H: ... @overload def substitute( @@ -2261,8 +2291,7 @@ def substitute( *, max_depth: None, precision_limit: Union[RationalLikeMixedU, RealLike], - ) -> H: - ... + ) -> H: ... @overload def substitute( @@ -2271,8 +2300,7 @@ def substitute( coalesce: _SubstituteCoalesceCallbackT, max_depth: None, precision_limit: Union[RationalLikeMixedU, RealLike], - ) -> H: - ... + ) -> H: ... @overload def substitute( @@ -2282,8 +2310,7 @@ def substitute( max_depth: None = None, *, precision_limit: Union[RationalLikeMixedU, RealLike], - ) -> H: - ... + ) -> H: ... @overload def substitute( @@ -2292,8 +2319,7 @@ def substitute( coalesce: _SubstituteCoalesceCallbackT = coalesce_replace, max_depth: None = None, precision_limit: None = None, - ) -> H: - ... + ) -> H: ... @deprecated @beartype diff --git a/dyce/p.py b/dyce/p.py index 5af7e1c..d982712 100644 --- a/dyce/p.py +++ b/dyce/p.py @@ -10,7 +10,7 @@ from fractions import Fraction from functools import cache from itertools import chain, groupby, product, repeat -from math import prod +from math import inf, prod from operator import __eq__, __index__, __ne__ from typing import ( Any, @@ -187,6 +187,7 @@ class P(Sequence[H], HableOpsMixin): ``` """ + __slots__: Any = ( "_hs", "_total", @@ -286,12 +287,10 @@ def __len__(self) -> int: return len(self._hs) @overload - def __getitem__(self, key: SupportsIndex) -> H: - ... + def __getitem__(self, key: SupportsIndex) -> H: ... @overload - def __getitem__(self, key: slice) -> "P": - ... + def __getitem__(self, key: slice) -> "P": ... @beartype def __getitem__(self, key: _GetItemT) -> Union[H, "P"]: @@ -302,7 +301,7 @@ def __getitem__(self, key: _GetItemT) -> Union[H, "P"]: @beartype def __iter__(self) -> Iterator[H]: - return iter(self._hs) + yield from self._hs @beartype def __matmul__(self, other: SupportsInt) -> "P": @@ -828,12 +827,10 @@ def rolls_with_counts(self, *which: _GetItemT) -> Iterator[_RollCountT]: homogeneous pool benefits from [Ilmari Karonen’s optimization](https://rpg.stackexchange.com/a/166663/71245), which appears to scale geometrically with $k$ times some factor of $n$ (e.g., $\log n$, - but I haven’t bothered to figure that out yet), such that—in observed - testing, at least—it is generally the fastest approach for $k < n$. - - Where $k = n$, we leverage the [*multinomial - coefficient*](https://en.wikipedia.org/wiki/Permutation#Permutations_of_multisets), - which appears to scale generally with $n$. + but I haven’t bothered to figure that out yet), such that—at least in + observed testing—it is generally the fastest approach, especially for $k < + n$. Where $k = n$, this is equivalent to leveraging the [*multinomial + coefficient*](https://en.wikipedia.org/wiki/Permutation#Permutations_of_multisets). $$ {{n} \choose {{{k}_{1}},{{k}_{2}},\ldots,{{k}_{m}}}} @@ -849,8 +846,8 @@ def rolls_with_counts(self, *which: _GetItemT) -> Iterator[_RollCountT]: m), (m, m, …, m))`` To determine the count for a particular roll ``#!python (a, b, …, n)``, we - compute the multinomial coefficient for that roll and multiply by the scalar - ``#!python H(m)[a] * H(m)[b] * … * H(m)[n]``. (See + compute the equivalent of the multinomial coefficient for that roll and + multiply by the scalar ``#!python H(m)[a] * H(m)[b] * … * H(m)[n]``. (See [this](https://www.lucamoroni.it/the-dice-roll-sum-problem/) for an in-depth exploration of the topic.) @@ -1054,11 +1051,11 @@ def _rwc_heterogeneous_h_groups( if k is not None: if k < 0: - sorted_outcomes = (None,) * ( + sorted_outcomes = (-inf,) * ( total_n - len(sorted_outcomes) ) + sorted_outcomes else: - sorted_outcomes = sorted_outcomes + (None,) * ( + sorted_outcomes = sorted_outcomes + (inf,) * ( total_n - len(sorted_outcomes) ) @@ -1114,23 +1111,23 @@ def _rwc_homogeneous_n_h_using_partial_selection( if k == 0 or k > n: # Maintain consistency with comb(n, k) == 0 where k > n - return iter(()) - - total_count = h.total**n + yield from () + else: + total_count = h.total**n - for outcomes, prob_nmr8r, prob_dnmn8r in _selected_distros_memoized( - h, n, k, from_right - ): - count = total_count * prob_nmr8r // prob_dnmn8r + for outcomes, prob_nmr8r, prob_dnmn8r in _selected_distros_memoized( + h, n, k, from_right + ): + count = total_count * prob_nmr8r // prob_dnmn8r - if fill is not None: - outcomes = ( - (fill,) * (n - k) + outcomes - if from_right - else outcomes + (fill,) * (n - k) - ) + if fill is not None: + outcomes = ( + (fill,) * (n - k) + outcomes + if from_right + else outcomes + (fill,) * (n - k) + ) - yield (outcomes, count) + yield (outcomes, count) @cache diff --git a/dyce/r.py b/dyce/r.py index 6673ee7..fe28349 100644 --- a/dyce/r.py +++ b/dyce/r.py @@ -266,6 +266,7 @@ class R: -- END MONKEY PATCH --> """ + __slots__: Any = ("_annotation", "_sources") # ---- Initializer ----------------------------------------------------------------- @@ -953,10 +954,10 @@ def annotate(self, annotation: Any = "") -> "R": Generates a copy of the roller with the desired annotation. ``` python - >>> r_just_the_n_of_us = R.from_value(5, annotation="But I'm 42!") ; r_just_the_n_of_us - ValueRoller(value=5, annotation="But I'm 42!") - >>> r_just_the_n_of_us.annotate("I'm a 42-year-old investment banker!") - ValueRoller(value=5, annotation="I'm a 42-year-old investment banker!") + >>> r_just_the_n_of_us = R.from_value(5, annotation="But I’m 42!") ; r_just_the_n_of_us + ValueRoller(value=5, annotation='But I’m 42!') + >>> r_just_the_n_of_us.annotate("I’m a 42-year-old investment banker!") + ValueRoller(value=5, annotation='I’m a 42-year-old investment banker!') ``` """ @@ -1218,6 +1219,7 @@ class ValueRoller(R): [``RollOutcome`` objects][dyce.r.RollOutcome], or even [``Roll`` objects][dyce.r.Roll], instead of other source rollers. """ + __slots__: Any = ("_value",) # ---- Initializer ----------------------------------------------------------------- @@ -1331,6 +1333,7 @@ class PoolRoller(R): ``` """ + __slots__: Any = () # ---- Overrides ------------------------------------------------------------------- @@ -1371,6 +1374,7 @@ class RepeatRoller(R): ``` """ + __slots__: Any = ("_n",) # ---- Initializer ----------------------------------------------------------------- @@ -1437,6 +1441,7 @@ class BasicOpRoller(R): Any [``RollOutcome``][dyce.r.RollOutcome]s returned by *op* are used directly in the creation of a new [``Roll``][dyce.r.Roll]. """ + __slots__: Any = ("_op",) # ---- Initializer ----------------------------------------------------------------- @@ -1502,6 +1507,7 @@ class NarySumOpRoller(BasicOpRoller): A [``BasicOpRoller``][dyce.r.BasicOpRoller] for applying *op* to the sum of outcomes grouped by each of *sources*. """ + __slots__: Any = () # ---- Overrides ------------------------------------------------------------------- @@ -1534,6 +1540,7 @@ class BinarySumOpRoller(NarySumOpRoller): *bin_op* to the sum of all outcomes from its *left_source* and the sum of all outcomes from its *right_source*. """ + __slots__: Any = ("_bin_op",) # ---- Initializer ----------------------------------------------------------------- @@ -1597,6 +1604,7 @@ class UnarySumOpRoller(NarySumOpRoller): An [``NarySumOpRoller``][dyce.r.NarySumOpRoller] for applying a unary operator *un_op* to the sum of all outcomes from its sole *source*. """ + __slots__: Any = ("_un_op",) # ---- Initializer ----------------------------------------------------------------- @@ -1742,6 +1750,7 @@ class FilterRoller(R): See the section on “[Filtering and substitution](rollin.md#filtering-and-substitution)” more examples. """ + __slots__: Any = ("_predicate",) # ---- Initializer ----------------------------------------------------------------- @@ -1881,6 +1890,7 @@ class SelectionRoller(R): ``` """ + __slots__: Any = ("_which",) # ---- Initializer ----------------------------------------------------------------- @@ -2006,6 +2016,7 @@ class SubstitutionRoller(R): See the section on “[Filtering and substitution](rollin.md#filtering-and-substitution)” more examples. """ + __slots__: Any = ("_coalesce_mode", "_expansion_op", "_max_depth") # ---- Initializer ----------------------------------------------------------------- @@ -2134,6 +2145,7 @@ class RollOutcome: A single, ([mostly][dyce.r.Roll.__init__]) immutable outcome generated by a roll. """ + __slots__: Any = ("_roll", "_sources", "_value") # ---- Initializer ----------------------------------------------------------------- @@ -2746,6 +2758,7 @@ class Roll(Sequence[RollOutcome]): calling the [``R.roll`` method][dyce.r.R.roll]. Rolls are sequences of [``RollOutcome`` objects][dyce.r.RollOutcome] that can be assembled into trees. """ + __slots__: Any = ("_r", "_roll_outcomes", "_source_rolls") # ---- Initializer ----------------------------------------------------------------- @@ -2830,12 +2843,10 @@ def __len__(self) -> int: return len(self._roll_outcomes) @overload - def __getitem__(self, key: SupportsIndex) -> RollOutcome: - ... + def __getitem__(self, key: SupportsIndex) -> RollOutcome: ... @overload - def __getitem__(self, key: slice) -> tuple[RollOutcome, ...]: - ... + def __getitem__(self, key: slice) -> tuple[RollOutcome, ...]: ... @beartype def __getitem__( @@ -2849,7 +2860,7 @@ def __getitem__( @beartype def __iter__(self) -> Iterator[RollOutcome]: - return iter(self._roll_outcomes) + yield from self._roll_outcomes # ---- Properties ------------------------------------------------------------------ @@ -2950,13 +2961,13 @@ class RollWalkerVisitor: Abstract visitor interface for use with [``walk``][dyce.r.walk] called for each [``Roll`` object][dyce.r.Roll] found. """ + __slots__: Any = () # ---- Overrides ------------------------------------------------------------------- @abstractmethod - def on_roll(self, roll: Roll, parents: Iterator[Roll]) -> None: - ... + def on_roll(self, roll: Roll, parents: Iterator[Roll]) -> None: ... class RollOutcomeWalkerVisitor: @@ -2969,6 +2980,7 @@ class RollOutcomeWalkerVisitor: Abstract visitor interface for use with [``walk``][dyce.r.walk] called for each [``RollOutcome`` object][dyce.r.RollOutcome] found. """ + __slots__: Any = () # ---- Overrides ------------------------------------------------------------------- @@ -2976,8 +2988,7 @@ class RollOutcomeWalkerVisitor: @abstractmethod def on_roll_outcome( self, roll_outcome: RollOutcome, parents: Iterator[RollOutcome] - ) -> None: - ... + ) -> None: ... class RollerWalkerVisitor: @@ -2990,13 +3001,13 @@ class RollerWalkerVisitor: Abstract visitor interface for use with [``walk``][dyce.r.walk] called for each [``R`` object][dyce.r.R] found. """ + __slots__: Any = () # ---- Overrides ------------------------------------------------------------------- @abstractmethod - def on_roller(self, r: R, parents: Iterator[R]) -> None: - ... + def on_roller(self, r: R, parents: Iterator[R]) -> None: ... @experimental diff --git a/dyce/rng.py b/dyce/rng.py index 8780c83..9ddf3c5 100644 --- a/dyce/rng.py +++ b/dyce/rng.py @@ -121,6 +121,7 @@ class PCG64DXSMRandom(NumPyRandomBase): A [``NumPyRandomBase``][dyce.rng.NumPyRandomBase] based on [``numpy.random.PCG64DXSM``](https://numpy.org/doc/stable/reference/random/bit_generators/pcg64dxsm.html#numpy.random.PCG64DXSM). """ + bit_generator = PCG64DXSM DEFAULT_RNG = PCG64DXSMRandom() diff --git a/pyproject.toml b/pyproject.toml index 6cee4a0..4b20067 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,20 +11,12 @@ requires = ["setuptools>=45", "versioningit~=2.0", "wheel"] build-backend = "setuptools.build_meta" -[tool.versioningit.next-version] # ---------------------------------------------------- - -method = "smallest-release" - -[tool.versioningit.vcs] # ------------------------------------------------------------- - -default-tag = "0.0.0" +[tool.isort] # ------------------------------------------------------------------------ -[tool.versioningit.write] # ----------------------------------------------------------- - -file = "dyce/_version.py" -template = '''__vers_str__ = "{version}" -__version__ = {version_tuple} -''' +# See +profile = "black" +# See +skip_gitignore = true [tool.mypy] # ------------------------------------------------------------------------- @@ -46,3 +38,18 @@ doctest_optionflags = [ "NORMALIZE_WHITESPACE", "NUMBER", ] + +[tool.versioningit.next-version] # ---------------------------------------------------- + +method = "smallest-release" + +[tool.versioningit.vcs] # ------------------------------------------------------------- + +default-tag = "0.0.0" + +[tool.versioningit.write] # ----------------------------------------------------------- + +file = "dyce/_version.py" +template = '''__vers_str__ = "{version}" +__version__ = {version_tuple} +''' diff --git a/setup.cfg b/setup.cfg index cd18ea5..5e95253 100644 --- a/setup.cfg +++ b/setup.cfg @@ -63,7 +63,7 @@ dyce = py.typed [tox:tox] # --------------------------------------------------------------------------- -envlist = check, py{39,310,311}{,-lint}{,-beartype}, pypy39{,-beartype} +envlist = check, py{39,310,311,312}{,-lint}{,-beartype}, pypy{39,310}{,-beartype} skipsdist = true skip_missing_interpreters = true @@ -73,7 +73,9 @@ python = 3.9: py39{,-lint}{,-beartype} 3.10: check, py310{,-lint}{,-beartype} 3.11: py311{,-lint}{,-beartype} + 3.12: py312{,-lint}{,-beartype} pypy-3.9: pypy39{,-beartype} + pypy-3.10: pypy310{,-beartype} fail_on_no_env = True [testenv] # --------------------------------------------------------------------------- @@ -84,7 +86,7 @@ commands = deps = --editable . anydyce~=0.4.0 # for docs/assets/plot….py - !pypy39: numpy + !pypy39-!pypy310: numpy pytest # Because ${HOME} is not passed, ~/.gitconfig is not read. To overcome this, port any # desired user-specific exclusion configuration to .git/config. E.G.: @@ -164,7 +166,7 @@ deps = --editable . jupyterlab -[testenv:py{39,310,311}-lint{,-beartype}] # ------------------------------------------- +[testenv:py{39,310,311,312}-lint{,-beartype}] # --------------------------------------- commands = pre-commit run --all-files --show-diff-on-failure @@ -188,5 +190,7 @@ ignore = E203 # line too long (... > ... characters) E501 + # multiple statements on one line + E704 # line break occurred before a binary operator W503 diff --git a/tests/test_h.py b/tests/test_h.py index 3cd97bd..13c7c58 100644 --- a/tests/test_h.py +++ b/tests/test_h.py @@ -34,6 +34,7 @@ Decimal, Fraction, ) +_INCONSISTENT_EQUALITY_OUTCOME_TYPES: tuple[Type, ...] = () _COUNT_TYPES: tuple[Type, ...] = _INTEGRAL_OUTCOME_TYPES @@ -54,7 +55,7 @@ except ImportError: pass else: - _OUTCOME_TYPES += ( + _INCONSISTENT_EQUALITY_OUTCOME_TYPES += ( sympy.Integer, sympy.Float, sympy.Number, @@ -256,6 +257,16 @@ def test_cmp_eq(self) -> None: assert h.eq(h) == H( (True, False, False, False, True, False, False, False, True) ), f"o_type: {o_type}; c_type: {c_type}" + for o_type, c_type in itertools.product( + _INCONSISTENT_EQUALITY_OUTCOME_TYPES, _COUNT_TYPES + ): + h = H({o_type(i): c_type(1) for i in range(-1, 2)}) + assert h.eq(o_type(0)) == H( + (False, True, False) + ), f"o_type: {o_type}; c_type: {c_type}" + assert h.eq(h) == H( + (True, False, False, False, True, False, False, False, True) + ), f"o_type: {o_type}; c_type: {c_type}" def test_cmp_ne(self) -> None: for o_type, c_type in itertools.product(_OUTCOME_TYPES, _COUNT_TYPES): @@ -266,6 +277,16 @@ def test_cmp_ne(self) -> None: assert h.ne(h) == H( (False, True, True, True, False, True, True, True, False) ), f"o_type: {o_type}; c_type: {c_type}" + for o_type, c_type in itertools.product( + _INCONSISTENT_EQUALITY_OUTCOME_TYPES, _COUNT_TYPES + ): + h = H({o_type(i): c_type(1) for i in range(-1, 2)}) + assert h.ne(o_type(0)) == H( + (True, False, True) + ), f"o_type: {o_type}; c_type: {c_type}" + assert h.ne(h) == H( + (False, True, True, True, False, True, True, True, False) + ), f"o_type: {o_type}; c_type: {c_type}" def test_cmp_lt(self) -> None: for o_type, c_type in itertools.product(_OUTCOME_TYPES, _COUNT_TYPES):