From a7fdf6cf69c93e76856049b3cbe7e5b4cec1026d Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Mon, 1 Aug 2022 13:27:09 +0000 Subject: [PATCH 1/7] the unfusion strategy --- tilings/algorithms/minimal_gridded_perms.py | 2 +- tilings/strategies/fusion/component.py | 3 +- tilings/strategies/fusion/fusion.py | 4 +- tilings/strategies/fusion/unfusion.py | 233 ++++++++++++++++++++ tilings/tilescope.py | 7 +- 5 files changed, 243 insertions(+), 6 deletions(-) create mode 100644 tilings/strategies/fusion/unfusion.py diff --git a/tilings/algorithms/minimal_gridded_perms.py b/tilings/algorithms/minimal_gridded_perms.py index 8e3dc839..985ff5a6 100644 --- a/tilings/algorithms/minimal_gridded_perms.py +++ b/tilings/algorithms/minimal_gridded_perms.py @@ -548,7 +548,7 @@ def odd_even_minimal_gridded_perms( self, odd_cells: Set[Cell], even_cells: Set[Cell] ) -> Set[GriddedPerm]: if odd_cells.intersection(even_cells): - return + return set() def cell_count(gp: GriddedPerm, cell: Cell) -> int: return sum(1 for _ in filter(cell.__eq__, gp.pos)) diff --git a/tilings/strategies/fusion/component.py b/tilings/strategies/fusion/component.py index afb76fe3..3d480ba3 100644 --- a/tilings/strategies/fusion/component.py +++ b/tilings/strategies/fusion/component.py @@ -5,7 +5,6 @@ from tilings import GriddedPerm, Tiling from tilings.algorithms import ComponentFusion -from .constructor import FusionConstructor from .fusion import FusionStrategy @@ -39,7 +38,7 @@ def is_positive_or_empty_fusion(self, tiling: Tiling) -> bool: def constructor( self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None - ) -> FusionConstructor: + ) -> Constructor: if self.tracked and self.is_positive_or_empty_fusion(comb_class): raise NotImplementedError( "Can't count positive or empty fusion. Try a cell insertion!" diff --git a/tilings/strategies/fusion/fusion.py b/tilings/strategies/fusion/fusion.py index a646f9d0..ccbb7e09 100644 --- a/tilings/strategies/fusion/fusion.py +++ b/tilings/strategies/fusion/fusion.py @@ -177,6 +177,8 @@ def is_two_way(comb_class: Tiling): return False def is_reversible(self, comb_class: Tiling) -> bool: + if comb_class.predicate_assumptions: + return False algo = self.fusion_algorithm(comb_class) new_ass = algo.new_assumption() fused_assumptions = ( @@ -195,7 +197,7 @@ def shifts( def constructor( self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None - ) -> FusionConstructor: + ) -> Constructor: if not self.tracked: # constructor only enumerates when tracked. raise NotImplementedError("The fusion strategy was not tracked.") diff --git a/tilings/strategies/fusion/unfusion.py b/tilings/strategies/fusion/unfusion.py new file mode 100644 index 00000000..a351c98d --- /dev/null +++ b/tilings/strategies/fusion/unfusion.py @@ -0,0 +1,233 @@ +from itertools import chain +from typing import FrozenSet, Iterator, Optional, Set, Tuple, Union + +from comb_spec_searcher.exception import StrategyDoesNotApply +from comb_spec_searcher.strategies.constructor.base import Constructor +from tilings import GriddedPerm, Tiling +from tilings.assumptions import Assumption, Cell, TrackingAssumption +from tilings.strategies.fusion.constructor import ReverseFusionConstructor +from tilings.strategies.fusion.fusion import FusionRule, FusionStrategy +from tilings.strategies.pointing import DivideByK + + +class UnfusionRule(FusionRule): + def _ensure_level_objects(self, n: int) -> None: + raise NotImplementedError + + def random_sample_object_of_size(self, n: int, **parameters: int) -> GriddedPerm: + raise NotImplementedError + + def _forward_order( + self, + obj: GriddedPerm, + image: Tuple[Optional[GriddedPerm], ...], + data: Optional[object] = None, + ) -> int: + raise NotImplementedError + + def _backward_order_item( + self, + idx: int, + objs: Tuple[Optional[GriddedPerm], ...], + data: Optional[object] = None, + ) -> GriddedPerm: + raise NotImplementedError + + +class UnfusionStrategy(FusionStrategy): + def __init__( + self, + row_idx=None, + col_idx=None, + tracked: bool = False, + left: bool = False, + right: bool = False, + both: bool = False, + ): + super().__init__(row_idx, col_idx, tracked) + self.left = left + self.right = right + self.both = both + assert left or right or both or not self.tracked + + def __call__( + self, + comb_class: Tiling, + children: Tuple[Tiling, ...] = None, + ) -> UnfusionRule: + if children is None: + children = self.decomposition_function(comb_class) + if children is None: + raise StrategyDoesNotApply("Strategy does not apply") + return UnfusionRule(self, comb_class, children=children) + + def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling]: + fused_region = self.fused_region(comb_class) + + def valid_fusion_assumptions(): + + if ( + TrackingAssumption.from_cells(fused_region) + not in comb_class.assumptions + ): + return False + for assumption in comb_class.tracking_assumptions: + intersected = fused_region.intersection(assumption.cells) + if intersected and intersected != fused_region: + # strategy does not apply + return False + return True + + if valid_fusion_assumptions(): + return (self.unfused_tiling(comb_class),) + + def unfused_tiling(self, tiling: Tiling) -> Tiling: + algo = self.fusion_algorithm(tiling) + obs = chain(*[algo.unfuse_gridded_perm(ob) for ob in tiling.obstructions]) + reqs = [ + [gp for req_gp in req_list for gp in algo.unfuse_gridded_perm(req_gp)] + for req_list in tiling.requirements + ] + ass = set( + ass.__class__( + [gp for ass_gp in ass.gps for gp in algo.unfuse_gridded_perm(ass_gp)] + ) + for ass in tiling.assumptions + ) + if self.tracked: + + def add_or_remove( + assumptions: Set[Assumption], assumption: TrackingAssumption, add: bool + ) -> Set[Assumption]: + if add: + assumptions.add(assumption) + else: + assumptions.discard(assumption) + + add_or_remove(ass, self.left_tracking_assumption(tiling), self.left) + add_or_remove(ass, self.right_tracking_assumption(tiling), self.right) + add_or_remove(ass, self.both_tracking_assumption(tiling), self.both) + return Tiling(obs, reqs, ass) + + def fused_region(self, tiling: Tiling) -> FrozenSet[Cell]: + if self.row_idx is not None: + return tiling.cells_in_row(self.row_idx) + return tiling.cells_in_col(self.col_idx) + + def left_unfused_region(self, tiling: Tiling) -> FrozenSet[Cell]: + return self.fused_region(tiling) + + def right_unfused_region(self, tiling: Tiling) -> FrozenSet[Cell]: + if self.row_idx is not None: + return frozenset((x, y + 1) for (x, y) in self.fused_region(tiling)) + return frozenset((x + 1, y) for (x, y) in self.fused_region(tiling)) + + def unfused_region(self, tiling: Tiling) -> FrozenSet[Cell]: + return self.left_unfused_region(tiling).union(self.right_unfused_region(tiling)) + + def left_tracking_assumption(self, tiling: Tiling) -> TrackingAssumption: + return TrackingAssumption.from_cells(self.left_unfused_region(tiling)) + + def right_tracking_assumption(self, tiling: Tiling) -> TrackingAssumption: + return TrackingAssumption.from_cells(self.right_unfused_region(tiling)) + + def both_tracking_assumption(self, tiling: Tiling) -> TrackingAssumption: + return TrackingAssumption.from_cells(self.unfused_region(tiling)) + + def is_reversible(self, comb_class: Tiling) -> bool: + return False + + def constructor( + self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None + ) -> Union[DivideByK, ReverseFusionConstructor]: + if not self.tracked: + # constructor only enumerates when tracked. + raise NotImplementedError("The fusion strategy was not tracked.") + if children is None: + children = self.decomposition_function(comb_class) + assert children is not None + fused_tiling, unfused_tiling = comb_class, children[0] + # Need to recompute some info to count, so ignoring passed in children + algo = self.fusion_algorithm(unfused_tiling) + if not algo.fusable(): + raise StrategyDoesNotApply("Strategy does not apply") + if algo.min_left_right_points() != (0, 0): + raise NotImplementedError( + "Reverse positive fusion counting not implemented" + ) + ( + left_sided_params, + right_sided_params, + _, + ) = self.left_right_both_sided_parameters(unfused_tiling) + if not self.left and not self.right: + assert self.both + unfused_assumption = self.both_tracking_assumption(comb_class) + assert unfused_assumption in unfused_tiling.assumptions + return DivideByK( + fused_tiling, + (unfused_tiling,), + 1, + unfused_tiling.get_assumption_parameter(unfused_assumption), + self.extra_parameters(unfused_tiling, (fused_tiling,)), + ) + return ReverseFusionConstructor( + unfused_tiling, + comb_class, + self._fuse_parameter(fused_tiling), + self.extra_parameters(unfused_tiling, (fused_tiling,))[0], + tuple(left_sided_params), + tuple(right_sided_params), + ) + + def reverse_constructor( + self, + idx: int, + comb_class: Tiling, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Constructor: + raise NotImplementedError + + def _fuse_parameter(self, comb_class: Tiling) -> str: + return comb_class.get_assumption_parameter( + TrackingAssumption.from_cells(self.fused_region(comb_class)) + ) + + def formal_step(self) -> str: + fusing = "rows" if self.row_idx is not None else "columns" + idx = self.row_idx if self.row_idx is not None else self.col_idx + return f"unfuse {fusing} {idx}" + + def backward_map( + self, + comb_class: Tiling, + objs: Tuple[Optional[GriddedPerm], ...], + children: Optional[Tuple[Tiling, ...]] = None, + left_points: int = None, + ) -> Iterator[GriddedPerm]: + raise NotImplementedError + + def forward_map( + self, + comb_class: Tiling, + obj: GriddedPerm, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Tuple[Optional[GriddedPerm], ...]: + raise NotImplementedError + + +# if __name__ == "__main__": +# t = Tiling.from_string("123").add_assumption( +# TrackingAssumption.from_cells([(0, 0)]) +# ) +# for left in (True, False): +# for right in (True, False): +# for both in (True, False): +# if left or right or both: +# strat = UnfusionStrategy( +# 0, None, True, left=left, right=right, both=both +# ) +# rule = strat(t) +# for i in range(6): +# print(i, rule.sanity_check(i)) +# # rule.sanity_check(i) diff --git a/tilings/tilescope.py b/tilings/tilescope.py index b04e8e3a..56aa8db7 100644 --- a/tilings/tilescope.py +++ b/tilings/tilescope.py @@ -434,8 +434,11 @@ def backward_map( ) -> Iterator[GriddedPerm]: if children is None: children = self.decomposition_function(comb_class) - gp = next((gp for gp in objs if gp is not None)) - yield gp + try: + gp = next((gp for gp in objs if gp is not None)) + yield gp + except StopIteration as e: + raise ValueError from e def forward_map( self, From e169e704a64a2c1677e7102ffcb9a468166803a4 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Mon, 1 Aug 2022 13:51:15 +0000 Subject: [PATCH 2/7] rename previous unfusion to strat.UnfusionRowColumnFactory --- tilings/strategies/__init__.py | 8 ++++++-- tilings/strategies/fusion/fusion.py | 26 +++++++++++++++++++++++++- tilings/strategies/fusion/unfusion.py | 4 ++-- tilings/strategies/unfusion.py | 2 +- tilings/strategy_pack.py | 2 +- tilings/tiling.py | 3 +++ 6 files changed, 38 insertions(+), 7 deletions(-) diff --git a/tilings/strategies/__init__.py b/tilings/strategies/__init__.py index 9e59df4f..5422db36 100644 --- a/tilings/strategies/__init__.py +++ b/tilings/strategies/__init__.py @@ -48,7 +48,11 @@ from .row_and_col_separation import RowColumnSeparationStrategy from .sliding import SlidingFactory from .symmetry import SymmetriesFactory -from .unfusion import UnfusionColumnStrategy, UnfusionFactory, UnfusionRowStrategy +from .unfusion import ( + UnfusionColumnStrategy, + UnfusionRowColumnFactory, + UnfusionRowStrategy, +) from .verification import ( BasicVerificationStrategy, ComponentVerificationStrategy, @@ -93,7 +97,7 @@ "RequirementPointingFactory", "UnfusionColumnStrategy", "UnfusionRowStrategy", - "UnfusionFactory", + "UnfusionRowColumnFactory", # Equivalence "MonotoneSlidingFactory", "PatternPlacementFactory", diff --git a/tilings/strategies/fusion/fusion.py b/tilings/strategies/fusion/fusion.py index ccbb7e09..ae946570 100644 --- a/tilings/strategies/fusion/fusion.py +++ b/tilings/strategies/fusion/fusion.py @@ -1,5 +1,5 @@ from collections import defaultdict -from itertools import chain, islice +from itertools import chain, islice, product from random import randint from typing import Callable, Dict, Iterator, List, Optional, Set, Tuple, cast @@ -410,6 +410,8 @@ def make_tracked(self) -> "FusionFactory": return self.__class__(tracked=True, isolation_level=self.isolation_level) def __call__(self, comb_class: Tiling) -> Iterator[Rule]: + from tilings.strategies.fusion.unfusion import UnfusionStrategy + cols, rows = comb_class.dimensions for row_idx in range(rows - 1): algo = Fusion( @@ -423,6 +425,17 @@ def __call__(self, comb_class: Tiling) -> Iterator[Rule]: yield FusionStrategy(row_idx=row_idx, tracked=self.tracked)( comb_class, (fused_tiling,) ) + for left, right, both in product( + (True, False), (True, False), (True, False) + ): + if left or right or both: + yield UnfusionStrategy( + row_idx=row_idx, + tracked=self.tracked, + left=left, + right=right, + both=both, + )(fused_tiling) for col_idx in range(cols - 1): algo = Fusion( comb_class, @@ -435,6 +448,17 @@ def __call__(self, comb_class: Tiling) -> Iterator[Rule]: yield FusionStrategy(col_idx=col_idx, tracked=self.tracked)( comb_class, (fused_tiling,) ) + for left, right, both in product( + (True, False), (True, False), (True, False) + ): + if left or right or both: + yield UnfusionStrategy( + col_idx=col_idx, + tracked=self.tracked, + left=left, + right=right, + both=both, + )(fused_tiling) def __str__(self) -> str: if self.tracked: diff --git a/tilings/strategies/fusion/unfusion.py b/tilings/strategies/fusion/unfusion.py index a351c98d..632ed65d 100644 --- a/tilings/strategies/fusion/unfusion.py +++ b/tilings/strategies/fusion/unfusion.py @@ -149,8 +149,8 @@ def constructor( fused_tiling, unfused_tiling = comb_class, children[0] # Need to recompute some info to count, so ignoring passed in children algo = self.fusion_algorithm(unfused_tiling) - if not algo.fusable(): - raise StrategyDoesNotApply("Strategy does not apply") + # if not algo.fusable(): + # raise StrategyDoesNotApply("Strategy does not apply") if algo.min_left_right_points() != (0, 0): raise NotImplementedError( "Reverse positive fusion counting not implemented" diff --git a/tilings/strategies/unfusion.py b/tilings/strategies/unfusion.py index a9ac38f3..cb3f0efa 100644 --- a/tilings/strategies/unfusion.py +++ b/tilings/strategies/unfusion.py @@ -301,7 +301,7 @@ def __init__( super().__init__(ignore_parent, inferrable, possibly_empty, workable, cols) -class UnfusionFactory(StrategyFactory[Tiling]): +class UnfusionRowColumnFactory(StrategyFactory[Tiling]): def __init__(self, max_width: int = 4, max_height: int = 4) -> None: self.max_height = max_height self.max_width = max_width diff --git a/tilings/strategy_pack.py b/tilings/strategy_pack.py index 7bc21a9e..b28bab47 100644 --- a/tilings/strategy_pack.py +++ b/tilings/strategy_pack.py @@ -379,7 +379,7 @@ def kitchen_sinkify( # pylint: disable=R0912 strat.AssumptionPointingFactory(), strat.RequirementPointingFactory(), strat.PointingStrategy(), - strat.UnfusionFactory(), + strat.UnfusionRowColumnFactory(), strat.FusableRowAndColumnPlacementFactory(), ), ) diff --git a/tilings/tiling.py b/tilings/tiling.py index bdc1b726..c6e6fbf9 100644 --- a/tilings/tiling.py +++ b/tilings/tiling.py @@ -873,9 +873,12 @@ def add_assumptions(self, assumptions: Iterable[Assumption]) -> "Tiling": remove_empty_rows_and_cols=remove_empty_rows_and_cols, derive_empty=derive_empty, simplify=simplify, + checked=True, ) if not simplify: tiling.clean_assumptions() + if DEBUG: + tiling._check_init(False) return tiling def remove_assumption(self, assumption: Assumption): From 1a54df7114a7da1c5ee6a0ba23c5490bdb84c218 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Mon, 1 Aug 2022 15:25:37 +0000 Subject: [PATCH 3/7] forbid 2x2 fusion --- tilings/algorithms/fusion.py | 7 +++++++ tilings/strategies/fusion/fusion.py | 15 ++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/tilings/algorithms/fusion.py b/tilings/algorithms/fusion.py index f2553074..5b3b13ad 100644 --- a/tilings/algorithms/fusion.py +++ b/tilings/algorithms/fusion.py @@ -423,6 +423,13 @@ def predicate_fusable(self) -> bool: elif isinstance(ass, OppositeParityAssumption): opposite.add(next(iter(ass.cells))) left = self.left_fuse_region() + if len(left) > 1: + # TODO: can't fuse things like + # O | O + # - + - + # O | O + # need entire rows/columns to be tracked as odd. + return False for (x, y) in left: xn, yn = x, y if self._fuse_row: diff --git a/tilings/strategies/fusion/fusion.py b/tilings/strategies/fusion/fusion.py index ae946570..e2c3632d 100644 --- a/tilings/strategies/fusion/fusion.py +++ b/tilings/strategies/fusion/fusion.py @@ -9,7 +9,7 @@ from comb_spec_searcher.typing import Objects from tilings import GriddedPerm, Tiling from tilings.algorithms import Fusion -from tilings.assumptions import OddCountAssumption +from tilings.assumptions import EvenCountAssumption, OddCountAssumption from ..pointing import DivideByK from .constructor import FusionConstructor, ReverseFusionConstructor @@ -209,6 +209,15 @@ def constructor( assert children is None or children == (child,) min_left, min_right = algo.min_left_right_points() left, right = algo.left_fuse_region(), algo.right_fuse_region() + odd_left, odd_right = None, None + if OddCountAssumption.from_cells(left) in comb_class.assumptions: + odd_left = True + elif EvenCountAssumption.from_cells(left) in comb_class.assumptions: + odd_left = False + if OddCountAssumption.from_cells(right) in comb_class.assumptions: + odd_right = True + if EvenCountAssumption.from_cells(right) in comb_class.assumptions: + odd_right = False return FusionConstructor( comb_class, child, @@ -217,8 +226,8 @@ def constructor( *self.left_right_both_sided_parameters(comb_class), min_left, min_right, - odd_left=(OddCountAssumption.from_cells(left) in comb_class.assumptions), - odd_right=(OddCountAssumption.from_cells(right) in comb_class.assumptions), + odd_left=odd_left, + odd_right=odd_right, ) def reverse_constructor( # pylint: disable=no-self-use From 963b91f6a5ebc57dacdcc5447398a749e77c64b5 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Mon, 1 Aug 2022 15:48:30 +0000 Subject: [PATCH 4/7] tidying up --- tilings/algorithms/fusion.py | 4 +++- tilings/strategies/fusion/fusion.py | 1 + tilings/strategies/unfusion.py | 2 +- tilings/tiling.py | 7 ++++--- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/tilings/algorithms/fusion.py b/tilings/algorithms/fusion.py index 5b3b13ad..0b885861 100644 --- a/tilings/algorithms/fusion.py +++ b/tilings/algorithms/fusion.py @@ -457,7 +457,9 @@ def fused_tiling(self) -> "Tiling": assumptions.extend(self.fused_predicates()) if self._tracked: assumptions.append(self.new_assumption()) - requirements = list(list(fc) for fc in self.requirements_fuse_counters) + requirements: List[Iterable[GriddedPerm]] = list( + list(fc) for fc in self.requirements_fuse_counters + ) if self._positive_left or self._positive_right: new_positive_requirement = self.new_positive_requirement() requirements = requirements + [new_positive_requirement] diff --git a/tilings/strategies/fusion/fusion.py b/tilings/strategies/fusion/fusion.py index e2c3632d..91c434ca 100644 --- a/tilings/strategies/fusion/fusion.py +++ b/tilings/strategies/fusion/fusion.py @@ -419,6 +419,7 @@ def make_tracked(self) -> "FusionFactory": return self.__class__(tracked=True, isolation_level=self.isolation_level) def __call__(self, comb_class: Tiling) -> Iterator[Rule]: + # pylint: disable=import-outside-toplevel from tilings.strategies.fusion.unfusion import UnfusionStrategy cols, rows = comb_class.dimensions diff --git a/tilings/strategies/unfusion.py b/tilings/strategies/unfusion.py index cb3f0efa..b6ce95ad 100644 --- a/tilings/strategies/unfusion.py +++ b/tilings/strategies/unfusion.py @@ -321,5 +321,5 @@ def __repr__(self) -> str: return self.__class__.__name__ + "()" @classmethod - def from_dict(cls, d: dict) -> "UnfusionFactory": + def from_dict(cls, d: dict) -> "UnfusionRowColumnFactory": return cls() diff --git a/tilings/tiling.py b/tilings/tiling.py index c6e6fbf9..4836eea3 100644 --- a/tilings/tiling.py +++ b/tilings/tiling.py @@ -164,7 +164,7 @@ def __init__( else: self.set_empty() - self._check_init(checked) + self.check_init(checked) def _set_obstructions_requirements_and_assumptions( self, @@ -197,7 +197,7 @@ def _set_obstructions_requirements_and_assumptions( _assumptions.append(ass) self._assumptions = tuple(sorted(_assumptions)) - def _check_init(self, checked: bool): + def check_init(self, checked: bool): if DEBUG and not checked: redone = Tiling( self._obstructions, self._requirements, self._assumptions, checked=True @@ -878,7 +878,7 @@ def add_assumptions(self, assumptions: Iterable[Assumption]) -> "Tiling": if not simplify: tiling.clean_assumptions() if DEBUG: - tiling._check_init(False) + tiling.check_init(False) return tiling def remove_assumption(self, assumption: Assumption): @@ -1707,6 +1707,7 @@ def is_empty(self, experimental_bound: Optional[int] = None) -> bool: TODO: this method ignores predicates """ + # pylint: disable = arguments-differ if any(ob.is_empty() for ob in self.obstructions) or any( not ass.can_be_satisfied(self) for ass in self.predicate_assumptions ): From 414b8e0886cbab3c6781b7811e5815302b10c791 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Sat, 6 Aug 2022 02:06:17 +0000 Subject: [PATCH 5/7] add extra insertion rule and make fusion rules reversible --- tilings/algorithms/fusion.py | 15 +++++- tilings/strategies/fusion/fusion.py | 73 +++++++++++++++++---------- tilings/strategies/fusion/unfusion.py | 49 ++++++++++++------ 3 files changed, 94 insertions(+), 43 deletions(-) diff --git a/tilings/algorithms/fusion.py b/tilings/algorithms/fusion.py index 0b885861..26098a53 100644 --- a/tilings/algorithms/fusion.py +++ b/tilings/algorithms/fusion.py @@ -443,7 +443,7 @@ def predicate_fusable(self) -> bool: return False return True - def fused_tiling(self) -> "Tiling": + def fused_tiling(self, add_odd_req: bool = True) -> "Tiling": """ Return the fused tiling. """ @@ -463,6 +463,8 @@ def fused_tiling(self) -> "Tiling": if self._positive_left or self._positive_right: new_positive_requirement = self.new_positive_requirement() requirements = requirements + [new_positive_requirement] + if add_odd_req and self.is_odd_next_to_odd_fusion(): + requirements += [map(GriddedPerm.point_perm, self.left_fuse_region())] self._fused_tiling = self._tiling.__class__( obstructions=self.obstruction_fuse_counter.keys(), requirements=requirements, @@ -472,6 +474,17 @@ def fused_tiling(self) -> "Tiling": ) return self._fused_tiling + def clear_fused_tiling(self) -> None: + self._fused_tiling = None + + def is_odd_next_to_odd_fusion(self) -> bool: + return ( + OddCountAssumption.from_cells(self.left_fuse_region()) + in self._tiling.assumptions + and OddCountAssumption.from_cells(self.right_fuse_region()) + in self._tiling.assumptions + ) + def fuse_assumption(self, assumption: AssumptionClass) -> AssumptionClass: return assumption.__class__(self.fuse_gridded_perm(gp) for gp in assumption.gps) diff --git a/tilings/strategies/fusion/fusion.py b/tilings/strategies/fusion/fusion.py index 91c434ca..14b7b685 100644 --- a/tilings/strategies/fusion/fusion.py +++ b/tilings/strategies/fusion/fusion.py @@ -10,6 +10,7 @@ from tilings import GriddedPerm, Tiling from tilings.algorithms import Fusion from tilings.assumptions import EvenCountAssumption, OddCountAssumption +from tilings.strategies.requirement_insertion import RequirementInsertionStrategy from ..pointing import DivideByK from .constructor import FusionConstructor, ReverseFusionConstructor @@ -177,8 +178,6 @@ def is_two_way(comb_class: Tiling): return False def is_reversible(self, comb_class: Tiling) -> bool: - if comb_class.predicate_assumptions: - return False algo = self.fusion_algorithm(comb_class) new_ass = algo.new_assumption() fused_assumptions = ( @@ -419,9 +418,6 @@ def make_tracked(self) -> "FusionFactory": return self.__class__(tracked=True, isolation_level=self.isolation_level) def __call__(self, comb_class: Tiling) -> Iterator[Rule]: - # pylint: disable=import-outside-toplevel - from tilings.strategies.fusion.unfusion import UnfusionStrategy - cols, rows = comb_class.dimensions for row_idx in range(rows - 1): algo = Fusion( @@ -435,17 +431,10 @@ def __call__(self, comb_class: Tiling) -> Iterator[Rule]: yield FusionStrategy(row_idx=row_idx, tracked=self.tracked)( comb_class, (fused_tiling,) ) - for left, right, both in product( - (True, False), (True, False), (True, False) - ): - if left or right or both: - yield UnfusionStrategy( - row_idx=row_idx, - tracked=self.tracked, - left=left, - right=right, - both=both, - )(fused_tiling) + # yield from self._all_unfusion_rules(True, row_idx, fused_tiling) + if algo.is_odd_next_to_odd_fusion(): + yield self._extra_cell_insertion_rule(algo) + for col_idx in range(cols - 1): algo = Fusion( comb_class, @@ -458,17 +447,47 @@ def __call__(self, comb_class: Tiling) -> Iterator[Rule]: yield FusionStrategy(col_idx=col_idx, tracked=self.tracked)( comb_class, (fused_tiling,) ) - for left, right, both in product( - (True, False), (True, False), (True, False) - ): - if left or right or both: - yield UnfusionStrategy( - col_idx=col_idx, - tracked=self.tracked, - left=left, - right=right, - both=both, - )(fused_tiling) + # yield from self._all_unfusion_rules(False, col_idx, fused_tiling) + if algo.is_odd_next_to_odd_fusion(): + yield self._extra_cell_insertion_rule(algo) + + def _all_unfusion_rules( + self, row: bool, idx: int, tiling: Tiling + ) -> Iterator[Rule]: + # pylint: disable=import-outside-toplevel + from tilings.strategies.fusion.unfusion import UnfusionStrategy + + for left, right, both in product((True, False), (True, False), (True, False)): + if left or right or both: + if row: + yield UnfusionStrategy( + row_idx=idx, + tracked=self.tracked, + left=left, + right=right, + both=both, + )(tiling) + else: + yield UnfusionStrategy( + col_idx=idx, + tracked=self.tracked, + left=left, + right=right, + both=both, + )(tiling) + + @staticmethod + def _extra_cell_insertion_rule(algo: Fusion) -> Rule: + algo.clear_fused_tiling() + fused = algo.fused_tiling(add_odd_req=False) + algo.clear_fused_tiling() + cells = [ + fused.forward_map.map_cell(cell) + for cell in filter( + fused.forward_map.is_mappable_cell, algo.left_fuse_region() + ) + ] + return RequirementInsertionStrategy(map(GriddedPerm.point_perm, cells))(fused) def __str__(self) -> str: if self.tracked: diff --git a/tilings/strategies/fusion/unfusion.py b/tilings/strategies/fusion/unfusion.py index 632ed65d..29b17c04 100644 --- a/tilings/strategies/fusion/unfusion.py +++ b/tilings/strategies/fusion/unfusion.py @@ -216,18 +216,37 @@ def forward_map( raise NotImplementedError -# if __name__ == "__main__": -# t = Tiling.from_string("123").add_assumption( -# TrackingAssumption.from_cells([(0, 0)]) -# ) -# for left in (True, False): -# for right in (True, False): -# for both in (True, False): -# if left or right or both: -# strat = UnfusionStrategy( -# 0, None, True, left=left, right=right, both=both -# ) -# rule = strat(t) -# for i in range(6): -# print(i, rule.sanity_check(i)) -# # rule.sanity_check(i) +if __name__ == "__main__": + from tilings.assumptions import OddCountAssumption, OppositeParityAssumption + from tilings.strategies import FusionFactory + + t = Tiling( + obstructions=[ + GriddedPerm((0, 1, 2), ((0, 0), (0, 0), (0, 0))), + GriddedPerm((0, 1, 2), ((0, 0), (0, 0), (1, 0))), + GriddedPerm((0, 1, 2), ((0, 0), (1, 0), (1, 0))), + GriddedPerm((0, 1, 2), ((1, 0), (1, 0), (1, 0))), + ], + assumptions=[ + TrackingAssumption.from_cells([(0, 0)]), + OddCountAssumption.from_cells([(0, 0)]), + OddCountAssumption.from_cells([(1, 0)]), + OppositeParityAssumption.from_cells([(0, 0)]), + OppositeParityAssumption.from_cells([(1, 0)]), + ], + ) + + for rule in FusionFactory()(t): + print(rule) + # print(t) + # for left in (True, False): + # for right in (True, False): + # for both in (True, False): + # if left or right or both: + # strat = UnfusionStrategy( + # 0, None, True, left=left, right=right, both=both + # ) + # rule = strat(t) + # for i in range(6): + # print(i, rule.sanity_check(i)) + # # rule.sanity_check(i) From 0d55e583d73281415ee182e4001837226778d84e Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Sat, 6 Aug 2022 02:13:21 +0000 Subject: [PATCH 6/7] comment out test --- tilings/strategies/fusion/unfusion.py | 68 +++++++++++++-------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/tilings/strategies/fusion/unfusion.py b/tilings/strategies/fusion/unfusion.py index 29b17c04..b3b000d3 100644 --- a/tilings/strategies/fusion/unfusion.py +++ b/tilings/strategies/fusion/unfusion.py @@ -216,37 +216,37 @@ def forward_map( raise NotImplementedError -if __name__ == "__main__": - from tilings.assumptions import OddCountAssumption, OppositeParityAssumption - from tilings.strategies import FusionFactory - - t = Tiling( - obstructions=[ - GriddedPerm((0, 1, 2), ((0, 0), (0, 0), (0, 0))), - GriddedPerm((0, 1, 2), ((0, 0), (0, 0), (1, 0))), - GriddedPerm((0, 1, 2), ((0, 0), (1, 0), (1, 0))), - GriddedPerm((0, 1, 2), ((1, 0), (1, 0), (1, 0))), - ], - assumptions=[ - TrackingAssumption.from_cells([(0, 0)]), - OddCountAssumption.from_cells([(0, 0)]), - OddCountAssumption.from_cells([(1, 0)]), - OppositeParityAssumption.from_cells([(0, 0)]), - OppositeParityAssumption.from_cells([(1, 0)]), - ], - ) - - for rule in FusionFactory()(t): - print(rule) - # print(t) - # for left in (True, False): - # for right in (True, False): - # for both in (True, False): - # if left or right or both: - # strat = UnfusionStrategy( - # 0, None, True, left=left, right=right, both=both - # ) - # rule = strat(t) - # for i in range(6): - # print(i, rule.sanity_check(i)) - # # rule.sanity_check(i) +# if __name__ == "__main__": +# from tilings.assumptions import OddCountAssumption, OppositeParityAssumption +# from tilings.strategies import FusionFactory + +# t = Tiling( +# obstructions=[ +# GriddedPerm((0, 1, 2), ((0, 0), (0, 0), (0, 0))), +# GriddedPerm((0, 1, 2), ((0, 0), (0, 0), (1, 0))), +# GriddedPerm((0, 1, 2), ((0, 0), (1, 0), (1, 0))), +# GriddedPerm((0, 1, 2), ((1, 0), (1, 0), (1, 0))), +# ], +# assumptions=[ +# TrackingAssumption.from_cells([(0, 0)]), +# OddCountAssumption.from_cells([(0, 0)]), +# OddCountAssumption.from_cells([(1, 0)]), +# OppositeParityAssumption.from_cells([(0, 0)]), +# OppositeParityAssumption.from_cells([(1, 0)]), +# ], +# ) + +# for rule in FusionFactory()(t): +# print(rule) +# print(t) +# for left in (True, False): +# for right in (True, False): +# for both in (True, False): +# if left or right or both: +# strat = UnfusionStrategy( +# 0, None, True, left=left, right=right, both=both +# ) +# rule = strat(t) +# for i in range(6): +# print(i, rule.sanity_check(i)) +# # rule.sanity_check(i) From cdad0014e34962af10e9022c65440142660f9992 Mon Sep 17 00:00:00 2001 From: Jay Pantone Date: Thu, 11 Aug 2022 12:36:26 -0500 Subject: [PATCH 7/7] adapts obstruction inferral --- tilings/algorithms/obstruction_inferral.py | 32 +++++++++++++++++++--- tilings/strategies/obstruction_inferral.py | 6 +++- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/tilings/algorithms/obstruction_inferral.py b/tilings/algorithms/obstruction_inferral.py index cab6fd21..1fbb8f14 100644 --- a/tilings/algorithms/obstruction_inferral.py +++ b/tilings/algorithms/obstruction_inferral.py @@ -1,8 +1,10 @@ import abc from typing import TYPE_CHECKING, Iterable, List, Optional, Set, Tuple +from permuta import Perm from tilings import GriddedPerm from tilings.algorithms.gridded_perm_generation import GriddedPermsOnTiling +from tilings.assumptions import OddCountAssumption if TYPE_CHECKING: from tilings import Tiling @@ -39,13 +41,35 @@ def new_obs(self, yield_non_minimal: bool = False) -> List[GriddedPerm]: self._new_obs = [] return self._new_obs + extra_reqs = [] + assumptions = list(self._tiling.assumptions) + for ass in assumptions: + if isinstance(ass, OddCountAssumption) and len(ass.cells) == 1: + to_add = [GriddedPerm.single_cell(Perm((0,)), next(iter(ass.cells)))] + if to_add not in self._tiling.requirements: + extra_reqs.append(to_add) + + fake_tiling = self._tiling.__class__( + self._tiling.obstructions, + self._tiling.requirements + tuple(extra_reqs), + self._tiling.assumptions, + remove_empty_rows_and_cols=False, + derive_empty=False, + simplify=False, + sorted_input=False, + already_minimized_obs=True, + checked=True, + ) + max_len_of_perms_to_check = max(map(len, perms_to_check)) max_length = ( - self._tiling.maximum_length_of_minimum_gridded_perm() + fake_tiling.maximum_length_of_minimum_gridded_perm() + max_len_of_perms_to_check ) + GP = GriddedPermsOnTiling( - self._tiling, yield_non_minimal=yield_non_minimal + fake_tiling, + yield_non_minimal=yield_non_minimal, ).gridded_perms(max_length, place_at_most=max_len_of_perms_to_check) perms_left = set(perms_to_check) for gp in GP: @@ -122,8 +146,8 @@ def potential_new_obs(self) -> List[GriddedPerm]: """ Iterator over all possible obstruction of `self.obstruction_length`. """ - if not self._tiling.requirements: - return [] + # if not self._tiling.requirements: + # return [] no_req_tiling = self._tiling.__class__(self._tiling.obstructions) n = self._obs_len pot_obs = filter(self.not_required, no_req_tiling.gridded_perms(n)) diff --git a/tilings/strategies/obstruction_inferral.py b/tilings/strategies/obstruction_inferral.py index 35020ce9..6a1faeb1 100644 --- a/tilings/strategies/obstruction_inferral.py +++ b/tilings/strategies/obstruction_inferral.py @@ -124,7 +124,11 @@ def new_obs(self, tiling: Tiling) -> Sequence[GriddedPerm]: def __call__(self, comb_class: Tiling) -> Iterator[ObstructionInferralStrategy]: gps = self.new_obs(comb_class) if gps: - yield ObstructionInferralStrategy(gps) + OIS = ObstructionInferralStrategy(gps) + # print("=" * 30) + # print(comb_class) + # print(OIS.decomposition_function(comb_class)[0]) + yield OIS def to_jsonable(self) -> dict: d: dict = super().to_jsonable()