Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
HighDiceRoller committed Nov 8, 2024
2 parents fd0e792 + 85a85d1 commit aa5b8d0
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 8 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## v1.6.1

* Add `pointwise_max`, `pointwise_min` arguments to take pointwise maximum or minimum of CDFs.

## v1.6.0 - 20 September 2024

* Breaking change: outcomes with zero quantities are removed when constructing `Die` and `Deck`.
Expand Down
12 changes: 6 additions & 6 deletions src/icepool/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

__docformat__ = 'google'

__version__ = '1.6.0'
__version__ = '1.6.1a1'

from typing import Final

Expand Down Expand Up @@ -48,10 +48,10 @@

from icepool.function import (d, z, __getattr__, coin, stochastic_round,
one_hot, iter_cartesian_product, from_cumulative,
from_rv, min_outcome, max_outcome, consecutive,
sorted_union, commonize_denominator, reduce,
accumulate, map, map_function, map_and_time,
map_to_pool)
from_rv, pointwise_max, pointwise_min, min_outcome,
max_outcome, consecutive, sorted_union,
commonize_denominator, reduce, accumulate, map,
map_function, map_and_time, map_to_pool)

from icepool.population.base import Population
from icepool.population.die import implicit_convert_to_die, Die
Expand Down Expand Up @@ -147,7 +147,7 @@
'd', 'z', 'coin', 'stochastic_round', 'one_hot', 'Outcome', 'Die',
'Population', 'tupleize', 'vectorize', 'Vector', 'Symbols', 'Again',
'CountsKeysView', 'CountsValuesView', 'CountsItemsView', 'from_cumulative',
'from_rv', 'lowest', 'highest', 'middle', 'min_outcome', 'max_outcome',
'from_rv', 'pointwise_max', 'pointwise_min', 'lowest', 'highest', 'middle', 'min_outcome', 'max_outcome',
'consecutive', 'sorted_union', 'commonize_denominator', 'reduce',
'accumulate', 'map', 'map_function', 'map_and_time', 'map_to_pool',
'Reroll', 'RerollType', 'Pool', 'standard_pool', 'MultisetGenerator',
Expand Down
77 changes: 76 additions & 1 deletion src/icepool/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,79 @@ def _iter_outcomes(
else:
yield arg

@overload
def pointwise_max(arg0: 'Iterable[icepool.Die[T]]', /,) -> 'icepool.Die[T]':
...

@overload
def pointwise_max(arg0: 'icepool.Die[T]', arg1: 'icepool.Die[T]', /, *args: 'icepool.Die[T]') -> 'icepool.Die[T]':
...

def pointwise_max(arg0, /, *more_args: 'icepool.Die[T]') -> 'icepool.Die[T]':
"""Selects the highest chance of rolling >= each outcome among the arguments.
Naming not finalized.
Specifically, for each outcome, the chance of the result rolling >= to that
outcome is the same as the highest chance of rolling >= that outcome among
the arguments.
Equivalently, any quantile in the result is the highest of that quantile
among the arguments.
This is useful for selecting from several possible moves where you are
trying to get >= a threshold that is known but could change depending on the
situation.
Args:
dice: Either an iterable of dice, or two or more dice as separate
arguments.
"""
if len(more_args) == 0:
args = arg0
else:
args = (arg0, ) + more_args
args = commonize_denominator(*args)
outcomes = sorted_union(*args)
cumulative = [min(die.quantity('<=', outcome) for die in args) for outcome in outcomes]
return from_cumulative(outcomes, cumulative)

@overload
def pointwise_min(arg0: 'Iterable[icepool.Die[T]]', /,) -> 'icepool.Die[T]':
...

@overload
def pointwise_min(arg0: 'icepool.Die[T]', arg1: 'icepool.Die[T]', /, *args: 'icepool.Die[T]') -> 'icepool.Die[T]':
...

def pointwise_min(arg0, /, *more_args: 'icepool.Die[T]') -> 'icepool.Die[T]':
"""Selects the highest chance of rolling <= each outcome among the arguments.
Naming not finalized.
Specifically, for each outcome, the chance of the result rolling <= to that
outcome is the same as the highest chance of rolling <= that outcome among
the arguments.
Equivalently, any quantile in the result is the lowest of that quantile
among the arguments.
This is useful for selecting from several possible moves where you are
trying to get <= a threshold that is known but could change depending on the
situation.
Args:
dice: Either an iterable of dice, or two or more dice as separate
arguments.
"""
if len(more_args) == 0:
args = arg0
else:
args = (arg0, ) + more_args
args = commonize_denominator(*args)
outcomes = sorted_union(*args)
cumulative = [max(die.quantity('<=', outcome) for die in args) for outcome in outcomes]
return from_cumulative(outcomes, cumulative)

@overload
def min_outcome(arg: 'Iterable[T | icepool.Population[T]]', /) -> T:
Expand Down Expand Up @@ -294,14 +367,16 @@ def commonize_denominator(
*dice: 'T | icepool.Die[T]') -> tuple['icepool.Die[T]', ...]:
"""Scale the quantities of the dice so that all of them have the same denominator.
The denominator is the LCM of the denominators of the arguments.
Args:
*dice: Any number of dice or single outcomes convertible to dice.
Returns:
A tuple of dice with the same denominator.
"""
converted_dice = [
icepool.implicit_convert_to_die(die).simplify() for die in dice
icepool.implicit_convert_to_die(die) for die in dice
]
denominator_lcm = math.lcm(*(die.denominator() for die in converted_dice
if die.denominator() > 0))
Expand Down
30 changes: 29 additions & 1 deletion tests/function_test.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import icepool
import pytest

from icepool import d4, d6, d8, d10, d12, min_outcome, max_outcome
from icepool import d4, d6, d8, d10, d12, d20, min_outcome, max_outcome, pointwise_max, pointwise_min


def test_min_outcome_single_arg():
Expand All @@ -26,3 +26,31 @@ def test_max_outcome_multiple_arg():

def test_max_outcome_bare_outcome():
assert max_outcome(d6, d8, 10, 2) == 10

def test_pointwise_max():
result = pointwise_max(3 @ d6, d20)
for outcome in range(1, 21):
assert result.probability('>=', outcome) == max(
(3 @ d6).probability('>=', outcome),
d20.probability('>=', outcome))

def test_pointwise_max_single_argument():
result = pointwise_max([3 @ d6, d20])
for outcome in range(1, 21):
assert result.probability('>=', outcome) == max(
(3 @ d6).probability('>=', outcome),
d20.probability('>=', outcome))

def test_pointwise_min():
result = pointwise_min(3 @ d6, d20)
for outcome in range(1, 21):
assert result.probability('<=', outcome) == max(
(3 @ d6).probability('<=', outcome),
d20.probability('<=', outcome))

def test_pointwise_min_single_argument():
result = pointwise_min([3 @ d6, d20])
for outcome in range(1, 21):
assert result.probability('<=', outcome) == max(
(3 @ d6).probability('<=', outcome),
d20.probability('<=', outcome))

0 comments on commit aa5b8d0

Please sign in to comment.