Skip to content

Commit

Permalink
replaced _estimate_order_costs with a simpler _preferred_pop_order
Browse files Browse the repository at this point in the history
  • Loading branch information
HighDiceRoller committed Aug 25, 2024
1 parent 0a4fbe0 commit db8eb5a
Show file tree
Hide file tree
Showing 11 changed files with 189 additions and 130 deletions.
41 changes: 16 additions & 25 deletions src/icepool/evaluator/multiset_evaluator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

import icepool
from icepool.collection.counts import sorted_union

from icepool.typing import Order, T_contra, U_co
from icepool.generator.pop_order import PopOrderReason, merge_pop_orders

from abc import ABC, abstractmethod
from collections import defaultdict
Expand All @@ -12,6 +11,8 @@
import itertools
import math

from icepool.typing import Order, T_contra, U_co

from typing import Any, Callable, Collection, Generic, Hashable, Mapping, MutableMapping, Sequence, cast, TYPE_CHECKING, overload

if TYPE_CHECKING:
Expand Down Expand Up @@ -338,35 +339,25 @@ def _select_algorithm(
# No generators.
return self._eval_internal, eval_order

pop_min_costs, pop_max_costs = zip(*(generator._estimate_order_costs()
for generator in generators))
preferred_pop_order, pop_order_reason = merge_pop_orders(
*(generator._preferred_pop_order() for generator in generators))

pop_min_cost = math.prod(pop_min_costs)
pop_max_cost = math.prod(pop_max_costs)
if preferred_pop_order is None:
preferred_pop_order = Order.Any
pop_order_reason = PopOrderReason.NoPreference

# No preferred order case: go directly with cost.
# No mandatory evaluation order, go with preferred algorithm.
# Note that this has order *opposite* the pop order.
if eval_order == Order.Any:
if pop_max_cost <= pop_min_cost:
return self._eval_internal, Order.Ascending
else:
return self._eval_internal, Order.Descending

# Preferred order case.
# Go with the preferred order unless there is a "significant"
# cost factor.

if PREFERRED_ORDER_COST_FACTOR * pop_max_cost < pop_min_cost:
cost_order = Order.Ascending
elif PREFERRED_ORDER_COST_FACTOR * pop_min_cost < pop_max_cost:
cost_order = Order.Descending
else:
cost_order = Order.Any
return self._eval_internal, Order(-preferred_pop_order
or Order.Ascending)

if cost_order == Order.Any or eval_order == cost_order:
# Use the preferred algorithm.
# Mandatory evaluation order.
if preferred_pop_order == Order.Any:
return self._eval_internal, eval_order
elif eval_order != preferred_pop_order:
return self._eval_internal, eval_order
else:
# Use the less-preferred algorithm.
return self._eval_internal_forward, eval_order

def _eval_internal(
Expand Down
8 changes: 4 additions & 4 deletions src/icepool/generator/alignment.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
__docformat__ = 'google'

from icepool.generator.multiset_generator import MultisetGenerator
from icepool.typing import Outcome, T
from icepool.generator.pop_order import PopOrderReason

from functools import cached_property

from icepool.typing import Order, T
from typing import Collection, Hashable, Iterator, Sequence, TypeAlias

InitialAlignmentGenerator: TypeAlias = Iterator[tuple['Alignment', int]]
Expand Down Expand Up @@ -49,9 +50,8 @@ def _generate_max(self, max_outcome) -> AlignmentGenerator:
else:
yield Alignment(self.outcomes()[:-1]), (), 1

def _estimate_order_costs(self) -> tuple[int, int]:
result = len(self.outcomes())
return result, result
def _preferred_pop_order(self) -> tuple[Order | None, PopOrderReason]:
return Order.Any, PopOrderReason.NoPreference

def denominator(self) -> int:
return 0
Expand Down
14 changes: 5 additions & 9 deletions src/icepool/generator/compound_keep.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
from icepool.collection.counts import sorted_union
from icepool.generator.keep import KeepGenerator, pop_max_from_keep_tuple, pop_min_from_keep_tuple
from icepool.generator.multiset_generator import InitialMultisetGenerator, NextMultisetGenerator, MultisetGenerator
from icepool.generator.pop_order import PopOrderReason, merge_pop_orders

import itertools
import math

from typing import Hashable, Sequence
from icepool.typing import T
from icepool.typing import Order, T


class CompoundKeepGenerator(KeepGenerator[T]):
Expand Down Expand Up @@ -54,14 +55,9 @@ def _generate_max(self, max_outcome) -> NextMultisetGenerator:
yield CompoundKeepGenerator(
generators, popped_keep_tuple), (result_count, ), total_weight

def _estimate_order_costs(self) -> tuple[int, int]:
total_pop_min_cost = 1
total_pop_max_cost = 1
for inner in self._inners:
pop_min_cost, pop_max_cost = inner._estimate_order_costs()
total_pop_min_cost *= pop_min_cost
total_pop_max_cost *= pop_max_cost
return total_pop_min_cost, total_pop_max_cost
def _preferred_pop_order(self) -> tuple[Order | None, PopOrderReason]:
return merge_pop_orders(*(inner._preferred_pop_order()
for inner in self._inners))

def denominator(self) -> int:
return math.prod(inner.denominator() for inner in self._inners)
Expand Down
11 changes: 6 additions & 5 deletions src/icepool/generator/deal.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
__docformat__ = 'google'

from icepool.generator.keep import KeepGenerator, pop_max_from_keep_tuple, pop_min_from_keep_tuple
import icepool
from icepool.generator.keep import KeepGenerator, pop_max_from_keep_tuple, pop_min_from_keep_tuple
from icepool.collection.counts import CountsKeysView
from icepool.generator.multiset_generator import InitialMultisetGenerator, NextMultisetGenerator
from icepool.generator.pop_order import PopOrderReason

from functools import cached_property

from icepool.typing import T
from icepool.typing import Order, T
from typing import Hashable


Expand Down Expand Up @@ -120,9 +121,9 @@ def _generate_max(self, max_outcome) -> NextMultisetGenerator:
weight = icepool.math.comb(deck_count, count)
yield popped_deal, (result_count, ), weight

def _estimate_order_costs(self) -> tuple[int, int]:
result = len(self.outcomes()) * self._hand_size
return result, result
def _preferred_pop_order(self) -> tuple[Order | None, PopOrderReason]:
# TODO: implement skips
return Order.Any, PopOrderReason.NoPreference

@cached_property
def _hash_key(self) -> Hashable:
Expand Down
15 changes: 6 additions & 9 deletions src/icepool/generator/mixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@

from icepool.collection.counts import sorted_union
from icepool.generator.multiset_generator import InitialMultisetGenerator, NextMultisetGenerator, MultisetGenerator
from icepool.typing import Outcome, Qs, T, U
from icepool.generator.pop_order import PopOrderReason, merge_pop_orders

import math

from collections import defaultdict
from functools import cached_property

from icepool.typing import Order, Qs, T, U
from types import EllipsisType
from typing import TYPE_CHECKING, Callable, Hashable, Literal, Mapping, MutableMapping, Sequence, overload

Expand Down Expand Up @@ -90,14 +92,9 @@ def _generate_max(self, max_outcome) -> NextMultisetGenerator:
'MixtureMultisetGenerator should have decayed to another generator type by this point.'
)

def _estimate_order_costs(self) -> tuple[int, int]:
total_pop_min_cost = 0
total_pop_max_cost = 0
for inner in self._inners:
pop_min_cost, pop_max_cost = inner._estimate_order_costs()
total_pop_min_cost += pop_min_cost
total_pop_max_cost += pop_max_cost
return total_pop_min_cost, total_pop_max_cost
def _preferred_pop_order(self) -> tuple[Order | None, PopOrderReason]:
return merge_pop_orders(*(inner._preferred_pop_order()
for inner in self._inners))

@cached_property
def _denominator(self) -> int:
Expand Down
8 changes: 4 additions & 4 deletions src/icepool/generator/multi_deal.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
__docformat__ = 'google'

from icepool.typing import Outcome, Qs, T
from icepool.typing import Order, Qs, T

from typing import Any, Hashable, cast
import icepool
from icepool.collection.counts import CountsKeysView
from icepool.generator.multiset_generator import InitialMultisetGenerator, NextMultisetGenerator, MultisetGenerator
from icepool.math import iter_hypergeom
from icepool.generator.pop_order import PopOrderReason

from functools import cached_property
import math
Expand Down Expand Up @@ -129,9 +130,8 @@ def _generate_max(self, max_outcome) -> NextMultisetGenerator:

yield from self._generate_common(popped_deck, deck_count)

def _estimate_order_costs(self) -> tuple[int, int]:
result = len(self.outcomes()) * math.prod(self.hand_sizes())
return result, result
def _preferred_pop_order(self) -> tuple[Order | None, PopOrderReason]:
return Order.Any, PopOrderReason.NoPreference

@cached_property
def _hash_key(self) -> Hashable:
Expand Down
15 changes: 8 additions & 7 deletions src/icepool/generator/multiset_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import icepool.generator
from icepool.collection.counts import Counts
from icepool.expression.multiset_expression import MultisetExpression
from icepool.generator.pop_order import PopOrderReason
from icepool.typing import Order, Outcome, Qs, T

import bisect
Expand Down Expand Up @@ -108,12 +109,12 @@ def _generate_max(self, max_outcome) -> NextMultisetGenerator:
"""

@abstractmethod
def _estimate_order_costs(self) -> tuple[int, int]:
"""Estimates the cost of popping from the min and max sides during an evaluation.
def _preferred_pop_order(self) -> tuple[Order | None, PopOrderReason]:
"""Returns the preferred pop order of the generator, along with the priority of that pop order.
Returns:
pop_min_cost: A positive `int`.
pop_max_cost: A positive `int`.
Greater priorities strictly outrank lower priorities.
An order of `None` represents conflicting orders and can occur in the
argument and/or return value.
"""

@abstractmethod
Expand Down Expand Up @@ -236,9 +237,9 @@ def sample(self) -> tuple[tuple, ...]:
if not self.outcomes():
raise ValueError('Cannot sample from an empty set of outcomes.')

min_cost, max_cost = self._estimate_order_costs()
preferred_pop_order, pop_order_reason = self._preferred_pop_order()

if min_cost < max_cost:
if preferred_pop_order is not None and preferred_pop_order > 0:
outcome = self.min_outcome()
generated = tuple(self._generate_min(outcome))
else:
Expand Down
29 changes: 19 additions & 10 deletions src/icepool/generator/pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@
import icepool
import icepool.expression
import icepool.math
import icepool.generator.pop_order
import icepool.creation_args
from icepool.generator.keep import KeepGenerator, pop_max_from_keep_tuple, pop_min_from_keep_tuple
from icepool.generator.multiset_generator import InitialMultisetGenerator, NextMultisetGenerator
import icepool.generator.pop_order
from icepool.generator.pop_order import PopOrderReason

import itertools
import math
import operator
from collections import defaultdict
from functools import cache, cached_property, reduce

from icepool.typing import T
from icepool.typing import T, Order
from typing import TYPE_CHECKING, Any, Collection, Iterator, Mapping, MutableMapping, Sequence, cast

if TYPE_CHECKING:
Expand Down Expand Up @@ -174,14 +175,22 @@ def outcomes(self) -> Sequence[T]:
def output_arity(self) -> int:
return 1

def _estimate_order_costs(self) -> tuple[int, int]:
"""Estimates the cost of popping from the min and max sides.
Returns:
pop_min_cost
pop_max_cost
"""
return icepool.generator.pop_order.estimate_costs(self)
def _preferred_pop_order(self) -> tuple[Order | None, PopOrderReason]:
can_truncate_min, can_truncate_max = icepool.generator.pop_order.can_truncate(
self.unique_dice())
if can_truncate_min and not can_truncate_max:
return Order.Ascending, PopOrderReason.PoolComposition
if can_truncate_max and not can_truncate_min:
return Order.Descending, PopOrderReason.PoolComposition

lo_skip, hi_skip = icepool.generator.pop_order.lo_hi_skip(
self.keep_tuple())
if lo_skip > hi_skip:
return Order.Descending, PopOrderReason.KeepSkip
if hi_skip > lo_skip:
return Order.Ascending, PopOrderReason.KeepSkip

return Order.Any, PopOrderReason.NoPreference

@cached_property
def _min_outcome(self) -> T:
Expand Down
Loading

0 comments on commit db8eb5a

Please sign in to comment.