-
+
icepool API documentation
@@ -2023,848 +2023,852 @@
Arguments:
735def_sum_all(self,rolls:int,/)->'Die': 736"""Roll this `Die` `rolls` times and sum the results. 737
- 738 If `rolls` is negative, roll the `Die` `abs(rolls)` times and negate
- 739 the result.
+ 738 The sum is computed one at a time, with the new item on the right,
+ 739 similar to `functools.reduce()`. 740
- 741 If you instead want to replace tuple (or other sequence) outcomes with
- 742 their sum, use `die.map(sum)`.
- 743 """
- 744ifrollsinself._sum_cache:
- 745returnself._sum_cache[rolls]
- 746
- 747ifrolls<0:
- 748result=-self._sum_all(-rolls)
- 749elifrolls==0:
- 750result=self.zero().simplify()
- 751elifrolls==1:
- 752result=self
- 753else:
- 754# Binary split seems to perform much worse.
- 755result=self+self._sum_all(rolls-1)
- 756
- 757self._sum_cache[rolls]=result
- 758returnresult
- 759
- 760def__matmul__(self:'Die[int]',other)->'Die':
- 761"""Roll the left `Die`, then roll the right `Die` that many times and sum the outcomes."""
- 762ifisinstance(other,icepool.AgainExpression):
- 763returnNotImplemented
- 764other=implicit_convert_to_die(other)
- 765
- 766data:MutableMapping[int,Any]=defaultdict(int)
- 767
- 768max_abs_die_count=max(abs(self.min_outcome()),
- 769abs(self.max_outcome()))
- 770fordie_count,die_count_quantityinself.items():
- 771factor=other.denominator()**(max_abs_die_count-abs(die_count))
- 772subresult=other._sum_all(die_count)
- 773foroutcome,subresult_quantityinsubresult.items():
- 774data[
- 775outcome]+=subresult_quantity*die_count_quantity*factor
- 776
- 777returnicepool.Die(data)
- 778
- 779def__rmatmul__(self,other:'int | Die[int]')->'Die':
- 780"""Roll the left `Die`, then roll the right `Die` that many times and sum the outcomes."""
- 781ifisinstance(other,icepool.AgainExpression):
- 782returnNotImplemented
- 783other=implicit_convert_to_die(other)
- 784returnother.__matmul__(self)
- 785
- 786defpool(self,rolls:int|Sequence[int]=1,/)->'icepool.Pool[T_co]':
- 787"""Creates a `Pool` from this `Die`.
- 788
- 789 You might subscript the pool immediately afterwards, e.g.
- 790 `d6.pool(5)[-1, ..., 1]` takes the difference between the highest and
- 791 lowest of 5d6.
+ 741 If `rolls` is negative, roll the `Die` `abs(rolls)` times and negate
+ 742 the result.
+ 743
+ 744 If you instead want to replace tuple (or other sequence) outcomes with
+ 745 their sum, use `die.map(sum)`.
+ 746 """
+ 747ifrollsinself._sum_cache:
+ 748returnself._sum_cache[rolls]
+ 749
+ 750ifrolls<0:
+ 751result=-self._sum_all(-rolls)
+ 752elifrolls==0:
+ 753result=self.zero().simplify()
+ 754elifrolls==1:
+ 755result=self
+ 756else:
+ 757# In addition to working similar to reduce(), this seems to perform
+ 758# better than binary split.
+ 759result=self._sum_all(rolls-1)+self
+ 760
+ 761self._sum_cache[rolls]=result
+ 762returnresult
+ 763
+ 764def__matmul__(self:'Die[int]',other)->'Die':
+ 765"""Roll the left `Die`, then roll the right `Die` that many times and sum the outcomes."""
+ 766ifisinstance(other,icepool.AgainExpression):
+ 767returnNotImplemented
+ 768other=implicit_convert_to_die(other)
+ 769
+ 770data:MutableMapping[int,Any]=defaultdict(int)
+ 771
+ 772max_abs_die_count=max(abs(self.min_outcome()),
+ 773abs(self.max_outcome()))
+ 774fordie_count,die_count_quantityinself.items():
+ 775factor=other.denominator()**(max_abs_die_count-abs(die_count))
+ 776subresult=other._sum_all(die_count)
+ 777foroutcome,subresult_quantityinsubresult.items():
+ 778data[
+ 779outcome]+=subresult_quantity*die_count_quantity*factor
+ 780
+ 781returnicepool.Die(data)
+ 782
+ 783def__rmatmul__(self,other:'int | Die[int]')->'Die':
+ 784"""Roll the left `Die`, then roll the right `Die` that many times and sum the outcomes."""
+ 785ifisinstance(other,icepool.AgainExpression):
+ 786returnNotImplemented
+ 787other=implicit_convert_to_die(other)
+ 788returnother.__matmul__(self)
+ 789
+ 790defpool(self,rolls:int|Sequence[int]=1,/)->'icepool.Pool[T_co]':
+ 791"""Creates a `Pool` from this `Die`. 792
- 793 Args:
- 794 rolls: The number of copies of this `Die` to put in the pool.
- 795 Or, a sequence of one `int` per die acting as
- 796 `keep_tuple`. Note that `...` cannot be used in the
- 797 argument to this method, as the argument determines the size of
- 798 the pool.
- 799 """
- 800ifisinstance(rolls,int):
- 801returnicepool.Pool({self:rolls})
- 802else:
- 803pool_size=len(rolls)
- 804# Haven't dealt with narrowing return type.
- 805returnicepool.Pool({self:pool_size})[rolls]# type: ignore
- 806
- 807@overload
- 808defkeep(self,rolls:Sequence[int],/)->'Die':
- 809"""Selects elements after drawing and sorting and sums them.
+ 793 You might subscript the pool immediately afterwards, e.g.
+ 794 `d6.pool(5)[-1, ..., 1]` takes the difference between the highest and
+ 795 lowest of 5d6.
+ 796
+ 797 Args:
+ 798 rolls: The number of copies of this `Die` to put in the pool.
+ 799 Or, a sequence of one `int` per die acting as
+ 800 `keep_tuple`. Note that `...` cannot be used in the
+ 801 argument to this method, as the argument determines the size of
+ 802 the pool.
+ 803 """
+ 804ifisinstance(rolls,int):
+ 805returnicepool.Pool({self:rolls})
+ 806else:
+ 807pool_size=len(rolls)
+ 808# Haven't dealt with narrowing return type.
+ 809returnicepool.Pool({self:pool_size})[rolls]# type: ignore 810
- 811 Args:
- 812 rolls: A sequence of `int` specifying how many times to count each
- 813 element in ascending order.
- 814 """
- 815
- 816@overload
- 817defkeep(self,rolls:int,
- 818index:slice|Sequence[int|EllipsisType]|int,/):
- 819"""Selects elements after drawing and sorting and sums them.
- 820
- 821 Args:
- 822 rolls: The number of dice to roll.
- 823 index: One of the following:
- 824 * An `int`. This will count only the roll at the specified index.
- 825 In this case, the result is a `Die` rather than a generator.
- 826 * A `slice`. The selected dice are counted once each.
- 827 * A sequence of one `int` for each `Die`.
- 828 Each roll is counted that many times, which could be multiple or
- 829 negative times.
- 830
- 831 Up to one `...` (`Ellipsis`) may be used.
- 832 `...` will be replaced with a number of zero
- 833 counts depending on the `rolls`.
- 834 This number may be "negative" if more `int`s are provided than
- 835 `rolls`. Specifically:
- 836
- 837 * If `index` is shorter than `rolls`, `...`
- 838 acts as enough zero counts to make up the difference.
- 839 E.g. `(1, ..., 1)` on five dice would act as
- 840 `(1, 0, 0, 0, 1)`.
- 841 * If `index` has length equal to `rolls`, `...` has no effect.
- 842 E.g. `(1, ..., 1)` on two dice would act as `(1, 1)`.
- 843 * If `index` is longer than `rolls` and `...` is on one side,
- 844 elements will be dropped from `index` on the side with `...`.
- 845 E.g. `(..., 1, 2, 3)` on two dice would act as `(2, 3)`.
- 846 * If `index` is longer than `rolls` and `...`
- 847 is in the middle, the counts will be as the sum of two
- 848 one-sided `...`.
- 849 E.g. `(-1, ..., 1)` acts like `(-1, ...)` plus `(..., 1)`.
- 850 If `rolls` was 1 this would have the -1 and 1 cancel each other out.
- 851 """
- 852
- 853defkeep(self,
- 854rolls:int|Sequence[int],
- 855index:slice|Sequence[int|EllipsisType]|int|None=None,
- 856/)->'Die':
- 857"""Selects elements after drawing and sorting and sums them.
- 858
- 859 Args:
- 860 rolls: The number of dice to roll.
- 861 index: One of the following:
- 862 * An `int`. This will count only the roll at the specified index.
- 863 In this case, the result is a `Die` rather than a generator.
- 864 * A `slice`. The selected dice are counted once each.
- 865 * A sequence of `int`s with length equal to `rolls`.
- 866 Each roll is counted that many times, which could be multiple or
- 867 negative times.
- 868
- 869 Up to one `...` (`Ellipsis`) may be used. If no `...` is used,
- 870 the `rolls` argument may be omitted.
- 871
- 872 `...` will be replaced with a number of zero counts in order
- 873 to make up any missing elements compared to `rolls`.
- 874 This number may be "negative" if more `int`s are provided than
- 875 `rolls`. Specifically:
- 876
- 877 * If `index` is shorter than `rolls`, `...`
- 878 acts as enough zero counts to make up the difference.
- 879 E.g. `(1, ..., 1)` on five dice would act as
- 880 `(1, 0, 0, 0, 1)`.
- 881 * If `index` has length equal to `rolls`, `...` has no effect.
- 882 E.g. `(1, ..., 1)` on two dice would act as `(1, 1)`.
- 883 * If `index` is longer than `rolls` and `...` is on one side,
- 884 elements will be dropped from `index` on the side with `...`.
- 885 E.g. `(..., 1, 2, 3)` on two dice would act as `(2, 3)`.
- 886 * If `index` is longer than `rolls` and `...`
- 887 is in the middle, the counts will be as the sum of two
- 888 one-sided `...`.
- 889 E.g. `(-1, ..., 1)` acts like `(-1, ...)` plus `(..., 1)`.
- 890 If `rolls` was 1 this would have the -1 and 1 cancel each other out.
- 891 """
- 892ifisinstance(rolls,int):
- 893ifindexisNone:
- 894raiseValueError(
- 895'If the number of rolls is an integer, an index argument must be provided.'
- 896)
- 897ifisinstance(index,int):
- 898returnself.pool(rolls).keep(index)
- 899else:
- 900returnself.pool(rolls).keep(index).sum()# type: ignore
- 901else:
- 902ifindexisnotNone:
- 903raiseValueError('Only one index sequence can be given.')
- 904returnself.pool(len(rolls)).keep(rolls).sum()# type: ignore
- 905
- 906deflowest(self,
- 907rolls:int,
- 908/,
- 909keep:int|None=None,
- 910drop:int|None=None)->'Die':
- 911"""Roll several of this `Die` and return the lowest result, or the sum of some of the lowest.
- 912
- 913 The outcomes should support addition and multiplication if `keep != 1`.
- 914
- 915 Args:
- 916 rolls: The number of dice to roll. All dice will have the same
- 917 outcomes as `self`.
- 918 keep, drop: These arguments work together:
- 919 * If neither are provided, the single lowest die will be taken.
- 920 * If only `keep` is provided, the `keep` lowest dice will be summed.
- 921 * If only `drop` is provided, the `drop` lowest dice will be dropped
- 922 and the rest will be summed.
- 923 * If both are provided, `drop` lowest dice will be dropped, then
- 924 the next `keep` lowest dice will be summed.
- 925
- 926 Returns:
- 927 A `Die` representing the probability distribution of the sum.
- 928 """
- 929index=lowest_slice(keep,drop)
- 930canonical=canonical_slice(index,rolls)
- 931ifcanonical.start==0andcanonical.stop==1:
- 932returnself._lowest_single(rolls)
- 933# Expression evaluators are difficult to type.
- 934returnself.pool(rolls)[index].sum()# type: ignore
- 935
- 936def_lowest_single(self,rolls:int,/)->'Die':
- 937"""Roll this die several times and keep the lowest."""
- 938ifrolls==0:
- 939returnself.zero().simplify()
- 940returnicepool.from_cumulative(
- 941self.outcomes(),[x**rollsforxinself.quantities('>=')],
- 942reverse=True)
- 943
- 944defhighest(self,
- 945rolls:int,
- 946/,
- 947keep:int|None=None,
- 948drop:int|None=None)->'Die[T_co]':
- 949"""Roll several of this `Die` and return the highest result, or the sum of some of the highest.
- 950
- 951 The outcomes should support addition and multiplication if `keep != 1`.
- 952
- 953 Args:
- 954 rolls: The number of dice to roll.
- 955 keep, drop: These arguments work together:
- 956 * If neither are provided, the single highest die will be taken.
- 957 * If only `keep` is provided, the `keep` highest dice will be summed.
- 958 * If only `drop` is provided, the `drop` highest dice will be dropped
- 959 and the rest will be summed.
- 960 * If both are provided, `drop` highest dice will be dropped, then
- 961 the next `keep` highest dice will be summed.
- 962
- 963 Returns:
- 964 A `Die` representing the probability distribution of the sum.
- 965 """
- 966index=highest_slice(keep,drop)
- 967canonical=canonical_slice(index,rolls)
- 968ifcanonical.start==rolls-1andcanonical.stop==rolls:
- 969returnself._highest_single(rolls)
- 970# Expression evaluators are difficult to type.
- 971returnself.pool(rolls)[index].sum()# type: ignore
- 972
- 973def_highest_single(self,rolls:int,/)->'Die[T_co]':
- 974"""Roll this die several times and keep the highest."""
- 975ifrolls==0:
- 976returnself.zero().simplify()
- 977returnicepool.from_cumulative(
- 978self.outcomes(),[x**rollsforxinself.quantities('<=')])
- 979
- 980defmiddle(
- 981self,
- 982rolls:int,
- 983/,
- 984keep:int=1,
- 985*,
- 986tie:Literal['error','high','low']='error')->'icepool.Die':
- 987"""Roll several of this `Die` and sum the sorted results in the middle.
- 988
- 989 The outcomes should support addition and multiplication if `keep != 1`.
- 990
- 991 Args:
- 992 rolls: The number of dice to roll.
- 993 keep: The number of outcomes to sum. If this is greater than the
- 994 current keep_size, all are kept.
- 995 tie: What to do if `keep` is odd but the current keep_size
- 996 is even, or vice versa.
- 997 * 'error' (default): Raises `IndexError`.
- 998 * 'high': The higher outcome is taken.
- 999 * 'low': The lower outcome is taken.
-1000 """
-1001# Expression evaluators are difficult to type.
-1002returnself.pool(rolls).middle(keep,tie=tie).sum()# type: ignore
-1003
-1004defmap_to_pool(
-1005self,
-1006repl:
-1007'Callable[..., Sequence[icepool.Die[U] | U] | Mapping[icepool.Die[U], int] | Mapping[U, int] | icepool.RerollType] | None'=None,
-1008/,
-1009*extra_args:'Outcome | icepool.Die | icepool.MultisetExpression',
-1010star:bool|None=None,
-1011denominator:int|None=None
-1012)->'icepool.MultisetGenerator[U, tuple[int]]':
-1013"""EXPERIMENTAL: Maps outcomes of this `Die` to `Pools`, creating a `MultisetGenerator`.
-1014
-1015 As `icepool.map_to_pool(repl, self, ...)`.
-1016
-1017 If no argument is provided, the outcomes will be used to construct a
-1018 mixture of pools directly, similar to the inverse of `pool.expand()`.
-1019 Note that this is not particularly efficient since it does not make much
-1020 use of dynamic programming.
-1021
-1022 Args:
-1023 repl: One of the following:
-1024 * A callable that takes in one outcome per element of args and
-1025 produces a `Pool` (or something convertible to such).
-1026 * A mapping from old outcomes to `Pool`
-1027 (or something convertible to such).
-1028 In this case args must have exactly one element.
-1029 The new outcomes may be dice rather than just single outcomes.
-1030 The special value `icepool.Reroll` will reroll that old outcome.
-1031 star: If `True`, the first of the args will be unpacked before
-1032 giving them to `repl`.
-1033 If not provided, it will be guessed based on the signature of
-1034 `repl` and the number of arguments.
-1035 denominator: If provided, the denominator of the result will be this
-1036 value. Otherwise it will be the minimum to correctly weight the
-1037 pools.
-1038
-1039 Returns:
-1040 A `MultisetGenerator` representing the mixture of `Pool`s. Note
-1041 that this is not technically a `Pool`, though it supports most of
-1042 the same operations.
-1043
-1044 Raises:
-1045 ValueError: If `denominator` cannot be made consistent with the
-1046 resulting mixture of pools.
-1047 """
-1048ifreplisNone:
-1049repl=lambdax:x
-1050returnicepool.map_to_pool(repl,
-1051self,
-1052*extra_args,
-1053star=star,
-1054denominator=denominator)
-1055
-1056defexplode_to_pool(
-1057self,
-1058rolls:int,
-1059which:Collection[T_co]|Callable[...,bool]|None=None,
-1060/,
-1061*,
-1062star:bool|None=None,
-1063depth:int=9)->'icepool.MultisetGenerator[T_co, tuple[int]]':
-1064"""EXPERIMENTAL: Causes outcomes to be rolled again, keeping that outcome as an individual die in a pool.
-1065
-1066 Args:
-1067 rolls: The number of initial dice.
-1068 which: Which outcomes to explode. Options:
-1069 * A single outcome to explode.
-1070 * An collection of outcomes to explode.
-1071 * A callable that takes an outcome and returns `True` if it
-1072 should be exploded.
-1073 * If not supplied, the max outcome will explode.
-1074 star: Whether outcomes should be unpacked into separate arguments
-1075 before sending them to a callable `which`.
-1076 If not provided, this will be guessed based on the function
-1077 signature.
-1078 depth: The maximum depth of explosions for an individual dice.
-1079
-1080 Returns:
-1081 A `MultisetGenerator` representing the mixture of `Pool`s. Note
-1082 that this is not technically a `Pool`, though it supports most of
-1083 the same operations.
-1084 """
-1085ifdepth==0:
-1086returnself.pool(rolls)
-1087ifwhichisNone:
-1088explode_set={self.max_outcome()}
-1089else:
-1090explode_set=self._select_outcomes(which,star)
-1091ifnotexplode_set:
-1092returnself.pool(rolls)
-1093explode,not_explode=self.split(explode_set)
-1094
-1095single_data:'MutableMapping[icepool.Vector[int], int]'=defaultdict(
-1096int)
-1097foriinrange(depth+1):
-1098weight=explode.denominator()**i*self.denominator()**(
-1099depth-i)*not_explode.denominator()
-1100single_data[icepool.Vector((i,1))]+=weight
-1101single_data[icepool.Vector(
-1102(depth+1,0))]+=explode.denominator()**(depth+1)
-1103
-1104single_count_die:'Die[icepool.Vector[int]]'=Die(single_data)
-1105count_die=rolls@single_count_die
-1106
-1107returncount_die.map_to_pool(
-1108lambdax,nx:[explode]*x+[not_explode]*nx)
-1109
-1110defreroll_to_pool(
-1111self,
-1112rolls:int,
-1113which:Callable[...,bool]|Collection[T_co],
-1114/,
-1115max_rerolls:int,
-1116*,
-1117star:bool|None=None,
-1118mode:Literal['random','lowest','highest','drop']='random'
-1119)->'icepool.MultisetGenerator[T_co, tuple[int]]':
-1120"""EXPERIMENTAL: Applies a limited number of rerolls shared across a pool.
-1121
-1122 Each die can only be rerolled once (effectively `depth=1`), and no more
-1123 than `max_rerolls` dice may be rerolled.
-1124
-1125 Args:
-1126 rolls: How many dice in the pool.
-1127 which: Selects which outcomes are eligible to be rerolled. Options:
-1128 * A collection of outcomes to reroll.
-1129 * A callable that takes an outcome and returns `True` if it
-1130 could be rerolled.
-1131 max_rerolls: The maximum number of dice to reroll.
-1132 Note that each die can only be rerolled once, so if the number
-1133 of eligible dice is less than this, the excess rerolls have no
-1134 effect.
-1135 star: Whether outcomes should be unpacked into separate arguments
-1136 before sending them to a callable `which`.
-1137 If not provided, this will be guessed based on the function
-1138 signature.
-1139 mode: How dice are selected for rerolling if there are more eligible
-1140 dice than `max_rerolls`. Options:
-1141 * `'random'` (default): Eligible dice will be chosen uniformly
-1142 at random.
-1143 * `'lowest'`: The lowest eligible dice will be rerolled.
-1144 * `'highest'`: The highest eligible dice will be rerolled.
-1145 * `'drop'`: All dice that ended up on an outcome selected by
-1146 `which` will be dropped. This includes both dice that rolled
-1147 into `which` initially and were not rerolled, and dice that
-1148 were rerolled but rolled into `which` again. This can be
-1149 considerably more efficient than the other modes.
-1150
-1151 Returns:
-1152 A `MultisetGenerator` representing the mixture of `Pool`s. Note
-1153 that this is not technically a `Pool`, though it supports most of
-1154 the same operations.
-1155 """
-1156rerollable_set=self._select_outcomes(which,star)
-1157ifnotrerollable_set:
-1158returnself.pool(rolls)
-1159
-1160rerollable_die,not_rerollable_die=self.split(rerollable_set)
-1161single_is_rerollable=icepool.coin(rerollable_die.denominator(),
-1162self.denominator())
-1163rerollable=rolls@single_is_rerollable
-1164
-1165defsplit(initial_rerollable:int)->Die[tuple[int,int,int]]:
-1166"""Computes the composition of the pool.
-1167
-1168 Returns:
-1169 initial_rerollable: The number of dice that initially fell into
-1170 the rerollable set.
-1171 rerolled_to_rerollable: The number of dice that were rerolled,
-1172 but fell into the rerollable set again.
-1173 not_rerollable: The number of dice that ended up outside the
-1174 rerollable set, including both initial and rerolled dice.
-1175 not_rerolled: The number of dice that were eligible for
-1176 rerolling but were not rerolled.
-1177 """
-1178initial_not_rerollable=rolls-initial_rerollable
-1179rerolled=min(initial_rerollable,max_rerolls)
-1180not_rerolled=initial_rerollable-rerolled
-1181
-1182defsecond_split(rerolled_to_rerollable):
-1183"""Splits the rerolled dice into those that fell into the rerollable and not-rerollable sets."""
-1184rerolled_to_not_rerollable=rerolled-rerolled_to_rerollable
-1185returnicepool.tupleize(
-1186initial_rerollable,rerolled_to_rerollable,
-1187initial_not_rerollable+rerolled_to_not_rerollable,
-1188not_rerolled)
-1189
-1190returnicepool.map(second_split,
-1191rerolled@single_is_rerollable,
-1192star=False)
+ 811@overload
+ 812defkeep(self,rolls:Sequence[int],/)->'Die':
+ 813"""Selects elements after drawing and sorting and sums them.
+ 814
+ 815 Args:
+ 816 rolls: A sequence of `int` specifying how many times to count each
+ 817 element in ascending order.
+ 818 """
+ 819
+ 820@overload
+ 821defkeep(self,rolls:int,
+ 822index:slice|Sequence[int|EllipsisType]|int,/):
+ 823"""Selects elements after drawing and sorting and sums them.
+ 824
+ 825 Args:
+ 826 rolls: The number of dice to roll.
+ 827 index: One of the following:
+ 828 * An `int`. This will count only the roll at the specified index.
+ 829 In this case, the result is a `Die` rather than a generator.
+ 830 * A `slice`. The selected dice are counted once each.
+ 831 * A sequence of one `int` for each `Die`.
+ 832 Each roll is counted that many times, which could be multiple or
+ 833 negative times.
+ 834
+ 835 Up to one `...` (`Ellipsis`) may be used.
+ 836 `...` will be replaced with a number of zero
+ 837 counts depending on the `rolls`.
+ 838 This number may be "negative" if more `int`s are provided than
+ 839 `rolls`. Specifically:
+ 840
+ 841 * If `index` is shorter than `rolls`, `...`
+ 842 acts as enough zero counts to make up the difference.
+ 843 E.g. `(1, ..., 1)` on five dice would act as
+ 844 `(1, 0, 0, 0, 1)`.
+ 845 * If `index` has length equal to `rolls`, `...` has no effect.
+ 846 E.g. `(1, ..., 1)` on two dice would act as `(1, 1)`.
+ 847 * If `index` is longer than `rolls` and `...` is on one side,
+ 848 elements will be dropped from `index` on the side with `...`.
+ 849 E.g. `(..., 1, 2, 3)` on two dice would act as `(2, 3)`.
+ 850 * If `index` is longer than `rolls` and `...`
+ 851 is in the middle, the counts will be as the sum of two
+ 852 one-sided `...`.
+ 853 E.g. `(-1, ..., 1)` acts like `(-1, ...)` plus `(..., 1)`.
+ 854 If `rolls` was 1 this would have the -1 and 1 cancel each other out.
+ 855 """
+ 856
+ 857defkeep(self,
+ 858rolls:int|Sequence[int],
+ 859index:slice|Sequence[int|EllipsisType]|int|None=None,
+ 860/)->'Die':
+ 861"""Selects elements after drawing and sorting and sums them.
+ 862
+ 863 Args:
+ 864 rolls: The number of dice to roll.
+ 865 index: One of the following:
+ 866 * An `int`. This will count only the roll at the specified index.
+ 867 In this case, the result is a `Die` rather than a generator.
+ 868 * A `slice`. The selected dice are counted once each.
+ 869 * A sequence of `int`s with length equal to `rolls`.
+ 870 Each roll is counted that many times, which could be multiple or
+ 871 negative times.
+ 872
+ 873 Up to one `...` (`Ellipsis`) may be used. If no `...` is used,
+ 874 the `rolls` argument may be omitted.
+ 875
+ 876 `...` will be replaced with a number of zero counts in order
+ 877 to make up any missing elements compared to `rolls`.
+ 878 This number may be "negative" if more `int`s are provided than
+ 879 `rolls`. Specifically:
+ 880
+ 881 * If `index` is shorter than `rolls`, `...`
+ 882 acts as enough zero counts to make up the difference.
+ 883 E.g. `(1, ..., 1)` on five dice would act as
+ 884 `(1, 0, 0, 0, 1)`.
+ 885 * If `index` has length equal to `rolls`, `...` has no effect.
+ 886 E.g. `(1, ..., 1)` on two dice would act as `(1, 1)`.
+ 887 * If `index` is longer than `rolls` and `...` is on one side,
+ 888 elements will be dropped from `index` on the side with `...`.
+ 889 E.g. `(..., 1, 2, 3)` on two dice would act as `(2, 3)`.
+ 890 * If `index` is longer than `rolls` and `...`
+ 891 is in the middle, the counts will be as the sum of two
+ 892 one-sided `...`.
+ 893 E.g. `(-1, ..., 1)` acts like `(-1, ...)` plus `(..., 1)`.
+ 894 If `rolls` was 1 this would have the -1 and 1 cancel each other out.
+ 895 """
+ 896ifisinstance(rolls,int):
+ 897ifindexisNone:
+ 898raiseValueError(
+ 899'If the number of rolls is an integer, an index argument must be provided.'
+ 900)
+ 901ifisinstance(index,int):
+ 902returnself.pool(rolls).keep(index)
+ 903else:
+ 904returnself.pool(rolls).keep(index).sum()# type: ignore
+ 905else:
+ 906ifindexisnotNone:
+ 907raiseValueError('Only one index sequence can be given.')
+ 908returnself.pool(len(rolls)).keep(rolls).sum()# type: ignore
+ 909
+ 910deflowest(self,
+ 911rolls:int,
+ 912/,
+ 913keep:int|None=None,
+ 914drop:int|None=None)->'Die':
+ 915"""Roll several of this `Die` and return the lowest result, or the sum of some of the lowest.
+ 916
+ 917 The outcomes should support addition and multiplication if `keep != 1`.
+ 918
+ 919 Args:
+ 920 rolls: The number of dice to roll. All dice will have the same
+ 921 outcomes as `self`.
+ 922 keep, drop: These arguments work together:
+ 923 * If neither are provided, the single lowest die will be taken.
+ 924 * If only `keep` is provided, the `keep` lowest dice will be summed.
+ 925 * If only `drop` is provided, the `drop` lowest dice will be dropped
+ 926 and the rest will be summed.
+ 927 * If both are provided, `drop` lowest dice will be dropped, then
+ 928 the next `keep` lowest dice will be summed.
+ 929
+ 930 Returns:
+ 931 A `Die` representing the probability distribution of the sum.
+ 932 """
+ 933index=lowest_slice(keep,drop)
+ 934canonical=canonical_slice(index,rolls)
+ 935ifcanonical.start==0andcanonical.stop==1:
+ 936returnself._lowest_single(rolls)
+ 937# Expression evaluators are difficult to type.
+ 938returnself.pool(rolls)[index].sum()# type: ignore
+ 939
+ 940def_lowest_single(self,rolls:int,/)->'Die':
+ 941"""Roll this die several times and keep the lowest."""
+ 942ifrolls==0:
+ 943returnself.zero().simplify()
+ 944returnicepool.from_cumulative(
+ 945self.outcomes(),[x**rollsforxinself.quantities('>=')],
+ 946reverse=True)
+ 947
+ 948defhighest(self,
+ 949rolls:int,
+ 950/,
+ 951keep:int|None=None,
+ 952drop:int|None=None)->'Die[T_co]':
+ 953"""Roll several of this `Die` and return the highest result, or the sum of some of the highest.
+ 954
+ 955 The outcomes should support addition and multiplication if `keep != 1`.
+ 956
+ 957 Args:
+ 958 rolls: The number of dice to roll.
+ 959 keep, drop: These arguments work together:
+ 960 * If neither are provided, the single highest die will be taken.
+ 961 * If only `keep` is provided, the `keep` highest dice will be summed.
+ 962 * If only `drop` is provided, the `drop` highest dice will be dropped
+ 963 and the rest will be summed.
+ 964 * If both are provided, `drop` highest dice will be dropped, then
+ 965 the next `keep` highest dice will be summed.
+ 966
+ 967 Returns:
+ 968 A `Die` representing the probability distribution of the sum.
+ 969 """
+ 970index=highest_slice(keep,drop)
+ 971canonical=canonical_slice(index,rolls)
+ 972ifcanonical.start==rolls-1andcanonical.stop==rolls:
+ 973returnself._highest_single(rolls)
+ 974# Expression evaluators are difficult to type.
+ 975returnself.pool(rolls)[index].sum()# type: ignore
+ 976
+ 977def_highest_single(self,rolls:int,/)->'Die[T_co]':
+ 978"""Roll this die several times and keep the highest."""
+ 979ifrolls==0:
+ 980returnself.zero().simplify()
+ 981returnicepool.from_cumulative(
+ 982self.outcomes(),[x**rollsforxinself.quantities('<=')])
+ 983
+ 984defmiddle(
+ 985self,
+ 986rolls:int,
+ 987/,
+ 988keep:int=1,
+ 989*,
+ 990tie:Literal['error','high','low']='error')->'icepool.Die':
+ 991"""Roll several of this `Die` and sum the sorted results in the middle.
+ 992
+ 993 The outcomes should support addition and multiplication if `keep != 1`.
+ 994
+ 995 Args:
+ 996 rolls: The number of dice to roll.
+ 997 keep: The number of outcomes to sum. If this is greater than the
+ 998 current keep_size, all are kept.
+ 999 tie: What to do if `keep` is odd but the current keep_size
+1000 is even, or vice versa.
+1001 * 'error' (default): Raises `IndexError`.
+1002 * 'high': The higher outcome is taken.
+1003 * 'low': The lower outcome is taken.
+1004 """
+1005# Expression evaluators are difficult to type.
+1006returnself.pool(rolls).middle(keep,tie=tie).sum()# type: ignore
+1007
+1008defmap_to_pool(
+1009self,
+1010repl:
+1011'Callable[..., Sequence[icepool.Die[U] | U] | Mapping[icepool.Die[U], int] | Mapping[U, int] | icepool.RerollType] | None'=None,
+1012/,
+1013*extra_args:'Outcome | icepool.Die | icepool.MultisetExpression',
+1014star:bool|None=None,
+1015denominator:int|None=None
+1016)->'icepool.MultisetGenerator[U, tuple[int]]':
+1017"""EXPERIMENTAL: Maps outcomes of this `Die` to `Pools`, creating a `MultisetGenerator`.
+1018
+1019 As `icepool.map_to_pool(repl, self, ...)`.
+1020
+1021 If no argument is provided, the outcomes will be used to construct a
+1022 mixture of pools directly, similar to the inverse of `pool.expand()`.
+1023 Note that this is not particularly efficient since it does not make much
+1024 use of dynamic programming.
+1025
+1026 Args:
+1027 repl: One of the following:
+1028 * A callable that takes in one outcome per element of args and
+1029 produces a `Pool` (or something convertible to such).
+1030 * A mapping from old outcomes to `Pool`
+1031 (or something convertible to such).
+1032 In this case args must have exactly one element.
+1033 The new outcomes may be dice rather than just single outcomes.
+1034 The special value `icepool.Reroll` will reroll that old outcome.
+1035 star: If `True`, the first of the args will be unpacked before
+1036 giving them to `repl`.
+1037 If not provided, it will be guessed based on the signature of
+1038 `repl` and the number of arguments.
+1039 denominator: If provided, the denominator of the result will be this
+1040 value. Otherwise it will be the minimum to correctly weight the
+1041 pools.
+1042
+1043 Returns:
+1044 A `MultisetGenerator` representing the mixture of `Pool`s. Note
+1045 that this is not technically a `Pool`, though it supports most of
+1046 the same operations.
+1047
+1048 Raises:
+1049 ValueError: If `denominator` cannot be made consistent with the
+1050 resulting mixture of pools.
+1051 """
+1052ifreplisNone:
+1053repl=lambdax:x
+1054returnicepool.map_to_pool(repl,
+1055self,
+1056*extra_args,
+1057star=star,
+1058denominator=denominator)
+1059
+1060defexplode_to_pool(
+1061self,
+1062rolls:int,
+1063which:Collection[T_co]|Callable[...,bool]|None=None,
+1064/,
+1065*,
+1066star:bool|None=None,
+1067depth:int=9)->'icepool.MultisetGenerator[T_co, tuple[int]]':
+1068"""EXPERIMENTAL: Causes outcomes to be rolled again, keeping that outcome as an individual die in a pool.
+1069
+1070 Args:
+1071 rolls: The number of initial dice.
+1072 which: Which outcomes to explode. Options:
+1073 * A single outcome to explode.
+1074 * An collection of outcomes to explode.
+1075 * A callable that takes an outcome and returns `True` if it
+1076 should be exploded.
+1077 * If not supplied, the max outcome will explode.
+1078 star: Whether outcomes should be unpacked into separate arguments
+1079 before sending them to a callable `which`.
+1080 If not provided, this will be guessed based on the function
+1081 signature.
+1082 depth: The maximum depth of explosions for an individual dice.
+1083
+1084 Returns:
+1085 A `MultisetGenerator` representing the mixture of `Pool`s. Note
+1086 that this is not technically a `Pool`, though it supports most of
+1087 the same operations.
+1088 """
+1089ifdepth==0:
+1090returnself.pool(rolls)
+1091ifwhichisNone:
+1092explode_set={self.max_outcome()}
+1093else:
+1094explode_set=self._select_outcomes(which,star)
+1095ifnotexplode_set:
+1096returnself.pool(rolls)
+1097explode,not_explode=self.split(explode_set)
+1098
+1099single_data:'MutableMapping[icepool.Vector[int], int]'=defaultdict(
+1100int)
+1101foriinrange(depth+1):
+1102weight=explode.denominator()**i*self.denominator()**(
+1103depth-i)*not_explode.denominator()
+1104single_data[icepool.Vector((i,1))]+=weight
+1105single_data[icepool.Vector(
+1106(depth+1,0))]+=explode.denominator()**(depth+1)
+1107
+1108single_count_die:'Die[icepool.Vector[int]]'=Die(single_data)
+1109count_die=rolls@single_count_die
+1110
+1111returncount_die.map_to_pool(
+1112lambdax,nx:[explode]*x+[not_explode]*nx)
+1113
+1114defreroll_to_pool(
+1115self,
+1116rolls:int,
+1117which:Callable[...,bool]|Collection[T_co],
+1118/,
+1119max_rerolls:int,
+1120*,
+1121star:bool|None=None,
+1122mode:Literal['random','lowest','highest','drop']='random'
+1123)->'icepool.MultisetGenerator[T_co, tuple[int]]':
+1124"""EXPERIMENTAL: Applies a limited number of rerolls shared across a pool.
+1125
+1126 Each die can only be rerolled once (effectively `depth=1`), and no more
+1127 than `max_rerolls` dice may be rerolled.
+1128
+1129 Args:
+1130 rolls: How many dice in the pool.
+1131 which: Selects which outcomes are eligible to be rerolled. Options:
+1132 * A collection of outcomes to reroll.
+1133 * A callable that takes an outcome and returns `True` if it
+1134 could be rerolled.
+1135 max_rerolls: The maximum number of dice to reroll.
+1136 Note that each die can only be rerolled once, so if the number
+1137 of eligible dice is less than this, the excess rerolls have no
+1138 effect.
+1139 star: Whether outcomes should be unpacked into separate arguments
+1140 before sending them to a callable `which`.
+1141 If not provided, this will be guessed based on the function
+1142 signature.
+1143 mode: How dice are selected for rerolling if there are more eligible
+1144 dice than `max_rerolls`. Options:
+1145 * `'random'` (default): Eligible dice will be chosen uniformly
+1146 at random.
+1147 * `'lowest'`: The lowest eligible dice will be rerolled.
+1148 * `'highest'`: The highest eligible dice will be rerolled.
+1149 * `'drop'`: All dice that ended up on an outcome selected by
+1150 `which` will be dropped. This includes both dice that rolled
+1151 into `which` initially and were not rerolled, and dice that
+1152 were rerolled but rolled into `which` again. This can be
+1153 considerably more efficient than the other modes.
+1154
+1155 Returns:
+1156 A `MultisetGenerator` representing the mixture of `Pool`s. Note
+1157 that this is not technically a `Pool`, though it supports most of
+1158 the same operations.
+1159 """
+1160rerollable_set=self._select_outcomes(which,star)
+1161ifnotrerollable_set:
+1162returnself.pool(rolls)
+1163
+1164rerollable_die,not_rerollable_die=self.split(rerollable_set)
+1165single_is_rerollable=icepool.coin(rerollable_die.denominator(),
+1166self.denominator())
+1167rerollable=rolls@single_is_rerollable
+1168
+1169defsplit(initial_rerollable:int)->Die[tuple[int,int,int]]:
+1170"""Computes the composition of the pool.
+1171
+1172 Returns:
+1173 initial_rerollable: The number of dice that initially fell into
+1174 the rerollable set.
+1175 rerolled_to_rerollable: The number of dice that were rerolled,
+1176 but fell into the rerollable set again.
+1177 not_rerollable: The number of dice that ended up outside the
+1178 rerollable set, including both initial and rerolled dice.
+1179 not_rerolled: The number of dice that were eligible for
+1180 rerolling but were not rerolled.
+1181 """
+1182initial_not_rerollable=rolls-initial_rerollable
+1183rerolled=min(initial_rerollable,max_rerolls)
+1184not_rerolled=initial_rerollable-rerolled
+1185
+1186defsecond_split(rerolled_to_rerollable):
+1187"""Splits the rerolled dice into those that fell into the rerollable and not-rerollable sets."""
+1188rerolled_to_not_rerollable=rerolled-rerolled_to_rerollable
+1189returnicepool.tupleize(
+1190initial_rerollable,rerolled_to_rerollable,
+1191initial_not_rerollable+rerolled_to_not_rerollable,
+1192not_rerolled)1193
-1194pool_composition=rerollable.map(split,star=False)
-1195
-1196defmake_pool(initial_rerollable,rerolled_to_rerollable,
-1197not_rerollable,not_rerolled):
-1198common=rerollable_die.pool(
-1199rerolled_to_rerollable)+not_rerollable_die.pool(
-1200not_rerollable)
-1201matchmode:
-1202case'random':
-1203returncommon+rerollable_die.pool(not_rerolled)
-1204case'lowest':
-1205returncommon+rerollable_die.pool(
-1206initial_rerollable).highest(not_rerolled)
-1207case'highest':
-1208returncommon+rerollable_die.pool(
-1209initial_rerollable).lowest(not_rerolled)
-1210case'drop':
-1211returnnot_rerollable_die.pool(not_rerollable)
-1212case_:
-1213raiseValueError(
-1214f"Invalid reroll_priority '{mode}'. Allowed values are 'random', 'lowest', 'highest', 'drop'."
-1215)
-1216
-1217denominator=self.denominator()**(rolls+min(rolls,max_rerolls))
-1218
-1219returnpool_composition.map_to_pool(make_pool,
-1220star=True,
-1221denominator=denominator)
+1194returnicepool.map(second_split,
+1195rerolled@single_is_rerollable,
+1196star=False)
+1197
+1198pool_composition=rerollable.map(split,star=False)
+1199
+1200defmake_pool(initial_rerollable,rerolled_to_rerollable,
+1201not_rerollable,not_rerolled):
+1202common=rerollable_die.pool(
+1203rerolled_to_rerollable)+not_rerollable_die.pool(
+1204not_rerollable)
+1205matchmode:
+1206case'random':
+1207returncommon+rerollable_die.pool(not_rerolled)
+1208case'lowest':
+1209returncommon+rerollable_die.pool(
+1210initial_rerollable).highest(not_rerolled)
+1211case'highest':
+1212returncommon+rerollable_die.pool(
+1213initial_rerollable).lowest(not_rerolled)
+1214case'drop':
+1215returnnot_rerollable_die.pool(not_rerollable)
+1216case_:
+1217raiseValueError(
+1218f"Invalid reroll_priority '{mode}'. Allowed values are 'random', 'lowest', 'highest', 'drop'."
+1219)
+1220
+1221denominator=self.denominator()**(rolls+min(rolls,max_rerolls))1222
-1223# Unary operators.
-1224
-1225def__neg__(self)->'Die[T_co]':
-1226returnself.unary_operator(operator.neg)
-1227
-1228def__pos__(self)->'Die[T_co]':
-1229returnself.unary_operator(operator.pos)
-1230
-1231def__invert__(self)->'Die[T_co]':
-1232returnself.unary_operator(operator.invert)
-1233
-1234defabs(self)->'Die[T_co]':
-1235returnself.unary_operator(operator.abs)
-1236
-1237__abs__=abs
-1238
-1239defround(self,ndigits:int|None=None)->'Die':
-1240returnself.unary_operator(round,ndigits)
-1241
-1242__round__=round
-1243
-1244defstochastic_round(self,
-1245*,
-1246max_denominator:int|None=None)->'Die[int]':
-1247"""Randomly rounds outcomes up or down to the nearest integer according to the two distances.
-1248
-1249 Specificially, rounds `x` up with probability `x - floor(x)` and down
-1250 otherwise.
-1251
-1252 Args:
-1253 max_denominator: If provided, each rounding will be performed
-1254 using `fractions.Fraction.limit_denominator(max_denominator)`.
-1255 Otherwise, the rounding will be performed without
-1256 `limit_denominator`.
-1257 """
-1258returnself.map(lambdax:icepool.stochastic_round(
-1259x,max_denominator=max_denominator))
-1260
-1261deftrunc(self)->'Die':
-1262returnself.unary_operator(math.trunc)
-1263
-1264__trunc__=trunc
-1265
-1266deffloor(self)->'Die':
-1267returnself.unary_operator(math.floor)
-1268
-1269__floor__=floor
-1270
-1271defceil(self)->'Die':
-1272returnself.unary_operator(math.ceil)
-1273
-1274__ceil__=ceil
-1275
-1276# Binary operators.
+1223returnpool_composition.map_to_pool(make_pool,
+1224star=True,
+1225denominator=denominator)
+1226
+1227# Unary operators.
+1228
+1229def__neg__(self)->'Die[T_co]':
+1230returnself.unary_operator(operator.neg)
+1231
+1232def__pos__(self)->'Die[T_co]':
+1233returnself.unary_operator(operator.pos)
+1234
+1235def__invert__(self)->'Die[T_co]':
+1236returnself.unary_operator(operator.invert)
+1237
+1238defabs(self)->'Die[T_co]':
+1239returnself.unary_operator(operator.abs)
+1240
+1241__abs__=abs
+1242
+1243defround(self,ndigits:int|None=None)->'Die':
+1244returnself.unary_operator(round,ndigits)
+1245
+1246__round__=round
+1247
+1248defstochastic_round(self,
+1249*,
+1250max_denominator:int|None=None)->'Die[int]':
+1251"""Randomly rounds outcomes up or down to the nearest integer according to the two distances.
+1252
+1253 Specificially, rounds `x` up with probability `x - floor(x)` and down
+1254 otherwise.
+1255
+1256 Args:
+1257 max_denominator: If provided, each rounding will be performed
+1258 using `fractions.Fraction.limit_denominator(max_denominator)`.
+1259 Otherwise, the rounding will be performed without
+1260 `limit_denominator`.
+1261 """
+1262returnself.map(lambdax:icepool.stochastic_round(
+1263x,max_denominator=max_denominator))
+1264
+1265deftrunc(self)->'Die':
+1266returnself.unary_operator(math.trunc)
+1267
+1268__trunc__=trunc
+1269
+1270deffloor(self)->'Die':
+1271returnself.unary_operator(math.floor)
+1272
+1273__floor__=floor
+1274
+1275defceil(self)->'Die':
+1276returnself.unary_operator(math.ceil)1277
-1278def__add__(self,other)->'Die':
-1279ifisinstance(other,icepool.AgainExpression):
-1280returnNotImplemented
-1281other=implicit_convert_to_die(other)
-1282returnself.binary_operator(other,operator.add)
-1283
-1284def__radd__(self,other)->'Die':
-1285ifisinstance(other,icepool.AgainExpression):
-1286returnNotImplemented
-1287other=implicit_convert_to_die(other)
-1288returnother.binary_operator(self,operator.add)
-1289
-1290def__sub__(self,other)->'Die':
-1291ifisinstance(other,icepool.AgainExpression):
-1292returnNotImplemented
-1293other=implicit_convert_to_die(other)
-1294returnself.binary_operator(other,operator.sub)
-1295
-1296def__rsub__(self,other)->'Die':
-1297ifisinstance(other,icepool.AgainExpression):
-1298returnNotImplemented
-1299other=implicit_convert_to_die(other)
-1300returnother.binary_operator(self,operator.sub)
-1301
-1302def__mul__(self,other)->'Die':
-1303ifisinstance(other,icepool.AgainExpression):
-1304returnNotImplemented
-1305other=implicit_convert_to_die(other)
-1306returnself.binary_operator(other,operator.mul)
-1307
-1308def__rmul__(self,other)->'Die':
-1309ifisinstance(other,icepool.AgainExpression):
-1310returnNotImplemented
-1311other=implicit_convert_to_die(other)
-1312returnother.binary_operator(self,operator.mul)
-1313
-1314def__truediv__(self,other)->'Die':
-1315ifisinstance(other,icepool.AgainExpression):
-1316returnNotImplemented
-1317other=implicit_convert_to_die(other)
-1318returnself.binary_operator(other,operator.truediv)
-1319
-1320def__rtruediv__(self,other)->'Die':
-1321ifisinstance(other,icepool.AgainExpression):
-1322returnNotImplemented
-1323other=implicit_convert_to_die(other)
-1324returnother.binary_operator(self,operator.truediv)
-1325
-1326def__floordiv__(self,other)->'Die':
-1327ifisinstance(other,icepool.AgainExpression):
-1328returnNotImplemented
-1329other=implicit_convert_to_die(other)
-1330returnself.binary_operator(other,operator.floordiv)
-1331
-1332def__rfloordiv__(self,other)->'Die':
-1333ifisinstance(other,icepool.AgainExpression):
-1334returnNotImplemented
-1335other=implicit_convert_to_die(other)
-1336returnother.binary_operator(self,operator.floordiv)
-1337
-1338def__pow__(self,other)->'Die':
-1339ifisinstance(other,icepool.AgainExpression):
-1340returnNotImplemented
-1341other=implicit_convert_to_die(other)
-1342returnself.binary_operator(other,operator.pow)
-1343
-1344def__rpow__(self,other)->'Die':
-1345ifisinstance(other,icepool.AgainExpression):
-1346returnNotImplemented
-1347other=implicit_convert_to_die(other)
-1348returnother.binary_operator(self,operator.pow)
-1349
-1350def__mod__(self,other)->'Die':
-1351ifisinstance(other,icepool.AgainExpression):
-1352returnNotImplemented
-1353other=implicit_convert_to_die(other)
-1354returnself.binary_operator(other,operator.mod)
-1355
-1356def__rmod__(self,other)->'Die':
-1357ifisinstance(other,icepool.AgainExpression):
-1358returnNotImplemented
-1359other=implicit_convert_to_die(other)
-1360returnother.binary_operator(self,operator.mod)
-1361
-1362def__lshift__(self,other)->'Die':
-1363ifisinstance(other,icepool.AgainExpression):
-1364returnNotImplemented
-1365other=implicit_convert_to_die(other)
-1366returnself.binary_operator(other,operator.lshift)
-1367
-1368def__rlshift__(self,other)->'Die':
-1369ifisinstance(other,icepool.AgainExpression):
-1370returnNotImplemented
-1371other=implicit_convert_to_die(other)
-1372returnother.binary_operator(self,operator.lshift)
-1373
-1374def__rshift__(self,other)->'Die':
-1375ifisinstance(other,icepool.AgainExpression):
-1376returnNotImplemented
-1377other=implicit_convert_to_die(other)
-1378returnself.binary_operator(other,operator.rshift)
-1379
-1380def__rrshift__(self,other)->'Die':
-1381ifisinstance(other,icepool.AgainExpression):
-1382returnNotImplemented
-1383other=implicit_convert_to_die(other)
-1384returnother.binary_operator(self,operator.rshift)
-1385
-1386def__and__(self,other)->'Die':
-1387ifisinstance(other,icepool.AgainExpression):
-1388returnNotImplemented
-1389other=implicit_convert_to_die(other)
-1390returnself.binary_operator(other,operator.and_)
-1391
-1392def__rand__(self,other)->'Die':
-1393ifisinstance(other,icepool.AgainExpression):
-1394returnNotImplemented
-1395other=implicit_convert_to_die(other)
-1396returnother.binary_operator(self,operator.and_)
-1397
-1398def__or__(self,other)->'Die':
-1399ifisinstance(other,icepool.AgainExpression):
-1400returnNotImplemented
-1401other=implicit_convert_to_die(other)
-1402returnself.binary_operator(other,operator.or_)
-1403
-1404def__ror__(self,other)->'Die':
-1405ifisinstance(other,icepool.AgainExpression):
-1406returnNotImplemented
-1407other=implicit_convert_to_die(other)
-1408returnother.binary_operator(self,operator.or_)
-1409
-1410def__xor__(self,other)->'Die':
-1411ifisinstance(other,icepool.AgainExpression):
-1412returnNotImplemented
-1413other=implicit_convert_to_die(other)
-1414returnself.binary_operator(other,operator.xor)
-1415
-1416def__rxor__(self,other)->'Die':
-1417ifisinstance(other,icepool.AgainExpression):
-1418returnNotImplemented
-1419other=implicit_convert_to_die(other)
-1420returnother.binary_operator(self,operator.xor)
-1421
-1422# Comparators.
-1423
-1424def__lt__(self,other)->'Die[bool]':
-1425ifisinstance(other,icepool.AgainExpression):
-1426returnNotImplemented
-1427other=implicit_convert_to_die(other)
-1428returnself.binary_operator(other,operator.lt)
-1429
-1430def__le__(self,other)->'Die[bool]':
-1431ifisinstance(other,icepool.AgainExpression):
-1432returnNotImplemented
-1433other=implicit_convert_to_die(other)
-1434returnself.binary_operator(other,operator.le)
-1435
-1436def__ge__(self,other)->'Die[bool]':
-1437ifisinstance(other,icepool.AgainExpression):
-1438returnNotImplemented
-1439other=implicit_convert_to_die(other)
-1440returnself.binary_operator(other,operator.ge)
-1441
-1442def__gt__(self,other)->'Die[bool]':
-1443ifisinstance(other,icepool.AgainExpression):
-1444returnNotImplemented
-1445other=implicit_convert_to_die(other)
-1446returnself.binary_operator(other,operator.gt)
-1447
-1448# Equality operators. These produce a `DieWithTruth`.
-1449
-1450# The result has a truth value, but is not a bool.
-1451def__eq__(self,other)->'icepool.DieWithTruth[bool]':# type: ignore
-1452ifisinstance(other,icepool.AgainExpression):
-1453returnNotImplemented
-1454other_die:Die=implicit_convert_to_die(other)
-1455
-1456defdata_callback()->Counts[bool]:
-1457returnself.binary_operator(other_die,operator.eq)._data
-1458
-1459deftruth_value_callback()->bool:
-1460returnself.equals(other)
-1461
-1462returnicepool.DieWithTruth(data_callback,truth_value_callback)
-1463
-1464# The result has a truth value, but is not a bool.
-1465def__ne__(self,other)->'icepool.DieWithTruth[bool]':# type: ignore
-1466ifisinstance(other,icepool.AgainExpression):
-1467returnNotImplemented
-1468other_die:Die=implicit_convert_to_die(other)
-1469
-1470defdata_callback()->Counts[bool]:
-1471returnself.binary_operator(other_die,operator.ne)._data
-1472
-1473deftruth_value_callback()->bool:
-1474returnnotself.equals(other)
-1475
-1476returnicepool.DieWithTruth(data_callback,truth_value_callback)
-1477
-1478defcmp(self,other)->'Die[int]':
-1479"""A `Die` with outcomes 1, -1, and 0.
-1480
-1481 The quantities are equal to the positive outcome of `self > other`,
-1482 `self < other`, and the remainder respectively.
-1483
-1484 This will include all three outcomes even if they have zero quantity.
-1485 """
-1486other=implicit_convert_to_die(other)
+1278__ceil__=ceil
+1279
+1280# Binary operators.
+1281
+1282def__add__(self,other)->'Die':
+1283ifisinstance(other,icepool.AgainExpression):
+1284returnNotImplemented
+1285other=implicit_convert_to_die(other)
+1286returnself.binary_operator(other,operator.add)
+1287
+1288def__radd__(self,other)->'Die':
+1289ifisinstance(other,icepool.AgainExpression):
+1290returnNotImplemented
+1291other=implicit_convert_to_die(other)
+1292returnother.binary_operator(self,operator.add)
+1293
+1294def__sub__(self,other)->'Die':
+1295ifisinstance(other,icepool.AgainExpression):
+1296returnNotImplemented
+1297other=implicit_convert_to_die(other)
+1298returnself.binary_operator(other,operator.sub)
+1299
+1300def__rsub__(self,other)->'Die':
+1301ifisinstance(other,icepool.AgainExpression):
+1302returnNotImplemented
+1303other=implicit_convert_to_die(other)
+1304returnother.binary_operator(self,operator.sub)
+1305
+1306def__mul__(self,other)->'Die':
+1307ifisinstance(other,icepool.AgainExpression):
+1308returnNotImplemented
+1309other=implicit_convert_to_die(other)
+1310returnself.binary_operator(other,operator.mul)
+1311
+1312def__rmul__(self,other)->'Die':
+1313ifisinstance(other,icepool.AgainExpression):
+1314returnNotImplemented
+1315other=implicit_convert_to_die(other)
+1316returnother.binary_operator(self,operator.mul)
+1317
+1318def__truediv__(self,other)->'Die':
+1319ifisinstance(other,icepool.AgainExpression):
+1320returnNotImplemented
+1321other=implicit_convert_to_die(other)
+1322returnself.binary_operator(other,operator.truediv)
+1323
+1324def__rtruediv__(self,other)->'Die':
+1325ifisinstance(other,icepool.AgainExpression):
+1326returnNotImplemented
+1327other=implicit_convert_to_die(other)
+1328returnother.binary_operator(self,operator.truediv)
+1329
+1330def__floordiv__(self,other)->'Die':
+1331ifisinstance(other,icepool.AgainExpression):
+1332returnNotImplemented
+1333other=implicit_convert_to_die(other)
+1334returnself.binary_operator(other,operator.floordiv)
+1335
+1336def__rfloordiv__(self,other)->'Die':
+1337ifisinstance(other,icepool.AgainExpression):
+1338returnNotImplemented
+1339other=implicit_convert_to_die(other)
+1340returnother.binary_operator(self,operator.floordiv)
+1341
+1342def__pow__(self,other)->'Die':
+1343ifisinstance(other,icepool.AgainExpression):
+1344returnNotImplemented
+1345other=implicit_convert_to_die(other)
+1346returnself.binary_operator(other,operator.pow)
+1347
+1348def__rpow__(self,other)->'Die':
+1349ifisinstance(other,icepool.AgainExpression):
+1350returnNotImplemented
+1351other=implicit_convert_to_die(other)
+1352returnother.binary_operator(self,operator.pow)
+1353
+1354def__mod__(self,other)->'Die':
+1355ifisinstance(other,icepool.AgainExpression):
+1356returnNotImplemented
+1357other=implicit_convert_to_die(other)
+1358returnself.binary_operator(other,operator.mod)
+1359
+1360def__rmod__(self,other)->'Die':
+1361ifisinstance(other,icepool.AgainExpression):
+1362returnNotImplemented
+1363other=implicit_convert_to_die(other)
+1364returnother.binary_operator(self,operator.mod)
+1365
+1366def__lshift__(self,other)->'Die':
+1367ifisinstance(other,icepool.AgainExpression):
+1368returnNotImplemented
+1369other=implicit_convert_to_die(other)
+1370returnself.binary_operator(other,operator.lshift)
+1371
+1372def__rlshift__(self,other)->'Die':
+1373ifisinstance(other,icepool.AgainExpression):
+1374returnNotImplemented
+1375other=implicit_convert_to_die(other)
+1376returnother.binary_operator(self,operator.lshift)
+1377
+1378def__rshift__(self,other)->'Die':
+1379ifisinstance(other,icepool.AgainExpression):
+1380returnNotImplemented
+1381other=implicit_convert_to_die(other)
+1382returnself.binary_operator(other,operator.rshift)
+1383
+1384def__rrshift__(self,other)->'Die':
+1385ifisinstance(other,icepool.AgainExpression):
+1386returnNotImplemented
+1387other=implicit_convert_to_die(other)
+1388returnother.binary_operator(self,operator.rshift)
+1389
+1390def__and__(self,other)->'Die':
+1391ifisinstance(other,icepool.AgainExpression):
+1392returnNotImplemented
+1393other=implicit_convert_to_die(other)
+1394returnself.binary_operator(other,operator.and_)
+1395
+1396def__rand__(self,other)->'Die':
+1397ifisinstance(other,icepool.AgainExpression):
+1398returnNotImplemented
+1399other=implicit_convert_to_die(other)
+1400returnother.binary_operator(self,operator.and_)
+1401
+1402def__or__(self,other)->'Die':
+1403ifisinstance(other,icepool.AgainExpression):
+1404returnNotImplemented
+1405other=implicit_convert_to_die(other)
+1406returnself.binary_operator(other,operator.or_)
+1407
+1408def__ror__(self,other)->'Die':
+1409ifisinstance(other,icepool.AgainExpression):
+1410returnNotImplemented
+1411other=implicit_convert_to_die(other)
+1412returnother.binary_operator(self,operator.or_)
+1413
+1414def__xor__(self,other)->'Die':
+1415ifisinstance(other,icepool.AgainExpression):
+1416returnNotImplemented
+1417other=implicit_convert_to_die(other)
+1418returnself.binary_operator(other,operator.xor)
+1419
+1420def__rxor__(self,other)->'Die':
+1421ifisinstance(other,icepool.AgainExpression):
+1422returnNotImplemented
+1423other=implicit_convert_to_die(other)
+1424returnother.binary_operator(self,operator.xor)
+1425
+1426# Comparators.
+1427
+1428def__lt__(self,other)->'Die[bool]':
+1429ifisinstance(other,icepool.AgainExpression):
+1430returnNotImplemented
+1431other=implicit_convert_to_die(other)
+1432returnself.binary_operator(other,operator.lt)
+1433
+1434def__le__(self,other)->'Die[bool]':
+1435ifisinstance(other,icepool.AgainExpression):
+1436returnNotImplemented
+1437other=implicit_convert_to_die(other)
+1438returnself.binary_operator(other,operator.le)
+1439
+1440def__ge__(self,other)->'Die[bool]':
+1441ifisinstance(other,icepool.AgainExpression):
+1442returnNotImplemented
+1443other=implicit_convert_to_die(other)
+1444returnself.binary_operator(other,operator.ge)
+1445
+1446def__gt__(self,other)->'Die[bool]':
+1447ifisinstance(other,icepool.AgainExpression):
+1448returnNotImplemented
+1449other=implicit_convert_to_die(other)
+1450returnself.binary_operator(other,operator.gt)
+1451
+1452# Equality operators. These produce a `DieWithTruth`.
+1453
+1454# The result has a truth value, but is not a bool.
+1455def__eq__(self,other)->'icepool.DieWithTruth[bool]':# type: ignore
+1456ifisinstance(other,icepool.AgainExpression):
+1457returnNotImplemented
+1458other_die:Die=implicit_convert_to_die(other)
+1459
+1460defdata_callback()->Counts[bool]:
+1461returnself.binary_operator(other_die,operator.eq)._data
+1462
+1463deftruth_value_callback()->bool:
+1464returnself.equals(other)
+1465
+1466returnicepool.DieWithTruth(data_callback,truth_value_callback)
+1467
+1468# The result has a truth value, but is not a bool.
+1469def__ne__(self,other)->'icepool.DieWithTruth[bool]':# type: ignore
+1470ifisinstance(other,icepool.AgainExpression):
+1471returnNotImplemented
+1472other_die:Die=implicit_convert_to_die(other)
+1473
+1474defdata_callback()->Counts[bool]:
+1475returnself.binary_operator(other_die,operator.ne)._data
+1476
+1477deftruth_value_callback()->bool:
+1478returnnotself.equals(other)
+1479
+1480returnicepool.DieWithTruth(data_callback,truth_value_callback)
+1481
+1482defcmp(self,other)->'Die[int]':
+1483"""A `Die` with outcomes 1, -1, and 0.
+1484
+1485 The quantities are equal to the positive outcome of `self > other`,
+1486 `self < other`, and the remainder respectively.1487
-1488data={}
-1489
-1490lt=self<other
-1491ifTrueinlt:
-1492data[-1]=lt[True]
-1493eq=self==other
-1494ifTrueineq:
-1495data[0]=eq[True]
-1496gt=self>other
-1497ifTrueingt:
-1498data[1]=gt[True]
-1499
-1500returnDie(data)
-1501
-1502@staticmethod
-1503def_sign(x)->int:
-1504z=Die._zero(x)
-1505ifx>z:
-1506return1
-1507elifx<z:
-1508return-1
-1509else:
-1510return0
-1511
-1512defsign(self)->'Die[int]':
-1513"""Outcomes become 1 if greater than `zero()`, -1 if less than `zero()`, and 0 otherwise.
-1514
-1515 Note that for `float`s, +0.0, -0.0, and nan all become 0.
-1516 """
-1517returnself.unary_operator(Die._sign)
+1488 This will include all three outcomes even if they have zero quantity.
+1489 """
+1490other=implicit_convert_to_die(other)
+1491
+1492data={}
+1493
+1494lt=self<other
+1495ifTrueinlt:
+1496data[-1]=lt[True]
+1497eq=self==other
+1498ifTrueineq:
+1499data[0]=eq[True]
+1500gt=self>other
+1501ifTrueingt:
+1502data[1]=gt[True]
+1503
+1504returnDie(data)
+1505
+1506@staticmethod
+1507def_sign(x)->int:
+1508z=Die._zero(x)
+1509ifx>z:
+1510return1
+1511elifx<z:
+1512return-1
+1513else:
+1514return0
+1515
+1516defsign(self)->'Die[int]':
+1517"""Outcomes become 1 if greater than `zero()`, -1 if less than `zero()`, and 0 otherwise.1518
-1519# Equality and hashing.
-1520
-1521def__bool__(self)->bool:
-1522raiseTypeError(
-1523'A `Die` only has a truth value if it is the result of == or !=.\n'
-1524'This could result from trying to use a die in an if-statement,\n'
-1525'in which case you should use `die.if_else()` instead.\n'
-1526'Or it could result from trying to use a `Die` inside a tuple or vector outcome,\n'
-1527'in which case you should use `tupleize()` or `vectorize().')
-1528
-1529@cached_property
-1530def_hash_key(self)->tuple:
-1531"""A tuple that uniquely (as `equals()`) identifies this die.
+1519 Note that for `float`s, +0.0, -0.0, and nan all become 0.
+1520 """
+1521returnself.unary_operator(Die._sign)
+1522
+1523# Equality and hashing.
+1524
+1525def__bool__(self)->bool:
+1526raiseTypeError(
+1527'A `Die` only has a truth value if it is the result of == or !=.\n'
+1528'This could result from trying to use a die in an if-statement,\n'
+1529'in which case you should use `die.if_else()` instead.\n'
+1530'Or it could result from trying to use a `Die` inside a tuple or vector outcome,\n'
+1531'in which case you should use `tupleize()` or `vectorize().')1532
-1533 Apart from being hashable and totally orderable, this is not guaranteed
-1534 to be in any particular format or have any other properties.
-1535 """
-1536returntuple(self.items())
-1537
-1538@cached_property
-1539def_hash(self)->int:
-1540returnhash(self._hash_key)
+1533@cached_property
+1534def_hash_key(self)->tuple:
+1535"""A tuple that uniquely (as `equals()`) identifies this die.
+1536
+1537 Apart from being hashable and totally orderable, this is not guaranteed
+1538 to be in any particular format or have any other properties.
+1539 """
+1540returntuple(self.items())1541
-1542def__hash__(self)->int:
-1543returnself._hash
-1544
-1545defequals(self,other,*,simplify:bool=False)->bool:
-1546"""`True` iff both dice have the same outcomes and quantities.
-1547
-1548 This is `False` if `other` is not a `Die`, even if it would convert
-1549 to an equal `Die`.
-1550
-1551 Truth value does NOT matter.
-1552
-1553 If one `Die` has a zero-quantity outcome and the other `Die` does not
-1554 contain that outcome, they are treated as unequal by this function.
-1555
-1556 The `==` and `!=` operators have a dual purpose; they return a `Die`
-1557 with a truth value determined by this method.
-1558 Only dice returned by these methods have a truth value. The data of
-1559 these dice is lazily evaluated since the caller may only be interested
-1560 in the `Die` value or the truth value.
-1561
-1562 Args:
-1563 simplify: If `True`, the dice will be simplified before comparing.
-1564 Otherwise, e.g. a 2:2 coin is not `equals()` to a 1:1 coin.
-1565 """
-1566ifnotisinstance(other,Die):
-1567returnFalse
-1568
-1569ifsimplify:
-1570returnself.simplify()._hash_key==other.simplify()._hash_key
-1571else:
-1572returnself._hash_key==other._hash_key
-1573
-1574# Strings.
-1575
-1576def__repr__(self)->str:
-1577inner=', '.join(f'{repr(outcome)}: {weight}'
-1578foroutcome,weightinself.items())
-1579returntype(self).__qualname__+'({'+inner+'})'
+1542@cached_property
+1543def_hash(self)->int:
+1544returnhash(self._hash_key)
+1545
+1546def__hash__(self)->int:
+1547returnself._hash
+1548
+1549defequals(self,other,*,simplify:bool=False)->bool:
+1550"""`True` iff both dice have the same outcomes and quantities.
+1551
+1552 This is `False` if `other` is not a `Die`, even if it would convert
+1553 to an equal `Die`.
+1554
+1555 Truth value does NOT matter.
+1556
+1557 If one `Die` has a zero-quantity outcome and the other `Die` does not
+1558 contain that outcome, they are treated as unequal by this function.
+1559
+1560 The `==` and `!=` operators have a dual purpose; they return a `Die`
+1561 with a truth value determined by this method.
+1562 Only dice returned by these methods have a truth value. The data of
+1563 these dice is lazily evaluated since the caller may only be interested
+1564 in the `Die` value or the truth value.
+1565
+1566 Args:
+1567 simplify: If `True`, the dice will be simplified before comparing.
+1568 Otherwise, e.g. a 2:2 coin is not `equals()` to a 1:1 coin.
+1569 """
+1570ifnotisinstance(other,Die):
+1571returnFalse
+1572
+1573ifsimplify:
+1574returnself.simplify()._hash_key==other.simplify()._hash_key
+1575else:
+1576returnself._hash_key==other._hash_key
+1577
+1578# Strings.
+1579
+1580def__repr__(self)->str:
+1581inner=', '.join(f'{repr(outcome)}: {weight}'
+1582foroutcome,weightinself.items())
+1583returntype(self).__qualname__+'({'+inner+'})'
@@ -4173,26 +4177,26 @@
Arguments:
-
786defpool(self,rolls:int|Sequence[int]=1,/)->'icepool.Pool[T_co]':
-787"""Creates a `Pool` from this `Die`.
-788
-789 You might subscript the pool immediately afterwards, e.g.
-790 `d6.pool(5)[-1, ..., 1]` takes the difference between the highest and
-791 lowest of 5d6.
+
790defpool(self,rolls:int|Sequence[int]=1,/)->'icepool.Pool[T_co]':
+791"""Creates a `Pool` from this `Die`.792
-793 Args:
-794 rolls: The number of copies of this `Die` to put in the pool.
-795 Or, a sequence of one `int` per die acting as
-796 `keep_tuple`. Note that `...` cannot be used in the
-797 argument to this method, as the argument determines the size of
-798 the pool.
-799 """
-800ifisinstance(rolls,int):
-801returnicepool.Pool({self:rolls})
-802else:
-803pool_size=len(rolls)
-804# Haven't dealt with narrowing return type.
-805returnicepool.Pool({self:pool_size})[rolls]# type: ignore
+793 You might subscript the pool immediately afterwards, e.g.
+794 `d6.pool(5)[-1, ..., 1]` takes the difference between the highest and
+795 lowest of 5d6.
+796
+797 Args:
+798 rolls: The number of copies of this `Die` to put in the pool.
+799 Or, a sequence of one `int` per die acting as
+800 `keep_tuple`. Note that `...` cannot be used in the
+801 argument to this method, as the argument determines the size of
+802 the pool.
+803 """
+804ifisinstance(rolls,int):
+805returnicepool.Pool({self:rolls})
+806else:
+807pool_size=len(rolls)
+808# Haven't dealt with narrowing return type.
+809returnicepool.Pool({self:pool_size})[rolls]# type: ignore
@@ -4226,58 +4230,58 @@
Arguments:
-
853defkeep(self,
-854rolls:int|Sequence[int],
-855index:slice|Sequence[int|EllipsisType]|int|None=None,
-856/)->'Die':
-857"""Selects elements after drawing and sorting and sums them.
-858
-859 Args:
-860 rolls: The number of dice to roll.
-861 index: One of the following:
-862 * An `int`. This will count only the roll at the specified index.
-863 In this case, the result is a `Die` rather than a generator.
-864 * A `slice`. The selected dice are counted once each.
-865 * A sequence of `int`s with length equal to `rolls`.
-866 Each roll is counted that many times, which could be multiple or
-867 negative times.
-868
-869 Up to one `...` (`Ellipsis`) may be used. If no `...` is used,
-870 the `rolls` argument may be omitted.
-871
-872 `...` will be replaced with a number of zero counts in order
-873 to make up any missing elements compared to `rolls`.
-874 This number may be "negative" if more `int`s are provided than
-875 `rolls`. Specifically:
-876
-877 * If `index` is shorter than `rolls`, `...`
-878 acts as enough zero counts to make up the difference.
-879 E.g. `(1, ..., 1)` on five dice would act as
-880 `(1, 0, 0, 0, 1)`.
-881 * If `index` has length equal to `rolls`, `...` has no effect.
-882 E.g. `(1, ..., 1)` on two dice would act as `(1, 1)`.
-883 * If `index` is longer than `rolls` and `...` is on one side,
-884 elements will be dropped from `index` on the side with `...`.
-885 E.g. `(..., 1, 2, 3)` on two dice would act as `(2, 3)`.
-886 * If `index` is longer than `rolls` and `...`
-887 is in the middle, the counts will be as the sum of two
-888 one-sided `...`.
-889 E.g. `(-1, ..., 1)` acts like `(-1, ...)` plus `(..., 1)`.
-890 If `rolls` was 1 this would have the -1 and 1 cancel each other out.
-891 """
-892ifisinstance(rolls,int):
-893ifindexisNone:
-894raiseValueError(
-895'If the number of rolls is an integer, an index argument must be provided.'
-896)
-897ifisinstance(index,int):
-898returnself.pool(rolls).keep(index)
-899else:
-900returnself.pool(rolls).keep(index).sum()# type: ignore
-901else:
-902ifindexisnotNone:
-903raiseValueError('Only one index sequence can be given.')
-904returnself.pool(len(rolls)).keep(rolls).sum()# type: ignore
+
857defkeep(self,
+858rolls:int|Sequence[int],
+859index:slice|Sequence[int|EllipsisType]|int|None=None,
+860/)->'Die':
+861"""Selects elements after drawing and sorting and sums them.
+862
+863 Args:
+864 rolls: The number of dice to roll.
+865 index: One of the following:
+866 * An `int`. This will count only the roll at the specified index.
+867 In this case, the result is a `Die` rather than a generator.
+868 * A `slice`. The selected dice are counted once each.
+869 * A sequence of `int`s with length equal to `rolls`.
+870 Each roll is counted that many times, which could be multiple or
+871 negative times.
+872
+873 Up to one `...` (`Ellipsis`) may be used. If no `...` is used,
+874 the `rolls` argument may be omitted.
+875
+876 `...` will be replaced with a number of zero counts in order
+877 to make up any missing elements compared to `rolls`.
+878 This number may be "negative" if more `int`s are provided than
+879 `rolls`. Specifically:
+880
+881 * If `index` is shorter than `rolls`, `...`
+882 acts as enough zero counts to make up the difference.
+883 E.g. `(1, ..., 1)` on five dice would act as
+884 `(1, 0, 0, 0, 1)`.
+885 * If `index` has length equal to `rolls`, `...` has no effect.
+886 E.g. `(1, ..., 1)` on two dice would act as `(1, 1)`.
+887 * If `index` is longer than `rolls` and `...` is on one side,
+888 elements will be dropped from `index` on the side with `...`.
+889 E.g. `(..., 1, 2, 3)` on two dice would act as `(2, 3)`.
+890 * If `index` is longer than `rolls` and `...`
+891 is in the middle, the counts will be as the sum of two
+892 one-sided `...`.
+893 E.g. `(-1, ..., 1)` acts like `(-1, ...)` plus `(..., 1)`.
+894 If `rolls` was 1 this would have the -1 and 1 cancel each other out.
+895 """
+896ifisinstance(rolls,int):
+897ifindexisNone:
+898raiseValueError(
+899'If the number of rolls is an integer, an index argument must be provided.'
+900)
+901ifisinstance(index,int):
+902returnself.pool(rolls).keep(index)
+903else:
+904returnself.pool(rolls).keep(index).sum()# type: ignore
+905else:
+906ifindexisnotNone:
+907raiseValueError('Only one index sequence can be given.')
+908returnself.pool(len(rolls)).keep(rolls).sum()# type: ignore
@@ -4345,35 +4349,35 @@
Arguments:
-
906deflowest(self,
-907rolls:int,
-908/,
-909keep:int|None=None,
-910drop:int|None=None)->'Die':
-911"""Roll several of this `Die` and return the lowest result, or the sum of some of the lowest.
-912
-913 The outcomes should support addition and multiplication if `keep != 1`.
-914
-915 Args:
-916 rolls: The number of dice to roll. All dice will have the same
-917 outcomes as `self`.
-918 keep, drop: These arguments work together:
-919 * If neither are provided, the single lowest die will be taken.
-920 * If only `keep` is provided, the `keep` lowest dice will be summed.
-921 * If only `drop` is provided, the `drop` lowest dice will be dropped
-922 and the rest will be summed.
-923 * If both are provided, `drop` lowest dice will be dropped, then
-924 the next `keep` lowest dice will be summed.
-925
-926 Returns:
-927 A `Die` representing the probability distribution of the sum.
-928 """
-929index=lowest_slice(keep,drop)
-930canonical=canonical_slice(index,rolls)
-931ifcanonical.start==0andcanonical.stop==1:
-932returnself._lowest_single(rolls)
-933# Expression evaluators are difficult to type.
-934returnself.pool(rolls)[index].sum()# type: ignore
+
910deflowest(self,
+911rolls:int,
+912/,
+913keep:int|None=None,
+914drop:int|None=None)->'Die':
+915"""Roll several of this `Die` and return the lowest result, or the sum of some of the lowest.
+916
+917 The outcomes should support addition and multiplication if `keep != 1`.
+918
+919 Args:
+920 rolls: The number of dice to roll. All dice will have the same
+921 outcomes as `self`.
+922 keep, drop: These arguments work together:
+923 * If neither are provided, the single lowest die will be taken.
+924 * If only `keep` is provided, the `keep` lowest dice will be summed.
+925 * If only `drop` is provided, the `drop` lowest dice will be dropped
+926 and the rest will be summed.
+927 * If both are provided, `drop` lowest dice will be dropped, then
+928 the next `keep` lowest dice will be summed.
+929
+930 Returns:
+931 A `Die` representing the probability distribution of the sum.
+932 """
+933index=lowest_slice(keep,drop)
+934canonical=canonical_slice(index,rolls)
+935ifcanonical.start==0andcanonical.stop==1:
+936returnself._lowest_single(rolls)
+937# Expression evaluators are difficult to type.
+938returnself.pool(rolls)[index].sum()# type: ignore
@@ -4417,34 +4421,34 @@
Returns:
-
944defhighest(self,
-945rolls:int,
-946/,
-947keep:int|None=None,
-948drop:int|None=None)->'Die[T_co]':
-949"""Roll several of this `Die` and return the highest result, or the sum of some of the highest.
-950
-951 The outcomes should support addition and multiplication if `keep != 1`.
-952
-953 Args:
-954 rolls: The number of dice to roll.
-955 keep, drop: These arguments work together:
-956 * If neither are provided, the single highest die will be taken.
-957 * If only `keep` is provided, the `keep` highest dice will be summed.
-958 * If only `drop` is provided, the `drop` highest dice will be dropped
-959 and the rest will be summed.
-960 * If both are provided, `drop` highest dice will be dropped, then
-961 the next `keep` highest dice will be summed.
-962
-963 Returns:
-964 A `Die` representing the probability distribution of the sum.
-965 """
-966index=highest_slice(keep,drop)
-967canonical=canonical_slice(index,rolls)
-968ifcanonical.start==rolls-1andcanonical.stop==rolls:
-969returnself._highest_single(rolls)
-970# Expression evaluators are difficult to type.
-971returnself.pool(rolls)[index].sum()# type: ignore
+
948defhighest(self,
+949rolls:int,
+950/,
+951keep:int|None=None,
+952drop:int|None=None)->'Die[T_co]':
+953"""Roll several of this `Die` and return the highest result, or the sum of some of the highest.
+954
+955 The outcomes should support addition and multiplication if `keep != 1`.
+956
+957 Args:
+958 rolls: The number of dice to roll.
+959 keep, drop: These arguments work together:
+960 * If neither are provided, the single highest die will be taken.
+961 * If only `keep` is provided, the `keep` highest dice will be summed.
+962 * If only `drop` is provided, the `drop` highest dice will be dropped
+963 and the rest will be summed.
+964 * If both are provided, `drop` highest dice will be dropped, then
+965 the next `keep` highest dice will be summed.
+966
+967 Returns:
+968 A `Die` representing the probability distribution of the sum.
+969 """
+970index=highest_slice(keep,drop)
+971canonical=canonical_slice(index,rolls)
+972ifcanonical.start==rolls-1andcanonical.stop==rolls:
+973returnself._highest_single(rolls)
+974# Expression evaluators are difficult to type.
+975returnself.pool(rolls)[index].sum()# type: ignore
@@ -4487,29 +4491,29 @@
Returns:
-
980defmiddle(
- 981self,
- 982rolls:int,
- 983/,
- 984keep:int=1,
- 985*,
- 986tie:Literal['error','high','low']='error')->'icepool.Die':
- 987"""Roll several of this `Die` and sum the sorted results in the middle.
- 988
- 989 The outcomes should support addition and multiplication if `keep != 1`.
- 990
- 991 Args:
- 992 rolls: The number of dice to roll.
- 993 keep: The number of outcomes to sum. If this is greater than the
- 994 current keep_size, all are kept.
- 995 tie: What to do if `keep` is odd but the current keep_size
- 996 is even, or vice versa.
- 997 * 'error' (default): Raises `IndexError`.
- 998 * 'high': The higher outcome is taken.
- 999 * 'low': The lower outcome is taken.
-1000 """
-1001# Expression evaluators are difficult to type.
-1002returnself.pool(rolls).middle(keep,tie=tie).sum()# type: ignore
+
984defmiddle(
+ 985self,
+ 986rolls:int,
+ 987/,
+ 988keep:int=1,
+ 989*,
+ 990tie:Literal['error','high','low']='error')->'icepool.Die':
+ 991"""Roll several of this `Die` and sum the sorted results in the middle.
+ 992
+ 993 The outcomes should support addition and multiplication if `keep != 1`.
+ 994
+ 995 Args:
+ 996 rolls: The number of dice to roll.
+ 997 keep: The number of outcomes to sum. If this is greater than the
+ 998 current keep_size, all are kept.
+ 999 tie: What to do if `keep` is odd but the current keep_size
+1000 is even, or vice versa.
+1001 * 'error' (default): Raises `IndexError`.
+1002 * 'high': The higher outcome is taken.
+1003 * 'low': The lower outcome is taken.
+1004 """
+1005# Expression evaluators are difficult to type.
+1006returnself.pool(rolls).middle(keep,tie=tie).sum()# type: ignore
@@ -4546,57 +4550,57 @@
Arguments:
-
1004defmap_to_pool(
-1005self,
-1006repl:
-1007'Callable[..., Sequence[icepool.Die[U] | U] | Mapping[icepool.Die[U], int] | Mapping[U, int] | icepool.RerollType] | None'=None,
-1008/,
-1009*extra_args:'Outcome | icepool.Die | icepool.MultisetExpression',
-1010star:bool|None=None,
-1011denominator:int|None=None
-1012)->'icepool.MultisetGenerator[U, tuple[int]]':
-1013"""EXPERIMENTAL: Maps outcomes of this `Die` to `Pools`, creating a `MultisetGenerator`.
-1014
-1015 As `icepool.map_to_pool(repl, self, ...)`.
-1016
-1017 If no argument is provided, the outcomes will be used to construct a
-1018 mixture of pools directly, similar to the inverse of `pool.expand()`.
-1019 Note that this is not particularly efficient since it does not make much
-1020 use of dynamic programming.
-1021
-1022 Args:
-1023 repl: One of the following:
-1024 * A callable that takes in one outcome per element of args and
-1025 produces a `Pool` (or something convertible to such).
-1026 * A mapping from old outcomes to `Pool`
-1027 (or something convertible to such).
-1028 In this case args must have exactly one element.
-1029 The new outcomes may be dice rather than just single outcomes.
-1030 The special value `icepool.Reroll` will reroll that old outcome.
-1031 star: If `True`, the first of the args will be unpacked before
-1032 giving them to `repl`.
-1033 If not provided, it will be guessed based on the signature of
-1034 `repl` and the number of arguments.
-1035 denominator: If provided, the denominator of the result will be this
-1036 value. Otherwise it will be the minimum to correctly weight the
-1037 pools.
-1038
-1039 Returns:
-1040 A `MultisetGenerator` representing the mixture of `Pool`s. Note
-1041 that this is not technically a `Pool`, though it supports most of
-1042 the same operations.
-1043
-1044 Raises:
-1045 ValueError: If `denominator` cannot be made consistent with the
-1046 resulting mixture of pools.
-1047 """
-1048ifreplisNone:
-1049repl=lambdax:x
-1050returnicepool.map_to_pool(repl,
-1051self,
-1052*extra_args,
-1053star=star,
-1054denominator=denominator)
+
1008defmap_to_pool(
+1009self,
+1010repl:
+1011'Callable[..., Sequence[icepool.Die[U] | U] | Mapping[icepool.Die[U], int] | Mapping[U, int] | icepool.RerollType] | None'=None,
+1012/,
+1013*extra_args:'Outcome | icepool.Die | icepool.MultisetExpression',
+1014star:bool|None=None,
+1015denominator:int|None=None
+1016)->'icepool.MultisetGenerator[U, tuple[int]]':
+1017"""EXPERIMENTAL: Maps outcomes of this `Die` to `Pools`, creating a `MultisetGenerator`.
+1018
+1019 As `icepool.map_to_pool(repl, self, ...)`.
+1020
+1021 If no argument is provided, the outcomes will be used to construct a
+1022 mixture of pools directly, similar to the inverse of `pool.expand()`.
+1023 Note that this is not particularly efficient since it does not make much
+1024 use of dynamic programming.
+1025
+1026 Args:
+1027 repl: One of the following:
+1028 * A callable that takes in one outcome per element of args and
+1029 produces a `Pool` (or something convertible to such).
+1030 * A mapping from old outcomes to `Pool`
+1031 (or something convertible to such).
+1032 In this case args must have exactly one element.
+1033 The new outcomes may be dice rather than just single outcomes.
+1034 The special value `icepool.Reroll` will reroll that old outcome.
+1035 star: If `True`, the first of the args will be unpacked before
+1036 giving them to `repl`.
+1037 If not provided, it will be guessed based on the signature of
+1038 `repl` and the number of arguments.
+1039 denominator: If provided, the denominator of the result will be this
+1040 value. Otherwise it will be the minimum to correctly weight the
+1041 pools.
+1042
+1043 Returns:
+1044 A `MultisetGenerator` representing the mixture of `Pool`s. Note
+1045 that this is not technically a `Pool`, though it supports most of
+1046 the same operations.
+1047
+1048 Raises:
+1049 ValueError: If `denominator` cannot be made consistent with the
+1050 resulting mixture of pools.
+1051 """
+1052ifreplisNone:
+1053repl=lambdax:x
+1054returnicepool.map_to_pool(repl,
+1055self,
+1056*extra_args,
+1057star=star,
+1058denominator=denominator)
@@ -4660,59 +4664,59 @@
Raises:
-
1056defexplode_to_pool(
-1057self,
-1058rolls:int,
-1059which:Collection[T_co]|Callable[...,bool]|None=None,
-1060/,
-1061*,
-1062star:bool|None=None,
-1063depth:int=9)->'icepool.MultisetGenerator[T_co, tuple[int]]':
-1064"""EXPERIMENTAL: Causes outcomes to be rolled again, keeping that outcome as an individual die in a pool.
-1065
-1066 Args:
-1067 rolls: The number of initial dice.
-1068 which: Which outcomes to explode. Options:
-1069 * A single outcome to explode.
-1070 * An collection of outcomes to explode.
-1071 * A callable that takes an outcome and returns `True` if it
-1072 should be exploded.
-1073 * If not supplied, the max outcome will explode.
-1074 star: Whether outcomes should be unpacked into separate arguments
-1075 before sending them to a callable `which`.
-1076 If not provided, this will be guessed based on the function
-1077 signature.
-1078 depth: The maximum depth of explosions for an individual dice.
-1079
-1080 Returns:
-1081 A `MultisetGenerator` representing the mixture of `Pool`s. Note
-1082 that this is not technically a `Pool`, though it supports most of
-1083 the same operations.
-1084 """
-1085ifdepth==0:
-1086returnself.pool(rolls)
-1087ifwhichisNone:
-1088explode_set={self.max_outcome()}
-1089else:
-1090explode_set=self._select_outcomes(which,star)
-1091ifnotexplode_set:
-1092returnself.pool(rolls)
-1093explode,not_explode=self.split(explode_set)
-1094
-1095single_data:'MutableMapping[icepool.Vector[int], int]'=defaultdict(
-1096int)
-1097foriinrange(depth+1):
-1098weight=explode.denominator()**i*self.denominator()**(
-1099depth-i)*not_explode.denominator()
-1100single_data[icepool.Vector((i,1))]+=weight
-1101single_data[icepool.Vector(
-1102(depth+1,0))]+=explode.denominator()**(depth+1)
-1103
-1104single_count_die:'Die[icepool.Vector[int]]'=Die(single_data)
-1105count_die=rolls@single_count_die
-1106
-1107returncount_die.map_to_pool(
-1108lambdax,nx:[explode]*x+[not_explode]*nx)
+
1060defexplode_to_pool(
+1061self,
+1062rolls:int,
+1063which:Collection[T_co]|Callable[...,bool]|None=None,
+1064/,
+1065*,
+1066star:bool|None=None,
+1067depth:int=9)->'icepool.MultisetGenerator[T_co, tuple[int]]':
+1068"""EXPERIMENTAL: Causes outcomes to be rolled again, keeping that outcome as an individual die in a pool.
+1069
+1070 Args:
+1071 rolls: The number of initial dice.
+1072 which: Which outcomes to explode. Options:
+1073 * A single outcome to explode.
+1074 * An collection of outcomes to explode.
+1075 * A callable that takes an outcome and returns `True` if it
+1076 should be exploded.
+1077 * If not supplied, the max outcome will explode.
+1078 star: Whether outcomes should be unpacked into separate arguments
+1079 before sending them to a callable `which`.
+1080 If not provided, this will be guessed based on the function
+1081 signature.
+1082 depth: The maximum depth of explosions for an individual dice.
+1083
+1084 Returns:
+1085 A `MultisetGenerator` representing the mixture of `Pool`s. Note
+1086 that this is not technically a `Pool`, though it supports most of
+1087 the same operations.
+1088 """
+1089ifdepth==0:
+1090returnself.pool(rolls)
+1091ifwhichisNone:
+1092explode_set={self.max_outcome()}
+1093else:
+1094explode_set=self._select_outcomes(which,star)
+1095ifnotexplode_set:
+1096returnself.pool(rolls)
+1097explode,not_explode=self.split(explode_set)
+1098
+1099single_data:'MutableMapping[icepool.Vector[int], int]'=defaultdict(
+1100int)
+1101foriinrange(depth+1):
+1102weight=explode.denominator()**i*self.denominator()**(
+1103depth-i)*not_explode.denominator()
+1104single_data[icepool.Vector((i,1))]+=weight
+1105single_data[icepool.Vector(
+1106(depth+1,0))]+=explode.denominator()**(depth+1)
+1107
+1108single_count_die:'Die[icepool.Vector[int]]'=Die(single_data)
+1109count_die=rolls@single_count_die
+1110
+1111returncount_die.map_to_pool(
+1112lambdax,nx:[explode]*x+[not_explode]*nx)
@@ -4759,118 +4763,118 @@
Returns:
-
1110defreroll_to_pool(
-1111self,
-1112rolls:int,
-1113which:Callable[...,bool]|Collection[T_co],
-1114/,
-1115max_rerolls:int,
-1116*,
-1117star:bool|None=None,
-1118mode:Literal['random','lowest','highest','drop']='random'
-1119)->'icepool.MultisetGenerator[T_co, tuple[int]]':
-1120"""EXPERIMENTAL: Applies a limited number of rerolls shared across a pool.
-1121
-1122 Each die can only be rerolled once (effectively `depth=1`), and no more
-1123 than `max_rerolls` dice may be rerolled.
-1124
-1125 Args:
-1126 rolls: How many dice in the pool.
-1127 which: Selects which outcomes are eligible to be rerolled. Options:
-1128 * A collection of outcomes to reroll.
-1129 * A callable that takes an outcome and returns `True` if it
-1130 could be rerolled.
-1131 max_rerolls: The maximum number of dice to reroll.
-1132 Note that each die can only be rerolled once, so if the number
-1133 of eligible dice is less than this, the excess rerolls have no
-1134 effect.
-1135 star: Whether outcomes should be unpacked into separate arguments
-1136 before sending them to a callable `which`.
-1137 If not provided, this will be guessed based on the function
-1138 signature.
-1139 mode: How dice are selected for rerolling if there are more eligible
-1140 dice than `max_rerolls`. Options:
-1141 * `'random'` (default): Eligible dice will be chosen uniformly
-1142 at random.
-1143 * `'lowest'`: The lowest eligible dice will be rerolled.
-1144 * `'highest'`: The highest eligible dice will be rerolled.
-1145 * `'drop'`: All dice that ended up on an outcome selected by
-1146 `which` will be dropped. This includes both dice that rolled
-1147 into `which` initially and were not rerolled, and dice that
-1148 were rerolled but rolled into `which` again. This can be
-1149 considerably more efficient than the other modes.
-1150
-1151 Returns:
-1152 A `MultisetGenerator` representing the mixture of `Pool`s. Note
-1153 that this is not technically a `Pool`, though it supports most of
-1154 the same operations.
-1155 """
-1156rerollable_set=self._select_outcomes(which,star)
-1157ifnotrerollable_set:
-1158returnself.pool(rolls)
-1159
-1160rerollable_die,not_rerollable_die=self.split(rerollable_set)
-1161single_is_rerollable=icepool.coin(rerollable_die.denominator(),
-1162self.denominator())
-1163rerollable=rolls@single_is_rerollable
-1164
-1165defsplit(initial_rerollable:int)->Die[tuple[int,int,int]]:
-1166"""Computes the composition of the pool.
-1167
-1168 Returns:
-1169 initial_rerollable: The number of dice that initially fell into
-1170 the rerollable set.
-1171 rerolled_to_rerollable: The number of dice that were rerolled,
-1172 but fell into the rerollable set again.
-1173 not_rerollable: The number of dice that ended up outside the
-1174 rerollable set, including both initial and rerolled dice.
-1175 not_rerolled: The number of dice that were eligible for
-1176 rerolling but were not rerolled.
-1177 """
-1178initial_not_rerollable=rolls-initial_rerollable
-1179rerolled=min(initial_rerollable,max_rerolls)
-1180not_rerolled=initial_rerollable-rerolled
-1181
-1182defsecond_split(rerolled_to_rerollable):
-1183"""Splits the rerolled dice into those that fell into the rerollable and not-rerollable sets."""
-1184rerolled_to_not_rerollable=rerolled-rerolled_to_rerollable
-1185returnicepool.tupleize(
-1186initial_rerollable,rerolled_to_rerollable,
-1187initial_not_rerollable+rerolled_to_not_rerollable,
-1188not_rerolled)
-1189
-1190returnicepool.map(second_split,
-1191rerolled@single_is_rerollable,
-1192star=False)
+
1114defreroll_to_pool(
+1115self,
+1116rolls:int,
+1117which:Callable[...,bool]|Collection[T_co],
+1118/,
+1119max_rerolls:int,
+1120*,
+1121star:bool|None=None,
+1122mode:Literal['random','lowest','highest','drop']='random'
+1123)->'icepool.MultisetGenerator[T_co, tuple[int]]':
+1124"""EXPERIMENTAL: Applies a limited number of rerolls shared across a pool.
+1125
+1126 Each die can only be rerolled once (effectively `depth=1`), and no more
+1127 than `max_rerolls` dice may be rerolled.
+1128
+1129 Args:
+1130 rolls: How many dice in the pool.
+1131 which: Selects which outcomes are eligible to be rerolled. Options:
+1132 * A collection of outcomes to reroll.
+1133 * A callable that takes an outcome and returns `True` if it
+1134 could be rerolled.
+1135 max_rerolls: The maximum number of dice to reroll.
+1136 Note that each die can only be rerolled once, so if the number
+1137 of eligible dice is less than this, the excess rerolls have no
+1138 effect.
+1139 star: Whether outcomes should be unpacked into separate arguments
+1140 before sending them to a callable `which`.
+1141 If not provided, this will be guessed based on the function
+1142 signature.
+1143 mode: How dice are selected for rerolling if there are more eligible
+1144 dice than `max_rerolls`. Options:
+1145 * `'random'` (default): Eligible dice will be chosen uniformly
+1146 at random.
+1147 * `'lowest'`: The lowest eligible dice will be rerolled.
+1148 * `'highest'`: The highest eligible dice will be rerolled.
+1149 * `'drop'`: All dice that ended up on an outcome selected by
+1150 `which` will be dropped. This includes both dice that rolled
+1151 into `which` initially and were not rerolled, and dice that
+1152 were rerolled but rolled into `which` again. This can be
+1153 considerably more efficient than the other modes.
+1154
+1155 Returns:
+1156 A `MultisetGenerator` representing the mixture of `Pool`s. Note
+1157 that this is not technically a `Pool`, though it supports most of
+1158 the same operations.
+1159 """
+1160rerollable_set=self._select_outcomes(which,star)
+1161ifnotrerollable_set:
+1162returnself.pool(rolls)
+1163
+1164rerollable_die,not_rerollable_die=self.split(rerollable_set)
+1165single_is_rerollable=icepool.coin(rerollable_die.denominator(),
+1166self.denominator())
+1167rerollable=rolls@single_is_rerollable
+1168
+1169defsplit(initial_rerollable:int)->Die[tuple[int,int,int]]:
+1170"""Computes the composition of the pool.
+1171
+1172 Returns:
+1173 initial_rerollable: The number of dice that initially fell into
+1174 the rerollable set.
+1175 rerolled_to_rerollable: The number of dice that were rerolled,
+1176 but fell into the rerollable set again.
+1177 not_rerollable: The number of dice that ended up outside the
+1178 rerollable set, including both initial and rerolled dice.
+1179 not_rerolled: The number of dice that were eligible for
+1180 rerolling but were not rerolled.
+1181 """
+1182initial_not_rerollable=rolls-initial_rerollable
+1183rerolled=min(initial_rerollable,max_rerolls)
+1184not_rerolled=initial_rerollable-rerolled
+1185
+1186defsecond_split(rerolled_to_rerollable):
+1187"""Splits the rerolled dice into those that fell into the rerollable and not-rerollable sets."""
+1188rerolled_to_not_rerollable=rerolled-rerolled_to_rerollable
+1189returnicepool.tupleize(
+1190initial_rerollable,rerolled_to_rerollable,
+1191initial_not_rerollable+rerolled_to_not_rerollable,
+1192not_rerolled)1193
-1194pool_composition=rerollable.map(split,star=False)
-1195
-1196defmake_pool(initial_rerollable,rerolled_to_rerollable,
-1197not_rerollable,not_rerolled):
-1198common=rerollable_die.pool(
-1199rerolled_to_rerollable)+not_rerollable_die.pool(
-1200not_rerollable)
-1201matchmode:
-1202case'random':
-1203returncommon+rerollable_die.pool(not_rerolled)
-1204case'lowest':
-1205returncommon+rerollable_die.pool(
-1206initial_rerollable).highest(not_rerolled)
-1207case'highest':
-1208returncommon+rerollable_die.pool(
-1209initial_rerollable).lowest(not_rerolled)
-1210case'drop':
-1211returnnot_rerollable_die.pool(not_rerollable)
-1212case_:
-1213raiseValueError(
-1214f"Invalid reroll_priority '{mode}'. Allowed values are 'random', 'lowest', 'highest', 'drop'."
-1215)
-1216
-1217denominator=self.denominator()**(rolls+min(rolls,max_rerolls))
-1218
-1219returnpool_composition.map_to_pool(make_pool,
-1220star=True,
-1221denominator=denominator)
+1194returnicepool.map(second_split,
+1195rerolled@single_is_rerollable,
+1196star=False)
+1197
+1198pool_composition=rerollable.map(split,star=False)
+1199
+1200defmake_pool(initial_rerollable,rerolled_to_rerollable,
+1201not_rerollable,not_rerolled):
+1202common=rerollable_die.pool(
+1203rerolled_to_rerollable)+not_rerollable_die.pool(
+1204not_rerollable)
+1205matchmode:
+1206case'random':
+1207returncommon+rerollable_die.pool(not_rerolled)
+1208case'lowest':
+1209returncommon+rerollable_die.pool(
+1210initial_rerollable).highest(not_rerolled)
+1211case'highest':
+1212returncommon+rerollable_die.pool(
+1213initial_rerollable).lowest(not_rerolled)
+1214case'drop':
+1215returnnot_rerollable_die.pool(not_rerollable)
+1216case_:
+1217raiseValueError(
+1218f"Invalid reroll_priority '{mode}'. Allowed values are 'random', 'lowest', 'highest', 'drop'."
+1219)
+1220
+1221denominator=self.denominator()**(rolls+min(rolls,max_rerolls))
+1222
+1223returnpool_composition.map_to_pool(make_pool,
+1224star=True,
+1225denominator=denominator)
1244defstochastic_round(self,
-1245*,
-1246max_denominator:int|None=None)->'Die[int]':
-1247"""Randomly rounds outcomes up or down to the nearest integer according to the two distances.
-1248
-1249 Specificially, rounds `x` up with probability `x - floor(x)` and down
-1250 otherwise.
-1251
-1252 Args:
-1253 max_denominator: If provided, each rounding will be performed
-1254 using `fractions.Fraction.limit_denominator(max_denominator)`.
-1255 Otherwise, the rounding will be performed without
-1256 `limit_denominator`.
-1257 """
-1258returnself.map(lambdax:icepool.stochastic_round(
-1259x,max_denominator=max_denominator))
+
1248defstochastic_round(self,
+1249*,
+1250max_denominator:int|None=None)->'Die[int]':
+1251"""Randomly rounds outcomes up or down to the nearest integer according to the two distances.
+1252
+1253 Specificially, rounds `x` up with probability `x - floor(x)` and down
+1254 otherwise.
+1255
+1256 Args:
+1257 max_denominator: If provided, each rounding will be performed
+1258 using `fractions.Fraction.limit_denominator(max_denominator)`.
+1259 Otherwise, the rounding will be performed without
+1260 `limit_denominator`.
+1261 """
+1262returnself.map(lambdax:icepool.stochastic_round(
+1263x,max_denominator=max_denominator))
1478defcmp(self,other)->'Die[int]':
-1479"""A `Die` with outcomes 1, -1, and 0.
-1480
-1481 The quantities are equal to the positive outcome of `self > other`,
-1482 `self < other`, and the remainder respectively.
-1483
-1484 This will include all three outcomes even if they have zero quantity.
-1485 """
-1486other=implicit_convert_to_die(other)
+
1482defcmp(self,other)->'Die[int]':
+1483"""A `Die` with outcomes 1, -1, and 0.
+1484
+1485 The quantities are equal to the positive outcome of `self > other`,
+1486 `self < other`, and the remainder respectively.1487
-1488data={}
-1489
-1490lt=self<other
-1491ifTrueinlt:
-1492data[-1]=lt[True]
-1493eq=self==other
-1494ifTrueineq:
-1495data[0]=eq[True]
-1496gt=self>other
-1497ifTrueingt:
-1498data[1]=gt[True]
-1499
-1500returnDie(data)
+1488 This will include all three outcomes even if they have zero quantity.
+1489 """
+1490other=implicit_convert_to_die(other)
+1491
+1492data={}
+1493
+1494lt=self<other
+1495ifTrueinlt:
+1496data[-1]=lt[True]
+1497eq=self==other
+1498ifTrueineq:
+1499data[0]=eq[True]
+1500gt=self>other
+1501ifTrueingt:
+1502data[1]=gt[True]
+1503
+1504returnDie(data)
@@ -5123,12 +5127,12 @@
Arguments:
-
1512defsign(self)->'Die[int]':
-1513"""Outcomes become 1 if greater than `zero()`, -1 if less than `zero()`, and 0 otherwise.
-1514
-1515 Note that for `float`s, +0.0, -0.0, and nan all become 0.
-1516 """
-1517returnself.unary_operator(Die._sign)
+
1516defsign(self)->'Die[int]':
+1517"""Outcomes become 1 if greater than `zero()`, -1 if less than `zero()`, and 0 otherwise.
+1518
+1519 Note that for `float`s, +0.0, -0.0, and nan all become 0.
+1520 """
+1521returnself.unary_operator(Die._sign)
@@ -5150,34 +5154,34 @@
Arguments:
-
1545defequals(self,other,*,simplify:bool=False)->bool:
-1546"""`True` iff both dice have the same outcomes and quantities.
-1547
-1548 This is `False` if `other` is not a `Die`, even if it would convert
-1549 to an equal `Die`.
-1550
-1551 Truth value does NOT matter.
-1552
-1553 If one `Die` has a zero-quantity outcome and the other `Die` does not
-1554 contain that outcome, they are treated as unequal by this function.
-1555
-1556 The `==` and `!=` operators have a dual purpose; they return a `Die`
-1557 with a truth value determined by this method.
-1558 Only dice returned by these methods have a truth value. The data of
-1559 these dice is lazily evaluated since the caller may only be interested
-1560 in the `Die` value or the truth value.
-1561
-1562 Args:
-1563 simplify: If `True`, the dice will be simplified before comparing.
-1564 Otherwise, e.g. a 2:2 coin is not `equals()` to a 1:1 coin.
-1565 """
-1566ifnotisinstance(other,Die):
-1567returnFalse
-1568
-1569ifsimplify:
-1570returnself.simplify()._hash_key==other.simplify()._hash_key
-1571else:
-1572returnself._hash_key==other._hash_key
+
1549defequals(self,other,*,simplify:bool=False)->bool:
+1550"""`True` iff both dice have the same outcomes and quantities.
+1551
+1552 This is `False` if `other` is not a `Die`, even if it would convert
+1553 to an equal `Die`.
+1554
+1555 Truth value does NOT matter.
+1556
+1557 If one `Die` has a zero-quantity outcome and the other `Die` does not
+1558 contain that outcome, they are treated as unequal by this function.
+1559
+1560 The `==` and `!=` operators have a dual purpose; they return a `Die`
+1561 with a truth value determined by this method.
+1562 Only dice returned by these methods have a truth value. The data of
+1563 these dice is lazily evaluated since the caller may only be interested
+1564 in the `Die` value or the truth value.
+1565
+1566 Args:
+1567 simplify: If `True`, the dice will be simplified before comparing.
+1568 Otherwise, e.g. a 2:2 coin is not `equals()` to a 1:1 coin.
+1569 """
+1570ifnotisinstance(other,Die):
+1571returnFalse
+1572
+1573ifsimplify:
+1574returnself.simplify()._hash_key==other.simplify()._hash_key
+1575else:
+1576returnself._hash_key==other._hash_key
diff --git a/apidoc/latest/icepool/evaluator.html b/apidoc/latest/icepool/evaluator.html
index dfbd8dfd..677c0a18 100644
--- a/apidoc/latest/icepool/evaluator.html
+++ b/apidoc/latest/icepool/evaluator.html
@@ -3,7 +3,7 @@
-
+
icepool.evaluator API documentation
diff --git a/apidoc/latest/icepool/function.html b/apidoc/latest/icepool/function.html
index 81f634a0..397e0cf6 100644
--- a/apidoc/latest/icepool/function.html
+++ b/apidoc/latest/icepool/function.html
@@ -3,7 +3,7 @@
-
+
icepool.function API documentation
diff --git a/apidoc/latest/icepool/typing.html b/apidoc/latest/icepool/typing.html
index 4431bfaa..8290be09 100644
--- a/apidoc/latest/icepool/typing.html
+++ b/apidoc/latest/icepool/typing.html
@@ -3,7 +3,7 @@
-
+
icepool.typing API documentation