Skip to content

Commit

Permalink
expression orders now include reason, allow mandatory order rather th…
Browse files Browse the repository at this point in the history
…an having a separate `order` method
  • Loading branch information
HighDiceRoller committed Dec 26, 2024
1 parent 13cc33e commit 94a4043
Show file tree
Hide file tree
Showing 23 changed files with 104 additions and 157 deletions.
2 changes: 1 addition & 1 deletion src/icepool/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@
from icepool.generator.compound_keep import CompoundKeepGenerator
from icepool.generator.mixture import MixtureGenerator

from icepool.multiset_expression import MultisetExpression, implicit_convert_to_expression
from icepool.multiset_expression import MultisetExpression, implicit_convert_to_expression, InitialMultisetGeneration, PopMultisetGeneration

from icepool.generator.multiset_generator import MultisetGenerator
from icepool.generator.alignment import Alignment
Expand Down
4 changes: 2 additions & 2 deletions src/icepool/evaluator/argsort.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from icepool.evaluator.multiset_evaluator import MultisetEvaluator

from icepool.order import Order
from icepool.order import Order, OrderReason
from typing import Any


Expand Down Expand Up @@ -30,5 +30,5 @@ def next_state(self, state, _, *counts):
def final_outcome(self, final_state):
return final_state or ()

def order(self) -> Order:
def order(self):
return self._order
14 changes: 5 additions & 9 deletions src/icepool/evaluator/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import icepool
from icepool.evaluator.multiset_evaluator import MultisetEvaluator
from icepool.order import Order
from icepool.order import Order, OrderReason

import operator

Expand All @@ -26,8 +26,7 @@ def next_state(self, state, outcome, count):
"""Implementation."""
return (state or ()) + (outcome, ) * count

def order(self) -> Literal[Order.Any]:
"""Allows any order."""
def order(self):
return Order.Any

def final_outcome(self, final_state) -> tuple:
Expand Down Expand Up @@ -75,8 +74,7 @@ def next_state(self, state, outcome, count):
else:
return state + outcome * count

def order(self) -> Literal[Order.Any]:
"""Allows any order."""
def order(self):
return Order.Any


Expand All @@ -99,8 +97,7 @@ def final_outcome(self, final_state) -> int:
"""Implementation."""
return final_state or 0

def order(self) -> Literal[Order.Any]:
"""Allows any order."""
def order(self):
return Order.Any


Expand All @@ -119,8 +116,7 @@ def final_outcome(self, final_state) -> bool:
"""Implementation."""
return final_state or False

def order(self) -> Literal[Order.Any]:
"""Allows any order."""
def order(self):
return Order.Any


Expand Down
5 changes: 2 additions & 3 deletions src/icepool/evaluator/comparison.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
__docformat__ = 'google'

import icepool
from icepool.order import Order
from icepool.order import Order, OrderReason
from icepool.evaluator.multiset_evaluator import MultisetEvaluator

from abc import abstractmethod
Expand Down Expand Up @@ -42,8 +42,7 @@ def final_outcome(self, final_state) -> bool:
has_any, has_all = final_state
return has_any and has_all

def order(self) -> Literal[Order.Any]:
"""Allows any order."""
def order(self):
return Order.Any


Expand Down
22 changes: 11 additions & 11 deletions src/icepool/evaluator/multiset_evaluator.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
__docformat__ = 'google'

import icepool
from icepool.order import Order, OrderReason, merge_pop_orders
from icepool.order import Order, OrderReason, merge_order_preferences

from abc import ABC, abstractmethod
from collections import defaultdict
Expand Down Expand Up @@ -315,23 +315,23 @@ def _select_algorithm(
# No generators.
return self._eval_internal, eval_order

preferred_pop_order, pop_order_reason = merge_pop_orders(
*(expression._preferred_pop_order() for expression in expressions))
expression_order, expression_order_reason = merge_order_preferences(
*(expression.order_preference() for expression in expressions))

if preferred_pop_order is None:
preferred_pop_order = Order.Any
pop_order_reason = OrderReason.NoPreference
if expression_order is None:
expression_order = Order.Any
expression_order_reason = OrderReason.NoPreference

# No mandatory evaluation order, go with preferred algorithm.
# Note that this has order *opposite* the pop order.
if eval_order == Order.Any:
return self._eval_internal, Order(-preferred_pop_order
return self._eval_internal, Order(-expression_order
or Order.Ascending)

# Mandatory evaluation order.
if preferred_pop_order == Order.Any:
if expression_order == Order.Any:
return self._eval_internal, eval_order
elif eval_order != preferred_pop_order:
elif eval_order != expression_order:
return self._eval_internal, eval_order
else:
return self._eval_internal_forward, eval_order
Expand Down Expand Up @@ -385,7 +385,7 @@ def _eval_internal_forward(
self,
order: Order,
extra_outcomes: 'Alignment[T]',
generators: 'tuple[icepool.MultisetGenerator[T, Any], ...]',
generators: 'tuple[icepool.MultisetExpression[T], ...]',
state: Hashable = None) -> Mapping[Any, int]:
"""Internal algorithm for iterating in the less-preferred order.
Expand All @@ -395,7 +395,7 @@ def _eval_internal_forward(
order: The order in which to send outcomes to `next_state()`.
extra_outcomes: As `extra_outcomes()`. Elements will be popped off this
during recursion.
generators: One or more `MultisetGenerators`s to evaluate. Elements
generators: One or more `MultisetExpression`s to evaluate. Elements
will be popped off this during recursion.
Returns:
Expand Down
21 changes: 7 additions & 14 deletions src/icepool/evaluator/poker.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ def next_state(self, state, outcome, count):

return state

def order(self) -> Literal[Order.Any]:
"""Allows any order."""
def order(self):
return Order.Any

def extra_outcomes(self, outcomes: Sequence) -> Collection:
Expand Down Expand Up @@ -76,8 +75,7 @@ def final_outcome(self, final_state) -> tuple:
else:
return final_state

def order(self) -> Literal[Order.Any]:
"""Allows any order."""
def order(self):
return Order.Any

def extra_outcomes(self, outcomes: Sequence) -> Collection:
Expand All @@ -92,8 +90,7 @@ def next_state(self, state, _, count):
"""Implementation."""
return max(state or count, count)

def order(self) -> Literal[Order.Any]:
"""Allows any order."""
def order(self):
return Order.Any


Expand All @@ -108,8 +105,7 @@ def next_state(self, state, outcome, count):
"""Implementation."""
return max(state or (count, outcome), (count, outcome))

def order(self) -> Literal[Order.Any]:
"""Allows any order."""
def order(self):
return Order.Any


Expand Down Expand Up @@ -138,8 +134,7 @@ def next_state(self, state, _, left, right):
else:
return min(state, current)

def order(self) -> Literal[Order.Any]:
"""Allows any order."""
def order(self):
return Order.Any

def final_outcome(self, final_state):
Expand Down Expand Up @@ -195,8 +190,7 @@ def final_outcome(self, final_state) -> tuple[int, int]:
"""Implementation."""
return final_state[0]

def order(self) -> Literal[Order.Ascending]:
"""Ascending order."""
def order(self):
return Order.Ascending

extra_outcomes = MultisetEvaluator.consecutive
Expand Down Expand Up @@ -234,8 +228,7 @@ def final_outcome(self, final_state) -> tuple[int, ...]:
current_runs, ended_runs = final_state or ((), ())
return tuple(sorted(current_runs + ended_runs, reverse=True))

def order(self) -> Literal[Order.Ascending]:
"""Ascending order."""
def order(self):
return Order.Ascending

extra_outcomes = MultisetEvaluator.consecutive
Expand Down
2 changes: 1 addition & 1 deletion src/icepool/generator/alignment.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def _generate_max(self, max_outcome) -> AlignmentGenerator:
else:
yield Alignment(self.outcomes()[:-1]), (), 1

def _local_preferred_pop_order(self) -> tuple[Order | None, OrderReason]:
def local_order_preference(self) -> tuple[Order | None, OrderReason]:
return Order.Any, OrderReason.NoPreference

def denominator(self) -> int:
Expand Down
8 changes: 4 additions & 4 deletions src/icepool/generator/compound_keep.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from icepool.multiset_expression import InitialMultisetGeneration, PopMultisetGeneration
from icepool.generator.keep import KeepGenerator, pop_max_from_keep_tuple, pop_min_from_keep_tuple
from icepool.generator.multiset_generator import MultisetGenerator
from icepool.order import Order, OrderReason, merge_pop_orders
from icepool.order import Order, OrderReason, merge_order_preferences

import itertools
import math
Expand Down Expand Up @@ -56,9 +56,9 @@ def _generate_max(self, max_outcome) -> PopMultisetGeneration:
yield CompoundKeepGenerator(
generators, popped_keep_tuple), (result_count, ), total_weight

def _local_preferred_pop_order(self) -> tuple[Order | None, OrderReason]:
return merge_pop_orders(*(inner._local_preferred_pop_order()
for inner in self._inner_generators))
def local_order_preference(self) -> tuple[Order | None, OrderReason]:
return merge_order_preferences(*(inner.local_order_preference()
for inner in self._inner_generators))

def denominator(self) -> int:
return math.prod(inner.denominator()
Expand Down
3 changes: 2 additions & 1 deletion src/icepool/generator/deal.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
__docformat__ = 'google'

import icepool
import icepool.order
from icepool.generator.keep import KeepGenerator, pop_max_from_keep_tuple, pop_min_from_keep_tuple
from icepool.collection.counts import CountsKeysView
from icepool.multiset_expression import InitialMultisetGeneration, PopMultisetGeneration
Expand Down Expand Up @@ -141,7 +142,7 @@ def _generate_max(self, max_outcome) -> PopMultisetGeneration:
popped_deal = Deal._new_raw(popped_deck, 0, ())
yield popped_deal, (sum(self.keep_tuple()), ), skip_weight

def _local_preferred_pop_order(self) -> tuple[Order | None, OrderReason]:
def local_order_preference(self) -> tuple[Order | None, OrderReason]:
lo_skip, hi_skip = icepool.order.lo_hi_skip(self.keep_tuple())
if lo_skip > hi_skip:
return Order.Descending, OrderReason.KeepSkip
Expand Down
8 changes: 4 additions & 4 deletions src/icepool/generator/mixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from icepool.multiset_expression import InitialMultisetGeneration, PopMultisetGeneration
from icepool.generator.multiset_generator import MultisetGenerator
from icepool.order import Order, OrderReason, merge_pop_orders
from icepool.order import Order, OrderReason, merge_order_preferences

import math

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

def _local_preferred_pop_order(self) -> tuple[Order | None, OrderReason]:
return merge_pop_orders(*(inner._local_preferred_pop_order()
for inner in self._inner_generators))
def local_order_preference(self) -> tuple[Order | None, OrderReason]:
return merge_order_preferences(*(inner.local_order_preference()
for inner in self._inner_generators))

@cached_property
def _denominator(self) -> int:
Expand Down
2 changes: 1 addition & 1 deletion src/icepool/generator/multi_deal.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ def _generate_max(self, max_outcome) -> PopMultisetGeneration:

yield from self._generate_common(popped_deck, deck_count)

def _local_preferred_pop_order(self) -> tuple[Order | None, OrderReason]:
def local_order_preference(self) -> tuple[Order | None, OrderReason]:
return Order.Any, OrderReason.NoPreference

@cached_property
Expand Down
3 changes: 0 additions & 3 deletions src/icepool/generator/multiset_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,6 @@ def _can_keep(self) -> bool:
def _free_arity(self) -> int:
return 0

def local_order(self) -> Order:
return Order.Any

# Overridden to switch bound generators with variables.

@property
Expand Down
3 changes: 2 additions & 1 deletion src/icepool/generator/pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import icepool.multiset_expression
import icepool.math
import icepool.creation_args
import icepool.order
from icepool.multiset_expression import InitialMultisetGeneration, PopMultisetGeneration
from icepool.generator.keep import KeepGenerator, pop_max_from_keep_tuple, pop_min_from_keep_tuple
from icepool.order import Order, OrderReason
Expand Down Expand Up @@ -169,7 +170,7 @@ def outcomes(self) -> Sequence[T]:
def output_arity(self) -> int:
return 1

def _local_preferred_pop_order(self) -> tuple[Order | None, OrderReason]:
def local_order_preference(self) -> tuple[Order | None, OrderReason]:
can_truncate_min, can_truncate_max = icepool.order.can_truncate(
self.unique_dice())
if can_truncate_min and not can_truncate_max:
Expand Down
26 changes: 8 additions & 18 deletions src/icepool/multiset_expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import icepool
from icepool.collection.counts import Counts
from icepool.order import Order, OrderReason, merge_pop_orders
from icepool.order import Order, OrderReason, merge_order_preferences
from icepool.population.keep import highest_slice, lowest_slice

import bisect
Expand Down Expand Up @@ -168,17 +168,8 @@ def _generate_max(self, max_outcome: T) -> PopMultisetGeneration:
"""

@abstractmethod
def _local_preferred_pop_order(self) -> tuple[Order | None, OrderReason]:
"""Returns the preferred pop order of this expression node, along with the priority of that pop order.
Greater priorities strictly outrank lower priorities.
An order of `None` represents conflicting orders and can occur in the
argument and/or return value.
"""

@abstractmethod
def local_order(self) -> Order:
"""Any ordering that is required by this expression node."""
def local_order_preference(self) -> tuple[Order | None, OrderReason]:
"""Any ordering that is preferred or required by this expression node."""

@abstractmethod
def _free_arity(self) -> int:
Expand Down Expand Up @@ -258,9 +249,9 @@ def _iter_nodes(self) -> 'Iterator[MultisetExpression]':
yield from child._iter_nodes()
yield self

def _preferred_pop_order(self) -> tuple[Order | None, OrderReason]:
return merge_pop_orders(*(node._local_preferred_pop_order()
for node in self._iter_nodes()))
def order_preference(self) -> tuple[Order | None, OrderReason]:
return merge_order_preferences(*(node.local_order_preference()
for node in self._iter_nodes()))

@property
def _items_for_cartesian_product(
Expand All @@ -282,10 +273,9 @@ def sample(self) -> tuple[tuple, ...]:
if not self.outcomes():
raise ValueError('Cannot sample from an empty set of outcomes.')

preferred_pop_order, pop_order_reason = self._local_preferred_pop_order(
)
order, order_reason = self.order_preference()

if preferred_pop_order is not None and preferred_pop_order > 0:
if order is not None and order > 0:
outcome = self.min_outcome()
generated = tuple(self._generate_min(outcome))
else:
Expand Down
7 changes: 2 additions & 5 deletions src/icepool/multiset_variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,8 @@ def _generate_min(self, min_outcome) -> PopMultisetGeneration:
def _generate_max(self, max_outcome) -> PopMultisetGeneration:
raise UnboundMultisetExpressionError()

def _local_preferred_pop_order(self) -> tuple[Order | None, OrderReason]:
raise UnboundMultisetExpressionError()

def local_order(self) -> Order:
return Order.Any
def local_order_preference(self) -> tuple[Order | None, OrderReason]:
return Order.Any, OrderReason.NoPreference

def _free_arity(self) -> int:
return self._index + 1
Expand Down
Loading

0 comments on commit 94a4043

Please sign in to comment.