From 65fe462c07c39081a197dfa62ada60acc8e35982 Mon Sep 17 00:00:00 2001 From: jaypantone Date: Tue, 15 Feb 2022 08:18:10 -0600 Subject: [PATCH 001/100] f (#413) --- tilings/strategy_pack.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tilings/strategy_pack.py b/tilings/strategy_pack.py index 35b9e64a..ebb1bde6 100644 --- a/tilings/strategy_pack.py +++ b/tilings/strategy_pack.py @@ -327,7 +327,7 @@ def point_placements( ) -> "TileScopePack": name = "".join( [ - "length_{length}_" if length > 1 else "", + f"length_{length}_" if length > 1 else "", "partial_" if partial else "", "point_placements", ] @@ -524,7 +524,7 @@ def requirement_placements( ) -> "TileScopePack": name = "".join( [ - "length_{length}_" if length != 2 else "", + f"length_{length}_" if length != 2 else "", "partial_" if partial else "", "requirement_placements", ] @@ -570,7 +570,7 @@ def point_and_row_and_col_placements( both = place_col and place_row name = "".join( [ - "length_{length}_" if length > 1 else "", + f"length_{length}_" if length > 1 else "", "partial_" if partial else "", "point_and_", "row" if not col_only else "", @@ -620,5 +620,5 @@ def cell_insertions(cls, length: int): ], inferral_strats=[], expansion_strats=[[strat.CellInsertionFactory(maxreqlen=length)]], - name="length_{length}_cell_insertions", + name=f"length_{length}_cell_insertions", ) From 68e001bc765e237c963744bd8510991bcc65383f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20Nadeau?= Date: Tue, 15 Feb 2022 14:43:21 +0000 Subject: [PATCH 002/100] skip test with database that is to slow (#415) --- tests/test_tilescope.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_tilescope.py b/tests/test_tilescope.py index e5a6a545..7c067c37 100644 --- a/tests/test_tilescope.py +++ b/tests/test_tilescope.py @@ -114,6 +114,7 @@ def test_123(): @pytest.mark.timeout(120) +@pytest.mark.skip(reason="Too inconsistent connection db") def test_123_with_db(): searcher = TileScope("123", all_the_strategies_verify_database) spec = searcher.auto_search(smallest=True) From 54a75b31e5365454d6de4c9cd2dce9ba788e6dab Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 15 Feb 2022 15:04:32 +0000 Subject: [PATCH 003/100] point jumping strategy (#412) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * point jumping strategy * pylint and flake * extra param method for counting * skip test with database that is to slow Co-authored-by: Émile Nadeau Co-authored-by: Jay Pantone --- CHANGELOG.md | 2 + tests/strategies/test_encoding.py | 17 ++- tests/test_strategy_pack.py | 3 +- tilings/strategies/__init__.py | 2 + tilings/strategies/point_jumping.py | 169 ++++++++++++++++++++++++++++ 5 files changed, 189 insertions(+), 4 deletions(-) create mode 100644 tilings/strategies/point_jumping.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fe25859..cc9dda6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ tiling after we first remove that requirement. This is added to multiple different obs and one requirement list of size possibly greater than one. Previously it was only doing the case where a single ob's factor is implied by a requirement. +- `PointJumpingFactory` which adds rules where requirements and assumptions can be +swapped around a fusable row or column. ### Fixed - `ForgetTrackedSearcher` was not retroactively applying strategies that had a `basis`. diff --git a/tests/strategies/test_encoding.py b/tests/strategies/test_encoding.py index 370fa24d..58729ab0 100644 --- a/tests/strategies/test_encoding.py +++ b/tests/strategies/test_encoding.py @@ -26,6 +26,7 @@ ObstructionTransitivityFactory, OneByOneVerificationStrategy, PatternPlacementFactory, + PointJumpingFactory, RearrangeAssumptionFactory, RequirementCorroborationFactory, RequirementExtensionFactory, @@ -49,6 +50,7 @@ ) from tilings.strategies.fusion import ComponentFusionStrategy, FusionStrategy from tilings.strategies.obstruction_inferral import ObstructionInferralStrategy +from tilings.strategies.point_jumping import PointJumpingStrategy from tilings.strategies.rearrange_assumption import RearrangeAssumptionStrategy from tilings.strategies.requirement_insertion import RequirementInsertionStrategy from tilings.strategies.requirement_placement import RequirementPlacementStrategy @@ -321,9 +323,7 @@ def sliding_strategy_arguments(strategy): def short_length_arguments(strategy): return [ - ShortObstructionVerificationStrategy( - basis=basis, short_length=short_length, ignore_parent=ignore_parent - ) + strategy(basis=basis, short_length=short_length, ignore_parent=ignore_parent) for short_length in range(4) for ignore_parent in (True, False) for basis in ( @@ -334,6 +334,15 @@ def short_length_arguments(strategy): ] +def indices_and_row(strategy): + return [ + strategy(idx1, idx2, row) + for idx1 in range(3) + for idx2 in range(3) + for row in (True, False) + ] + + strategy_objects = ( maxreqlen_extrabasis_ignoreparent_one_cell_only(CellInsertionFactory) + ignoreparent(FactorInsertionFactory) @@ -409,6 +418,8 @@ def short_length_arguments(strategy): TrackingAssumption([GriddedPerm((0,), [(0, 0)])]), ) ] + + [PointJumpingFactory()] + + indices_and_row(PointJumpingStrategy) ) __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) diff --git a/tests/test_strategy_pack.py b/tests/test_strategy_pack.py index 53795fcf..d5461ef7 100644 --- a/tests/test_strategy_pack.py +++ b/tests/test_strategy_pack.py @@ -6,7 +6,7 @@ from permuta import Perm from permuta.misc import DIRS from tilings import strategies as strat -from tilings.strategies import SlidingFactory +from tilings.strategies import PointJumpingFactory, SlidingFactory from tilings.strategy_pack import TileScopePack @@ -100,6 +100,7 @@ def row_col_partial(pack): + [pack.make_interleaving() for pack in packs] + [pack.add_initial(SlidingFactory()) for pack in packs] + [pack.add_initial(SlidingFactory(use_symmetries=True)) for pack in packs] + + [pack.add_initial(PointJumpingFactory()) for pack in packs] ) diff --git a/tilings/strategies/__init__.py b/tilings/strategies/__init__.py index b4df26f9..1d45bddc 100644 --- a/tilings/strategies/__init__.py +++ b/tilings/strategies/__init__.py @@ -13,6 +13,7 @@ ObstructionTransitivityFactory, SubobstructionInferralFactory, ) +from .point_jumping import PointJumpingFactory from .rearrange_assumption import RearrangeAssumptionFactory from .requirement_insertion import ( CellInsertionFactory, @@ -65,6 +66,7 @@ "FactorFactory", # Equivalence "PatternPlacementFactory", + "PointJumpingFactory", "SlidingFactory", # Fusion "ComponentFusionFactory", diff --git a/tilings/strategies/point_jumping.py b/tilings/strategies/point_jumping.py new file mode 100644 index 00000000..5e6a6e90 --- /dev/null +++ b/tilings/strategies/point_jumping.py @@ -0,0 +1,169 @@ +from itertools import chain +from typing import Dict, Iterator, Optional, Tuple + +from comb_spec_searcher.exception import StrategyDoesNotApply +from comb_spec_searcher.strategies import DisjointUnionStrategy, StrategyFactory +from tilings import GriddedPerm, Tiling, TrackingAssumption +from tilings.algorithms import Fusion + +Cell = Tuple[int, int] + + +class PointJumpingStrategy(DisjointUnionStrategy[Tiling, GriddedPerm]): + """ + A strategy which moves requirements and assumptions from a column (or row) + to its neighbouring column (or row) if the two columns are fusable. + """ + + def __init__(self, idx1: int, idx2: int, row: bool): + self.idx1 = idx1 + self.idx2 = idx2 + self.row = row + super().__init__() + + def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling]: + return ( + Tiling( + comb_class.obstructions, + self.swapped_requirements(comb_class), + self.swapped_assumptions(comb_class), + ), + ) + + def swapped_requirements( + self, tiling: Tiling + ) -> Tuple[Tuple[GriddedPerm, ...], ...]: + return tuple(tuple(map(self._swapped_gp, req)) for req in tiling.requirements) + + def swapped_assumptions(self, tiling: Tiling) -> Tuple[TrackingAssumption, ...]: + return tuple( + TrackingAssumption(map(self._swapped_gp, ass.gps)) + for ass in tiling.assumptions + ) + + def _swapped_gp(self, gp: GriddedPerm) -> GriddedPerm: + if self._in_both_columns(gp): + return gp + return GriddedPerm(gp.patt, map(self._swap_cell, gp.pos)) + + def _in_both_columns(self, gp: GriddedPerm) -> bool: + if self.row: + return any(y == self.idx1 for _, y in gp.pos) and any( + y == self.idx2 for _, y in gp.pos + ) + return any(x == self.idx1 for x, _ in gp.pos) and any( + x == self.idx2 for x, _ in gp.pos + ) + + def _swap_cell(self, cell: Cell) -> Cell: + x, y = cell + if self.row: + if y == self.idx1: + y = self.idx2 + elif y == self.idx2: + y = self.idx1 + else: + if x == self.idx1: + x = self.idx2 + elif x == self.idx2: + x = self.idx1 + return x, y + + def _swap_assumption(self, assumption: TrackingAssumption) -> TrackingAssumption: + return TrackingAssumption(self._swapped_gp(gp) for gp in assumption.gps) + + def backward_map( + self, + comb_class: Tiling, + objs: Tuple[Optional[GriddedPerm], ...], + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Iterator[GriddedPerm]: + gp = objs[0] + assert gp is not None + yield gp + + def forward_map( + self, + comb_class: Tiling, + obj: GriddedPerm, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Tuple[Optional[GriddedPerm]]: + return (obj,) + + def extra_parameters( + self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None + ) -> Tuple[Dict[str, str], ...]: + if not comb_class.extra_parameters: + return super().extra_parameters(comb_class, children) + if children is None: + children = self.decomposition_function(comb_class) + if children is None: + raise StrategyDoesNotApply("Strategy does not apply") + child = children[0] + return ( + { + comb_class.get_assumption_parameter( + ass + ): child.get_assumption_parameter(self._swap_assumption(ass)) + for ass in comb_class.assumptions + }, + ) + + def formal_step(self) -> str: + row_or_col = "row" if self.row else "col" + return f"swapping reqs in {row_or_col} {self.idx1} and {self.idx2}" + + def to_jsonable(self) -> dict: + d = super().to_jsonable() + d.pop("ignore_parent") + d["idx1"] = self.idx1 + d["idx2"] = self.idx2 + d["row"] = self.row + return d + + @classmethod + def from_dict(cls, d: dict) -> "PointJumpingStrategy": + return cls(d.pop("idx1"), d.pop("idx2"), d.pop("row")) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.idx1}, {self.idx2}, {self.row})" + + +class PointJumpingFactory(StrategyFactory[Tiling]): + """ + A factory returning the disjoint union strategies that moves requirements + across the boundary of two fusable columns (or rows). + """ + + def __call__(self, comb_class: Tiling) -> Iterator[DisjointUnionStrategy]: + cols, rows = comb_class.dimensions + gps_to_be_swapped = chain( + *comb_class.requirements, *[ass.gps for ass in comb_class.assumptions] + ) + for col in range(cols - 1): + if any(x in (col, col + 1) for gp in gps_to_be_swapped for x, _ in gp.pos): + algo = Fusion(comb_class, col_idx=col) + if algo.fusable(): + yield PointJumpingStrategy(col, col + 1, False) + for row in range(rows - 1): + if any(y in (row, row + 1) for gp in gps_to_be_swapped for y, _ in gp.pos): + algo = Fusion(comb_class, row_idx=row) + if algo.fusable(): + yield PointJumpingStrategy(row, row + 1, True) + + def __str__(self) -> str: + return "point jumping" + + def __repr__(self) -> str: + return f"{self.__class__.__name__}()" + + def __eq__(self, other: object) -> bool: + return self.__class__ == other.__class__ and self.__dict__ == other.__dict__ + + def __hash__(self) -> int: + return hash(self.__class__) + + @classmethod + def from_dict(cls, d: dict) -> "PointJumpingFactory": + assert not d + return PointJumpingFactory() From f6c4b143381f3b0e9ed44b1b42d3d07c62da8fe9 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 15 Feb 2022 15:48:49 +0000 Subject: [PATCH 004/100] requirement and row and col placements strategy pack (#414) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * requirement and row and col placements strategy pack * f * skip test with database that is to slow Co-authored-by: Émile Nadeau Co-authored-by: Jay Pantone --- CHANGELOG.md | 1 + tests/test_strategy_pack.py | 13 +++++++++ tilings/strategy_pack.py | 54 +++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc9dda6e..c1e890c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ tiling after we first remove that requirement. This is added to multiple different obs and one requirement list of size possibly greater than one. Previously it was only doing the case where a single ob's factor is implied by a requirement. +- added `TileScopePack.requirement_and_row_and_col_placements` - `PointJumpingFactory` which adds rules where requirements and assumptions can be swapped around a fusable row or column. diff --git a/tests/test_strategy_pack.py b/tests/test_strategy_pack.py index d5461ef7..2eef78c5 100644 --- a/tests/test_strategy_pack.py +++ b/tests/test_strategy_pack.py @@ -70,6 +70,17 @@ def row_col_partial(pack): ] +def length_row_col_partial(pack): + return [ + pack(length=length, row_only=row_only, col_only=col_only, partial=partial) + for row_only, col_only, partial in product( + (True, False), (True, False), (True, False) + ) + if not row_only or not col_only + for length in (1, 2, 3) + ] + + packs = ( length(TileScopePack.all_the_strategies) + partial(TileScopePack.insertion_point_placements) @@ -87,6 +98,8 @@ def row_col_partial(pack): + length_partial(TileScopePack.requirement_placements) + row_col_partial(TileScopePack.row_and_col_placements) + length(TileScopePack.cell_insertions) + + length_row_col_partial(TileScopePack.point_and_row_and_col_placements) + + length_row_col_partial(TileScopePack.requirement_and_row_and_col_placements) ) packs.extend( diff --git a/tilings/strategy_pack.py b/tilings/strategy_pack.py index ebb1bde6..e58e4856 100644 --- a/tilings/strategy_pack.py +++ b/tilings/strategy_pack.py @@ -609,6 +609,60 @@ def point_and_row_and_col_placements( name=name, ) + @classmethod + def requirement_and_row_and_col_placements( + cls, + length: int = 1, + row_only: bool = False, + col_only: bool = False, + partial: bool = False, + ) -> "TileScopePack": + if row_only and col_only: + raise ValueError("Can't be row and col only.") + place_row = not col_only + place_col = not row_only + both = place_col and place_row + name = "".join( + [ + f"length_{length}_" if length > 1 else "", + "partial_" if partial else "", + "requirement_and_", + "row" if not col_only else "", + "_and_" if both else "", + "col" if not row_only else "", + "_placements", + ] + ) + rowcol_strat = strat.RowAndColumnPlacementFactory( + place_row=place_row, place_col=place_col, partial=partial + ) + + initial_strats: List[CSSstrategy] = [strat.FactorFactory()] + if length > 1: + initial_strats.append(strat.RequirementCorroborationFactory()) + + return TileScopePack( + initial_strats=initial_strats, + ver_strats=[ + strat.BasicVerificationStrategy(), + strat.InsertionEncodingVerificationStrategy(), + strat.OneByOneVerificationStrategy(), + strat.LocallyFactorableVerificationStrategy(), + ], + inferral_strats=[ + strat.RowColumnSeparationStrategy(), + strat.ObstructionTransitivityFactory(), + ], + expansion_strats=[ + [ + strat.RequirementInsertionFactory(maxreqlen=length), + strat.PatternPlacementFactory(partial=partial), + rowcol_strat, + ], + ], + name=name, + ) + @classmethod def cell_insertions(cls, length: int): return TileScopePack( From 8150b7301abc6d3226a7fd2334f4e1ba82d6b4ed Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 22 Feb 2022 14:31:28 +0000 Subject: [PATCH 005/100] limit the size of obs in minimal obs call (#418) * limit the size of obs in minimal obs call * if no obs, can't infer obs --- CHANGELOG.md | 3 +++ tilings/algorithms/gridded_perm_reduction.py | 6 +++++- tilings/algorithms/minimal_gridded_perms.py | 9 +++++++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1e890c7..1fa57063 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,9 @@ swapped around a fusable row or column. - Verification strategies no longer ignore parent - `TrackedSearcher` now uses a `TrackedQueue` and is able to work with all packs and new future strategies. +- The `GriddedPermReduction` limits the size of obstructions it tries to infer in + the `minimal_obs` method to the size of the largest obstruction already on the + tiling. ### Deprecated - Python 3.7 is no longer supported diff --git a/tilings/algorithms/gridded_perm_reduction.py b/tilings/algorithms/gridded_perm_reduction.py index b520148f..51f4383c 100644 --- a/tilings/algorithms/gridded_perm_reduction.py +++ b/tilings/algorithms/gridded_perm_reduction.py @@ -95,6 +95,8 @@ def minimal_obs(self) -> bool: Reduce the obstruction according to the requirements. Return True if something changed. """ + if not self._obstructions: + return False changed = False new_obs: Set[GriddedPerm] = set() for requirement in self.requirements: @@ -109,7 +111,9 @@ def minimal_obs(self) -> bool: ) for gp in requirement ), - ).minimal_gridded_perms() + ).minimal_gridded_perms( + max_length_to_build=max(map(len, self._obstructions)) + ) ) if new_obs: changed = True diff --git a/tilings/algorithms/minimal_gridded_perms.py b/tilings/algorithms/minimal_gridded_perms.py index 8dd324c2..bb0a6c1c 100644 --- a/tilings/algorithms/minimal_gridded_perms.py +++ b/tilings/algorithms/minimal_gridded_perms.py @@ -379,7 +379,7 @@ def insert_point( yield idx, nextgp def minimal_gridded_perms( - self, yield_non_minimal: bool = False + self, yield_non_minimal: bool = False, max_length_to_build: Optional[int] = None ) -> Iterator[GriddedPerm]: """ Yield all minimal gridded perms on the tiling. @@ -388,6 +388,9 @@ def minimal_gridded_perms( that are non-minimal, found by the initial_gp method. Even though it may not be minimal, this is useful when trying to determine whether or not a tiling is empty. + + If `max_length_to_build` it will only try to build minimal gridded + perms of size shorter than this. """ if not self.requirements: if GriddedPerm.empty_perm() not in self.obstructions: @@ -453,7 +456,9 @@ def _process_work_packet( # perm containing it. yielded.add(nextgp) yield nextgp - else: + elif ( + max_length_to_build is None or len(nextgp) < max_length_to_build + ): # Update the minimum index that we inserted a # a point into each cell. next_mindices = { From e1da37c8ac96e9e330b18ced44f777cddb4a7ee6 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 22 Feb 2022 14:34:00 +0000 Subject: [PATCH 006/100] make tracked also adds tracking int factor method (#419) --- CHANGELOG.md | 2 ++ tilings/strategy_pack.py | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fa57063..f4280e40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,8 @@ swapped around a fusable row or column. - Verification strategies no longer ignore parent - `TrackedSearcher` now uses a `TrackedQueue` and is able to work with all packs and new future strategies. +- `TileScopePack.make_tracked` will add the appropriate tracking methods for + interleaving factors. - The `GriddedPermReduction` limits the size of obstructions it tries to infer in the `minimal_obs` method to the size of the largest obstruction already on the tiling. diff --git a/tilings/strategy_pack.py b/tilings/strategy_pack.py index e58e4856..d3801552 100644 --- a/tilings/strategy_pack.py +++ b/tilings/strategy_pack.py @@ -123,6 +123,14 @@ def replace_list(strats): for strategy in strats: if isinstance(strategy, strat.FusionFactory): res.append(strategy.make_tracked()) + elif isinstance(strategy, strat.FactorFactory): + d = strategy.to_jsonable() + if not d["tracked"] and d["interleaving"] in ("all", "monotone"): + res.append( + strat.AddInterleavingAssumptionFactory(unions=d["unions"]) + ) + d["tracked"] = True + res.append(AbstractStrategy.from_dict(d)) else: res.append(strategy) return res From 82cad6c2144f4818c0ddaf48a5d6fe250010096e Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 22 Feb 2022 18:54:42 +0000 Subject: [PATCH 007/100] Deflation (#416) * resurrecting old deflation strategy * Add get equation method in the easy case * add test for json/repr methods * remove the skew/sum indec condition * unused import --- CHANGELOG.md | 3 + tests/strategies/test_encoding.py | 4 +- tilings/strategies/__init__.py | 3 + tilings/strategies/deflation.py | 301 ++++++++++++++++++++++++++++++ 4 files changed, 310 insertions(+), 1 deletion(-) create mode 100644 tilings/strategies/deflation.py diff --git a/CHANGELOG.md b/CHANGELOG.md index f4280e40..a433bf96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,9 @@ requirement. - added `TileScopePack.requirement_and_row_and_col_placements` - `PointJumpingFactory` which adds rules where requirements and assumptions can be swapped around a fusable row or column. +- `DeflationFactory` which adds rules where cells can be deflated into increasing or + decreasing cells as obstructions can't occur across the sum/skew components in that + cell. ### Fixed - `ForgetTrackedSearcher` was not retroactively applying strategies that had a `basis`. diff --git a/tests/strategies/test_encoding.py b/tests/strategies/test_encoding.py index 58729ab0..8d309279 100644 --- a/tests/strategies/test_encoding.py +++ b/tests/strategies/test_encoding.py @@ -14,6 +14,7 @@ CellInsertionFactory, ComponentFusionFactory, DatabaseVerificationStrategy, + DeflationFactory, ElementaryVerificationStrategy, EmptyCellInferralFactory, FactorFactory, @@ -400,7 +401,8 @@ def indices_and_row(strategy): + [ComponentFusionStrategy(row_idx=1)] + [ComponentFusionStrategy(col_idx=3)] + [ComponentFusionStrategy(col_idx=3)] - + [FusionFactory()] + + [FusionFactory(tracked=True), FusionFactory(tracked=False)] + + [DeflationFactory(tracked=True), DeflationFactory(tracked=False)] + [ComponentFusionFactory()] + [ObstructionInferralStrategy([GriddedPerm((0, 1, 2), ((0, 0), (1, 1), (1, 2)))])] + [ diff --git a/tilings/strategies/__init__.py b/tilings/strategies/__init__.py index 1d45bddc..81dbaa1d 100644 --- a/tilings/strategies/__init__.py +++ b/tilings/strategies/__init__.py @@ -1,5 +1,6 @@ from .assumption_insertion import AddAssumptionFactory, AddInterleavingAssumptionFactory from .assumption_splitting import SplittingStrategy +from .deflation import DeflationFactory from .detect_components import DetectComponentsStrategy from .experimental_verification import ( ShortObstructionVerificationStrategy, @@ -64,6 +65,8 @@ "RowAndColumnPlacementFactory", # Decomposition "FactorFactory", + # Deflation + "DeflationFactory", # Equivalence "PatternPlacementFactory", "PointJumpingFactory", diff --git a/tilings/strategies/deflation.py b/tilings/strategies/deflation.py new file mode 100644 index 00000000..30b26208 --- /dev/null +++ b/tilings/strategies/deflation.py @@ -0,0 +1,301 @@ +"""The deflation strategy.""" +from typing import Callable, Dict, Iterator, List, Optional, Tuple, cast + +import sympy + +from comb_spec_searcher import Constructor, Strategy, StrategyFactory +from comb_spec_searcher.exception import StrategyDoesNotApply +from comb_spec_searcher.typing import ( + Parameters, + RelianceProfile, + SubObjects, + SubRecs, + SubSamplers, + SubTerms, + Terms, +) +from permuta import Perm +from tilings import GriddedPerm, Tiling +from tilings.assumptions import TrackingAssumption + +Cell = Tuple[int, int] + + +class DeflationConstructor(Constructor): + def __init__(self, parameter: str): + self.parameter = parameter + + def get_equation( + self, lhs_func: sympy.Function, rhs_funcs: Tuple[sympy.Function, ...] + ) -> sympy.Eq: + raise NotImplementedError + + def reliance_profile(self, n: int, **parameters: int) -> RelianceProfile: + raise NotImplementedError + + def get_terms( + self, parent_terms: Callable[[int], Terms], subterms: SubTerms, n: int + ) -> Terms: + raise NotImplementedError + + def get_sub_objects( + self, subobjs: SubObjects, n: int + ) -> Iterator[Tuple[Parameters, Tuple[List[Optional[GriddedPerm]]]]]: + raise NotImplementedError + + def random_sample_sub_objects( + self, + parent_count: int, + subsamplers: SubSamplers, + subrecs: SubRecs, + n: int, + **parameters: int, + ) -> Tuple[GriddedPerm]: + raise NotImplementedError + + def equiv( + self, other: "Constructor", data: Optional[object] = None + ) -> Tuple[bool, Optional[object]]: + raise NotImplementedError + + +class DeflationStrategy(Strategy[Tiling, GriddedPerm]): + def __init__( + self, + cell: Cell, + sum_deflate: bool, + tracked: bool = True, + ): + self.cell = cell + self.tracked = tracked + self.sum_deflate = sum_deflate + super().__init__() + + @staticmethod + def can_be_equivalent() -> bool: + return False + + @staticmethod + def is_two_way(comb_class: Tiling) -> bool: + return False + + @staticmethod + def is_reversible(comb_class: Tiling) -> bool: + return False + + @staticmethod + def shifts( + comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None + ) -> Tuple[int, ...]: + return (0, 0) + + def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling, Tiling]: + if self.sum_deflate: + extra = Perm((1, 0)) + else: + extra = Perm((0, 1)) + deflated_tiling = comb_class.add_obstruction(extra, (self.cell, self.cell)) + local_basis = comb_class.sub_tiling([self.cell]) + if self.tracked: + return ( + deflated_tiling.add_assumption( + TrackingAssumption.from_cells([self.cell]) + ), + local_basis, + ) + return deflated_tiling, local_basis + + def constructor( + self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None + ) -> DeflationConstructor: + if not self.tracked: + raise NotImplementedError("The deflation strategy was not tracked") + if children is None: + children = self.decomposition_function(comb_class) + if children is None: + raise StrategyDoesNotApply("Can't deflate the cell") + ass = TrackingAssumption.from_cells([self.cell]) + child_param = children[0].get_assumption_parameter(ass) + gp = GriddedPerm.point_perm(self.cell) + if any(gp in assumption.gps for assumption in comb_class.assumptions): + raise NotImplementedError + return DeflationConstructor(child_param) + + def reverse_constructor( + self, + idx: int, + comb_class: Tiling, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Constructor: + if children is None: + children = self.decomposition_function(comb_class) + if children is None: + raise StrategyDoesNotApply("Can't deflate the cell") + raise NotImplementedError + + def extra_parameters( + self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None + ) -> Tuple[Dict[str, str]]: + if children is None: + children = self.decomposition_function(comb_class) + if children is None: + raise StrategyDoesNotApply("Strategy does not apply") + raise NotImplementedError + + def formal_step(self) -> str: + return f"deflating cell {self.cell}" + + def backward_map( + self, + comb_class: Tiling, + objs: Tuple[Optional[GriddedPerm], ...], + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Iterator[GriddedPerm]: + if children is None: + children = self.decomposition_function(comb_class) + raise NotImplementedError + + def forward_map( + self, + comb_class: Tiling, + obj: GriddedPerm, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Tuple[Optional[GriddedPerm], ...]: + if children is None: + children = self.decomposition_function(comb_class) + raise NotImplementedError + + def to_jsonable(self) -> dict: + d: dict = super().to_jsonable() + d.pop("ignore_parent") + d.pop("inferrable") + d.pop("possibly_empty") + d.pop("workable") + d["cell"] = self.cell + d["tracked"] = self.tracked + d["sum_deflate"] = self.sum_deflate + return d + + def __repr__(self) -> str: + args = ", ".join( + [ + f"cell={self.cell!r}", + f"sum_deflate={self.sum_deflate!r}", + f"tracked={self.tracked!r}", + ] + ) + return f"{self.__class__.__name__}({args})" + + @classmethod + def from_dict(cls, d: dict) -> "DeflationStrategy": + cell = d.pop("cell") + tracked = d.pop("tracked") + assert not d + return cls(cell, tracked) + + @staticmethod + def get_eq_symbol() -> str: + return "↣" + + +class DeflationFactory(StrategyFactory[Tiling]): + def __init__(self, tracked: bool): + self.tracked = tracked + super().__init__() + + def __call__(self, comb_class: Tiling) -> Iterator[DeflationStrategy]: + if comb_class.requirements: + # TODO: this is obviously too strong + return + for cell in comb_class.active_cells: + if self.can_deflate(comb_class, cell, True): + yield DeflationStrategy(cell, True, self.tracked) + if self.can_deflate(comb_class, cell, False): + yield DeflationStrategy(cell, False, self.tracked) + + @staticmethod + def can_deflate(tiling: Tiling, cell: Cell, sum_deflate: bool) -> bool: + alone_in_row = tiling.only_cell_in_row(cell) + alone_in_col = tiling.only_cell_in_col(cell) + + if alone_in_row and alone_in_col: + return False + + deflate_patt = GriddedPerm.single_cell( + Perm((1, 0)) if sum_deflate else Perm((0, 1)), cell + ) + + # we must be sure that no cell in a row or column can interleave + # with any reinflated components, so collect cells that do not. + cells_not_interleaving = set([cell]) + + for ob in tiling.obstructions: + if ob == deflate_patt: + break # False + if ob.is_single_cell() or not ob.occupies(cell): + continue + number_points_in_cell = sum(1 for c in ob.pos if c == cell) + if number_points_in_cell == 1: + if len(ob) == 2: + # not interleaving with cell as separating if + # in same row or column + other_cell = [c for c in ob.pos if c != cell][0] + cells_not_interleaving.add(other_cell) + elif number_points_in_cell == 2: + if len(ob) != 3: + break # False + patt_in_cell = ob.get_gridded_perm_in_cells((cell,)) + if patt_in_cell != deflate_patt: + # you can interleave with components + break # False + # we need the other cell to be in between the intended deflate + # patt in either the row or column + other_cell = [c for c in ob.pos if c != cell][0] + if DeflationFactory.point_in_between( + ob, True, cell, other_cell + ) or DeflationFactory.point_in_between(ob, False, cell, other_cell): + # this cell does not interleave with inflated components + cells_not_interleaving.add(other_cell) + continue + break # False + elif number_points_in_cell >= 3: + # you can interleave with components + break # False + else: + # check that do not interleave with any cells in row or column. + return cells_not_interleaving >= tiling.cells_in_row( + cell[1] + ) and cells_not_interleaving >= tiling.cells_in_col(cell[0]) + return False + + @staticmethod + def point_in_between( + ob: GriddedPerm, row: bool, cell: Cell, other_cell: Cell + ) -> bool: + """Return true if point in other cell is in between point in cell. + Assumes a length 3 pattern, and to be told if row or column.""" + patt = cast(Tuple[int, int, int], ob.patt) + if row: + left = other_cell[0] < cell[0] + if left: + return bool(patt[0] == 1) + return bool(patt[2] == 1) + below = other_cell[1] < cell[1] + if below: + return bool(patt[1] == 0) + return bool(patt[1] == 2) + + def __repr__(self) -> str: + return self.__class__.__name__ + f"({self.tracked})" + + def __str__(self) -> str: + return ("tracked " if self.tracked else "") + "deflation factory" + + def to_jsonable(self) -> dict: + d: dict = super().to_jsonable() + d["tracked"] = self.tracked + return d + + @classmethod + def from_dict(cls, d: dict) -> "DeflationFactory": + return cls(d["tracked"]) From 0de23144f20973706eee30278c5090a02988b041 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 1 Mar 2022 14:17:08 +0000 Subject: [PATCH 008/100] stop reinitialising tilings in the from dict method (#424) --- CHANGELOG.md | 1 + tilings/tiling.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a433bf96..f7b4ff34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ swapped around a fusable row or column. - The `GriddedPermReduction` limits the size of obstructions it tries to infer in the `minimal_obs` method to the size of the largest obstruction already on the tiling. +- Don't reinitialise in the `Tiling.from_dict` method. ### Deprecated - Python 3.7 is no longer supported diff --git a/tilings/tiling.py b/tilings/tiling.py index b97233d1..9ae97cf1 100644 --- a/tilings/tiling.py +++ b/tilings/tiling.py @@ -545,6 +545,10 @@ def from_dict(cls, d: dict) -> "Tiling": obstructions=obstructions, requirements=requirements, assumptions=assumptions, + remove_empty_rows_and_cols=False, + derive_empty=False, + simplify=False, + sorted_input=True, ) # ------------------------------------------------------------- From 8c275cc3587e879b9004bddd6764bdc822e71dd0 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 1 Mar 2022 14:29:27 +0000 Subject: [PATCH 009/100] add empty cell obs during placement (#423) * add empty cell obs during placement * CHANGELOG.md * update partial placement test * updating black version * black formatting --- .pre-commit-config.yaml | 2 +- CHANGELOG.md | 2 + tests/algorithms/test_enumeration.py | 42 +++++++++--------- tests/strategies/test_verification.py | 18 ++++---- tests/test_tiling.py | 47 +++++---------------- tilings/algorithms/enumeration.py | 4 +- tilings/algorithms/requirement_placement.py | 8 ++++ tox.ini | 2 +- 8 files changed, 55 insertions(+), 70 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 765ae975..cf81be99 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ repos: - repo: https://github.com/psf/black - rev: 21.10b0 + rev: 22.1.0 hooks: - id: black diff --git a/CHANGELOG.md b/CHANGELOG.md index f7b4ff34..c665a53a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,8 @@ swapped around a fusable row or column. - The `GriddedPermReduction` limits the size of obstructions it tries to infer in the `minimal_obs` method to the size of the largest obstruction already on the tiling. +- `RequirementPlacement` adds empty cells when placing a point cell. This saves + some inferral in partial placements. - Don't reinitialise in the `Tiling.from_dict` method. ### Deprecated diff --git a/tests/algorithms/test_enumeration.py b/tests/algorithms/test_enumeration.py index 4a17469c..659c462e 100644 --- a/tests/algorithms/test_enumeration.py +++ b/tests/algorithms/test_enumeration.py @@ -256,10 +256,10 @@ def test_get_genf(self, enum_verified): x = sympy.Symbol("x") expected_gf = -( sympy.sqrt( - -(4 * x ** 3 - 14 * x ** 2 + 8 * x - 1) / (2 * x ** 2 - 4 * x + 1) + -(4 * x**3 - 14 * x**2 + 8 * x - 1) / (2 * x**2 - 4 * x + 1) ) - 1 - ) / (2 * x * (x ** 2 - 3 * x + 1)) + ) / (2 * x * (x**2 - 3 * x + 1)) assert sympy.simplify(enum_verified.get_genf() - expected_gf) == 0 t = Tiling( obstructions=[ @@ -318,18 +318,18 @@ def test_interleave_fixed_length(self, enum_verified): cell_var = enum_verified._cell_variable((1, 0)) dummy_var = enum_verified._cell_variable((0, 0)) x = sympy.var("x") - F = x ** 8 * track_var ** 3 * dummy_var ** 3 + F = x**8 * track_var**3 * dummy_var**3 assert ( enum_verified._interleave_fixed_length(F, (1, 0), 1) - == 4 * x ** 9 * dummy_var ** 3 * cell_var ** 1 + == 4 * x**9 * dummy_var**3 * cell_var**1 ) assert ( enum_verified._interleave_fixed_length(F, (1, 0), 3) - == 20 * x ** 11 * dummy_var ** 3 * cell_var ** 3 + == 20 * x**11 * dummy_var**3 * cell_var**3 ) assert ( enum_verified._interleave_fixed_length(F, (1, 0), 0) - == x ** 8 * dummy_var ** 3 + == x**8 * dummy_var**3 ) def test_interleave_fixed_lengths(self, enum_verified): @@ -337,30 +337,30 @@ def test_interleave_fixed_lengths(self, enum_verified): cell_var = enum_verified._cell_variable((1, 0)) dummy_var = enum_verified._cell_variable((0, 0)) x = sympy.var("x") - F = x ** 8 * track_var ** 3 * dummy_var ** 3 + F = x**8 * track_var**3 * dummy_var**3 assert ( enum_verified._interleave_fixed_lengths(F, (1, 0), 1, 1) - == 4 * x ** 9 * dummy_var ** 3 * cell_var ** 1 + == 4 * x**9 * dummy_var**3 * cell_var**1 ) assert ( enum_verified._interleave_fixed_lengths(F, (1, 0), 3, 3) - == 20 * x ** 11 * dummy_var ** 3 * cell_var ** 3 + == 20 * x**11 * dummy_var**3 * cell_var**3 ) assert ( enum_verified._interleave_fixed_lengths(F, (1, 0), 0, 0) - == x ** 8 * dummy_var ** 3 + == x**8 * dummy_var**3 ) assert ( enum_verified._interleave_fixed_lengths(F, (1, 0), 0, 2) - == x ** 8 * dummy_var ** 3 - + 4 * x ** 9 * dummy_var ** 3 * cell_var ** 1 - + 10 * x ** 10 * dummy_var ** 3 * cell_var ** 2 + == x**8 * dummy_var**3 + + 4 * x**9 * dummy_var**3 * cell_var**1 + + 10 * x**10 * dummy_var**3 * cell_var**2 ) assert ( enum_verified._interleave_fixed_lengths(F, (1, 0), 1, 3) - == 4 * x ** 9 * dummy_var ** 3 * cell_var ** 1 - + 10 * x ** 10 * dummy_var ** 3 * cell_var ** 2 - + 20 * x ** 11 * dummy_var ** 3 * cell_var ** 3 + == 4 * x**9 * dummy_var**3 * cell_var**1 + + 10 * x**10 * dummy_var**3 * cell_var**2 + + 20 * x**11 * dummy_var**3 * cell_var**3 ) def test_genf_with_req(self): @@ -399,11 +399,11 @@ def test_genf_with_big_finite_cell(self): genf == 1 + 2 * x - + 4 * x ** 2 - + 8 * x ** 3 - + 14 * x ** 4 - + 20 * x ** 5 - + 20 * x ** 6 + + 4 * x**2 + + 8 * x**3 + + 14 * x**4 + + 20 * x**5 + + 20 * x**6 ) def test_with_two_reqs(self): diff --git a/tests/strategies/test_verification.py b/tests/strategies/test_verification.py index d78da88c..fe7ffd0c 100644 --- a/tests/strategies/test_verification.py +++ b/tests/strategies/test_verification.py @@ -192,7 +192,7 @@ def test_get_genf(self, strategy, enum_verified): x = sympy.var("x") assert ( sympy.simplify( - strategy.get_genf(enum_verified[0]) - 1 / (2 * x ** 2 - 3 * x + 1) + strategy.get_genf(enum_verified[0]) - 1 / (2 * x**2 - 3 * x + 1) ) == 0 ) @@ -566,7 +566,7 @@ def test_get_specification(self, strategy, enum_verified): def test_get_genf(self, strategy, enum_verified): x = sympy.Symbol("x") - expected_gf = (1 - x) / (4 * x ** 2 - 4 * x + 1) + expected_gf = (1 - x) / (4 * x**2 - 4 * x + 1) assert sympy.simplify(strategy.get_genf(enum_verified[0]) - expected_gf) == 0 @@ -673,10 +673,10 @@ def test_get_genf(self, strategy, enum_verified): x = sympy.Symbol("x") expected_gf = -( sympy.sqrt( - -(4 * x ** 3 - 14 * x ** 2 + 8 * x - 1) / (2 * x ** 2 - 4 * x + 1) + -(4 * x**3 - 14 * x**2 + 8 * x - 1) / (2 * x**2 - 4 * x + 1) ) - 1 - ) / (2 * x * (x ** 2 - 3 * x + 1)) + ) / (2 * x * (x**2 - 3 * x + 1)) assert sympy.simplify(strategy.get_genf(enum_verified[0]) - expected_gf) == 0 expected_gf = -1 / ((x - 1) * (x / (x - 1) + 1)) @@ -756,11 +756,11 @@ def test_genf_with_big_finite_cell(self, strategy): genf == 1 + 2 * x - + 4 * x ** 2 - + 8 * x ** 3 - + 14 * x ** 4 - + 20 * x ** 5 - + 20 * x ** 6 + + 4 * x**2 + + 8 * x**3 + + 14 * x**4 + + 20 * x**5 + + 20 * x**6 ) def test_with_two_reqs(self, strategy): diff --git a/tests/test_tiling.py b/tests/test_tiling.py index dce056e2..53fdca0e 100644 --- a/tests/test_tiling.py +++ b/tests/test_tiling.py @@ -2212,29 +2212,7 @@ def test_partial_place_row(obs_inf_til): def test_partial_place_col(obs_inf_til): assert set(obs_inf_til.partial_place_col(0, 0)) == set( [ - Tiling( - obstructions=( - GriddedPerm((0,), ((1, 0),)), - GriddedPerm((0,), ((1, 2),)), - GriddedPerm((0, 1), ((0, 1), (0, 1))), - GriddedPerm((0, 1), ((0, 1), (1, 1))), - GriddedPerm((0, 1), ((1, 1), (1, 1))), - GriddedPerm((1, 0), ((0, 0), (0, 0))), - GriddedPerm((1, 0), ((0, 1), (0, 1))), - GriddedPerm((1, 0), ((0, 1), (1, 1))), - GriddedPerm((1, 0), ((1, 1), (1, 1))), - GriddedPerm((0, 2, 1), ((0, 0), (0, 2), (0, 2))), - GriddedPerm((0, 3, 2, 1), ((0, 0), (0, 2), (0, 1), (0, 0))), - GriddedPerm((0, 3, 2, 1), ((0, 1), (0, 2), (0, 2), (0, 2))), - GriddedPerm((0, 3, 2, 1), ((0, 2), (0, 2), (0, 2), (0, 2))), - GriddedPerm((1, 0, 3, 2), ((0, 2), (0, 1), (0, 2), (0, 2))), - GriddedPerm((1, 0, 3, 2), ((0, 2), (0, 2), (0, 2), (0, 2))), - ), - requirements=( - (GriddedPerm((0,), ((1, 1),)),), - (GriddedPerm((1, 0), ((0, 1), (0, 0))),), - ), - ), + Tiling([GriddedPerm(tuple(), tuple())]), Tiling( obstructions=( GriddedPerm((0,), ((1, 1),)), @@ -2402,19 +2380,16 @@ def test_not_enumerable(self): def test_enmerate_gp_up_to(): - assert ( - Tiling( - obstructions=( - GriddedPerm((0, 1), ((1, 2), (1, 2))), - GriddedPerm((1, 0), ((1, 2), (1, 2))), - GriddedPerm((0, 2, 1), ((0, 1), (0, 1), (0, 1))), - GriddedPerm((0, 2, 1), ((2, 0), (2, 0), (2, 0))), - ), - requirements=((GriddedPerm((0,), ((1, 2),)),),), - assumptions=(), - ).enmerate_gp_up_to(8) - == [0, 1, 2, 5, 14, 42, 132, 429, 1430] - ) + assert Tiling( + obstructions=( + GriddedPerm((0, 1), ((1, 2), (1, 2))), + GriddedPerm((1, 0), ((1, 2), (1, 2))), + GriddedPerm((0, 2, 1), ((0, 1), (0, 1), (0, 1))), + GriddedPerm((0, 2, 1), ((2, 0), (2, 0), (2, 0))), + ), + requirements=((GriddedPerm((0,), ((1, 2),)),),), + assumptions=(), + ).enmerate_gp_up_to(8) == [0, 1, 2, 5, 14, 42, 132, 429, 1430] def test_column_reverse(): diff --git a/tilings/algorithms/enumeration.py b/tilings/algorithms/enumeration.py index dcb5ccb7..0db58853 100644 --- a/tilings/algorithms/enumeration.py +++ b/tilings/algorithms/enumeration.py @@ -289,11 +289,11 @@ def _interleave_fixed_length(self, F, cell, num_point): `MonotoneTreeEnumeration._tracking_var` in `F`. A variable is added to track the number of point in cell. """ - new_genf = self._tracking_var ** num_point * F + new_genf = self._tracking_var**num_point * F for i in range(1, num_point + 1): new_genf = diff(new_genf, self._tracking_var) / i new_genf *= self._cell_variable(cell) ** num_point - new_genf *= x ** num_point + new_genf *= x**num_point return new_genf.subs({self._tracking_var: 1}) def _cell_num_point(self, cell): diff --git a/tilings/algorithms/requirement_placement.py b/tilings/algorithms/requirement_placement.py index 56f427e4..a30f409a 100644 --- a/tilings/algorithms/requirement_placement.py +++ b/tilings/algorithms/requirement_placement.py @@ -301,6 +301,14 @@ def forced_obstructions_from_requirement( """ placed_cell = self._placed_cell(cell) res = [] + if cell in self._tiling.point_cells: + x, y = placed_cell + if self.own_row: + res.append(GriddedPerm.point_perm((x, y + 1))) + res.append(GriddedPerm.point_perm((x, y - 1))) + if self.own_col: + res.append(GriddedPerm.point_perm((x + 1, y))) + res.append(GriddedPerm.point_perm((x - 1, y))) for idx, gp in zip(indices, gps): # if cell is farther in the direction than gp[idx], then don't need # to avoid any of the stretched grided perms diff --git a/tox.ini b/tox.ini index 62ccfa16..c9c98848 100644 --- a/tox.ini +++ b/tox.ini @@ -60,5 +60,5 @@ commands = mypy description = check that comply with autoformating basepython = {[default]basepython} deps = - black==21.10b0 + black==22.1.0 commands = black --check --diff . From de27640eb2f8ccd42297a2dfe600e67b817952bc Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 1 Mar 2022 14:31:23 +0000 Subject: [PATCH 010/100] make tracked works with deflation and every other future tracked strategy (#425) --- CHANGELOG.md | 2 +- tilings/strategy_pack.py | 46 +++++++++++++++++++++++----------------- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c665a53a..2aa4b461 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ swapped around a fusable row or column. - `TrackedSearcher` now uses a `TrackedQueue` and is able to work with all packs and new future strategies. - `TileScopePack.make_tracked` will add the appropriate tracking methods for - interleaving factors. + interleaving factors and make strategies tracked if it can be. - The `GriddedPermReduction` limits the size of obstructions it tries to infer in the `minimal_obs` method to the size of the largest obstruction already on the tiling. diff --git a/tilings/strategy_pack.py b/tilings/strategy_pack.py index d3801552..ff04a63a 100644 --- a/tilings/strategy_pack.py +++ b/tilings/strategy_pack.py @@ -118,36 +118,42 @@ def make_tracked(self): """Make a fusion pack tracked.""" def replace_list(strats): - """Return a new list with the replaced fusion strat.""" + """Return a new list with strats tracked.""" res = [] for strategy in strats: - if isinstance(strategy, strat.FusionFactory): - res.append(strategy.make_tracked()) - elif isinstance(strategy, strat.FactorFactory): - d = strategy.to_jsonable() + d = strategy.to_jsonable() + if "interleaving" in d: if not d["tracked"] and d["interleaving"] in ("all", "monotone"): res.append( strat.AddInterleavingAssumptionFactory(unions=d["unions"]) ) + if not d.get("tracked", True): d["tracked"] = True - res.append(AbstractStrategy.from_dict(d)) - else: - res.append(strategy) + strategy = AbstractStrategy.from_dict(d) + res.append(strategy) return res - return ( - self.__class__( - ver_strats=replace_list(self.ver_strats), - inferral_strats=replace_list(self.inferral_strats), - initial_strats=replace_list(self.initial_strats), - expansion_strats=list(map(replace_list, self.expansion_strats)), - name=self.name, - symmetries=self.symmetries, - iterative=self.iterative, - ) - .add_initial(strat.AddAssumptionFactory(), apply_first=True) - .add_initial(strat.RearrangeAssumptionFactory(), apply_first=True) + pack = self.__class__( + ver_strats=replace_list(self.ver_strats), + inferral_strats=replace_list(self.inferral_strats), + initial_strats=replace_list(self.initial_strats), + expansion_strats=list(map(replace_list, self.expansion_strats)), + name=self.name.replace("untracked", "tracked"), + symmetries=self.symmetries, + iterative=self.iterative, ) + if all( + not isinstance(strategy, strat.AddAssumptionFactory) for strategy in pack + ): + pack = pack.add_initial(strat.AddAssumptionFactory(), apply_first=True) + if all( + not isinstance(strategy, strat.RearrangeAssumptionFactory) + for strategy in pack + ): + pack = pack.add_initial( + strat.RearrangeAssumptionFactory(), apply_first=True + ) + return pack def make_fusion( self, From 6cf95b4f38b86012888ab3dfddcaaa697391ec96 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 1 Mar 2022 14:50:33 +0000 Subject: [PATCH 011/100] Selective symmetries (#420) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * a selective symmetry factory * replace symmetries with this new class, default to all syms * no basis given = all syms * mypy * changelog Co-authored-by: Jay Pantone Co-authored-by: Émile Nadeau --- CHANGELOG.md | 3 + tilings/strategies/symmetry.py | 151 ++++++++++++++++++++++++--------- tilings/strategy_pack.py | 6 +- 3 files changed, 121 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2aa4b461..a30740ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,9 @@ swapped around a fusable row or column. - The `GriddedPermReduction` limits the size of obstructions it tries to infer in the `minimal_obs` method to the size of the largest obstruction already on the tiling. +- The `SymmetriesFactory` takes a basis and will not return any symmetries where + any of the patterns of the obstruction are not subpatterns of some basis element. + If no basis is given, all symmetries are returned. - `RequirementPlacement` adds empty cells when placing a point cell. This saves some inferral in partial placements. - Don't reinitialise in the `Tiling.from_dict` method. diff --git a/tilings/strategies/symmetry.py b/tilings/strategies/symmetry.py index 54a8fe85..4e7a0421 100644 --- a/tilings/strategies/symmetry.py +++ b/tilings/strategies/symmetry.py @@ -1,9 +1,11 @@ import abc from functools import partial -from typing import Dict, Iterator, Optional, Tuple, cast +from itertools import chain, combinations +from typing import Dict, Iterable, Iterator, List, Optional, Set, Tuple, cast from comb_spec_searcher import StrategyFactory, SymmetryStrategy from comb_spec_searcher.exception import StrategyDoesNotApply +from permuta import Perm from tilings import GriddedPerm, Tiling __all__ = ("SymmetriesFactory",) @@ -263,47 +265,120 @@ def __str__(self) -> str: class SymmetriesFactory(StrategyFactory[Tiling]): """ - Yield all symmetry strategies for a tiling. + Yield symmetry strategies such that all of the underlying patterns of + obstructions of the symmetric tiling are subpatterns of the given basis. """ - def __call__(self, comb_class: Tiling) -> Iterator[TilingSymmetryStrategy]: - def strategy(rotations: int, inverse: bool) -> TilingSymmetryStrategy: - # pylint: disable=too-many-return-statements - if rotations == 0: - if inverse: - return TilingInverse() - if rotations == 1: - if inverse: - return TilingReverse() - return TilingRotate90() - if rotations == 2: - if inverse: - return TilingAntidiagonal() - return TilingRotate180() - if rotations == 3: - if inverse: - return TilingComplement() - return TilingRotate270() - - symmetries = set([comb_class]) - for rotations in range(4): - if comb_class not in symmetries: - yield strategy(rotations, False) - symmetries.add(comb_class) - comb_class_inverse = comb_class.inverse() - if comb_class_inverse not in symmetries: - yield strategy(rotations, True) - symmetries.add(comb_class_inverse) - comb_class = comb_class.rotate90() - if comb_class in symmetries: - break + def __init__( + self, + basis: Optional[Iterable[Perm]] = None, + ): + self._basis = tuple(basis) if basis is not None else None + if self._basis is not None: + assert all( + isinstance(p, Perm) for p in self._basis + ), "Element of the basis must be Perm" + self.subpatterns: Set[Perm] = set( + chain.from_iterable(self._subpatterns(p) for p in self._basis) + ) + self.acceptablesubpatterns: List[Set[Perm]] = [ + set(p for p in self.subpatterns if p.rotate() in self.subpatterns), + set(p for p in self.subpatterns if p.rotate(2) in self.subpatterns), + set(p for p in self.subpatterns if p.rotate(3) in self.subpatterns), + set(p for p in self.subpatterns if p.inverse() in self.subpatterns), + set(p for p in self.subpatterns if p.reverse() in self.subpatterns), + set( + p + for p in self.subpatterns + if p.flip_antidiagonal() in self.subpatterns + ), + set(p for p in self.subpatterns if p.complement() in self.subpatterns), + ] + super().__init__() + + def __call__(self, tiling: Tiling) -> Iterator[TilingSymmetryStrategy]: + underlying_patts = set(gp.patt for gp in tiling.obstructions) + if ( + self._basis is None + or all(p in self.acceptablesubpatterns[0] for p in underlying_patts) + or self._basis is None + ): + yield TilingRotate90() + if ( + self._basis is None + or all(p in self.acceptablesubpatterns[1] for p in underlying_patts) + or self._basis is None + ): + yield TilingRotate180() + if ( + self._basis is None + or all(p in self.acceptablesubpatterns[2] for p in underlying_patts) + or self._basis is None + ): + yield TilingRotate270() + if ( + self._basis is None + or all(p in self.acceptablesubpatterns[3] for p in underlying_patts) + or self._basis is None + ): + yield TilingInverse() + if ( + self._basis is None + or all(p in self.acceptablesubpatterns[4] for p in underlying_patts) + or self._basis is None + ): + yield TilingReverse() + if ( + self._basis is None + or all(p in self.acceptablesubpatterns[5] for p in underlying_patts) + or self._basis is None + ): + yield TilingAntidiagonal() + if ( + self._basis is None + or all(p in self.acceptablesubpatterns[6] for p in underlying_patts) + or self._basis is None + ): + yield TilingComplement() - def __str__(self) -> str: - return "all symmetries" + @staticmethod + def _subpatterns(perm: Perm) -> Iterator[Perm]: + for n in range(len(perm) + 1): + for indices in combinations(range(len(perm)), n): + yield Perm.to_standard([perm[i] for i in indices]) - def __repr__(self) -> str: - return self.__class__.__name__ + "()" + def change_basis( + self, + basis: Iterable[Perm], + ) -> "SymmetriesFactory": + """ + Return the version of the strategy with the given basis instead + of the current one. + """ + return self.__class__(tuple(basis)) + + @property + def basis(self) -> Optional[Tuple[Perm, ...]]: + return self._basis + + def to_jsonable(self) -> dict: + d: dict = super().to_jsonable() + d["basis"] = self._basis + return d @classmethod def from_dict(cls, d: dict) -> "SymmetriesFactory": - return cls() + if "basis" in d and d["basis"] is not None: + basis: Optional[List[Perm]] = [Perm(p) for p in d.pop("basis")] + else: + basis = d.pop("basis", None) + return cls(basis=basis) + + def __str__(self) -> str: + if self._basis is not None: + basis = ", ".join(str(p) for p in self._basis) + return f"symmetries in Av({basis})" + return "all symmetries" + + def __repr__(self) -> str: + return f"{self.__class__.__name__}(basis={self._basis})" diff --git a/tilings/strategy_pack.py b/tilings/strategy_pack.py index ff04a63a..c8f032ff 100644 --- a/tilings/strategy_pack.py +++ b/tilings/strategy_pack.py @@ -40,6 +40,10 @@ def replace_list(strats): if strategy.basis: logger.warning("Basis changed in %s", strategy) res.append(strategy.change_basis(basis, symmetry)) + elif isinstance(strategy, strat.SymmetriesFactory): + if strategy.basis: + logger.warning("Basis changed in %s", strategy) + res.append(strategy.change_basis(basis)) else: res.append(strategy) return res @@ -50,7 +54,7 @@ def replace_list(strats): initial_strats=replace_list(self.initial_strats), expansion_strats=list(map(replace_list, self.expansion_strats)), name=self.name, - symmetries=self.symmetries, + symmetries=replace_list(self.symmetries), iterative=self.iterative, ) From c27a17494bdd86a6278ae929627613772a2f59be Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 8 Mar 2022 14:07:49 +0000 Subject: [PATCH 012/100] monotone sliding (#417) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * an alternative sliding strategy based on a fusion argument * add encoding tests * if a column, then rotate -> slide -> rotate back" Co-authored-by: Émile Nadeau --- CHANGELOG.md | 3 + tests/strategies/test_encoding.py | 3 + tilings/strategies/__init__.py | 2 + tilings/strategies/monotone_sliding.py | 165 +++++++++++++++++++++++++ 4 files changed, 173 insertions(+) create mode 100644 tilings/strategies/monotone_sliding.py diff --git a/CHANGELOG.md b/CHANGELOG.md index a30740ba..5eddf6b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,9 @@ requirement. - added `TileScopePack.requirement_and_row_and_col_placements` - `PointJumpingFactory` which adds rules where requirements and assumptions can be swapped around a fusable row or column. +- `MonotoneSlidingFactory` that creates rules that swaps neighbouring cells if they + are 'monotone' fusable, i.e., they are a generalized fusion with a monotone local + extra obstruction. - `DeflationFactory` which adds rules where cells can be deflated into increasing or decreasing cells as obstructions can't occur across the sum/skew components in that cell. diff --git a/tests/strategies/test_encoding.py b/tests/strategies/test_encoding.py index 8d309279..ec812573 100644 --- a/tests/strategies/test_encoding.py +++ b/tests/strategies/test_encoding.py @@ -22,6 +22,7 @@ FusionFactory, LocallyFactorableVerificationStrategy, LocalVerificationStrategy, + MonotoneSlidingFactory, MonotoneTreeVerificationStrategy, ObstructionInferralFactory, ObstructionTransitivityFactory, @@ -50,6 +51,7 @@ FactorWithMonotoneInterleavingStrategy, ) from tilings.strategies.fusion import ComponentFusionStrategy, FusionStrategy +from tilings.strategies.monotone_sliding import GeneralizedSlidingStrategy from tilings.strategies.obstruction_inferral import ObstructionInferralStrategy from tilings.strategies.point_jumping import PointJumpingStrategy from tilings.strategies.rearrange_assumption import RearrangeAssumptionStrategy @@ -421,6 +423,7 @@ def indices_and_row(strategy): ) ] + [PointJumpingFactory()] + + [MonotoneSlidingFactory(), GeneralizedSlidingStrategy(1)] + indices_and_row(PointJumpingStrategy) ) diff --git a/tilings/strategies/__init__.py b/tilings/strategies/__init__.py index 81dbaa1d..34b50b36 100644 --- a/tilings/strategies/__init__.py +++ b/tilings/strategies/__init__.py @@ -8,6 +8,7 @@ ) from .factor import FactorFactory from .fusion import ComponentFusionFactory, FusionFactory +from .monotone_sliding import MonotoneSlidingFactory from .obstruction_inferral import ( EmptyCellInferralFactory, ObstructionInferralFactory, @@ -68,6 +69,7 @@ # Deflation "DeflationFactory", # Equivalence + "MonotoneSlidingFactory", "PatternPlacementFactory", "PointJumpingFactory", "SlidingFactory", diff --git a/tilings/strategies/monotone_sliding.py b/tilings/strategies/monotone_sliding.py new file mode 100644 index 00000000..95a6ef09 --- /dev/null +++ b/tilings/strategies/monotone_sliding.py @@ -0,0 +1,165 @@ +from itertools import chain +from typing import Dict, Iterator, Optional, Tuple + +from comb_spec_searcher import DisjointUnionStrategy, StrategyFactory +from comb_spec_searcher.exception import StrategyDoesNotApply +from tilings import GriddedPerm, Tiling, TrackingAssumption +from tilings.algorithms import Fusion + + +class GeneralizedSlidingStrategy(DisjointUnionStrategy[Tiling, GriddedPerm]): + """ + A strategy that slides column idx and idx + 1. + """ + + def __init__(self, idx: int, rotate: bool = False): + super().__init__() + self.idx = idx + self.rotate = rotate + + def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling]: + if self.rotate: + comb_class = comb_class.rotate270() + child = Tiling( + self.slide_gps(comb_class.obstructions), + map(self.slide_gps, comb_class.requirements), + [ + TrackingAssumption(self.slide_gps(ass.gps)) + for ass in comb_class.assumptions + ], + ) + if self.rotate: + child = child.rotate90() + return (child,) + + def formal_step(self) -> str: + return f"Sliding index {self.idx}" + + def backward_map( + self, + comb_class: Tiling, + objs: Tuple[Optional[GriddedPerm], ...], + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Iterator[GriddedPerm]: + raise NotImplementedError + + def forward_map( + self, + comb_class: Tiling, + obj: GriddedPerm, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Tuple[Optional[GriddedPerm], Optional[GriddedPerm]]: + raise NotImplementedError + + def extra_parameters( + self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None + ) -> Tuple[Dict[str, str], ...]: + if not comb_class.extra_parameters: + return super().extra_parameters(comb_class, children) + if children is None: + children = self.decomposition_function(comb_class) + if children is None: + raise StrategyDoesNotApply("Strategy does not apply") + if self.rotate: + raise NotImplementedError("Not implemented counting for columns") + child = children[0] + return ( + { + comb_class.get_assumption_parameter( + ass + ): child.get_assumption_parameter( + TrackingAssumption(self.slide_gps(ass.gps)) + ) + for ass in comb_class.assumptions + }, + ) + + def slide_gp(self, gp: GriddedPerm) -> GriddedPerm: + pos = sorted( + x if x < self.idx or x > self.idx + 1 else x + 1 if x == self.idx else x - 1 + for x, _ in gp.pos + ) + return GriddedPerm(gp.patt, ((x, 0) for x in pos)) + + def slide_gps(self, gps: Tuple[GriddedPerm, ...]) -> Tuple[GriddedPerm, ...]: + return tuple(map(self.slide_gp, gps)) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}(idx={self.idx}, rotate={self.rotate})" + + def __str__(self) -> str: + return f"slide column {self.idx} and {self.idx + 1}" + + def to_jsonable(self) -> dict: + d = super().to_jsonable() + d["idx"] = self.idx + d["rotate"] = self.rotate + return d + + @classmethod + def from_dict(cls, d: dict): + return cls(d["idx"], d["rotate"]) + + +class MonotoneSlidingFactory(StrategyFactory[Tiling]): + """ + A factory that creates rules that swaps neighbouring cells if they + are 'monotone' fusable, i.e., they are a generalized fusion with + a monotone local extra obstruction. + + This is only looks at n x 1 and 1 x n tilings. + """ + + def __call__(self, comb_class: Tiling) -> Iterator[GeneralizedSlidingStrategy]: + rotate = False + if ( + not comb_class.dimensions[1] == 1 + and comb_class.dimensions[0] == 1 + and not comb_class.requirements + ): + comb_class = comb_class.rotate270() + rotate = True + if comb_class.dimensions[1] == 1 and not comb_class.requirements: + for col in range(comb_class.dimensions[0] - 1): + local_cells = ( + comb_class.cell_basis()[(col, 0)][0], + comb_class.cell_basis()[(col + 1, 0)][0], + ) + if len(local_cells[0]) == 1 and len(local_cells[0]) == 1: + if ( + local_cells[0][0].is_increasing() + and local_cells[1][0].is_increasing() + ) or ( + ( + local_cells[0][0].is_decreasing() + and local_cells[1][0].is_decreasing() + ) + ): + shortest = ( + col + if len(local_cells[0][0]) <= len(local_cells[1][0]) + else col + 1 + ) + algo = Fusion(comb_class, col_idx=col) + fused_obs = tuple( + algo.fuse_gridded_perm(gp) + for gp in comb_class.obstructions + if not all(x == shortest for x, _ in gp.pos) + ) + unfused_obs = tuple( + chain.from_iterable( + algo.unfuse_gridded_perm(gp) for gp in fused_obs + ) + ) + if comb_class == comb_class.add_obstructions(unfused_obs): + yield GeneralizedSlidingStrategy(col, rotate) + + def __repr__(self): + return f"{self.__class__.__name__}()" + + def __str__(self): + return "monotone sliding" + + @classmethod + def from_dict(cls, d: dict): + return cls() From 98636687532ef7a9319b1cd115ac42efcfe88ffd Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 8 Mar 2022 14:10:04 +0000 Subject: [PATCH 013/100] a targeted cell insertion strategy (#421) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * a targeted cell insertion strategy * test the encoding * remove double import * circular import * Update requirement_insertion.py * disable import order warning * actually also need to make the import comply to isort * allow a list as input to strategy Co-authored-by: Émile Nadeau --- CHANGELOG.md | 2 + pylintrc | 1 + tests/strategies/test_encoding.py | 4 ++ tilings/strategies/__init__.py | 2 + tilings/strategies/requirement_insertion.py | 76 ++++++++++++++++++++- 5 files changed, 84 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5eddf6b1..14025747 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,8 @@ swapped around a fusable row or column. - `DeflationFactory` which adds rules where cells can be deflated into increasing or decreasing cells as obstructions can't occur across the sum/skew components in that cell. +- `TargetedCellInsertionFactory` which inserts factors of gridded perms if it can + lead to factoring out a verified sub tiling. ### Fixed - `ForgetTrackedSearcher` was not retroactively applying strategies that had a `basis`. diff --git a/pylintrc b/pylintrc index 393d3c14..a3471211 100644 --- a/pylintrc +++ b/pylintrc @@ -63,6 +63,7 @@ confidence= disable=missing-class-docstring, too-few-public-methods, unsubscriptable-object, # Pylint does not support Genric class see https://github.com/PyCQA/pylint/issues/3520 + wrong-import-order, # isort is taking care of import order for us cyclic-import, no-member, diff --git a/tests/strategies/test_encoding.py b/tests/strategies/test_encoding.py index ec812573..7e5b0865 100644 --- a/tests/strategies/test_encoding.py +++ b/tests/strategies/test_encoding.py @@ -20,6 +20,7 @@ FactorFactory, FactorInsertionFactory, FusionFactory, + InsertionEncodingVerificationStrategy, LocallyFactorableVerificationStrategy, LocalVerificationStrategy, MonotoneSlidingFactory, @@ -43,6 +44,7 @@ SubclassVerificationFactory, SubobstructionInferralFactory, SymmetriesFactory, + TargetedCellInsertionFactory, ) from tilings.strategies.experimental_verification import SubclassVerificationStrategy from tilings.strategies.factor import ( @@ -364,6 +366,7 @@ def indices_and_row(strategy): + ignoreparent(ElementaryVerificationStrategy) + ignoreparent(LocalVerificationStrategy) + ignoreparent(MonotoneTreeVerificationStrategy) + + ignoreparent(InsertionEncodingVerificationStrategy) + [ObstructionTransitivityFactory()] + [ OneByOneVerificationStrategy( @@ -425,6 +428,7 @@ def indices_and_row(strategy): + [PointJumpingFactory()] + [MonotoneSlidingFactory(), GeneralizedSlidingStrategy(1)] + indices_and_row(PointJumpingStrategy) + + [TargetedCellInsertionFactory()] ) __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) diff --git a/tilings/strategies/__init__.py b/tilings/strategies/__init__.py index 34b50b36..71a2d2c7 100644 --- a/tilings/strategies/__init__.py +++ b/tilings/strategies/__init__.py @@ -25,6 +25,7 @@ RequirementExtensionFactory, RequirementInsertionFactory, RootInsertionFactory, + TargetedCellInsertionFactory, ) from .requirement_placement import ( AllPlacementsFactory, @@ -64,6 +65,7 @@ "RequirementCorroborationFactory", "RootInsertionFactory", "RowAndColumnPlacementFactory", + "TargetedCellInsertionFactory", # Decomposition "FactorFactory", # Deflation diff --git a/tilings/strategies/requirement_insertion.py b/tilings/strategies/requirement_insertion.py index e3098cbd..82c5886e 100644 --- a/tilings/strategies/requirement_insertion.py +++ b/tilings/strategies/requirement_insertion.py @@ -1,12 +1,15 @@ import abc from itertools import chain, product -from typing import Dict, Iterable, Iterator, List, Optional, Tuple, cast +from typing import Dict, Iterable, Iterator, List, Optional, Set, Tuple, cast +import tilings.strategies as strat from comb_spec_searcher import DisjointUnionStrategy, StrategyFactory from comb_spec_searcher.exception import StrategyDoesNotApply from comb_spec_searcher.strategies import Rule +from comb_spec_searcher.strategies.strategy import VerificationStrategy from permuta import Av, Perm from tilings import GriddedPerm, Tiling +from tilings.algorithms import Factor ListRequirement = Tuple[GriddedPerm, ...] @@ -534,3 +537,74 @@ def from_dict(cls, d: dict) -> "RemoveRequirementFactory": def __repr__(self) -> str: return f"{self.__class__.__name__}()" + + +class FactorRowCol(Factor): + def _unite_all(self) -> None: + self._unite_rows_and_cols() + + +class TargetedCellInsertionFactory(AbstractRequirementInsertionFactory): + """ + Insert factors requirements or obstructions on the tiling if it can lead + to separating a verified factor. + """ + + def __init__( + self, + verification_strategies: Optional[Iterable[VerificationStrategy]] = None, + ignore_parent: bool = True, + ) -> None: + self.verification_strats: List[VerificationStrategy] = ( + list(verification_strategies) + if verification_strategies is not None + else [ + strat.BasicVerificationStrategy(), + strat.InsertionEncodingVerificationStrategy(), + strat.OneByOneVerificationStrategy(), + strat.LocallyFactorableVerificationStrategy(), + ] + ) + super().__init__(ignore_parent) + + def verified(self, tiling: Tiling) -> bool: + """Return True if any verification strategy verifies the tiling""" + return any(strategy.verified(tiling) for strategy in self.verification_strats) + + def req_lists_to_insert(self, tiling: Tiling) -> Iterator[ListRequirement]: + factor_class = FactorRowCol(tiling) + potential_factors = factor_class.get_components() + reqs_and_obs: Set[GriddedPerm] = set( + chain(tiling.obstructions, *tiling.requirements) + ) + for cells in potential_factors: + if self.verified(tiling.sub_tiling(cells)): + for gp in reqs_and_obs: + if any(cell in cells for cell in gp.pos) and any( + cell not in cells for cell in gp.pos + ): + yield (gp.get_gridded_perm_in_cells(cells),) + + def to_jsonable(self) -> dict: + d = super().to_jsonable() + d["ver_strats"] = [ + strategy.to_jsonable() for strategy in self.verification_strats + ] + return d + + @classmethod + def from_dict(cls, d: dict) -> "TargetedCellInsertionFactory": + ver_strats = [ + cast(VerificationStrategy, VerificationStrategy.from_dict(strategy)) + for strategy in d["ver_strats"] + ] + return TargetedCellInsertionFactory(ver_strats, d["ignore_parent"]) + + def __repr__(self): + return ( + self.__class__.__name__ + + f"({self.verification_strats}, {self.ignore_parent})" + ) + + def __str__(self) -> str: + return "targeted cell insertions" From e4cc309ca70402102e65f5b417f2b0515a9373ee Mon Sep 17 00:00:00 2001 From: Henning Ulfarsson Date: Tue, 8 Mar 2022 14:11:42 +0000 Subject: [PATCH 014/100] No Root Cell verification (#422) * Actually adding the files this time * Pleasing black * tidying up * update string method, test * mypy * formal step test * Revert "formal step test" This reverts commit fd411371eaf0685a509d04c5b22a5fb7f61115bc. * formal step test Co-authored-by: Christian Bean --- tests/strategies/test_encoding.py | 11 ++ tests/strategies/test_verification.py | 108 ++++++++++++++++++ tilings/strategies/__init__.py | 2 + .../strategies/experimental_verification.py | 22 ++++ tilings/tilescope.py | 3 +- 5 files changed, 145 insertions(+), 1 deletion(-) diff --git a/tests/strategies/test_encoding.py b/tests/strategies/test_encoding.py index 7e5b0865..9b98c68e 100644 --- a/tests/strategies/test_encoding.py +++ b/tests/strategies/test_encoding.py @@ -25,6 +25,7 @@ LocalVerificationStrategy, MonotoneSlidingFactory, MonotoneTreeVerificationStrategy, + NoRootCellVerificationStrategy, ObstructionInferralFactory, ObstructionTransitivityFactory, OneByOneVerificationStrategy, @@ -378,6 +379,16 @@ def indices_and_row(strategy): OneByOneVerificationStrategy(basis=[], ignore_parent=False, symmetry=False), OneByOneVerificationStrategy(basis=None, ignore_parent=False, symmetry=False), ] + + [ + NoRootCellVerificationStrategy( + basis=[Perm((0, 1, 2)), Perm((2, 1, 0, 3))], ignore_parent=True + ), + NoRootCellVerificationStrategy( + basis=[Perm((2, 1, 0, 3))], ignore_parent=False, symmetry=True + ), + NoRootCellVerificationStrategy(basis=[], ignore_parent=False, symmetry=False), + NoRootCellVerificationStrategy(basis=None, ignore_parent=False, symmetry=False), + ] + [ SubclassVerificationFactory(perms_to_check=[Perm((0, 1, 2)), Perm((1, 0))]), SubclassVerificationFactory(perms_to_check=list(Perm.up_to_length(3))), diff --git a/tests/strategies/test_verification.py b/tests/strategies/test_verification.py index fe7ffd0c..789d6079 100644 --- a/tests/strategies/test_verification.py +++ b/tests/strategies/test_verification.py @@ -18,6 +18,7 @@ LocallyFactorableVerificationStrategy, LocalVerificationStrategy, MonotoneTreeVerificationStrategy, + NoRootCellVerificationStrategy, OneByOneVerificationStrategy, ShortObstructionVerificationStrategy, ) @@ -1147,6 +1148,113 @@ def test_subclass(self, strategy): assert strategy.verified(Tiling.from_string("132_1234")) +class TestNoRootCellVerificationStrategy(CommonTest): + @pytest.fixture + def strategy(self): + return NoRootCellVerificationStrategy(basis=[Perm((0, 1, 3, 2))]) + + @pytest.fixture + def formal_step(self): + return "tiling has no Av(0132) cell" + + @pytest.fixture + def enum_verified(self): + # +-+-+-+-+ + # |2| | | | + # +-+-+-+-+ + # | | |●| | + # +-+-+-+-+ + # |\| | | | + # +-+-+-+-+ + # | |●| | | + # +-+-+-+-+ + # |1| | |3| + # +-+-+-+-+ + # 1: Av+(120, 0132) + # 2: Av(012) + # 3: Av(0132, 0231, 1203) + # \: Av(01) + # ●: point + # Crossing obstructions: + # 01: (0, 0), (3, 0) + # 012: (0, 0), (0, 0), (0, 2) + # 012: (0, 0), (0, 0), (0, 4) + # 012: (0, 0), (0, 2), (0, 4) + # 012: (0, 0), (0, 4), (0, 4) + # 012: (0, 2), (0, 4), (0, 4) + # 120: (0, 0), (0, 2), (0, 0) + # Requirement 0: + # 0: (0, 0) + # Requirement 1: + # 0: (1, 1) + # Requirement 2: + # 0: (2, 3) + return [ + Tiling( + obstructions=( + GriddedPerm((0, 1), ((0, 0), (3, 0))), + GriddedPerm((0, 1), ((0, 2), (0, 2))), + GriddedPerm((0, 1), ((1, 1), (1, 1))), + GriddedPerm((0, 1), ((2, 3), (2, 3))), + GriddedPerm((1, 0), ((1, 1), (1, 1))), + GriddedPerm((1, 0), ((2, 3), (2, 3))), + GriddedPerm((0, 1, 2), ((0, 0), (0, 0), (0, 2))), + GriddedPerm((0, 1, 2), ((0, 0), (0, 0), (0, 4))), + GriddedPerm((0, 1, 2), ((0, 0), (0, 2), (0, 4))), + GriddedPerm((0, 1, 2), ((0, 0), (0, 4), (0, 4))), + GriddedPerm((0, 1, 2), ((0, 2), (0, 4), (0, 4))), + GriddedPerm((0, 1, 2), ((0, 4), (0, 4), (0, 4))), + GriddedPerm((1, 2, 0), ((0, 0), (0, 0), (0, 0))), + GriddedPerm((1, 2, 0), ((0, 0), (0, 2), (0, 0))), + GriddedPerm((0, 1, 3, 2), ((0, 0), (0, 0), (0, 0), (0, 0))), + GriddedPerm((0, 1, 3, 2), ((3, 0), (3, 0), (3, 0), (3, 0))), + GriddedPerm((0, 2, 3, 1), ((3, 0), (3, 0), (3, 0), (3, 0))), + GriddedPerm((1, 2, 0, 3), ((3, 0), (3, 0), (3, 0), (3, 0))), + ), + requirements=( + (GriddedPerm((0,), ((0, 0),)),), + (GriddedPerm((0,), ((1, 1),)),), + (GriddedPerm((0,), ((2, 3),)),), + ), + assumptions=(), + ) + ] + + @pytest.fixture + def enum_not_verified(self): + return [ + Tiling.from_string("0132"), + Tiling( + obstructions=[ + GriddedPerm.single_cell((0, 1, 3, 2), ((0, 0))), + GriddedPerm.single_cell((0, 2, 1), ((0, 1))), + GriddedPerm((0, 1, 2), ((0, 0), (0, 0), (0, 1))), + ] + ), + ] + + def test_get_genf(self, strategy, enum_verified): + pass + + def test_children(self): + t2 = Tiling( + obstructions=[ + GriddedPerm.single_cell((0, 2, 1), ((0, 0))), + GriddedPerm.single_cell((0, 2, 1), ((0, 1))), + GriddedPerm((0, 1, 3, 2), ((0, 0), (0, 0), (0, 1), (0, 1))), + ] + ) + strategy = NoRootCellVerificationStrategy(basis=[Perm((0, 1, 3, 2))]) + assert strategy(t2).children == () + + def test_change_basis(self): + strategy = NoRootCellVerificationStrategy() + strategy1 = strategy.change_basis([Perm((0, 1, 2))], False) + strategy2 = strategy1.change_basis([Perm((0, 1, 3, 2))], False) + assert strategy1.basis == (Perm((0, 1, 2)),) + assert strategy2.basis == (Perm((0, 1, 3, 2)),) + + class TestShortObstructionVerificationStrategy(CommonTest): @pytest.fixture def strategy(self): diff --git a/tilings/strategies/__init__.py b/tilings/strategies/__init__.py index 71a2d2c7..c7bdbb88 100644 --- a/tilings/strategies/__init__.py +++ b/tilings/strategies/__init__.py @@ -3,6 +3,7 @@ from .deflation import DeflationFactory from .detect_components import DetectComponentsStrategy from .experimental_verification import ( + NoRootCellVerificationStrategy, ShortObstructionVerificationStrategy, SubclassVerificationFactory, ) @@ -94,6 +95,7 @@ "LocalVerificationStrategy", "InsertionEncodingVerificationStrategy", "MonotoneTreeVerificationStrategy", + "NoRootCellVerificationStrategy", "OneByOneVerificationStrategy", "ShortObstructionVerificationStrategy", "SubclassVerificationFactory", diff --git a/tilings/strategies/experimental_verification.py b/tilings/strategies/experimental_verification.py index 94f11b5f..b410650b 100644 --- a/tilings/strategies/experimental_verification.py +++ b/tilings/strategies/experimental_verification.py @@ -16,6 +16,7 @@ from .abstract import BasisAwareVerificationStrategy __all__ = [ + "NoRootCellVerificationStrategy", "ShortObstructionVerificationStrategy", "SubclassVerificationFactory", ] @@ -23,6 +24,27 @@ TileScopeVerificationStrategy = VerificationStrategy[Tiling, GriddedPerm] +class NoRootCellVerificationStrategy(BasisAwareVerificationStrategy): + """ + A strategy to mark as verified any tiling that does not contain the root + basis localized in a cell. Tilings with dimensions 1x1 are ignored. + """ + + def verified(self, comb_class: Tiling): + return comb_class.dimensions != (1, 1) and all( + frozenset(obs) not in self.symmetries + for obs, _ in comb_class.cell_basis().values() + ) + + def formal_step(self) -> str: + basis = ", ".join(str(p) for p in self.basis) + return f"tiling has no Av({basis}) cell" + + def __str__(self) -> str: + basis = ", ".join(str(p) for p in self.basis) + return f"no Av({basis}) cell verification" + + class ShortObstructionVerificationStrategy(BasisAwareVerificationStrategy): """ A strategy to mark as verified any tiling whose crossing obstructions all have diff --git a/tilings/tilescope.py b/tilings/tilescope.py index 330299f3..971ddbe0 100644 --- a/tilings/tilescope.py +++ b/tilings/tilescope.py @@ -192,7 +192,8 @@ def __init__( ) # reset to the trackedqueue! self.classqueue = cast( - DefaultQueue, TrackedQueue(strategy_pack, self, delay_next) + DefaultQueue, + TrackedQueue(cast(TileScopePack, self.strategy_pack), self, delay_next), ) # TODO: make CSS accept a CSSQueue as a kwarg self.classqueue.add(self.start_label) From 03990509744038d2faafe84d9b6d02d6d3890c3c Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 8 Mar 2022 14:14:06 +0000 Subject: [PATCH 015/100] Point corroboration (#426) * a point corroboration factory * change to insert into any pair of cells which can not be mutually positive * mypy * remove positive corroboration from bijection packs * remove unused import * README.rst * removing strategy from bijections test, and increasing timeout on forest test --- CHANGELOG.md | 3 ++ README.rst | 2 +- tests/test_bijections.py | 11 ++++- tests/test_tilescope.py | 2 +- tilings/strategies/__init__.py | 4 +- tilings/strategies/requirement_insertion.py | 34 +++++++++++++ tilings/strategy_pack.py | 54 ++++++++++++++++++--- tilings/tiling.py | 12 ++--- 8 files changed, 105 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14025747..fb0c883a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,9 @@ swapped around a fusable row or column. - `DeflationFactory` which adds rules where cells can be deflated into increasing or decreasing cells as obstructions can't occur across the sum/skew components in that cell. +- `PositiveCorroborationFactory` that inserts into cells which if positive makes + another cell empty. This strategy is added to most packs. +- `TileScopePack.remove_strategy` method that removes a strategy from a pack. - `TargetedCellInsertionFactory` which inserts factors of gridded perms if it can lead to factoring out a verified sub tiling. diff --git a/README.rst b/README.rst index c16f0463..fa152180 100644 --- a/README.rst +++ b/README.rst @@ -573,7 +573,7 @@ You can make any pack use the fusion strategy by using the method >>> print(pack) Looking for recursive combinatorial specification with the strategies: Inferral: row and column separation, obstruction transitivity - Initial: rearrange assumptions, add assumptions, factor, tracked fusion + Initial: rearrange assumptions, add assumptions, factor, point corroboration, tracked fusion Verification: verify atoms, insertion encoding verified, one by one verification, locally factorable verification Set 1: row placement diff --git a/tests/test_bijections.py b/tests/test_bijections.py index f1f3c07d..e6a6a858 100644 --- a/tests/test_bijections.py +++ b/tests/test_bijections.py @@ -4,6 +4,7 @@ import pytest import requests +from sympy import Point from comb_spec_searcher import ( AtomStrategy, @@ -20,7 +21,7 @@ FusionParallelSpecFinder, _AssumptionPathTracker, ) -from tilings.strategies import BasicVerificationStrategy +from tilings.strategies import BasicVerificationStrategy, PositiveCorroborationFactory from tilings.strategies.assumption_insertion import AddAssumptionsStrategy from tilings.strategies.factor import FactorStrategy from tilings.strategies.fusion import FusionStrategy @@ -50,6 +51,7 @@ def find_bijection_between_fusion( def _b2rc(basis: str) -> CombinatorialSpecificationSearcher: pack = TileScopePack.row_and_col_placements(row_only=True) + pack = pack.remove_strategy(PositiveCorroborationFactory()) pack = pack.add_verification(BasicVerificationStrategy(), replace=True) searcher = TileScope(basis, pack) assert isinstance(searcher, CombinatorialSpecificationSearcher) @@ -165,6 +167,7 @@ def test_bijection_8_cross_domain(): ) pack = TileScopePack.row_and_col_placements(row_only=True) pack = pack.add_verification(BasicVerificationStrategy(), replace=True) + pack = pack.remove_strategy(PositiveCorroborationFactory()) pack.inferral_strats = () searcher1 = TileScope(t, pack) @@ -230,9 +233,11 @@ def test_bijection_9_cross_domain(): def test_bijection_10(): pack1 = TileScopePack.requirement_placements() pack1 = pack1.add_verification(BasicVerificationStrategy(), replace=True) + pack1 = pack1.remove_strategy(PositiveCorroborationFactory()) searcher1 = TileScope("132_4312", pack1) pack2 = TileScopePack.requirement_placements() pack2 = pack2.add_verification(BasicVerificationStrategy(), replace=True) + pack2 = pack2.remove_strategy(PositiveCorroborationFactory()) searcher2 = TileScope("132_4231", pack2) _bijection_asserter(find_bijection_between(searcher1, searcher2)) @@ -453,6 +458,7 @@ def test_bijection_12(): def _pntrcpls(b1, b2): pack = TileScopePack.point_and_row_and_col_placements(row_only=True) pack = pack.add_verification(BasicVerificationStrategy(), replace=True) + pack = pack.remove_strategy(PositiveCorroborationFactory()) searcher1 = TileScope(b1, pack) searcher2 = TileScope(b2, pack) _bijection_asserter(find_bijection_between(searcher1, searcher2)) @@ -470,6 +476,7 @@ def _pntrcpls(b1, b2): def test_bijection_13(): pack = TileScopePack.point_and_row_and_col_placements(row_only=True) pack = pack.add_verification(BasicVerificationStrategy(), replace=True) + pack = pack.remove_strategy(PositiveCorroborationFactory()) searcher1 = TileScope("0132_0213_0231_0321_1032_1320_2031_2301_3021_3120", pack) searcher2 = TileScope("0132_0213_0231_0312_0321_1302_1320_2031_2301_3120", pack) _bijection_asserter(find_bijection_between(searcher1, searcher2)) @@ -489,11 +496,13 @@ def test_bijection_14_json(): def test_bijection_15_fusion(): pack = TileScopePack.row_and_col_placements(row_only=True).make_fusion(tracked=True) pack = pack.add_verification(BasicVerificationStrategy(), replace=True) + pack = pack.remove_strategy(PositiveCorroborationFactory()) pack2 = TileScopePack.row_and_col_placements(row_only=True).make_fusion( tracked=True ) pack2 = pack2.add_initial(SlidingFactory(True)) pack2 = pack2.add_verification(BasicVerificationStrategy(), replace=True) + pack2 = pack2.remove_strategy(PositiveCorroborationFactory()) long_1234 = Perm( ( 47, diff --git a/tests/test_tilescope.py b/tests/test_tilescope.py index 7c067c37..abb9bf14 100644 --- a/tests/test_tilescope.py +++ b/tests/test_tilescope.py @@ -348,7 +348,7 @@ def test_domino(): ] -@pytest.mark.timeout(15) +@pytest.mark.timeout(60) def test_parallel_forest(): expected_count = [1, 1, 2, 6, 22, 90, 394, 1806, 8558, 41586] pack = TileScopePack.only_root_placements(2, 1) diff --git a/tilings/strategies/__init__.py b/tilings/strategies/__init__.py index c7bdbb88..9417883a 100644 --- a/tilings/strategies/__init__.py +++ b/tilings/strategies/__init__.py @@ -21,6 +21,7 @@ from .requirement_insertion import ( CellInsertionFactory, FactorInsertionFactory, + PositiveCorroborationFactory, RemoveRequirementFactory, RequirementCorroborationFactory, RequirementExtensionFactory, @@ -56,9 +57,10 @@ "RearrangeAssumptionFactory", "SplittingStrategy", # Batch + "AllPlacementsFactory", "CellInsertionFactory", "FactorInsertionFactory", - "AllPlacementsFactory", + "PositiveCorroborationFactory", "RemoveRequirementFactory", "RequirementExtensionFactory", "RequirementInsertionFactory", diff --git a/tilings/strategies/requirement_insertion.py b/tilings/strategies/requirement_insertion.py index 82c5886e..fdcb079a 100644 --- a/tilings/strategies/requirement_insertion.py +++ b/tilings/strategies/requirement_insertion.py @@ -11,6 +11,7 @@ from tilings import GriddedPerm, Tiling from tilings.algorithms import Factor +Cell = Tuple[int, int] ListRequirement = Tuple[GriddedPerm, ...] EXTRA_BASIS_ERR = "'extra_basis' should be a list of Perm to avoid" @@ -517,6 +518,39 @@ def __str__(self) -> str: return "requirement corroboration" +class PositiveCorroborationFactory(AbstractRequirementInsertionFactory): + """ + The point corroboration strategy. + + The point corroboration strategy inserts points into any two point + or empty cells which can not both be a point, i.e., one is a point + and the other is empty. + """ + + def __init__(self, ignore_parent: bool = True): + super().__init__(ignore_parent) + + def req_lists_to_insert(self, tiling: Tiling) -> Iterator[ListRequirement]: + potential_cells: Set[Tuple[Cell, ...]] = set() + cells_to_yield: Set[Cell] = set() + for gp in tiling.obstructions: + if len(gp) == 2 and not gp.is_localized(): + if gp.is_interleaving(): + cells = tuple(sorted(gp.pos)) + if cells in potential_cells: + cells_to_yield.update(cells) + else: + potential_cells.add(cells) + else: + cells_to_yield.update(gp.pos) + for cell in cells_to_yield: + if cell not in tiling.point_cells: + yield (GriddedPerm.point_perm(cell),) + + def __str__(self) -> str: + return "point corroboration" + + class RemoveRequirementFactory(StrategyFactory[Tiling]): """ For a tiling T, and each requirement R on T, create the rules that diff --git a/tilings/strategy_pack.py b/tilings/strategy_pack.py index c8f032ff..b47fed5e 100644 --- a/tilings/strategy_pack.py +++ b/tilings/strategy_pack.py @@ -275,10 +275,35 @@ def add_all_symmetry(self) -> "TileScopePack": raise ValueError("Symmetries already turned on.") return super().add_symmetry(strat.SymmetriesFactory(), "symmetries") + def remove_strategy(self, strategy: CSSstrategy): + """remove the strategy from the pack""" + d = strategy.to_jsonable() + + def replace_list(strats): + """Return a new list with the replaced fusion strat.""" + res = [] + for strategy in strats: + if not strategy.to_jsonable() == d: + res.append(strategy) + return res + + return self.__class__( + ver_strats=replace_list(self.ver_strats), + inferral_strats=replace_list(self.inferral_strats), + initial_strats=replace_list(self.initial_strats), + expansion_strats=list(map(replace_list, self.expansion_strats)), + name=self.name, + symmetries=replace_list(self.symmetries), + iterative=self.iterative, + ) + # Creation of the base pack @classmethod def all_the_strategies(cls, length: int = 1) -> "TileScopePack": - initial_strats: List[CSSstrategy] = [strat.FactorFactory()] + initial_strats: List[CSSstrategy] = [ + strat.FactorFactory(), + strat.PositiveCorroborationFactory(), + ] if length > 1: initial_strats.append(strat.RequirementCorroborationFactory()) @@ -318,6 +343,7 @@ def pattern_placements( expansion_strats: List[CSSstrategy] = [ strat.FactorFactory(unions=True), + strat.PositiveCorroborationFactory(), strat.CellInsertionFactory(maxreqlen=length), ] if length > 1: @@ -351,7 +377,10 @@ def point_placements( ] ) - initial_strats: List[CSSstrategy] = [strat.FactorFactory()] + initial_strats: List[CSSstrategy] = [ + strat.FactorFactory(), + strat.PositiveCorroborationFactory(), + ] if length > 1: initial_strats.append(strat.RequirementCorroborationFactory()) @@ -458,7 +487,10 @@ def row_and_col_placements( if partial: expansion_strats.append([strat.PatternPlacementFactory(point_only=True)]) return TileScopePack( - initial_strats=[strat.FactorFactory()], + initial_strats=[ + strat.FactorFactory(), + strat.PositiveCorroborationFactory(), + ], ver_strats=[ strat.BasicVerificationStrategy(), strat.InsertionEncodingVerificationStrategy(), @@ -521,6 +553,7 @@ def only_root_placements( initial_strats=[ strat.RootInsertionFactory(maxreqlen=length, max_num_req=max_num_req), strat.FactorFactory(unions=True, ignore_parent=False, workable=False), + strat.PositiveCorroborationFactory(), ], ver_strats=[ strat.BasicVerificationStrategy(), @@ -548,7 +581,10 @@ def requirement_placements( ] ) - initial_strats: List[CSSstrategy] = [strat.FactorFactory()] + initial_strats: List[CSSstrategy] = [ + strat.FactorFactory(), + strat.PositiveCorroborationFactory(), + ] if length > 1: initial_strats.append(strat.RequirementCorroborationFactory()) @@ -601,7 +637,10 @@ def point_and_row_and_col_placements( place_row=place_row, place_col=place_col, partial=partial ) - initial_strats: List[CSSstrategy] = [strat.FactorFactory()] + initial_strats: List[CSSstrategy] = [ + strat.FactorFactory(), + strat.PositiveCorroborationFactory(), + ] if length > 1: initial_strats.append(strat.RequirementCorroborationFactory()) @@ -655,7 +694,10 @@ def requirement_and_row_and_col_placements( place_row=place_row, place_col=place_col, partial=partial ) - initial_strats: List[CSSstrategy] = [strat.FactorFactory()] + initial_strats: List[CSSstrategy] = [ + strat.FactorFactory(), + strat.PositiveCorroborationFactory(), + ] if length > 1: initial_strats.append(strat.RequirementCorroborationFactory()) diff --git a/tilings/tiling.py b/tilings/tiling.py index 9ae97cf1..c6331c6c 100644 --- a/tilings/tiling.py +++ b/tilings/tiling.py @@ -1561,14 +1561,12 @@ def minimum_size_of_object(self) -> int: return min(len(gp) for gp in self.requirements[0]) return len(next(self.minimal_gridded_perms())) - def is_point_or_empty(self) -> bool: - point_or_empty_tiling = Tiling( - obstructions=( - GriddedPerm((0, 1), ((0, 0), (0, 0))), - GriddedPerm((1, 0), ((0, 0), (0, 0))), - ) + def is_point_or_empty_cell(self, cell: Cell) -> bool: + point_or_empty_obs = ( + GriddedPerm((0, 1), (cell, cell)), + GriddedPerm((1, 0), (cell, cell)), ) - return self == point_or_empty_tiling + return all(ob in self.obstructions for ob in point_or_empty_obs) def is_empty_cell(self, cell: Cell) -> bool: """Check if the cell of the tiling is empty.""" From 0ec3b0ddc56950a8920a64539bb3cd1642111568 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 8 Mar 2022 14:15:58 +0000 Subject: [PATCH 016/100] work from every sym (#427) --- CHANGELOG.md | 1 + tilings/tilescope.py | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb0c883a..bfa85f49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ swapped around a fusable row or column. - `RequirementPlacement` adds empty cells when placing a point cell. This saves some inferral in partial placements. - Don't reinitialise in the `Tiling.from_dict` method. +- `GuidedSearcher` expands every symmetry ### Deprecated - Python 3.7 is no longer supported diff --git a/tilings/tilescope.py b/tilings/tilescope.py index 971ddbe0..897bc6ba 100644 --- a/tilings/tilescope.py +++ b/tilings/tilescope.py @@ -138,6 +138,7 @@ def __init__( is_empty = self.classdb.is_empty(t, class_label) if not is_empty: self.classqueue.add(class_label) + self._symmetry_expand(t, class_label) def _expand( self, @@ -150,6 +151,18 @@ def _expand( return return super()._expand(comb_class, label, strategies, inferral) + def _symmetry_expand(self, comb_class: CombinatorialClassType, label: int) -> None: + sym_labels = set([label]) + for strategy_generator in self.symmetries: + for start_label, end_labels, rule in self._expand_class_with_strategy( + comb_class, strategy_generator, label=label + ): + sym_label = end_labels[0] + self.ruledb.add(start_label, (sym_label,), rule) + self.classqueue.add(sym_label) + sym_labels.add(sym_label) + self.symmetry_expanded.update(sym_labels) + @classmethod def from_spec( cls, specification: CombinatorialSpecification, pack: TileScopePack From eafa6965b8cf189015c4b23bcbe57e27343816f5 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Thu, 10 Mar 2022 13:48:37 +0000 Subject: [PATCH 017/100] allows swapping points or assumptions alone (#429) * allows swapping points or assumptions alone * fixing tests * add flags --- CHANGELOG.md | 2 + tests/strategies/test_encoding.py | 13 +- tests/test_strategy_pack.py | 4 +- tilings/strategies/__init__.py | 5 +- tilings/strategies/point_jumping.py | 275 ++++++++++++++++++++++++---- 5 files changed, 261 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bfa85f49..969f5765 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ multiple different obs and one requirement list of size possibly greater than on Previously it was only doing the case where a single ob's factor is implied by a requirement. - added `TileScopePack.requirement_and_row_and_col_placements` +- `AssumptionAndPointJumpingFactory` which adds rules where requirements and/or + assumptions are swapped around a fusable row or column. - `PointJumpingFactory` which adds rules where requirements and assumptions can be swapped around a fusable row or column. - `MonotoneSlidingFactory` that creates rules that swaps neighbouring cells if they diff --git a/tests/strategies/test_encoding.py b/tests/strategies/test_encoding.py index 9b98c68e..4070a7cf 100644 --- a/tests/strategies/test_encoding.py +++ b/tests/strategies/test_encoding.py @@ -10,6 +10,7 @@ from tilings import GriddedPerm, TrackingAssumption from tilings.strategies import ( AllPlacementsFactory, + AssumptionAndPointJumpingFactory, BasicVerificationStrategy, CellInsertionFactory, ComponentFusionFactory, @@ -30,7 +31,6 @@ ObstructionTransitivityFactory, OneByOneVerificationStrategy, PatternPlacementFactory, - PointJumpingFactory, RearrangeAssumptionFactory, RequirementCorroborationFactory, RequirementExtensionFactory, @@ -56,7 +56,11 @@ from tilings.strategies.fusion import ComponentFusionStrategy, FusionStrategy from tilings.strategies.monotone_sliding import GeneralizedSlidingStrategy from tilings.strategies.obstruction_inferral import ObstructionInferralStrategy -from tilings.strategies.point_jumping import PointJumpingStrategy +from tilings.strategies.point_jumping import ( + AssumptionAndPointJumpingStrategy, + AssumptionJumpingStrategy, + PointJumpingStrategy, +) from tilings.strategies.rearrange_assumption import RearrangeAssumptionStrategy from tilings.strategies.requirement_insertion import RequirementInsertionStrategy from tilings.strategies.requirement_placement import RequirementPlacementStrategy @@ -436,7 +440,10 @@ def indices_and_row(strategy): TrackingAssumption([GriddedPerm((0,), [(0, 0)])]), ) ] - + [PointJumpingFactory()] + + [AssumptionAndPointJumpingFactory()] + + indices_and_row(PointJumpingStrategy) + + indices_and_row(AssumptionJumpingStrategy) + + indices_and_row(AssumptionAndPointJumpingStrategy) + [MonotoneSlidingFactory(), GeneralizedSlidingStrategy(1)] + indices_and_row(PointJumpingStrategy) + [TargetedCellInsertionFactory()] diff --git a/tests/test_strategy_pack.py b/tests/test_strategy_pack.py index 2eef78c5..df9004eb 100644 --- a/tests/test_strategy_pack.py +++ b/tests/test_strategy_pack.py @@ -6,7 +6,7 @@ from permuta import Perm from permuta.misc import DIRS from tilings import strategies as strat -from tilings.strategies import PointJumpingFactory, SlidingFactory +from tilings.strategies import AssumptionAndPointJumpingFactory, SlidingFactory from tilings.strategy_pack import TileScopePack @@ -113,7 +113,7 @@ def length_row_col_partial(pack): + [pack.make_interleaving() for pack in packs] + [pack.add_initial(SlidingFactory()) for pack in packs] + [pack.add_initial(SlidingFactory(use_symmetries=True)) for pack in packs] - + [pack.add_initial(PointJumpingFactory()) for pack in packs] + + [pack.add_initial(AssumptionAndPointJumpingFactory()) for pack in packs] ) diff --git a/tilings/strategies/__init__.py b/tilings/strategies/__init__.py index 9417883a..d186130f 100644 --- a/tilings/strategies/__init__.py +++ b/tilings/strategies/__init__.py @@ -16,7 +16,7 @@ ObstructionTransitivityFactory, SubobstructionInferralFactory, ) -from .point_jumping import PointJumpingFactory +from .point_jumping import AssumptionAndPointJumpingFactory from .rearrange_assumption import RearrangeAssumptionFactory from .requirement_insertion import ( CellInsertionFactory, @@ -76,8 +76,9 @@ # Equivalence "MonotoneSlidingFactory", "PatternPlacementFactory", - "PointJumpingFactory", "SlidingFactory", + # Experimental + "AssumptionAndPointJumpingFactory", # Fusion "ComponentFusionFactory", "FusionFactory", diff --git a/tilings/strategies/point_jumping.py b/tilings/strategies/point_jumping.py index 5e6a6e90..465f199f 100644 --- a/tilings/strategies/point_jumping.py +++ b/tilings/strategies/point_jumping.py @@ -1,18 +1,25 @@ +import abc from itertools import chain from typing import Dict, Iterator, Optional, Tuple +from comb_spec_searcher import Constructor from comb_spec_searcher.exception import StrategyDoesNotApply -from comb_spec_searcher.strategies import DisjointUnionStrategy, StrategyFactory +from comb_spec_searcher.strategies import ( + DisjointUnionStrategy, + Strategy, + StrategyFactory, +) from tilings import GriddedPerm, Tiling, TrackingAssumption from tilings.algorithms import Fusion Cell = Tuple[int, int] -class PointJumpingStrategy(DisjointUnionStrategy[Tiling, GriddedPerm]): +class AssumptionOrPointJumpingStrategy(Strategy[Tiling, GriddedPerm]): """ - A strategy which moves requirements and assumptions from a column (or row) - to its neighbouring column (or row) if the two columns are fusable. + An abstract strategy class which moves requirements or assumptions from a + column (or row) to its neighbouring column (or row) if the two columns + are fusable. """ def __init__(self, idx1: int, idx2: int, row: bool): @@ -21,14 +28,9 @@ def __init__(self, idx1: int, idx2: int, row: bool): self.row = row super().__init__() + @abc.abstractmethod def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling]: - return ( - Tiling( - comb_class.obstructions, - self.swapped_requirements(comb_class), - self.swapped_assumptions(comb_class), - ), - ) + pass def swapped_requirements( self, tiling: Tiling @@ -72,6 +74,66 @@ def _swap_cell(self, cell: Cell) -> Cell: def _swap_assumption(self, assumption: TrackingAssumption) -> TrackingAssumption: return TrackingAssumption(self._swapped_gp(gp) for gp in assumption.gps) + @abc.abstractmethod + def backward_map( + self, + comb_class: Tiling, + objs: Tuple[Optional[GriddedPerm], ...], + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Iterator[GriddedPerm]: + pass + + @abc.abstractmethod + def forward_map( + self, + comb_class: Tiling, + obj: GriddedPerm, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Tuple[Optional[GriddedPerm]]: + pass + + @abc.abstractmethod + def extra_parameters( + self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None + ) -> Tuple[Dict[str, str], ...]: + pass + + @abc.abstractmethod + def formal_step(self) -> str: + pass + + def to_jsonable(self) -> dict: + d = super().to_jsonable() + d.pop("ignore_parent") + d["idx1"] = self.idx1 + d["idx2"] = self.idx2 + d["row"] = self.row + return d + + @classmethod + def from_dict(cls, d: dict) -> "AssumptionOrPointJumpingStrategy": + return cls(d.pop("idx1"), d.pop("idx2"), d.pop("row")) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.idx1}, {self.idx2}, {self.row})" + + +class AssumptionAndPointJumpingStrategy( + AssumptionOrPointJumpingStrategy, + DisjointUnionStrategy[Tiling, GriddedPerm], +): + def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling]: + return ( + Tiling( + comb_class.obstructions, + self.swapped_requirements(comb_class), + self.swapped_assumptions(comb_class), + simplify=False, + derive_empty=False, + remove_empty_rows_and_cols=False, + ), + ) + def backward_map( self, comb_class: Tiling, @@ -110,32 +172,179 @@ def extra_parameters( ) def formal_step(self) -> str: - row_or_col = "row" if self.row else "col" - return f"swapping reqs in {row_or_col} {self.idx1} and {self.idx2}" + row_or_col = "rows" if self.row else "cols" + return ( + f"swapping reqs and assumptions in {row_or_col} {self.idx1} and {self.idx2}" + ) - def to_jsonable(self) -> dict: - d = super().to_jsonable() - d.pop("ignore_parent") - d["idx1"] = self.idx1 - d["idx2"] = self.idx2 - d["row"] = self.row - return d - @classmethod - def from_dict(cls, d: dict) -> "PointJumpingStrategy": - return cls(d.pop("idx1"), d.pop("idx2"), d.pop("row")) +class AssumptionJumpingStrategy(AssumptionOrPointJumpingStrategy): + def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling]: + return ( + Tiling( + comb_class.obstructions, + comb_class.requirements, + self.swapped_assumptions(comb_class), + simplify=False, + derive_empty=False, + remove_empty_rows_and_cols=False, + ), + ) - def __repr__(self) -> str: - return f"{self.__class__.__name__}({self.idx1}, {self.idx2}, {self.row})" + @staticmethod + def can_be_equivalent() -> bool: + return False + + @staticmethod + def is_two_way(comb_class: Tiling) -> bool: + return True + + @staticmethod + def is_reversible(comb_class: Tiling) -> bool: + return True + + @staticmethod + def shifts( + comb_class: Tiling, + children: Optional[Tuple[Tiling, ...]], + ) -> Tuple[int, ...]: + return (0,) + + def constructor( + self, + comb_class: Tiling, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Constructor: + raise NotImplementedError + + def reverse_constructor( + self, + idx: int, + comb_class: Tiling, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Constructor: + raise NotImplementedError + + def backward_map( + self, + comb_class: Tiling, + objs: Tuple[Optional[GriddedPerm], ...], + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Iterator[GriddedPerm]: + raise NotImplementedError + + def forward_map( + self, + comb_class: Tiling, + obj: GriddedPerm, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Tuple[Optional[GriddedPerm]]: + raise NotImplementedError + + def extra_parameters( + self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None + ) -> Tuple[Dict[str, str], ...]: + if not comb_class.extra_parameters: + return super().extra_parameters(comb_class, children) + if children is None: + children = self.decomposition_function(comb_class) + if children is None: + raise StrategyDoesNotApply("Strategy does not apply") + raise NotImplementedError + + def formal_step(self) -> str: + row_or_col = "rows" if self.row else "cols" + return f"swapping assumptions in {row_or_col} {self.idx1} and {self.idx2}" + + +class PointJumpingStrategy(AssumptionOrPointJumpingStrategy): + def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling]: + return ( + Tiling( + comb_class.obstructions, + self.swapped_requirements(comb_class), + comb_class.assumptions, + simplify=False, + derive_empty=False, + remove_empty_rows_and_cols=False, + ), + ) + + @staticmethod + def can_be_equivalent() -> bool: + return False + + @staticmethod + def is_two_way(comb_class: Tiling) -> bool: + return True + + @staticmethod + def is_reversible(comb_class: Tiling) -> bool: + return True + + @staticmethod + def shifts( + comb_class: Tiling, + children: Optional[Tuple[Tiling, ...]], + ) -> Tuple[int, ...]: + return (0,) + + def constructor( + self, + comb_class: Tiling, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Constructor: + raise NotImplementedError + + def reverse_constructor( + self, + idx: int, + comb_class: Tiling, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Constructor: + raise NotImplementedError + + def backward_map( + self, + comb_class: Tiling, + objs: Tuple[Optional[GriddedPerm], ...], + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Iterator[GriddedPerm]: + raise NotImplementedError + + def forward_map( + self, + comb_class: Tiling, + obj: GriddedPerm, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Tuple[Optional[GriddedPerm]]: + raise NotImplementedError + + def extra_parameters( + self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None + ) -> Tuple[Dict[str, str], ...]: + if not comb_class.extra_parameters: + return super().extra_parameters(comb_class, children) + if children is None: + children = self.decomposition_function(comb_class) + if children is None: + raise StrategyDoesNotApply("Strategy does not apply") + raise NotImplementedError + + def formal_step(self) -> str: + row_or_col = "rows" if self.row else "cols" + return f"swapping requirements in {row_or_col} {self.idx1} and {self.idx2}" -class PointJumpingFactory(StrategyFactory[Tiling]): +class AssumptionAndPointJumpingFactory(StrategyFactory[Tiling]): """ - A factory returning the disjoint union strategies that moves requirements + A factory returning the strategies that moves requirements and/or assumptions across the boundary of two fusable columns (or rows). """ - def __call__(self, comb_class: Tiling) -> Iterator[DisjointUnionStrategy]: + def __call__( + self, comb_class: Tiling + ) -> Iterator[AssumptionOrPointJumpingStrategy]: cols, rows = comb_class.dimensions gps_to_be_swapped = chain( *comb_class.requirements, *[ass.gps for ass in comb_class.assumptions] @@ -144,15 +353,19 @@ def __call__(self, comb_class: Tiling) -> Iterator[DisjointUnionStrategy]: if any(x in (col, col + 1) for gp in gps_to_be_swapped for x, _ in gp.pos): algo = Fusion(comb_class, col_idx=col) if algo.fusable(): + yield AssumptionAndPointJumpingStrategy(col, col + 1, False) + yield AssumptionJumpingStrategy(col, col + 1, False) yield PointJumpingStrategy(col, col + 1, False) for row in range(rows - 1): if any(y in (row, row + 1) for gp in gps_to_be_swapped for y, _ in gp.pos): algo = Fusion(comb_class, row_idx=row) if algo.fusable(): + yield AssumptionAndPointJumpingStrategy(row, row + 1, True) + yield AssumptionJumpingStrategy(row, row + 1, True) yield PointJumpingStrategy(row, row + 1, True) def __str__(self) -> str: - return "point jumping" + return "assumption and point jumping" def __repr__(self) -> str: return f"{self.__class__.__name__}()" @@ -164,6 +377,6 @@ def __hash__(self) -> int: return hash(self.__class__) @classmethod - def from_dict(cls, d: dict) -> "PointJumpingFactory": + def from_dict(cls, d: dict) -> "AssumptionAndPointJumpingFactory": assert not d - return PointJumpingFactory() + return cls() From c7c2af6656a6a0638a9cf89c9ed34225be9bdeb7 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Thu, 10 Mar 2022 13:48:58 +0000 Subject: [PATCH 018/100] adding init flags (#430) * adding init flags * remove test for fused tiling when unfusable --- tests/algorithms/test_fusion.py | 24 ------------------- tilings/algorithms/fusion.py | 2 ++ tilings/algorithms/sliding.py | 3 +++ tilings/strategies/assumption_splitting.py | 12 ++++++++-- tilings/tiling.py | 28 +++++++++++++++------- 5 files changed, 34 insertions(+), 35 deletions(-) diff --git a/tests/algorithms/test_fusion.py b/tests/algorithms/test_fusion.py index 0c153b86..1e7c8f2c 100644 --- a/tests/algorithms/test_fusion.py +++ b/tests/algorithms/test_fusion.py @@ -271,30 +271,6 @@ def test_fused_tiling( GriddedPerm((1, 0), ((0, 1), (0, 1))), ] ) - # We can get the fused tiling even for not fusable tilings - assert col_fusion_big.fused_tiling() == Tiling( - obstructions=[ - GriddedPerm((0,), ((0, 1),)), - GriddedPerm((0,), ((0, 2),)), - GriddedPerm((0,), ((0, 3),)), - GriddedPerm((1, 0), ((0, 0), (0, 0))), - GriddedPerm((1, 0), ((0, 1), (0, 1))), - GriddedPerm((1, 0), ((0, 1), (0, 0))), - GriddedPerm((1, 0), ((0, 0), (1, 0))), - GriddedPerm((1, 0), ((0, 1), (1, 0))), - GriddedPerm((1, 0), ((0, 1), (1, 1))), - GriddedPerm((1, 0), ((1, 0), (1, 0))), - GriddedPerm((1, 0), ((1, 1), (1, 0))), - GriddedPerm((1, 0), ((1, 1), (1, 1))), - GriddedPerm((1, 0), ((1, 2), (1, 0))), - GriddedPerm((1, 0), ((1, 2), (1, 1))), - GriddedPerm((1, 0), ((1, 2), (1, 2))), - GriddedPerm((2, 1, 0), ((1, 3), (1, 3), (1, 0))), - GriddedPerm((2, 1, 0), ((1, 3), (1, 3), (1, 1))), - GriddedPerm((2, 1, 0), ((1, 3), (1, 3), (1, 2))), - GriddedPerm((2, 1, 0), ((1, 3), (1, 3), (1, 3))), - ] - ) assert fusion_with_req.fused_tiling() == Tiling( obstructions=[ GriddedPerm((0, 1), ((0, 0), (0, 0))), diff --git a/tilings/algorithms/fusion.py b/tilings/algorithms/fusion.py index 61c16d17..a7d1db23 100644 --- a/tilings/algorithms/fusion.py +++ b/tilings/algorithms/fusion.py @@ -401,6 +401,8 @@ def fused_tiling(self) -> "Tiling": obstructions=self.obstruction_fuse_counter.keys(), requirements=requirements, assumptions=assumptions, + derive_empty=False, + already_minimized_obs=True, ) return self._fused_tiling diff --git a/tilings/algorithms/sliding.py b/tilings/algorithms/sliding.py index c1552b10..2780e652 100644 --- a/tilings/algorithms/sliding.py +++ b/tilings/algorithms/sliding.py @@ -61,6 +61,9 @@ def slide_column(self, av_12: int, av_123: int) -> "Tiling": requirements=self.tiling.requirements, obstructions=obstructions, assumptions=self._swap_assumptions(av_12, av_123), + derive_empty=False, + remove_empty_rows_and_cols=False, + simplify=False, ) @staticmethod diff --git a/tilings/strategies/assumption_splitting.py b/tilings/strategies/assumption_splitting.py index 71610128..d45ba87c 100644 --- a/tilings/strategies/assumption_splitting.py +++ b/tilings/strategies/assumption_splitting.py @@ -186,9 +186,17 @@ def decomposition_function(self, comb_class: Tiling) -> Optional[Tuple[Tiling]]: new_assumptions: List[TrackingAssumption] = [] for ass in comb_class.assumptions: new_assumptions.extend(self._split_assumption(ass, components)) - return ( - Tiling(comb_class.obstructions, comb_class.requirements, new_assumptions), + new_tiling = Tiling( + comb_class.obstructions, + comb_class.requirements, + sorted(new_assumptions), + remove_empty_rows_and_cols=False, + derive_empty=False, + simplify=False, + sorted_input=True, ) + new_tiling.clean_assumptions() + return (new_tiling,) def _split_assumption( self, assumption: TrackingAssumption, components: Tuple[Set[Cell], ...] diff --git a/tilings/tiling.py b/tilings/tiling.py index c6331c6c..8e84edc0 100644 --- a/tilings/tiling.py +++ b/tilings/tiling.py @@ -579,26 +579,31 @@ def insert_cell(self, cell: Cell) -> "Tiling": def add_obstruction(self, patt: Perm, pos: Iterable[Cell]) -> "Tiling": """Returns a new tiling with the obstruction of the pattern patt with positions pos.""" - return Tiling( - self._obstructions + (GriddedPerm(patt, pos),), - self._requirements, - self._assumptions, - ) + return self.add_obstructions((GriddedPerm(patt, pos),)) def add_obstructions(self, gps: Iterable[GriddedPerm]) -> "Tiling": """Returns a new tiling with the obstructions added.""" new_obs = tuple(gps) return Tiling( - self._obstructions + new_obs, self._requirements, self._assumptions + sorted(self._obstructions + new_obs), + self._requirements, + self._assumptions, + sorted_input=True, + derive_empty=False, ) def add_list_requirement(self, req_list: Iterable[GriddedPerm]) -> "Tiling": """ Return a new tiling with the requirement list added. """ - new_req = tuple(req_list) + new_req = tuple(sorted(req_list)) return Tiling( - self._obstructions, self._requirements + (new_req,), self._assumptions + self._obstructions, + sorted(self._requirements + (new_req,)), + self._assumptions, + sorted_input=True, + already_minimized_obs=True, + derive_empty=False, ) def add_requirement(self, patt: Perm, pos: Iterable[Cell]) -> "Tiling": @@ -901,6 +906,9 @@ def _transform( ass.__class__(gptransf(gp) for gp in ass.gps) for ass in self._assumptions ), + remove_empty_rows_and_cols=False, + derive_empty=False, + simplify=False, ) def reverse(self, regions=False): @@ -1774,9 +1782,11 @@ def rec( res: List[GriddedPerm] = [] rec(cols, patt, pos, used, 0, 0, res) return Tiling( - obstructions=list(self.obstructions) + res, + obstructions=sorted(list(self.obstructions) + res), requirements=self.requirements, assumptions=self.assumptions, + sorted_input=True, + derive_empty=False, ) @classmethod From bf402ef9018707391aa48d97eb264fc0a2bdba91 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 22 Mar 2022 13:08:26 +0000 Subject: [PATCH 019/100] the cell reduction strategy (#428) * the cell reduction strategy * manually change local basis to monotone in non finite cells, forbid if ob touches row and col --- CHANGELOG.md | 2 + tests/strategies/test_encoding.py | 16 ++ tilings/strategies/__init__.py | 3 + tilings/strategies/cell_reduction.py | 276 +++++++++++++++++++++++++++ tilings/strategies/deflation.py | 5 +- 5 files changed, 300 insertions(+), 2 deletions(-) create mode 100644 tilings/strategies/cell_reduction.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 969f5765..68d0dd8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,8 @@ swapped around a fusable row or column. - `DeflationFactory` which adds rules where cells can be deflated into increasing or decreasing cells as obstructions can't occur across the sum/skew components in that cell. +- `CellReductionFactory` which changes a cell to monotone if at most one point of + any crossing gp touches that cell. - `PositiveCorroborationFactory` that inserts into cells which if positive makes another cell empty. This strategy is added to most packs. - `TileScopePack.remove_strategy` method that removes a strategy from a pack. diff --git a/tests/strategies/test_encoding.py b/tests/strategies/test_encoding.py index 4070a7cf..56a3a565 100644 --- a/tests/strategies/test_encoding.py +++ b/tests/strategies/test_encoding.py @@ -13,6 +13,7 @@ AssumptionAndPointJumpingFactory, BasicVerificationStrategy, CellInsertionFactory, + CellReductionFactory, ComponentFusionFactory, DatabaseVerificationStrategy, DeflationFactory, @@ -47,6 +48,8 @@ SymmetriesFactory, TargetedCellInsertionFactory, ) +from tilings.strategies.cell_reduction import CellReductionStrategy +from tilings.strategies.deflation import DeflationStrategy from tilings.strategies.experimental_verification import SubclassVerificationStrategy from tilings.strategies.factor import ( FactorStrategy, @@ -423,6 +426,19 @@ def indices_and_row(strategy): + [ComponentFusionStrategy(col_idx=3)] + [FusionFactory(tracked=True), FusionFactory(tracked=False)] + [DeflationFactory(tracked=True), DeflationFactory(tracked=False)] + + [CellReductionFactory(tracked=True), DeflationFactory(tracked=False)] + + [ + CellReductionStrategy((0, 0), True, True), + CellReductionStrategy((2, 1), True, False), + CellReductionStrategy((3, 3), False, True), + CellReductionStrategy((4, 1), False, False), + ] + + [ + DeflationStrategy((0, 0), True, True), + DeflationStrategy((2, 1), True, False), + DeflationStrategy((3, 3), False, True), + DeflationStrategy((4, 1), False, False), + ] + [ComponentFusionFactory()] + [ObstructionInferralStrategy([GriddedPerm((0, 1, 2), ((0, 0), (1, 1), (1, 2)))])] + [ diff --git a/tilings/strategies/__init__.py b/tilings/strategies/__init__.py index d186130f..9998f82a 100644 --- a/tilings/strategies/__init__.py +++ b/tilings/strategies/__init__.py @@ -1,5 +1,6 @@ from .assumption_insertion import AddAssumptionFactory, AddInterleavingAssumptionFactory from .assumption_splitting import SplittingStrategy +from .cell_reduction import CellReductionFactory from .deflation import DeflationFactory from .detect_components import DetectComponentsStrategy from .experimental_verification import ( @@ -88,6 +89,8 @@ "ObstructionTransitivityFactory", "RowColumnSeparationStrategy", "SubobstructionInferralFactory", + # Reduction + "CellReductionFactory", # Symmetry "SymmetriesFactory", # Verification diff --git a/tilings/strategies/cell_reduction.py b/tilings/strategies/cell_reduction.py new file mode 100644 index 00000000..2442f4c8 --- /dev/null +++ b/tilings/strategies/cell_reduction.py @@ -0,0 +1,276 @@ +"""The cell reduction strategy.""" +from itertools import chain +from typing import Callable, Dict, Iterator, List, Optional, Set, Tuple, cast + +import sympy + +from comb_spec_searcher import Constructor, Strategy, StrategyFactory +from comb_spec_searcher.exception import StrategyDoesNotApply +from comb_spec_searcher.typing import ( + Parameters, + RelianceProfile, + SubObjects, + SubRecs, + SubSamplers, + SubTerms, + Terms, +) +from permuta import Perm +from tilings import GriddedPerm, Tiling +from tilings.assumptions import TrackingAssumption + +Cell = Tuple[int, int] + + +class CellReductionConstructor(Constructor): + def __init__(self, parameter: str): + self.parameter = parameter + + def get_equation( + self, lhs_func: sympy.Function, rhs_funcs: Tuple[sympy.Function, ...] + ) -> sympy.Eq: + raise NotImplementedError + + def reliance_profile(self, n: int, **parameters: int) -> RelianceProfile: + raise NotImplementedError + + def get_terms( + self, parent_terms: Callable[[int], Terms], subterms: SubTerms, n: int + ) -> Terms: + raise NotImplementedError + + def get_sub_objects( + self, subobjs: SubObjects, n: int + ) -> Iterator[Tuple[Parameters, Tuple[List[Optional[GriddedPerm]]]]]: + raise NotImplementedError + + def random_sample_sub_objects( + self, + parent_count: int, + subsamplers: SubSamplers, + subrecs: SubRecs, + n: int, + **parameters: int, + ) -> Tuple[GriddedPerm]: + raise NotImplementedError + + def equiv( + self, other: "Constructor", data: Optional[object] = None + ) -> Tuple[bool, Optional[object]]: + raise NotImplementedError + + +class CellReductionStrategy(Strategy[Tiling, GriddedPerm]): + """A strategy that replaces the cell with an increasing or decreasing cell.""" + + def __init__( + self, + cell: Cell, + increasing: bool, + tracked: bool = True, + ): + self.cell = cell + self.tracked = tracked + self.increasing = increasing + super().__init__() + + @staticmethod + def can_be_equivalent() -> bool: + return False + + @staticmethod + def is_two_way(comb_class: Tiling) -> bool: + return False + + @staticmethod + def is_reversible(comb_class: Tiling) -> bool: + return False + + @staticmethod + def shifts( + comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None + ) -> Tuple[int, ...]: + return (0, 0) + + def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling, Tiling]: + if self.increasing: + extra = Perm((1, 0)) + else: + extra = Perm((0, 1)) + reduced_obs = sorted( + [ + ob + for ob in comb_class.obstructions + if not ob.pos[0] == self.cell or not ob.is_single_cell() + ] + + [GriddedPerm.single_cell(extra, self.cell)] + ) + reduced_tiling = Tiling( + reduced_obs, + comb_class.requirements, + comb_class.assumptions, + remove_empty_rows_and_cols=False, + derive_empty=False, + simplify=False, + sorted_input=True, + ) + local_basis = comb_class.sub_tiling([self.cell]) + if self.tracked: + return ( + reduced_tiling.add_assumption( + TrackingAssumption.from_cells([self.cell]) + ), + local_basis, + ) + return reduced_tiling, local_basis + + def constructor( + self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None + ) -> CellReductionConstructor: + if not self.tracked: + raise NotImplementedError("The reduction strategy was not tracked") + if children is None: + children = self.decomposition_function(comb_class) + if children is None: + raise StrategyDoesNotApply("Can't reduce the cell") + ass = TrackingAssumption.from_cells([self.cell]) + child_param = children[0].get_assumption_parameter(ass) + gp = GriddedPerm.point_perm(self.cell) + if any(gp in assumption.gps for assumption in comb_class.assumptions): + raise NotImplementedError + return CellReductionConstructor(child_param) + + def reverse_constructor( + self, + idx: int, + comb_class: Tiling, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Constructor: + if children is None: + children = self.decomposition_function(comb_class) + if children is None: + raise StrategyDoesNotApply("Can't reduce the cell") + raise NotImplementedError + + def extra_parameters( + self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None + ) -> Tuple[Dict[str, str]]: + if children is None: + children = self.decomposition_function(comb_class) + if children is None: + raise StrategyDoesNotApply("Strategy does not apply") + raise NotImplementedError + + def formal_step(self) -> str: + return f"reducing cell {self.cell}" + + def backward_map( + self, + comb_class: Tiling, + objs: Tuple[Optional[GriddedPerm], ...], + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Iterator[GriddedPerm]: + if children is None: + children = self.decomposition_function(comb_class) + raise NotImplementedError + + def forward_map( + self, + comb_class: Tiling, + obj: GriddedPerm, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Tuple[Optional[GriddedPerm], ...]: + if children is None: + children = self.decomposition_function(comb_class) + raise NotImplementedError + + def to_jsonable(self) -> dict: + d: dict = super().to_jsonable() + d.pop("ignore_parent") + d.pop("inferrable") + d.pop("possibly_empty") + d.pop("workable") + d["cell"] = self.cell + d["tracked"] = self.tracked + d["increasing"] = self.increasing + return d + + def __repr__(self) -> str: + args = ", ".join( + [ + f"cell={self.cell!r}", + f"increasing={self.increasing!r}", + f"tracked={self.tracked!r}", + ] + ) + return f"{self.__class__.__name__}({args})" + + @classmethod + def from_dict(cls, d: dict) -> "CellReductionStrategy": + cell = cast(Tuple[int, int], tuple(d.pop("cell"))) + tracked = d.pop("tracked") + increasing = d.pop("increasing") + assert not d + return cls(cell, increasing, tracked) + + @staticmethod + def get_eq_symbol() -> str: + return "↣" + + +class CellReductionFactory(StrategyFactory[Tiling]): + def __init__(self, tracked: bool): + self.tracked = tracked + super().__init__() + + def __call__(self, comb_class: Tiling) -> Iterator[CellReductionStrategy]: + cell_bases = comb_class.cell_basis() + for cell in self.reducible_cells(comb_class): + if not ( # a finite cell + any(patt.is_increasing() for patt in cell_bases[cell][0]) + and any(patt.is_decreasing() for patt in cell_bases[cell][0]) + ): + yield CellReductionStrategy(cell, True, self.tracked) + yield CellReductionStrategy(cell, False, self.tracked) + + @staticmethod + def reducible_cells(tiling: Tiling) -> Set[Cell]: + """Return the set of cells with at most one point in a crossing + gridded permutation touching them""" + + def gp_in_row_and_col(gp: GriddedPerm, cell: Cell) -> bool: + """Return True if there are points touching a cell in the row and col of + cell that isn't cell itself.""" + x, y = cell + return ( + len(set(gp.pos[idx][1] for idx, _ in gp.get_points_col(x))) > 1 + and len(set(gp.pos[idx][0] for idx, _ in gp.get_points_row(y))) > 1 + ) + + gps: Iterator[GriddedPerm] = chain(tiling.obstructions, *tiling.requirements) + cells = set(tiling.active_cells) - set(tiling.point_cells) + for gp in gps: + if not cells: + break + if not gp.is_localized(): + seen = set() + for cell in gp.pos: + if cell in seen or gp_in_row_and_col(gp, cell): + cells.discard(cell) + seen.add(cell) + return cells + + def __repr__(self) -> str: + return self.__class__.__name__ + f"({self.tracked})" + + def __str__(self) -> str: + return ("tracked " if self.tracked else "") + "cell reduction factory" + + def to_jsonable(self) -> dict: + d: dict = super().to_jsonable() + d["tracked"] = self.tracked + return d + + @classmethod + def from_dict(cls, d: dict) -> "CellReductionFactory": + return cls(d["tracked"]) diff --git a/tilings/strategies/deflation.py b/tilings/strategies/deflation.py index 30b26208..e6f92c49 100644 --- a/tilings/strategies/deflation.py +++ b/tilings/strategies/deflation.py @@ -188,10 +188,11 @@ def __repr__(self) -> str: @classmethod def from_dict(cls, d: dict) -> "DeflationStrategy": - cell = d.pop("cell") + cell = cast(Tuple[int, int], tuple(d.pop("cell"))) tracked = d.pop("tracked") + sum_deflate = d.pop("sum_deflate") assert not d - return cls(cell, tracked) + return cls(cell, sum_deflate, tracked) @staticmethod def get_eq_symbol() -> str: From 599c92e91780bf68b29201a15ed43bd996e49f1f Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 22 Mar 2022 13:38:43 +0000 Subject: [PATCH 020/100] Improving initialiser (#432) * add a timer to init to catch slow initialising * add a heuristic to avoid large products * change pattern placement to factor sooner * an actual solution? Pick the product smarter. * tidying up * tidy up cleaned obs to reduce size of product --- CHANGELOG.md | 2 ++ tilings/algorithms/gridded_perm_reduction.py | 30 +++++++++------- tilings/algorithms/minimal_gridded_perms.py | 38 ++++++++++++++++++-- tilings/strategy_pack.py | 12 ++++--- 4 files changed, 63 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68d0dd8e..0ccedd50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,8 @@ swapped around a fusable row or column. some inferral in partial placements. - Don't reinitialise in the `Tiling.from_dict` method. - `GuidedSearcher` expands every symmetry +- `TileScopePack.pattern_placements` factors as an initial strategy. + ### Deprecated - Python 3.7 is no longer supported diff --git a/tilings/algorithms/gridded_perm_reduction.py b/tilings/algorithms/gridded_perm_reduction.py index 51f4383c..070cc178 100644 --- a/tilings/algorithms/gridded_perm_reduction.py +++ b/tilings/algorithms/gridded_perm_reduction.py @@ -17,6 +17,7 @@ def __init__( requirements: Tuple[Tuple[GriddedPerm, ...], ...], sorted_input: bool = False, already_minimized_obs: bool = False, + manual: bool = False, ): # Only using MGP for typing purposes. if sorted_input: @@ -25,8 +26,8 @@ def __init__( else: self._obstructions = tuple(sorted(obstructions)) self._requirements = tuple(sorted(tuple(sorted(r)) for r in requirements)) - - self._minimize_griddedperms(already_minimized_obs=already_minimized_obs) + if not manual: + self._minimize_griddedperms(already_minimized_obs=already_minimized_obs) @property def obstructions(self) -> Tuple[GriddedPerm, ...]: @@ -100,21 +101,26 @@ def minimal_obs(self) -> bool: changed = False new_obs: Set[GriddedPerm] = set() for requirement in self.requirements: - new_obs.update( + cleaned_obs = tuple( + tuple( + GriddedPermReduction._minimize( + self.clean_isolated(self._obstructions, gp) + ) + ) + for gp in requirement + ) + gpr = GriddedPermReduction(self._obstructions, cleaned_obs, manual=True) + gpr.minimal_reqs() + cleaned_obs = gpr.requirements + implied_obs = set( MinimalGriddedPerms( self._obstructions, - tuple( - tuple( - GriddedPermReduction._minimize( - self.clean_isolated(self._obstructions, gp) - ) - ) - for gp in requirement - ), + cleaned_obs, ).minimal_gridded_perms( - max_length_to_build=max(map(len, self._obstructions)) + max_length_to_build=max(map(len, self._obstructions)) - 1 ) ) + new_obs.update(implied_obs) if new_obs: changed = True self._obstructions = tuple( diff --git a/tilings/algorithms/minimal_gridded_perms.py b/tilings/algorithms/minimal_gridded_perms.py index bb0a6c1c..0d3e87ac 100644 --- a/tilings/algorithms/minimal_gridded_perms.py +++ b/tilings/algorithms/minimal_gridded_perms.py @@ -129,12 +129,44 @@ def contains(self, gp: GriddedPerm, *patts: GriddedPerm) -> bool: return True return False - def _prepare_queue(self, queue: List[QueuePacket]) -> Iterator[GriddedPerm]: + def _product_requirements( + self, max_length_to_build: Optional[int] = None + ) -> Iterator[GPTuple]: + if max_length_to_build is None: + yield from product(*self.requirements) + return + + def _rec_product_requirements( + requirements: Reqs, counter: Dict[Cell, int] + ) -> Iterator[GPTuple]: + if len(requirements) == 0: + yield tuple() + return + reqs = requirements[0] + rest = requirements[1:] + for gp in reqs: + gpcounter = Counter(gp.pos) + new_counter = Counter( + { + cell: max(gpcounter[cell], counter[cell]) + for cell in chain(gpcounter, counter) + } + ) + assert max_length_to_build is not None + if sum(new_counter.values()) <= max_length_to_build: + for gps in _rec_product_requirements(rest, new_counter): + yield (gp,) + gps + + yield from _rec_product_requirements(self.requirements, Counter()) + + def _prepare_queue( + self, queue: List[QueuePacket], max_length_to_build: Optional[int] = None + ) -> Iterator[GriddedPerm]: """Add cell counters with gridded permutations to the queue. The function yields all initial_gp that satisfy the requirements.""" if len(self.requirements) <= 1: return - for gps in product(*self.requirements): + for gps in self._product_requirements(max_length_to_build): # try to stitch together as much of the independent cells of the # gridded permutation together first initial_gp = self.initial_gp(*gps) @@ -407,7 +439,7 @@ def minimal_gridded_perms( initial_gps_to_auto_yield: Dict[int, Set[GriddedPerm]] = defaultdict(set) yielded: Set[GriddedPerm] = set() - for gp in self._prepare_queue(queue): + for gp in self._prepare_queue(queue, max_length_to_build): if yield_non_minimal: yielded.add(gp) yield gp diff --git a/tilings/strategy_pack.py b/tilings/strategy_pack.py index b47fed5e..ae6278c6 100644 --- a/tilings/strategy_pack.py +++ b/tilings/strategy_pack.py @@ -342,15 +342,16 @@ def pattern_placements( ) expansion_strats: List[CSSstrategy] = [ - strat.FactorFactory(unions=True), - strat.PositiveCorroborationFactory(), strat.CellInsertionFactory(maxreqlen=length), ] if length > 1: expansion_strats.append(strat.RequirementCorroborationFactory()) return TileScopePack( - initial_strats=[strat.PatternPlacementFactory(partial=partial)], + initial_strats=[ + strat.FactorFactory(unions=True, ignore_parent=False), + strat.PositiveCorroborationFactory(), + ], ver_strats=[ strat.BasicVerificationStrategy(), strat.InsertionEncodingVerificationStrategy(), @@ -361,7 +362,10 @@ def pattern_placements( strat.RowColumnSeparationStrategy(), strat.ObstructionTransitivityFactory(), ], - expansion_strats=[expansion_strats], + expansion_strats=[ + [strat.PatternPlacementFactory(partial=partial)], + expansion_strats, + ], name=name, ) From 1991825235a85e7dd646e8496b9d769264d3af1d Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 22 Mar 2022 18:03:15 +0000 Subject: [PATCH 021/100] ignore 1x1s and monotone cells (#433) --- tilings/strategies/cell_reduction.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tilings/strategies/cell_reduction.py b/tilings/strategies/cell_reduction.py index 2442f4c8..58c0b6ca 100644 --- a/tilings/strategies/cell_reduction.py +++ b/tilings/strategies/cell_reduction.py @@ -224,6 +224,8 @@ def __init__(self, tracked: bool): super().__init__() def __call__(self, comb_class: Tiling) -> Iterator[CellReductionStrategy]: + if comb_class.dimensions == (1, 1): + return cell_bases = comb_class.cell_basis() for cell in self.reducible_cells(comb_class): if not ( # a finite cell @@ -258,6 +260,8 @@ def gp_in_row_and_col(gp: GriddedPerm, cell: Cell) -> bool: if cell in seen or gp_in_row_and_col(gp, cell): cells.discard(cell) seen.add(cell) + elif len(gp) == 2: + cells.discard(gp.pos[0]) return cells def __repr__(self) -> str: From da04aa4976c6ff6031f3a94d042bf1f219d53fdd Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Wed, 23 Mar 2022 14:03:12 +0000 Subject: [PATCH 022/100] Point corroboration (#434) * point corrob * cleaning * tidying code * remove point in bijections --- CHANGELOG.md | 3 +- tests/test_bijections.py | 18 ++++----- tilings/strategies/__init__.py | 2 + tilings/strategies/requirement_insertion.py | 41 ++++++++++++++++++--- tilings/strategy_pack.py | 16 ++++---- 5 files changed, 57 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ccedd50..8f98082c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,7 +30,8 @@ swapped around a fusable row or column. - `CellReductionFactory` which changes a cell to monotone if at most one point of any crossing gp touches that cell. - `PositiveCorroborationFactory` that inserts into cells which if positive makes - another cell empty. This strategy is added to most packs. + another cell empty. Also, the `PointCorroborationFactory`, which does this for + point or empty cells which is added to most packs. - `TileScopePack.remove_strategy` method that removes a strategy from a pack. - `TargetedCellInsertionFactory` which inserts factors of gridded perms if it can lead to factoring out a verified sub tiling. diff --git a/tests/test_bijections.py b/tests/test_bijections.py index e6a6a858..bbe1bc41 100644 --- a/tests/test_bijections.py +++ b/tests/test_bijections.py @@ -21,7 +21,7 @@ FusionParallelSpecFinder, _AssumptionPathTracker, ) -from tilings.strategies import BasicVerificationStrategy, PositiveCorroborationFactory +from tilings.strategies import BasicVerificationStrategy, PointCorroborationFactory from tilings.strategies.assumption_insertion import AddAssumptionsStrategy from tilings.strategies.factor import FactorStrategy from tilings.strategies.fusion import FusionStrategy @@ -51,7 +51,7 @@ def find_bijection_between_fusion( def _b2rc(basis: str) -> CombinatorialSpecificationSearcher: pack = TileScopePack.row_and_col_placements(row_only=True) - pack = pack.remove_strategy(PositiveCorroborationFactory()) + pack = pack.remove_strategy(PointCorroborationFactory()) pack = pack.add_verification(BasicVerificationStrategy(), replace=True) searcher = TileScope(basis, pack) assert isinstance(searcher, CombinatorialSpecificationSearcher) @@ -167,7 +167,7 @@ def test_bijection_8_cross_domain(): ) pack = TileScopePack.row_and_col_placements(row_only=True) pack = pack.add_verification(BasicVerificationStrategy(), replace=True) - pack = pack.remove_strategy(PositiveCorroborationFactory()) + pack = pack.remove_strategy(PointCorroborationFactory()) pack.inferral_strats = () searcher1 = TileScope(t, pack) @@ -233,11 +233,11 @@ def test_bijection_9_cross_domain(): def test_bijection_10(): pack1 = TileScopePack.requirement_placements() pack1 = pack1.add_verification(BasicVerificationStrategy(), replace=True) - pack1 = pack1.remove_strategy(PositiveCorroborationFactory()) + pack1 = pack1.remove_strategy(PointCorroborationFactory()) searcher1 = TileScope("132_4312", pack1) pack2 = TileScopePack.requirement_placements() pack2 = pack2.add_verification(BasicVerificationStrategy(), replace=True) - pack2 = pack2.remove_strategy(PositiveCorroborationFactory()) + pack2 = pack2.remove_strategy(PointCorroborationFactory()) searcher2 = TileScope("132_4231", pack2) _bijection_asserter(find_bijection_between(searcher1, searcher2)) @@ -458,7 +458,7 @@ def test_bijection_12(): def _pntrcpls(b1, b2): pack = TileScopePack.point_and_row_and_col_placements(row_only=True) pack = pack.add_verification(BasicVerificationStrategy(), replace=True) - pack = pack.remove_strategy(PositiveCorroborationFactory()) + pack = pack.remove_strategy(PointCorroborationFactory()) searcher1 = TileScope(b1, pack) searcher2 = TileScope(b2, pack) _bijection_asserter(find_bijection_between(searcher1, searcher2)) @@ -476,7 +476,7 @@ def _pntrcpls(b1, b2): def test_bijection_13(): pack = TileScopePack.point_and_row_and_col_placements(row_only=True) pack = pack.add_verification(BasicVerificationStrategy(), replace=True) - pack = pack.remove_strategy(PositiveCorroborationFactory()) + pack = pack.remove_strategy(PointCorroborationFactory()) searcher1 = TileScope("0132_0213_0231_0321_1032_1320_2031_2301_3021_3120", pack) searcher2 = TileScope("0132_0213_0231_0312_0321_1302_1320_2031_2301_3120", pack) _bijection_asserter(find_bijection_between(searcher1, searcher2)) @@ -496,13 +496,13 @@ def test_bijection_14_json(): def test_bijection_15_fusion(): pack = TileScopePack.row_and_col_placements(row_only=True).make_fusion(tracked=True) pack = pack.add_verification(BasicVerificationStrategy(), replace=True) - pack = pack.remove_strategy(PositiveCorroborationFactory()) + pack = pack.remove_strategy(PointCorroborationFactory()) pack2 = TileScopePack.row_and_col_placements(row_only=True).make_fusion( tracked=True ) pack2 = pack2.add_initial(SlidingFactory(True)) pack2 = pack2.add_verification(BasicVerificationStrategy(), replace=True) - pack2 = pack2.remove_strategy(PositiveCorroborationFactory()) + pack2 = pack2.remove_strategy(PointCorroborationFactory()) long_1234 = Perm( ( 47, diff --git a/tilings/strategies/__init__.py b/tilings/strategies/__init__.py index 9998f82a..fe08cc2f 100644 --- a/tilings/strategies/__init__.py +++ b/tilings/strategies/__init__.py @@ -22,6 +22,7 @@ from .requirement_insertion import ( CellInsertionFactory, FactorInsertionFactory, + PointCorroborationFactory, PositiveCorroborationFactory, RemoveRequirementFactory, RequirementCorroborationFactory, @@ -61,6 +62,7 @@ "AllPlacementsFactory", "CellInsertionFactory", "FactorInsertionFactory", + "PointCorroborationFactory", "PositiveCorroborationFactory", "RemoveRequirementFactory", "RequirementExtensionFactory", diff --git a/tilings/strategies/requirement_insertion.py b/tilings/strategies/requirement_insertion.py index fdcb079a..0dc1ed16 100644 --- a/tilings/strategies/requirement_insertion.py +++ b/tilings/strategies/requirement_insertion.py @@ -520,17 +520,18 @@ def __str__(self) -> str: class PositiveCorroborationFactory(AbstractRequirementInsertionFactory): """ - The point corroboration strategy. + The positive corroboration strategy. - The point corroboration strategy inserts points into any two point - or empty cells which can not both be a point, i.e., one is a point + The positive corroboration strategy inserts points into any two + cells which can not both be positive, i.e., one is positive and the other is empty. """ def __init__(self, ignore_parent: bool = True): super().__init__(ignore_parent) - def req_lists_to_insert(self, tiling: Tiling) -> Iterator[ListRequirement]: + @staticmethod + def cells_to_yield(tiling: Tiling) -> Set[Cell]: potential_cells: Set[Tuple[Cell, ...]] = set() cells_to_yield: Set[Cell] = set() for gp in tiling.obstructions: @@ -543,10 +544,40 @@ def req_lists_to_insert(self, tiling: Tiling) -> Iterator[ListRequirement]: potential_cells.add(cells) else: cells_to_yield.update(gp.pos) - for cell in cells_to_yield: + return cells_to_yield + + def req_lists_to_insert(self, tiling: Tiling) -> Iterator[ListRequirement]: + for cell in self.cells_to_yield(tiling): if cell not in tiling.point_cells: yield (GriddedPerm.point_perm(cell),) + def __str__(self) -> str: + return "positive corroboration" + + +class PointCorroborationFactory(PositiveCorroborationFactory): + """ + The point corroboration strategy. + + The point corroboration strategy inserts points into any two point + or empty cells which can not both be a point, i.e., one is a point + and the other is empty. + """ + + @staticmethod + def cells_to_yield(tiling: Tiling) -> Set[Cell]: + cell_basis = tiling.cell_basis() + point_or_empty_cells = set() + for cell, (patts, _) in cell_basis.items(): + if patts == [Perm((0, 1)), Perm((1, 0))]: + point_or_empty_cells.add(cell) + point_or_empty_cells = point_or_empty_cells - tiling.point_cells + if point_or_empty_cells: + return point_or_empty_cells.intersection( + PositiveCorroborationFactory.cells_to_yield(tiling) + ) + return set() + def __str__(self) -> str: return "point corroboration" diff --git a/tilings/strategy_pack.py b/tilings/strategy_pack.py index ae6278c6..aa987a4a 100644 --- a/tilings/strategy_pack.py +++ b/tilings/strategy_pack.py @@ -302,7 +302,7 @@ def replace_list(strats): def all_the_strategies(cls, length: int = 1) -> "TileScopePack": initial_strats: List[CSSstrategy] = [ strat.FactorFactory(), - strat.PositiveCorroborationFactory(), + strat.PointCorroborationFactory(), ] if length > 1: initial_strats.append(strat.RequirementCorroborationFactory()) @@ -350,7 +350,7 @@ def pattern_placements( return TileScopePack( initial_strats=[ strat.FactorFactory(unions=True, ignore_parent=False), - strat.PositiveCorroborationFactory(), + strat.PointCorroborationFactory(), ], ver_strats=[ strat.BasicVerificationStrategy(), @@ -383,7 +383,7 @@ def point_placements( initial_strats: List[CSSstrategy] = [ strat.FactorFactory(), - strat.PositiveCorroborationFactory(), + strat.PointCorroborationFactory(), ] if length > 1: initial_strats.append(strat.RequirementCorroborationFactory()) @@ -493,7 +493,7 @@ def row_and_col_placements( return TileScopePack( initial_strats=[ strat.FactorFactory(), - strat.PositiveCorroborationFactory(), + strat.PointCorroborationFactory(), ], ver_strats=[ strat.BasicVerificationStrategy(), @@ -557,7 +557,7 @@ def only_root_placements( initial_strats=[ strat.RootInsertionFactory(maxreqlen=length, max_num_req=max_num_req), strat.FactorFactory(unions=True, ignore_parent=False, workable=False), - strat.PositiveCorroborationFactory(), + strat.PointCorroborationFactory(), ], ver_strats=[ strat.BasicVerificationStrategy(), @@ -587,7 +587,7 @@ def requirement_placements( initial_strats: List[CSSstrategy] = [ strat.FactorFactory(), - strat.PositiveCorroborationFactory(), + strat.PointCorroborationFactory(), ] if length > 1: initial_strats.append(strat.RequirementCorroborationFactory()) @@ -643,7 +643,7 @@ def point_and_row_and_col_placements( initial_strats: List[CSSstrategy] = [ strat.FactorFactory(), - strat.PositiveCorroborationFactory(), + strat.PointCorroborationFactory(), ] if length > 1: initial_strats.append(strat.RequirementCorroborationFactory()) @@ -700,7 +700,7 @@ def requirement_and_row_and_col_placements( initial_strats: List[CSSstrategy] = [ strat.FactorFactory(), - strat.PositiveCorroborationFactory(), + strat.PointCorroborationFactory(), ] if length > 1: initial_strats.append(strat.RequirementCorroborationFactory()) From 374d9a43117223aa444eed5516182cb3cd78b533 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 29 Mar 2022 13:25:53 +0000 Subject: [PATCH 023/100] test reqs separately (#437) * test reqs separately * not --- tilings/strategies/cell_reduction.py | 52 ++++++++++++++++++---------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/tilings/strategies/cell_reduction.py b/tilings/strategies/cell_reduction.py index 58c0b6ca..440cc202 100644 --- a/tilings/strategies/cell_reduction.py +++ b/tilings/strategies/cell_reduction.py @@ -1,5 +1,4 @@ """The cell reduction strategy.""" -from itertools import chain from typing import Callable, Dict, Iterator, List, Optional, Set, Tuple, cast import sympy @@ -105,9 +104,14 @@ def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling, Tiling]: ] + [GriddedPerm.single_cell(extra, self.cell)] ) + reduced_reqs = sorted( + req + for req in comb_class.requirements + if not all(gp.pos[0] == self.cell and gp.is_single_cell() for gp in req) + ) reduced_tiling = Tiling( reduced_obs, - comb_class.requirements, + reduced_reqs, comb_class.assumptions, remove_empty_rows_and_cols=False, derive_empty=False, @@ -231,33 +235,45 @@ def __call__(self, comb_class: Tiling) -> Iterator[CellReductionStrategy]: if not ( # a finite cell any(patt.is_increasing() for patt in cell_bases[cell][0]) and any(patt.is_decreasing() for patt in cell_bases[cell][0]) - ): + ) and self.can_reduce_cell_with_requirements(comb_class, cell): yield CellReductionStrategy(cell, True, self.tracked) yield CellReductionStrategy(cell, False, self.tracked) - @staticmethod - def reducible_cells(tiling: Tiling) -> Set[Cell]: - """Return the set of cells with at most one point in a crossing - gridded permutation touching them""" - - def gp_in_row_and_col(gp: GriddedPerm, cell: Cell) -> bool: - """Return True if there are points touching a cell in the row and col of - cell that isn't cell itself.""" - x, y = cell - return ( - len(set(gp.pos[idx][1] for idx, _ in gp.get_points_col(x))) > 1 - and len(set(gp.pos[idx][0] for idx, _ in gp.get_points_row(y))) > 1 + def can_reduce_cell_with_requirements(self, tiling: Tiling, cell: Cell) -> bool: + return all( # local + all(gp.pos[0] == cell and gp.is_single_cell() for gp in req) + or all( + # at most one point in cell + sum(1 for _ in gp.points_in_cell(cell)) < 2 + # no gp in row and col + and not self.gp_in_row_and_col(gp, cell) + for gp in req ) + for req in tiling.requirements + ) + + @staticmethod + def gp_in_row_and_col(gp: GriddedPerm, cell: Cell) -> bool: + """Return True if there are points touching a cell in the row and col of + cell that isn't cell itself.""" + x, y = cell + return ( + len(set(gp.pos[idx][1] for idx, _ in gp.get_points_col(x))) > 1 + and len(set(gp.pos[idx][0] for idx, _ in gp.get_points_row(y))) > 1 + ) - gps: Iterator[GriddedPerm] = chain(tiling.obstructions, *tiling.requirements) + def reducible_cells(self, tiling: Tiling) -> Set[Cell]: + """Return the set of non-monotone cells with at most one point in a crossing + obstrution touching them, and no obstruction touching a cell in the row and + a cell in the column.""" cells = set(tiling.active_cells) - set(tiling.point_cells) - for gp in gps: + for gp in tiling.obstructions: if not cells: break if not gp.is_localized(): seen = set() for cell in gp.pos: - if cell in seen or gp_in_row_and_col(gp, cell): + if cell in seen or self.gp_in_row_and_col(gp, cell): cells.discard(cell) seen.add(cell) elif len(gp) == 2: From ec52ee3925ec8adb557b0e67c2f92923b99d7763 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 29 Mar 2022 13:31:16 +0000 Subject: [PATCH 024/100] allow reqs, and more careful point in between (#436) * allow reqs, and more careful point in between * pylint * api.combopal -> api.permpal --- tilings/algorithms/enumeration.py | 2 +- tilings/strategies/deflation.py | 54 ++++++++++++++++++++++--------- 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/tilings/algorithms/enumeration.py b/tilings/algorithms/enumeration.py index 0db58853..b402b8e4 100644 --- a/tilings/algorithms/enumeration.py +++ b/tilings/algorithms/enumeration.py @@ -331,7 +331,7 @@ class DatabaseEnumeration(Enumeration): find the generating function and the minimal polynomial in the database. """ - API_ROOT_URL = "https://api.combopal.ru.is" + API_ROOT_URL = "https://api.permpal.com" all_verified_tilings: FrozenSet[bytes] = frozenset() num_verified_request = 0 diff --git a/tilings/strategies/deflation.py b/tilings/strategies/deflation.py index e6f92c49..220d3418 100644 --- a/tilings/strategies/deflation.py +++ b/tilings/strategies/deflation.py @@ -90,11 +90,7 @@ def shifts( return (0, 0) def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling, Tiling]: - if self.sum_deflate: - extra = Perm((1, 0)) - else: - extra = Perm((0, 1)) - deflated_tiling = comb_class.add_obstruction(extra, (self.cell, self.cell)) + deflated_tiling = self.deflated_tiling(comb_class) local_basis = comb_class.sub_tiling([self.cell]) if self.tracked: return ( @@ -105,6 +101,26 @@ def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling, Tiling]: ) return deflated_tiling, local_basis + def deflated_tiling(self, tiling: Tiling) -> Tiling: + if self.sum_deflate: + extra = Perm((1, 0)) + else: + extra = Perm((0, 1)) + reduced_reqs = tuple( + req + for req in tiling.requirements + if not all(gp.pos[0] == self.cell and gp.is_single_cell() for gp in req) + ) + return Tiling( + tiling.obstructions, + reduced_reqs, + tiling.assumptions, + remove_empty_rows_and_cols=False, + derive_empty=False, + simplify=False, + sorted_input=True, + ).add_obstruction(extra, (self.cell, self.cell)) + def constructor( self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None ) -> DeflationConstructor: @@ -205,15 +221,23 @@ def __init__(self, tracked: bool): super().__init__() def __call__(self, comb_class: Tiling) -> Iterator[DeflationStrategy]: - if comb_class.requirements: - # TODO: this is obviously too strong - return for cell in comb_class.active_cells: + if not self._can_deflate_requirements(comb_class, cell): + continue if self.can_deflate(comb_class, cell, True): yield DeflationStrategy(cell, True, self.tracked) if self.can_deflate(comb_class, cell, False): yield DeflationStrategy(cell, False, self.tracked) + @staticmethod + def _can_deflate_requirements(tiling: Tiling, cell: Cell) -> bool: + def can_deflate_req_list(req: Tuple[GriddedPerm, ...]) -> bool: + return all(gp.pos[0] == cell and gp.is_single_cell() for gp in req) or all( + len(list(gp.points_in_cell(cell))) < 2 for gp in req + ) + + return all(can_deflate_req_list(req) for req in tiling.requirements) + @staticmethod def can_deflate(tiling: Tiling, cell: Cell, sum_deflate: bool) -> bool: alone_in_row = tiling.only_cell_in_row(cell) @@ -252,9 +276,9 @@ def can_deflate(tiling: Tiling, cell: Cell, sum_deflate: bool) -> bool: # we need the other cell to be in between the intended deflate # patt in either the row or column other_cell = [c for c in ob.pos if c != cell][0] - if DeflationFactory.point_in_between( - ob, True, cell, other_cell - ) or DeflationFactory.point_in_between(ob, False, cell, other_cell): + if ( # in a row or column + cell[0] == other_cell[0] or cell[1] == other_cell[1] + ) and DeflationFactory.point_in_between(ob, cell, other_cell): # this cell does not interleave with inflated components cells_not_interleaving.add(other_cell) continue @@ -270,17 +294,17 @@ def can_deflate(tiling: Tiling, cell: Cell, sum_deflate: bool) -> bool: return False @staticmethod - def point_in_between( - ob: GriddedPerm, row: bool, cell: Cell, other_cell: Cell - ) -> bool: + def point_in_between(ob: GriddedPerm, cell: Cell, other_cell: Cell) -> bool: """Return true if point in other cell is in between point in cell. - Assumes a length 3 pattern, and to be told if row or column.""" + Assumes a length 3 pattern, and it is in a row or column.""" + row = cell[1] == other_cell[1] patt = cast(Tuple[int, int, int], ob.patt) if row: left = other_cell[0] < cell[0] if left: return bool(patt[0] == 1) return bool(patt[2] == 1) + assert cell[0] == other_cell[0] below = other_cell[1] < cell[1] if below: return bool(patt[1] == 0) From 02b3fc27e10001fd9d984a55e1ffc063b2253328 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20Nadeau?= Date: Thu, 7 Apr 2022 11:30:34 +0000 Subject: [PATCH 025/100] make not implemented reverse constructor for interleaving factor (#441) * make not implemented reverse constructor for interleaving factor * update black version --- .pre-commit-config.yaml | 2 +- tilings/strategies/factor.py | 8 ++++++++ tox.ini | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cf81be99..e3d11388 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ repos: - repo: https://github.com/psf/black - rev: 22.1.0 + rev: 22.3.0 hooks: - id: black diff --git a/tilings/strategies/factor.py b/tilings/strategies/factor.py index b069ae9f..daba25cc 100644 --- a/tilings/strategies/factor.py +++ b/tilings/strategies/factor.py @@ -292,6 +292,14 @@ def constructor( interleaving_parameters, ) + def reverse_constructor( + self, + idx: int, + comb_class: Tiling, + children: Optional[Tuple[Tiling, ...]] = None, + ): + raise NotImplementedError + def interleaving_parameters(self, comb_class: Tiling) -> List[Tuple[str, ...]]: """ Return the parameters on the parent tiling that needed to be interleaved. diff --git a/tox.ini b/tox.ini index c9c98848..0f503082 100644 --- a/tox.ini +++ b/tox.ini @@ -60,5 +60,5 @@ commands = mypy description = check that comply with autoformating basepython = {[default]basepython} deps = - black==22.1.0 + black==22.3.0 commands = black --check --diff . From 07312322977c8ea21214b157fbc45a0d231ec2a5 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 19 Apr 2022 15:38:54 +0200 Subject: [PATCH 026/100] subobstruction insertion factory and pack (#442) --- CHANGELOG.md | 2 ++ tests/strategies/test_encoding.py | 2 ++ tests/test_strategy_pack.py | 1 + tilings/strategies/__init__.py | 2 ++ tilings/strategies/requirement_insertion.py | 15 ++++++++++- tilings/strategy_pack.py | 29 +++++++++++++++++++++ 6 files changed, 50 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f98082c..17f8b46f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,8 @@ swapped around a fusable row or column. - `TileScopePack.remove_strategy` method that removes a strategy from a pack. - `TargetedCellInsertionFactory` which inserts factors of gridded perms if it can lead to factoring out a verified sub tiling. +- `SubobstructionInsertionFactory` that inserts subobstructions and the pack + `TileScopePack.subobstruction_placements` which uses it. ### Fixed - `ForgetTrackedSearcher` was not retroactively applying strategies that had a `basis`. diff --git a/tests/strategies/test_encoding.py b/tests/strategies/test_encoding.py index 56a3a565..b6664c7d 100644 --- a/tests/strategies/test_encoding.py +++ b/tests/strategies/test_encoding.py @@ -45,6 +45,7 @@ SplittingStrategy, SubclassVerificationFactory, SubobstructionInferralFactory, + SubobstructionInsertionFactory, SymmetriesFactory, TargetedCellInsertionFactory, ) @@ -463,6 +464,7 @@ def indices_and_row(strategy): + [MonotoneSlidingFactory(), GeneralizedSlidingStrategy(1)] + indices_and_row(PointJumpingStrategy) + [TargetedCellInsertionFactory()] + + ignoreparent(SubobstructionInsertionFactory) ) __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) diff --git a/tests/test_strategy_pack.py b/tests/test_strategy_pack.py index df9004eb..04a24bd9 100644 --- a/tests/test_strategy_pack.py +++ b/tests/test_strategy_pack.py @@ -84,6 +84,7 @@ def length_row_col_partial(pack): packs = ( length(TileScopePack.all_the_strategies) + partial(TileScopePack.insertion_point_placements) + + partial(TileScopePack.subobstruction_placements) + row_col_partial(TileScopePack.insertion_row_and_col_placements) + row_col_partial(TileScopePack.insertion_point_row_and_col_placements) + length_maxnumreq_partial(TileScopePack.only_root_placements) diff --git a/tilings/strategies/__init__.py b/tilings/strategies/__init__.py index fe08cc2f..ac0a73c8 100644 --- a/tilings/strategies/__init__.py +++ b/tilings/strategies/__init__.py @@ -29,6 +29,7 @@ RequirementExtensionFactory, RequirementInsertionFactory, RootInsertionFactory, + SubobstructionInsertionFactory, TargetedCellInsertionFactory, ) from .requirement_placement import ( @@ -71,6 +72,7 @@ "RequirementCorroborationFactory", "RootInsertionFactory", "RowAndColumnPlacementFactory", + "SubobstructionInsertionFactory", "TargetedCellInsertionFactory", # Decomposition "FactorFactory", diff --git a/tilings/strategies/requirement_insertion.py b/tilings/strategies/requirement_insertion.py index 0dc1ed16..65d830da 100644 --- a/tilings/strategies/requirement_insertion.py +++ b/tilings/strategies/requirement_insertion.py @@ -9,7 +9,7 @@ from comb_spec_searcher.strategies.strategy import VerificationStrategy from permuta import Av, Perm from tilings import GriddedPerm, Tiling -from tilings.algorithms import Factor +from tilings.algorithms import Factor, SubobstructionInferral Cell = Tuple[int, int] ListRequirement = Tuple[GriddedPerm, ...] @@ -673,3 +673,16 @@ def __repr__(self): def __str__(self) -> str: return "targeted cell insertions" + + +class SubobstructionInsertionFactory(AbstractRequirementInsertionFactory): + """ + Insert all subobstructions of the obstructions on the tiling. + """ + + def req_lists_to_insert(self, tiling: Tiling) -> Iterator[ListRequirement]: + for gp in SubobstructionInferral(tiling).potential_new_obs(): + yield (gp,) + + def __str__(self) -> str: + return "subobstruction insertion" diff --git a/tilings/strategy_pack.py b/tilings/strategy_pack.py index aa987a4a..919b8c22 100644 --- a/tilings/strategy_pack.py +++ b/tilings/strategy_pack.py @@ -613,6 +613,35 @@ def requirement_placements( name=name, ) + @classmethod + def subobstruction_placements(cls, partial: bool = False) -> "TileScopePack": + name = "partial_" if partial else "" + name += "subobstruction_placements" + return TileScopePack( + initial_strats=[ + strat.FactorFactory(), + strat.PointCorroborationFactory(), + strat.RequirementCorroborationFactory(), + ], + ver_strats=[ + strat.BasicVerificationStrategy(), + strat.InsertionEncodingVerificationStrategy(), + strat.OneByOneVerificationStrategy(), + strat.LocallyFactorableVerificationStrategy(), + ], + inferral_strats=[ + strat.RowColumnSeparationStrategy(), + strat.ObstructionTransitivityFactory(), + ], + expansion_strats=[ + [ + strat.SubobstructionInsertionFactory(), + strat.PatternPlacementFactory(partial=partial), + ], + ], + name=name, + ) + @classmethod def point_and_row_and_col_placements( cls, From 7f899b25ca737090211b89183532f6f7476dae89 Mon Sep 17 00:00:00 2001 From: jaypantone Date: Tue, 19 Apr 2022 08:52:56 -0500 Subject: [PATCH 027/100] kitchen_sinkify (#435) * creates kitchen_sinkify function * don't add short obs or obs inferral if the arguments passed are 0 * fixes CHANGELOG * adds DB verified and req corrob * tell pylint to go think about it more * add test of kitchen sinkify pack json Co-authored-by: Christian Bean --- CHANGELOG.md | 23 ++++----- tests/test_strategy_pack.py | 4 ++ tilings/strategy_pack.py | 95 +++++++++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17f8b46f..08ee045a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,24 +17,25 @@ multiple different obs and one requirement list of size possibly greater than on Previously it was only doing the case where a single ob's factor is implied by a requirement. - added `TileScopePack.requirement_and_row_and_col_placements` -- `AssumptionAndPointJumpingFactory` which adds rules where requirements and/or +- `AssumptionAndPointJumpingFactory` which adds rules where requirements and/or assumptions are swapped around a fusable row or column. -- `PointJumpingFactory` which adds rules where requirements and assumptions can be +- `PointJumpingFactory` which adds rules where requirements and assumptions can be swapped around a fusable row or column. - `MonotoneSlidingFactory` that creates rules that swaps neighbouring cells if they - are 'monotone' fusable, i.e., they are a generalized fusion with a monotone local + are 'monotone' fusable, i.e., they are a generalized fusion with a monotone local extra obstruction. - `DeflationFactory` which adds rules where cells can be deflated into increasing or decreasing cells as obstructions can't occur across the sum/skew components in that cell. -- `CellReductionFactory` which changes a cell to monotone if at most one point of +- `CellReductionFactory` which changes a cell to monotone if at most one point of any crossing gp touches that cell. - `PositiveCorroborationFactory` that inserts into cells which if positive makes another cell empty. Also, the `PointCorroborationFactory`, which does this for point or empty cells which is added to most packs. - `TileScopePack.remove_strategy` method that removes a strategy from a pack. -- `TargetedCellInsertionFactory` which inserts factors of gridded perms if it can - lead to factoring out a verified sub tiling. +- `TargetedCellInsertionFactory` which inserts factors of gridded perms if it can + lead to factoring out a verified sub tiling. +- `StrategyPack.kitchen_sinkify` to add many experimental strategies to the pack - `SubobstructionInsertionFactory` that inserts subobstructions and the pack `TileScopePack.subobstruction_placements` which uses it. @@ -48,15 +49,15 @@ swapped around a fusable row or column. - Verification strategies no longer ignore parent - `TrackedSearcher` now uses a `TrackedQueue` and is able to work with all packs and new future strategies. -- `TileScopePack.make_tracked` will add the appropriate tracking methods for +- `TileScopePack.make_tracked` will add the appropriate tracking methods for interleaving factors and make strategies tracked if it can be. -- The `GriddedPermReduction` limits the size of obstructions it tries to infer in - the `minimal_obs` method to the size of the largest obstruction already on the +- The `GriddedPermReduction` limits the size of obstructions it tries to infer in + the `minimal_obs` method to the size of the largest obstruction already on the tiling. -- The `SymmetriesFactory` takes a basis and will not return any symmetries where +- The `SymmetriesFactory` takes a basis and will not return any symmetries where any of the patterns of the obstruction are not subpatterns of some basis element. If no basis is given, all symmetries are returned. -- `RequirementPlacement` adds empty cells when placing a point cell. This saves +- `RequirementPlacement` adds empty cells when placing a point cell. This saves some inferral in partial placements. - Don't reinitialise in the `Tiling.from_dict` method. - `GuidedSearcher` expands every symmetry diff --git a/tests/test_strategy_pack.py b/tests/test_strategy_pack.py index 04a24bd9..77754090 100644 --- a/tests/test_strategy_pack.py +++ b/tests/test_strategy_pack.py @@ -115,6 +115,10 @@ def length_row_col_partial(pack): + [pack.add_initial(SlidingFactory()) for pack in packs] + [pack.add_initial(SlidingFactory(use_symmetries=True)) for pack in packs] + [pack.add_initial(AssumptionAndPointJumpingFactory()) for pack in packs] + + [ + pack.kitchen_sinkify(short_obs_len=4, obs_inferral_len=2, tracked=True) + for pack in packs + ] ) diff --git a/tilings/strategy_pack.py b/tilings/strategy_pack.py index 919b8c22..f734d821 100644 --- a/tilings/strategy_pack.py +++ b/tilings/strategy_pack.py @@ -297,6 +297,101 @@ def replace_list(strats): iterative=self.iterative, ) + def kitchen_sinkify( # pylint: disable=R0912 + self, short_obs_len: int, obs_inferral_len: int, tracked: bool + ) -> "TileScopePack": + """ + Create a new pack with the following added: + Short Obs verification (unless short_obs_len = 0) + No Root Cell verification + Database verification + Deflation + Point and/or Assumption Jumping + Generalized Monotone Sliding + Free Cell Reduction + Requirement corroboration + Obstruction Inferral (unless obs_inferral_len = 0) + Symmetries + Will be made tracked or not, depending on preference. + Note that nothing is done with positive / point corroboration, requirement + corroboration, or database verification. + """ + + ks_pack = self.__class__( + ver_strats=self.ver_strats, + inferral_strats=self.inferral_strats, + initial_strats=self.initial_strats, + expansion_strats=self.expansion_strats, + name=self.name, + symmetries=self.symmetries, + iterative=self.iterative, + ) + + if short_obs_len > 0: + try: + ks_pack = ks_pack.add_verification( + strat.ShortObstructionVerificationStrategy(short_obs_len) + ) + except ValueError: + pass + + try: + ks_pack = ks_pack.add_verification(strat.NoRootCellVerificationStrategy()) + except ValueError: + pass + + try: + ks_pack = ks_pack.add_verification(strat.DatabaseVerificationStrategy()) + except ValueError: + pass + + try: + ks_pack = ks_pack.add_initial(strat.DeflationFactory(tracked)) + except ValueError: + pass + + try: + ks_pack = ks_pack.add_initial(strat.AssumptionAndPointJumpingFactory()) + except ValueError: + pass + + try: + ks_pack = ks_pack.add_initial(strat.MonotoneSlidingFactory()) + except ValueError: + pass + + try: + ks_pack = ks_pack.add_initial(strat.CellReductionFactory(tracked)) + except ValueError: + pass + + try: + ks_pack = ks_pack.add_initial(strat.RequirementCorroborationFactory()) + except ValueError: + pass + + if obs_inferral_len > 0: + try: + ks_pack = ks_pack.add_inferral( + strat.ObstructionInferralFactory(obs_inferral_len) + ) + except ValueError: + pass + + ks_pack = ks_pack.make_interleaving(tracked=tracked, unions=True) + + try: + ks_pack = ks_pack.add_all_symmetry() + except ValueError: + pass + + if tracked: + ks_pack = ks_pack.make_tracked() + + ks_pack.name += "_kitchen_sink" + + return ks_pack + # Creation of the base pack @classmethod def all_the_strategies(cls, length: int = 1) -> "TileScopePack": From b29bb32073cff0f43964dab1accf66970f239ce1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20Nadeau?= Date: Wed, 20 Apr 2022 09:53:33 +0000 Subject: [PATCH 028/100] New collect_before decorator for some test. Now that we do strategic garbage collection in some of our code some test where timing out because the gc.collect() call where taking to long. I introduced a new decorator to collect before those test to avoid triggering out the timeout. --- tests/test_tilescope.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/test_tilescope.py b/tests/test_tilescope.py index abb9bf14..3ab0f3cd 100644 --- a/tests/test_tilescope.py +++ b/tests/test_tilescope.py @@ -1,3 +1,5 @@ +import gc + import pytest import sympy @@ -32,6 +34,20 @@ reginsenc = TileScopePack.regular_insertion_encoding(3) +def collect_before(func): + """ + Run gc collection before running the test. + + This ensure that the collection ran in the test won't take to much time. + """ + + def inner(): + gc.collect() + func() + + return inner + + @pytest.mark.timeout(20) def test_132(): searcher = TileScope("132", point_placements) @@ -284,6 +300,7 @@ def test_from_tiling(): assert sympy.simplify(spec.get_genf() - sympy.sympify("(1+x)/(1-x)")) == 0 +@collect_before @pytest.mark.timeout(5) def test_expansion(): """ @@ -348,6 +365,7 @@ def test_domino(): ] +@collect_before @pytest.mark.timeout(60) def test_parallel_forest(): expected_count = [1, 1, 2, 6, 22, 90, 394, 1806, 8558, 41586] @@ -361,6 +379,7 @@ def test_parallel_forest(): assert count == expected_count +@collect_before @pytest.mark.timeout(15) def forest_expansion(): """ From 7095524a997da8f82be484d064e6277a399c3f3a Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Wed, 20 Apr 2022 20:30:50 +0200 Subject: [PATCH 029/100] Updating component assumptions (#443) * use cell decomposition for uniting cells in factor * add component verification and add it to packs * ignore component assumptions in rearrange * more selective when verifying tilings with components * flipping sum and skew in symmetry strategies * if sum fusion, no skew assumption in region and vice versa * checking assumptions in comp fusion fusable check * not implemented counting for positive or empty fusion * ensure make_tracked adds component strategies * smarter is_component method to impove DetectComponentsStrategy * get_minimum_value changed for comp ass since can have fewer components than points * CHANGELOG.md * allow comp in monotone and insertion verified * component -> point assumption strategy * verification test packs allow comps in insenc and monotone --- CHANGELOG.md | 12 +- tests/strategies/test_verification.py | 9 +- tilings/algorithms/factor.py | 4 +- tilings/algorithms/fusion.py | 50 ++++--- tilings/assumptions.py | 83 ++++++----- tilings/strategies/__init__.py | 2 + tilings/strategies/fusion/component.py | 32 ++++- tilings/strategies/rearrange_assumption.py | 124 +++++++++++++++-- tilings/strategies/symmetry.py | 52 ++++++- tilings/strategies/verification.py | 153 ++++++++++++++++----- tilings/strategy_pack.py | 14 ++ tilings/tiling.py | 35 +++++ 12 files changed, 464 insertions(+), 106 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08ee045a..d61bc615 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,8 +33,11 @@ swapped around a fusable row or column. another cell empty. Also, the `PointCorroborationFactory`, which does this for point or empty cells which is added to most packs. - `TileScopePack.remove_strategy` method that removes a strategy from a pack. -- `TargetedCellInsertionFactory` which inserts factors of gridded perms if it can - lead to factoring out a verified sub tiling. +- `TargetedCellInsertionFactory` which inserts factors of gridded perms if it can + lead to factoring out a verified sub tiling. +- `ComponentVerificationStrategy` which is added to component fusion packs. +- `ComponentToPointAssumptionStrategy` that changes component assumptions to point + assumptions. These strategies are yielded in `RearrangeAssumptionFactory`. - `StrategyPack.kitchen_sinkify` to add many experimental strategies to the pack - `SubobstructionInsertionFactory` that inserts subobstructions and the pack `TileScopePack.subobstruction_placements` which uses it. @@ -43,6 +46,10 @@ swapped around a fusable row or column. - `ForgetTrackedSearcher` was not retroactively applying strategies that had a `basis`. - Bug with sliding symmetries - The tiling initialiser was not removing duplicate/redundant requirements. +- `Factor` was not factoring correctly with respect to component assumptions. +- `ComponentAssumption` are flipped when taking symmetries +- `Tiling.get_minimum_value` fixed for component assumptions +- `RearrangeAssumptionFactory` will ignore component assumptions ### Changed - One by one verification will now only verify subclasses of the given basis. @@ -62,6 +69,7 @@ swapped around a fusable row or column. - Don't reinitialise in the `Tiling.from_dict` method. - `GuidedSearcher` expands every symmetry - `TileScopePack.pattern_placements` factors as an initial strategy. +- `is_component` method of assumptions updated to consider cell decomposition ### Deprecated diff --git a/tests/strategies/test_verification.py b/tests/strategies/test_verification.py index 789d6079..d80536ee 100644 --- a/tests/strategies/test_verification.py +++ b/tests/strategies/test_verification.py @@ -22,6 +22,7 @@ OneByOneVerificationStrategy, ShortObstructionVerificationStrategy, ) +from tilings.strategies.detect_components import DetectComponentsStrategy from tilings.strategies.experimental_verification import SubclassVerificationStrategy from tilings.tilescope import TileScopePack @@ -469,7 +470,9 @@ def enum_not_verified(self, onebyone_enum): def test_pack(self, strategy, enum_verified): assert strategy.pack( enum_verified[0] - ) == TileScopePack.regular_insertion_encoding(3) + ) == TileScopePack.regular_insertion_encoding(3).add_initial( + DetectComponentsStrategy() + ) assert strategy.pack(enum_verified[1]).name == "factor pack" assert strategy.pack(enum_verified[2]).name == "factor pack" @@ -658,7 +661,9 @@ def test_pack(self, strategy, enum_verified): strategy.pack(enum_verified[0]) assert strategy.pack( enum_verified[1] - ) == TileScopePack.regular_insertion_encoding(3) + ) == TileScopePack.regular_insertion_encoding(3).add_initial( + DetectComponentsStrategy() + ) @pytest.mark.timeout(30) def test_get_specification(self, strategy, enum_verified): diff --git a/tilings/algorithms/factor.py b/tilings/algorithms/factor.py index 294f2298..16ed9cf2 100644 --- a/tilings/algorithms/factor.py +++ b/tilings/algorithms/factor.py @@ -78,8 +78,8 @@ def _unite_assumptions(self) -> None: """ for assumption in self._tiling.assumptions: if isinstance(assumption, ComponentAssumption): - for comp in assumption.get_components(self._tiling): - self._unite_cells(chain.from_iterable(gp.pos for gp in comp)) + for cells in assumption.cell_decomposition(self._tiling): + self._unite_cells(cells) else: for gp in assumption.gps: self._unite_cells(gp.pos) diff --git a/tilings/algorithms/fusion.py b/tilings/algorithms/fusion.py index a7d1db23..63a4d00b 100644 --- a/tilings/algorithms/fusion.py +++ b/tilings/algorithms/fusion.py @@ -569,22 +569,27 @@ def obstructions_to_add(self) -> Iterator[GriddedPerm]: self.unfuse_gridded_perm(ob) for ob in self.obstruction_fuse_counter ) - def _can_fuse_assumption( - self, assumption: TrackingAssumption, fuse_counter: Counter[GriddedPerm] - ) -> bool: + def _can_component_fuse_assumption(self, assumption: TrackingAssumption) -> bool: """ Return True if an assumption can be fused. That is, prefusion, the gps - are all contained entirely on the left of the fusion region, entirely - on the right, or split in every possible way. - """ - if not isinstance(assumption, ComponentAssumption): + do not touch the fuse region unless it is the correct sum or skew + assumption. + """ + if ( + not isinstance(assumption, ComponentAssumption) + or ( + isinstance(assumption, SumComponentAssumption) + and not self.is_sum_component_fusion() + ) + or ( + isinstance(assumption, SkewComponentAssumption) + and self.is_sum_component_fusion() + ) + ): return self.is_left_sided_assumption( assumption ) and self.is_right_sided_assumption(assumption) - return self._can_fuse_set_of_gridded_perms(fuse_counter) or ( - all(count == 1 for gp, count in fuse_counter.items()) - and self._is_one_sided_assumption(assumption) - ) + return True def _can_fuse_set_of_gridded_perms( self, fuse_counter: Counter[GriddedPerm] @@ -603,22 +608,35 @@ def fusable(self) -> bool: return False new_tiling = self._tiling.add_obstructions(self.obstructions_to_add()) - return self._tiling == new_tiling and self._check_isolation_level() + return ( + self._tiling == new_tiling + and self._check_isolation_level() + and all( + self._can_component_fuse_assumption(assumption) + for assumption in self._tiling.assumptions + ) + ) def new_assumption(self) -> ComponentAssumption: """ Return the assumption that needs to be counted in order to enumerate. """ fcell = self.first_cell - scell = self.second_cell gps = (GriddedPerm.single_cell((0,), fcell),) + if self.is_sum_component_fusion(): + return SumComponentAssumption(gps) + return SkewComponentAssumption(gps) + + def is_sum_component_fusion(self) -> bool: + """ + Return true if is a sum component fusion""" + fcell = self.first_cell + scell = self.second_cell if self._fuse_row: sum_ob = GriddedPerm((1, 0), (scell, fcell)) else: sum_ob = GriddedPerm((1, 0), (fcell, scell)) - if sum_ob in self._tiling.obstructions: - return SumComponentAssumption(gps) - return SkewComponentAssumption(gps) + return sum_ob in self._tiling.obstructions def __str__(self) -> str: s = "ComponentFusion Algorithm for:\n" diff --git a/tilings/assumptions.py b/tilings/assumptions.py index f1f792bc..beb0c95f 100644 --- a/tilings/assumptions.py +++ b/tilings/assumptions.py @@ -1,7 +1,7 @@ import abc from importlib import import_module from itertools import chain -from typing import TYPE_CHECKING, FrozenSet, Iterable, List, Optional, Tuple, Type +from typing import TYPE_CHECKING, Iterable, List, Optional, Tuple, Type from permuta import Perm @@ -142,15 +142,12 @@ def tiling_decomposition(self, tiling: "Tiling") -> List[List[Cell]]: """Return the components of a given tiling.""" @abc.abstractmethod - def is_component( - self, - cells: List[Cell], - point_cells: FrozenSet[Cell], - positive_cells: FrozenSet[Cell], - ) -> bool: - """ - Return True if cells form a component. - """ + def opposite_tiling_decomposition(self, tiling: "Tiling") -> List[List[Cell]]: + """Return the alternative components of a given tiling.""" + + @abc.abstractmethod + def one_or_fewer_components(self, tiling: "Tiling", cell: Cell) -> bool: + """Return True if the cell contains one or fewer components.""" def get_components(self, tiling: "Tiling") -> List[List[GriddedPerm]]: sub_tiling = tiling.sub_tiling(self.cells) @@ -163,9 +160,39 @@ def get_components(self, tiling: "Tiling") -> List[List[GriddedPerm]]: for cell in comp ] for comp in components - if self.is_component( - comp, separated_tiling.point_cells, separated_tiling.positive_cells + if self.is_component(comp, separated_tiling) + ] + + def is_component(self, cells: List[Cell], tiling: "Tiling") -> bool: + """ + Return True if cells form a component on the tiling. + + Cells are assumed to have come from cell_decomposition. + """ + sub_tiling = tiling.sub_tiling(cells) + skew_cells = self.opposite_tiling_decomposition(sub_tiling) + + if any( + scells[0] in sub_tiling.positive_cells + and self.one_or_fewer_components(sub_tiling, scells[0]) + for scells in skew_cells + if len(scells) == 1 + ): + return True + + def is_positive(scells) -> bool: + return any( + all(any(cell in gp.pos for cell in scells) for gp in req) + for req in sub_tiling.requirements ) + + return sum(1 for scells in skew_cells if is_positive(scells)) > 1 + + def cell_decomposition(self, tiling: "Tiling"): + sub_tiling = tiling.sub_tiling(self.cells) + return [ + [sub_tiling.backward_map.map_cell(cell) for cell in comp] + for comp in self.tiling_decomposition(sub_tiling) ] def get_value(self, gp: GriddedPerm) -> int: @@ -200,16 +227,12 @@ def tiling_decomposition(tiling: "Tiling") -> List[List[Cell]]: return tiling.sum_decomposition() @staticmethod - def is_component( - cells: List[Cell], point_cells: FrozenSet[Cell], positive_cells: FrozenSet[Cell] - ) -> bool: - if len(cells) == 2: - (x1, y1), (x2, y2) = sorted(cells) - if x1 != x2 and y1 > y2: # is skew - return all(cell in positive_cells for cell in cells) or any( - cell in point_cells for cell in cells - ) - return False + def opposite_tiling_decomposition(tiling: "Tiling") -> List[List[Cell]]: + return tiling.skew_decomposition() + + @staticmethod + def one_or_fewer_components(tiling: "Tiling", cell: Cell) -> bool: + return GriddedPerm.single_cell(Perm((0, 1)), cell) in tiling.obstructions def __str__(self): return f"can count sum components in cells {self.cells}" @@ -228,16 +251,12 @@ def tiling_decomposition(tiling: "Tiling") -> List[List[Cell]]: return tiling.skew_decomposition() @staticmethod - def is_component( - cells: List[Cell], point_cells: FrozenSet[Cell], positive_cells: FrozenSet[Cell] - ) -> bool: - if len(cells) == 2: - (x1, y1), (x2, y2) = sorted(cells) - if x1 != x2 and y1 < y2: # is sum - return all(cell in positive_cells for cell in cells) or any( - cell in point_cells for cell in cells - ) - return False + def opposite_tiling_decomposition(tiling: "Tiling") -> List[List[Cell]]: + return tiling.sum_decomposition() + + @staticmethod + def one_or_fewer_components(tiling: "Tiling", cell: Cell) -> bool: + return GriddedPerm.single_cell(Perm((1, 0)), cell) in tiling.obstructions def __str__(self): return f"can count skew components in cells {self.cells}" diff --git a/tilings/strategies/__init__.py b/tilings/strategies/__init__.py index ac0a73c8..e28957e6 100644 --- a/tilings/strategies/__init__.py +++ b/tilings/strategies/__init__.py @@ -43,6 +43,7 @@ from .symmetry import SymmetriesFactory from .verification import ( BasicVerificationStrategy, + ComponentVerificationStrategy, DatabaseVerificationStrategy, ElementaryVerificationStrategy, InsertionEncodingVerificationStrategy, @@ -99,6 +100,7 @@ "SymmetriesFactory", # Verification "BasicVerificationStrategy", + "ComponentVerificationStrategy", "DatabaseVerificationStrategy", "ElementaryVerificationStrategy", "LocallyFactorableVerificationStrategy", diff --git a/tilings/strategies/fusion/component.py b/tilings/strategies/fusion/component.py index d30e8dc0..afb76fe3 100644 --- a/tilings/strategies/fusion/component.py +++ b/tilings/strategies/fusion/component.py @@ -1,15 +1,16 @@ from typing import Iterator, Optional, Tuple -from comb_spec_searcher import StrategyFactory +from comb_spec_searcher import Constructor, StrategyFactory from comb_spec_searcher.strategies import Rule from tilings import GriddedPerm, Tiling -from tilings.algorithms import ComponentFusion, Fusion +from tilings.algorithms import ComponentFusion +from .constructor import FusionConstructor from .fusion import FusionStrategy class ComponentFusionStrategy(FusionStrategy): - def fusion_algorithm(self, tiling: Tiling) -> Fusion: + def fusion_algorithm(self, tiling: Tiling) -> ComponentFusion: return ComponentFusion( tiling, row_idx=self.row_idx, col_idx=self.col_idx, tracked=self.tracked ) @@ -32,6 +33,31 @@ def backward_map( """ raise NotImplementedError + def is_positive_or_empty_fusion(self, tiling: Tiling) -> bool: + algo = self.fusion_algorithm(tiling) + return sum(1 for ob in tiling.obstructions if algo.is_crossing_len2(ob)) > 1 + + def constructor( + self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None + ) -> FusionConstructor: + 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!" + ) + return super().constructor(comb_class, children) + + def reverse_constructor( # pylint: disable=no-self-use + self, + idx: int, + comb_class: Tiling, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> 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!" + ) + return super().reverse_constructor(idx, comb_class, children) + class ComponentFusionFactory(StrategyFactory[Tiling]): def __init__(self, tracked: bool = False, isolation_level: Optional[str] = None): diff --git a/tilings/strategies/rearrange_assumption.py b/tilings/strategies/rearrange_assumption.py index e1ade858..cce39339 100644 --- a/tilings/strategies/rearrange_assumption.py +++ b/tilings/strategies/rearrange_assumption.py @@ -1,11 +1,16 @@ from collections import Counter from functools import partial from itertools import combinations -from typing import Callable, Dict, Iterator, List, Optional, Tuple +from typing import Callable, Dict, Iterator, List, Optional, Tuple, Union import sympy -from comb_spec_searcher import Constructor, Strategy, StrategyFactory +from comb_spec_searcher import ( + Constructor, + DisjointUnionStrategy, + Strategy, + StrategyFactory, +) from comb_spec_searcher.exception import StrategyDoesNotApply from comb_spec_searcher.typing import ( Parameters, @@ -18,7 +23,12 @@ Terms, ) from tilings import GriddedPerm, Tiling -from tilings.assumptions import TrackingAssumption +from tilings.assumptions import ( + ComponentAssumption, + SkewComponentAssumption, + SumComponentAssumption, + TrackingAssumption, +) Cell = Tuple[int, int] @@ -364,8 +374,6 @@ def backward_map( The backward direction of the underlying bijection used for object generation and sampling. """ - if children is None: - children = self.decomposition_function(comb_class) assert len(objs) == 1 and objs[0] is not None yield objs[0] @@ -379,8 +387,6 @@ def forward_map( The forward direction of the underlying bijection used for object generation and sampling. """ - if children is None: - children = self.decomposition_function(comb_class) return (obj,) def to_jsonable(self) -> dict: @@ -414,15 +420,113 @@ def get_eq_symbol() -> str: return "↣" +class ComponentToPointAssumptionStrategy( + DisjointUnionStrategy[Tiling, GriddedPerm], +): + """A strategy that changes a component tracking assumption to a point + tracking assumption.""" + + def __init__( + self, + assumption: TrackingAssumption, + ignore_parent: bool = False, + workable: bool = False, + ): + assert isinstance(assumption, ComponentAssumption) + self.assumption = assumption + self.new_assumption = TrackingAssumption(assumption.gps) + super().__init__( + ignore_parent=ignore_parent, + inferrable=False, + possibly_empty=False, + workable=workable, + ) + + def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling]: + if self.assumption not in comb_class.assumptions: + raise StrategyDoesNotApply("Assumption not on tiling") + return ( + comb_class.remove_assumption(self.assumption).add_assumption( + self.new_assumption + ), + ) + + def backward_map( + self, + comb_class: Tiling, + objs: Tuple[Optional[GriddedPerm], ...], + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Iterator[GriddedPerm]: + assert len(objs) == 1 and objs[0] is not None + yield objs[0] + + def forward_map( + self, + comb_class: Tiling, + obj: GriddedPerm, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Tuple[Optional[GriddedPerm]]: + return (obj,) + + def extra_parameters( + self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None + ) -> Tuple[Dict[str, str], ...]: + if not comb_class.extra_parameters: + return super().extra_parameters(comb_class, children) + if children is None: + children = self.decomposition_function(comb_class) + if children is None: + raise StrategyDoesNotApply("Strategy does not apply") + child = children[0] + return ( + { + comb_class.get_assumption_parameter( + ass + ): child.get_assumption_parameter( + self.new_assumption if ass == self.assumption else ass + ) + for ass in comb_class.assumptions + }, + ) + + def formal_step(self) -> str: + cells = ", ".join(str(gp.pos[0]) for gp in self.assumption.gps) + return f"change component assumption in cells {cells} to point assumption" + + @classmethod + def from_dict(cls, d: dict) -> "ComponentToPointAssumptionStrategy": + return cls(**d) + + class RearrangeAssumptionFactory(StrategyFactory[Tiling]): - def __call__(self, comb_class: Tiling) -> Iterator[RearrangeAssumptionStrategy]: - assumptions = comb_class.assumptions - for ass1, ass2 in combinations(assumptions, 2): + def __call__( + self, comb_class: Tiling + ) -> Iterator[ + Union[RearrangeAssumptionStrategy, ComponentToPointAssumptionStrategy] + ]: + points: List[TrackingAssumption] = [] + components: List[TrackingAssumption] = [] + for ass in comb_class.assumptions: + (points, components)[isinstance(ass, ComponentAssumption)].append(ass) + + for ass1, ass2 in combinations(points, 2): if set(ass1.gps).issubset(set(ass2.gps)): yield RearrangeAssumptionStrategy(ass2, ass1) if set(ass2.gps).issubset(set(ass1.gps)): yield RearrangeAssumptionStrategy(ass1, ass2) + for ass in components: + if self.can_be_point_assumption(comb_class, ass): + yield ComponentToPointAssumptionStrategy(ass) + + @staticmethod + def can_be_point_assumption(tiling: Tiling, assumption: TrackingAssumption) -> bool: + sub_tiling = tiling.sub_tiling(tuple(gp.pos[0] for gp in assumption.gps)) + if isinstance(assumption, SumComponentAssumption): + return sub_tiling.is_increasing() + assert isinstance(assumption, SkewComponentAssumption) + return sub_tiling.is_decreasing() + def __repr__(self) -> str: return self.__class__.__name__ + "()" diff --git a/tilings/strategies/symmetry.py b/tilings/strategies/symmetry.py index 4e7a0421..2fa88e68 100644 --- a/tilings/strategies/symmetry.py +++ b/tilings/strategies/symmetry.py @@ -1,12 +1,18 @@ import abc from functools import partial from itertools import chain, combinations -from typing import Dict, Iterable, Iterator, List, Optional, Set, Tuple, cast +from typing import Dict, Iterable, Iterator, List, Optional, Set, Tuple, Type, cast from comb_spec_searcher import StrategyFactory, SymmetryStrategy from comb_spec_searcher.exception import StrategyDoesNotApply from permuta import Perm from tilings import GriddedPerm, Tiling +from tilings.assumptions import ( + ComponentAssumption, + SkewComponentAssumption, + SumComponentAssumption, + TrackingAssumption, +) __all__ = ("SymmetriesFactory",) @@ -20,6 +26,28 @@ def gp_transform(self, tiling: Tiling, gp: GriddedPerm) -> GriddedPerm: def inverse_gp_transform(self, tiling: Tiling, gp: GriddedPerm) -> GriddedPerm: pass + @staticmethod + def assumption_type_transform( + assumption: TrackingAssumption, + ) -> Type[TrackingAssumption]: + pass + + @staticmethod + def _assumption_type_swap( + assumption: TrackingAssumption, + ) -> Type[TrackingAssumption]: + if isinstance(assumption, ComponentAssumption): + if isinstance(assumption, SumComponentAssumption): + return SkewComponentAssumption + return SumComponentAssumption + return assumption.__class__ + + @staticmethod + def _assumption_type_identity( + assumption: TrackingAssumption, + ) -> Type[TrackingAssumption]: + return assumption.__class__ + def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling, ...]: return ( Tiling( @@ -31,7 +59,9 @@ def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling, ...]: for req in comb_class.requirements ), tuple( - ass.__class__(map(partial(self.gp_transform, comb_class), ass.gps)) + self.__class__.assumption_type_transform(ass)( + map(partial(self.gp_transform, comb_class), ass.gps) + ) for ass in comb_class.assumptions ), remove_empty_rows_and_cols=False, @@ -51,7 +81,9 @@ def extra_parameters( raise StrategyDoesNotApply("Strategy does not apply") child = children[0] mapped_assumptions = tuple( - ass.__class__(tuple(self.gp_transform(comb_class, gp) for gp in ass.gps)) + self.__class__.assumption_type_transform(ass)( + tuple(self.gp_transform(comb_class, gp) for gp in ass.gps) + ) for ass in comb_class.assumptions ) return ( @@ -105,6 +137,8 @@ def reverse_cell(cell): def inverse_gp_transform(self, tiling: Tiling, gp: GriddedPerm) -> GriddedPerm: return self.gp_transform(tiling, gp) + assumption_type_transform = TilingSymmetryStrategy._assumption_type_swap + @staticmethod def formal_step() -> str: return "reverse of the tiling" @@ -127,6 +161,8 @@ def complement_cell(cell): def inverse_gp_transform(self, tiling: Tiling, gp: GriddedPerm) -> GriddedPerm: return self.gp_transform(tiling, gp) + assumption_type_transform = TilingSymmetryStrategy._assumption_type_swap + @staticmethod def formal_step() -> str: return "complement of the tiling" @@ -149,6 +185,8 @@ def inverse_cell(cell): def inverse_gp_transform(self, tiling: Tiling, gp: GriddedPerm) -> GriddedPerm: return self.gp_transform(tiling, gp) + assumption_type_transform = TilingSymmetryStrategy._assumption_type_identity + @staticmethod def formal_step() -> str: return "inverse of the tiling" @@ -180,6 +218,8 @@ def antidiagonal_cell(cell): return gp.antidiagonal(antidiagonal_cell) + assumption_type_transform = TilingSymmetryStrategy._assumption_type_identity + @staticmethod def formal_step() -> str: return "antidiagonal of the tiling" @@ -205,6 +245,8 @@ def rotate270_cell(cell): return gp.rotate270(rotate270_cell) + assumption_type_transform = TilingSymmetryStrategy._assumption_type_swap + @staticmethod def formal_step() -> str: return "rotate the tiling 90 degrees clockwise" @@ -230,6 +272,8 @@ def rotate180_cell(cell): def inverse_gp_transform(self, tiling: Tiling, gp: GriddedPerm) -> GriddedPerm: return self.gp_transform(tiling, gp) + assumption_type_transform = TilingSymmetryStrategy._assumption_type_identity + @staticmethod def formal_step() -> str: return "rotate the tiling 180 degrees clockwise" @@ -255,6 +299,8 @@ def rotate90_cell(cell): return gp.rotate90(rotate90_cell) + assumption_type_transform = TilingSymmetryStrategy._assumption_type_swap + @staticmethod def formal_step() -> str: return "rotate the tiling 270 degrees clockwise" diff --git a/tilings/strategies/verification.py b/tilings/strategies/verification.py index 5d41ada9..42c94106 100644 --- a/tilings/strategies/verification.py +++ b/tilings/strategies/verification.py @@ -28,6 +28,7 @@ ) from tilings.assumptions import ComponentAssumption from tilings.strategies import ( + DetectComponentsStrategy, FactorFactory, FactorInsertionFactory, RemoveRequirementFactory, @@ -128,9 +129,7 @@ class OneByOneVerificationStrategy(BasisAwareVerificationStrategy): @staticmethod def pack(comb_class: Tiling) -> StrategyPack: if any(isinstance(ass, ComponentAssumption) for ass in comb_class.assumptions): - raise InvalidOperationError( - "Can't find generating function with component assumption." - ) + return ComponentVerificationStrategy.pack(comb_class) # pylint: disable=import-outside-toplevel from tilings.tilescope import TileScopePack @@ -187,9 +186,7 @@ def verified(self, comb_class: Tiling) -> bool: is_strict_subclass = any( tiling_class.is_subclass(cls) and cls != tiling_class for cls in sym_classes ) - return is_strict_subclass or any( - isinstance(ass, ComponentAssumption) for ass in comb_class.assumptions - ) + return is_strict_subclass def get_genf( self, comb_class: Tiling, funcs: Optional[Dict[Tiling, Function]] = None @@ -237,6 +234,72 @@ def __str__(self) -> str: return f"One by one subclass of {Av(self.basis)}" +class ComponentVerificationStrategy(TileScopeVerificationStrategy): + """Enumeration strategy for verifying 1x1s with component assumptions.""" + + @staticmethod + def pack(comb_class: Tiling) -> StrategyPack: + raise NotImplementedError("No pack for removing component assumption") + + @staticmethod + def verified(comb_class: Tiling) -> bool: + return comb_class.dimensions == (1, 1) and any( + isinstance(ass, ComponentAssumption) for ass in comb_class.assumptions + ) + + def decomposition_function( + self, comb_class: Tiling + ) -> Optional[Tuple[Tiling, ...]]: + """ + The rule as the root as children if one of the cell of the tiling is the root. + """ + if self.verified(comb_class): + return (comb_class.remove_assumptions(),) + return None + + def shifts( + self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None + ) -> Tuple[int, ...]: + return (0,) + + @staticmethod + def formal_step() -> str: + return "component verified" + + def get_genf( + self, comb_class: Tiling, funcs: Optional[Dict[Tiling, Function]] = None + ) -> Expr: + raise NotImplementedError( + "Not implemented method to count objects for component verified" + ) + + def get_terms(self, comb_class: Tiling, n: int) -> Terms: + raise NotImplementedError( + "Not implemented method to count objects for component verified" + ) + + def generate_objects_of_size( + self, comb_class: Tiling, n: int, **parameters: int + ) -> Iterator[GriddedPerm]: + raise NotImplementedError( + "Not implemented method to generate objects for component verified tilings" + ) + + def random_sample_object_of_size( + self, comb_class: Tiling, n: int, **parameters: int + ) -> GriddedPerm: + raise NotImplementedError( + "Not implemented random sample for component verified tilings" + ) + + def __str__(self) -> str: + return "component verification" + + @classmethod + def from_dict(cls, d: dict) -> "ComponentVerificationStrategy": + return cls(**d) + + class DatabaseVerificationStrategy(TileScopeVerificationStrategy): """ Enumeration strategy for a tilings that are in the database. @@ -309,13 +372,13 @@ class LocallyFactorableVerificationStrategy(BasisAwareVerificationStrategy): """ def pack(self, comb_class: Tiling) -> StrategyPack: - if any(isinstance(ass, ComponentAssumption) for ass in comb_class.assumptions): - raise InvalidOperationError( - "Can't find generating function with component assumption." - ) return StrategyPack( name="LocallyFactorable", - initial_strats=[FactorFactory(), RequirementCorroborationFactory()], + initial_strats=[ + FactorFactory(), + RequirementCorroborationFactory(), + DetectComponentsStrategy(), + ], inferral_strats=[], expansion_strats=[[FactorInsertionFactory()], [RemoveRequirementFactory()]], ver_strats=[ @@ -323,6 +386,7 @@ def pack(self, comb_class: Tiling) -> StrategyPack: OneByOneVerificationStrategy( basis=self._basis, symmetry=self._symmetry ), + ComponentVerificationStrategy(), InsertionEncodingVerificationStrategy(), MonotoneTreeVerificationStrategy(no_factors=True), LocalVerificationStrategy(no_factors=True), @@ -331,18 +395,19 @@ def pack(self, comb_class: Tiling) -> StrategyPack: @staticmethod def _pack_for_shift(comb_class: Tiling) -> StrategyPack: - if any(isinstance(ass, ComponentAssumption) for ass in comb_class.assumptions): - raise InvalidOperationError( - "Can't find generating function with component assumption." - ) return StrategyPack( name="LocallyFactorable", - initial_strats=[FactorFactory(), RequirementCorroborationFactory()], + initial_strats=[ + FactorFactory(), + RequirementCorroborationFactory(), + DetectComponentsStrategy(), + ], inferral_strats=[], expansion_strats=[[FactorInsertionFactory()]], ver_strats=[ BasicVerificationStrategy(), OneByOneVerificationStrategy(), + ComponentVerificationStrategy(), InsertionEncodingVerificationStrategy(), MonotoneTreeVerificationStrategy(no_factors=True), LocalVerificationStrategy(no_factors=True), @@ -369,6 +434,14 @@ def verified(self, comb_class: Tiling): not comb_class.dimensions == (1, 1) and self._locally_factorable_obstructions(comb_class) and self._locally_factorable_requirements(comb_class) + and all( + not isinstance(ass, ComponentAssumption) + or ( + len(ass.gps) == 1 + and comb_class.only_cell_in_row_and_col(list(ass.cells)[0]) + ) + for ass in comb_class.assumptions + ) ) def decomposition_function( @@ -429,7 +502,15 @@ class ElementaryVerificationStrategy(LocallyFactorableVerificationStrategy): @staticmethod def verified(comb_class: Tiling): - return comb_class.fully_isolated() and not comb_class.dimensions == (1, 1) + return ( + comb_class.fully_isolated() + and not comb_class.dimensions == (1, 1) + and all( + len(ass.gps) == 1 + for ass in comb_class.assumptions + if isinstance(ass, ComponentAssumption) + ) + ) @staticmethod def formal_step() -> str: @@ -461,21 +542,17 @@ def pack(self, comb_class: Tiling) -> StrategyPack: except StrategyDoesNotApply: pass if self.no_factors: - raise InvalidOperationError("Cannot get a simpler specification") - if ( - any(isinstance(ass, ComponentAssumption) for ass in comb_class.assumptions) - and len(comb_class.find_factors()) == 1 - ): raise InvalidOperationError( - "Can't find generating function with component assumption." + f"Cannot get a simpler specification for\n{comb_class}" ) return StrategyPack( - initial_strats=[FactorFactory()], + initial_strats=[FactorFactory(), DetectComponentsStrategy()], inferral_strats=[], expansion_strats=[], ver_strats=[ BasicVerificationStrategy(), OneByOneVerificationStrategy(), + ComponentVerificationStrategy(), InsertionEncodingVerificationStrategy(), MonotoneTreeVerificationStrategy(no_factors=True), LocalVerificationStrategy(no_factors=True), @@ -488,6 +565,14 @@ def verified(self, comb_class: Tiling) -> bool: comb_class.dimensions != (1, 1) and (not self.no_factors or len(comb_class.find_factors()) == 1) and LocalEnumeration(comb_class).verified() + and all( + not isinstance(ass, ComponentAssumption) + or ( + len(ass.gps) == 1 + and comb_class.only_cell_in_row_and_col(list(ass.cells)[0]) + ) + for ass in comb_class.assumptions + ) ) @staticmethod @@ -546,18 +631,18 @@ def __init__(self, ignore_parent: bool = False): super().__init__(ignore_parent=ignore_parent) def pack(self, comb_class: Tiling) -> StrategyPack: - if any(isinstance(ass, ComponentAssumption) for ass in comb_class.assumptions): - raise InvalidOperationError( - "Can't find generating function with component assumption." - ) # pylint: disable=import-outside-toplevel from tilings.strategy_pack import TileScopePack if self.has_rightmost_insertion_encoding(comb_class): - return TileScopePack.regular_insertion_encoding(2) - if self.has_topmost_insertion_encoding(comb_class): - return TileScopePack.regular_insertion_encoding(3) - raise StrategyDoesNotApply("tiling does not has a regular insertion encoding") + pack = TileScopePack.regular_insertion_encoding(2) + elif self.has_topmost_insertion_encoding(comb_class): + pack = TileScopePack.regular_insertion_encoding(3) + else: + raise StrategyDoesNotApply( + "tiling does not has a regular insertion encoding" + ) + return pack.add_initial(DetectComponentsStrategy()) @staticmethod def has_rightmost_insertion_encoding(tiling: Tiling) -> bool: @@ -622,10 +707,6 @@ def __init__(self, ignore_parent: bool = False, no_factors: bool = True): super().__init__(ignore_parent=ignore_parent) def pack(self, comb_class: Tiling) -> StrategyPack: - if any(isinstance(ass, ComponentAssumption) for ass in comb_class.assumptions): - raise InvalidOperationError( - "Can't find generating function with component assumption." - ) try: return InsertionEncodingVerificationStrategy().pack(comb_class) except StrategyDoesNotApply: diff --git a/tilings/strategy_pack.py b/tilings/strategy_pack.py index f734d821..1cd4677d 100644 --- a/tilings/strategy_pack.py +++ b/tilings/strategy_pack.py @@ -157,6 +157,19 @@ def replace_list(strats): pack = pack.add_initial( strat.RearrangeAssumptionFactory(), apply_first=True ) + if strat.ComponentFusionFactory() in pack: + if all( + not isinstance(strategy, strat.DetectComponentsStrategy) + for strategy in pack + ): + pack = pack.add_initial( + strat.DetectComponentsStrategy(ignore_parent=True) + ) + if all( + not isinstance(strategy, strat.ComponentVerificationStrategy) + for strategy in pack + ): + pack = pack.add_verification(strat.ComponentVerificationStrategy()) return pack def make_fusion( @@ -192,6 +205,7 @@ def make_fusion( pack = pack.add_initial( strat.DetectComponentsStrategy(ignore_parent=True), apply_first=True ) + pack = pack.add_verification(strat.ComponentVerificationStrategy()) pack = pack.add_initial( strat.RearrangeAssumptionFactory(), apply_first=True ) diff --git a/tilings/tiling.py b/tilings/tiling.py index 8e84edc0..b6427599 100644 --- a/tilings/tiling.py +++ b/tilings/tiling.py @@ -46,6 +46,7 @@ guess_obstructions, ) from .assumptions import ( + ComponentAssumption, SkewComponentAssumption, SumComponentAssumption, TrackingAssumption, @@ -1430,6 +1431,12 @@ def get_minimum_value(self, parameter: str) -> int: Return the minimum value that can be taken by the parameter. """ assumption = self.get_assumption(parameter) + if isinstance(assumption, ComponentAssumption): + return ( + 1 + if any(gp.pos[0] in self.positive_cells for gp in assumption.gps) + else 0 + ) return min(assumption.get_value(gp) for gp in self.minimal_gridded_perms()) def maximum_length_of_minimum_gridded_perm(self) -> int: @@ -1466,6 +1473,34 @@ def is_finite(self) -> bool: cell in increasing and cell in decreasing for cell in self.active_cells ) + def is_increasing(self) -> bool: + """Returns true if all gridded perms are increasing.""" + separated = self.row_and_column_separation() + components = separated.sum_decomposition() + if any(len(cells) > 1 for cells in components): + return False + cells = sorted(cells[0] for cells in components) + if any(b < a for a, b in zip(cells[:-1], cells[1:])): + return False + return all( + GriddedPerm.single_cell(Perm((1, 0)), cell) in separated.obstructions + for cell in cells + ) + + def is_decreasing(self) -> bool: + """Returns true if all gridded perms are decreasing.""" + separated = self.row_and_column_separation() + components = separated.skew_decomposition() + if any(len(cells) > 1 for cells in components): + return False + cells = sorted(cells[0] for cells in components) + if any(b > a for a, b in zip(cells[:-1], cells[1:])): + return False + return all( + GriddedPerm.single_cell(Perm((0, 1)), cell) in separated.obstructions + for cell in cells + ) + def objects_of_size(self, n: int, **parameters: int) -> Iterator[GriddedPerm]: for gp in self.gridded_perms_of_length(n): if all( From 609a9c8d8662fd330ffcd07ea74215ab9a12ae93 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Thu, 21 Apr 2022 14:59:55 +0200 Subject: [PATCH 030/100] dont change type of assumption (#445) --- tilings/strategies/point_jumping.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/tilings/strategies/point_jumping.py b/tilings/strategies/point_jumping.py index 465f199f..6869944a 100644 --- a/tilings/strategies/point_jumping.py +++ b/tilings/strategies/point_jumping.py @@ -39,8 +39,7 @@ def swapped_requirements( def swapped_assumptions(self, tiling: Tiling) -> Tuple[TrackingAssumption, ...]: return tuple( - TrackingAssumption(map(self._swapped_gp, ass.gps)) - for ass in tiling.assumptions + ass.__class__(map(self._swapped_gp, ass.gps)) for ass in tiling.assumptions ) def _swapped_gp(self, gp: GriddedPerm) -> GriddedPerm: @@ -140,9 +139,7 @@ def backward_map( objs: Tuple[Optional[GriddedPerm], ...], children: Optional[Tuple[Tiling, ...]] = None, ) -> Iterator[GriddedPerm]: - gp = objs[0] - assert gp is not None - yield gp + raise NotImplementedError("not implemented map for assumption or point jumping") def forward_map( self, @@ -150,7 +147,7 @@ def forward_map( obj: GriddedPerm, children: Optional[Tuple[Tiling, ...]] = None, ) -> Tuple[Optional[GriddedPerm]]: - return (obj,) + raise NotImplementedError("not implemented map for assumption or point jumping") def extra_parameters( self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None @@ -231,7 +228,7 @@ def backward_map( objs: Tuple[Optional[GriddedPerm], ...], children: Optional[Tuple[Tiling, ...]] = None, ) -> Iterator[GriddedPerm]: - raise NotImplementedError + raise NotImplementedError("not implemented map for assumption or point jumping") def forward_map( self, @@ -239,7 +236,7 @@ def forward_map( obj: GriddedPerm, children: Optional[Tuple[Tiling, ...]] = None, ) -> Tuple[Optional[GriddedPerm]]: - raise NotImplementedError + raise NotImplementedError("not implemented map for assumption or point jumping") def extra_parameters( self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None @@ -310,7 +307,7 @@ def backward_map( objs: Tuple[Optional[GriddedPerm], ...], children: Optional[Tuple[Tiling, ...]] = None, ) -> Iterator[GriddedPerm]: - raise NotImplementedError + raise NotImplementedError("not implemented map for assumption or point jumping") def forward_map( self, @@ -318,7 +315,7 @@ def forward_map( obj: GriddedPerm, children: Optional[Tuple[Tiling, ...]] = None, ) -> Tuple[Optional[GriddedPerm]]: - raise NotImplementedError + raise NotImplementedError("not implemented map for assumption or point jumping") def extra_parameters( self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None From d69ae21c00804b4206c353d36b9bca2bf5c98fd1 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Wed, 27 Apr 2022 14:08:56 +0200 Subject: [PATCH 031/100] fix from dict (#446) * fix from dict * fix to_jsonable --- tilings/assumptions.py | 2 +- tilings/strategies/rearrange_assumption.py | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/tilings/assumptions.py b/tilings/assumptions.py index beb0c95f..f476a5d6 100644 --- a/tilings/assumptions.py +++ b/tilings/assumptions.py @@ -25,7 +25,7 @@ def __init__(self, gps: Iterable[GriddedPerm]): @classmethod def from_cells(cls, cells: Iterable[Cell]) -> "TrackingAssumption": gps = [GriddedPerm.single_cell((0,), cell) for cell in cells] - return TrackingAssumption(gps) + return cls(gps) def avoiding( self, diff --git a/tilings/strategies/rearrange_assumption.py b/tilings/strategies/rearrange_assumption.py index cce39339..6cb9969b 100644 --- a/tilings/strategies/rearrange_assumption.py +++ b/tilings/strategies/rearrange_assumption.py @@ -493,9 +493,18 @@ def formal_step(self) -> str: cells = ", ".join(str(gp.pos[0]) for gp in self.assumption.gps) return f"change component assumption in cells {cells} to point assumption" + def to_jsonable(self) -> dict: + d: dict = super().to_jsonable() + d["assumption"] = self.assumption.to_jsonable() + return d + @classmethod def from_dict(cls, d: dict) -> "ComponentToPointAssumptionStrategy": - return cls(**d) + return cls( + assumption=TrackingAssumption.from_dict(d["assumption"]), + ignore_parent=d["ignore_parent"], + workable=d["workable"], + ) class RearrangeAssumptionFactory(StrategyFactory[Tiling]): From a745c1f7ea439de2652d0e7f6de6673da7113a21 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Thu, 28 Apr 2022 10:10:40 +0200 Subject: [PATCH 032/100] ass.__class__ (#447) --- tilings/strategies/monotone_sliding.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tilings/strategies/monotone_sliding.py b/tilings/strategies/monotone_sliding.py index 95a6ef09..981258ce 100644 --- a/tilings/strategies/monotone_sliding.py +++ b/tilings/strategies/monotone_sliding.py @@ -3,7 +3,7 @@ from comb_spec_searcher import DisjointUnionStrategy, StrategyFactory from comb_spec_searcher.exception import StrategyDoesNotApply -from tilings import GriddedPerm, Tiling, TrackingAssumption +from tilings import GriddedPerm, Tiling from tilings.algorithms import Fusion @@ -23,10 +23,7 @@ def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling]: child = Tiling( self.slide_gps(comb_class.obstructions), map(self.slide_gps, comb_class.requirements), - [ - TrackingAssumption(self.slide_gps(ass.gps)) - for ass in comb_class.assumptions - ], + [ass.__class__(self.slide_gps(ass.gps)) for ass in comb_class.assumptions], ) if self.rotate: child = child.rotate90() @@ -68,7 +65,7 @@ def extra_parameters( comb_class.get_assumption_parameter( ass ): child.get_assumption_parameter( - TrackingAssumption(self.slide_gps(ass.gps)) + ass.__class__(self.slide_gps(ass.gps)) ) for ass in comb_class.assumptions }, From c6c009669de82cb69370eb5e62ea4e57913b4316 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 3 May 2022 15:23:23 +0200 Subject: [PATCH 033/100] only deflate/reduce when component assumptions touching cell are 1x1 (#448) --- tilings/strategies/cell_reduction.py | 23 +++++++++++++++++++---- tilings/strategies/deflation.py | 21 ++++++++++++++++----- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/tilings/strategies/cell_reduction.py b/tilings/strategies/cell_reduction.py index 440cc202..899729f2 100644 --- a/tilings/strategies/cell_reduction.py +++ b/tilings/strategies/cell_reduction.py @@ -16,7 +16,7 @@ ) from permuta import Perm from tilings import GriddedPerm, Tiling -from tilings.assumptions import TrackingAssumption +from tilings.assumptions import ComponentAssumption, TrackingAssumption Cell = Tuple[int, int] @@ -109,10 +109,16 @@ def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling, Tiling]: for req in comb_class.requirements if not all(gp.pos[0] == self.cell and gp.is_single_cell() for gp in req) ) + reduced_ass = sorted( + ass + for ass in comb_class.assumptions + if not isinstance(ass, ComponentAssumption) + or GriddedPerm.point_perm(self.cell) not in ass.gps + ) reduced_tiling = Tiling( reduced_obs, reduced_reqs, - comb_class.assumptions, + reduced_ass, remove_empty_rows_and_cols=False, derive_empty=False, simplify=False, @@ -235,11 +241,15 @@ def __call__(self, comb_class: Tiling) -> Iterator[CellReductionStrategy]: if not ( # a finite cell any(patt.is_increasing() for patt in cell_bases[cell][0]) and any(patt.is_decreasing() for patt in cell_bases[cell][0]) - ) and self.can_reduce_cell_with_requirements(comb_class, cell): + ) and self.can_reduce_cell_with_requirements_and_assumptions( + comb_class, cell + ): yield CellReductionStrategy(cell, True, self.tracked) yield CellReductionStrategy(cell, False, self.tracked) - def can_reduce_cell_with_requirements(self, tiling: Tiling, cell: Cell) -> bool: + def can_reduce_cell_with_requirements_and_assumptions( + self, tiling: Tiling, cell: Cell + ) -> bool: return all( # local all(gp.pos[0] == cell and gp.is_single_cell() for gp in req) or all( @@ -250,6 +260,11 @@ def can_reduce_cell_with_requirements(self, tiling: Tiling, cell: Cell) -> bool: for gp in req ) for req in tiling.requirements + ) and all( + not isinstance(ass, ComponentAssumption) + or GriddedPerm.point_perm(cell) not in ass.gps + or len(ass.gps) == 1 + for ass in tiling.assumptions ) @staticmethod diff --git a/tilings/strategies/deflation.py b/tilings/strategies/deflation.py index 220d3418..458ff911 100644 --- a/tilings/strategies/deflation.py +++ b/tilings/strategies/deflation.py @@ -16,7 +16,7 @@ ) from permuta import Perm from tilings import GriddedPerm, Tiling -from tilings.assumptions import TrackingAssumption +from tilings.assumptions import ComponentAssumption, TrackingAssumption Cell = Tuple[int, int] @@ -111,10 +111,16 @@ def deflated_tiling(self, tiling: Tiling) -> Tiling: for req in tiling.requirements if not all(gp.pos[0] == self.cell and gp.is_single_cell() for gp in req) ) + reduced_ass = sorted( + ass + for ass in tiling.assumptions + if not isinstance(ass, ComponentAssumption) + or GriddedPerm.point_perm(self.cell) not in ass.gps + ) return Tiling( tiling.obstructions, reduced_reqs, - tiling.assumptions, + reduced_ass, remove_empty_rows_and_cols=False, derive_empty=False, simplify=False, @@ -222,7 +228,7 @@ def __init__(self, tracked: bool): def __call__(self, comb_class: Tiling) -> Iterator[DeflationStrategy]: for cell in comb_class.active_cells: - if not self._can_deflate_requirements(comb_class, cell): + if not self._can_deflate_requirements_and_assumptions(comb_class, cell): continue if self.can_deflate(comb_class, cell, True): yield DeflationStrategy(cell, True, self.tracked) @@ -230,13 +236,18 @@ def __call__(self, comb_class: Tiling) -> Iterator[DeflationStrategy]: yield DeflationStrategy(cell, False, self.tracked) @staticmethod - def _can_deflate_requirements(tiling: Tiling, cell: Cell) -> bool: + def _can_deflate_requirements_and_assumptions(tiling: Tiling, cell: Cell) -> bool: def can_deflate_req_list(req: Tuple[GriddedPerm, ...]) -> bool: return all(gp.pos[0] == cell and gp.is_single_cell() for gp in req) or all( len(list(gp.points_in_cell(cell))) < 2 for gp in req ) - return all(can_deflate_req_list(req) for req in tiling.requirements) + return all(can_deflate_req_list(req) for req in tiling.requirements) and all( + not isinstance(ass, ComponentAssumption) + or GriddedPerm.point_perm(cell) not in ass.gps + or len(ass.gps) == 1 + for ass in tiling.assumptions + ) @staticmethod def can_deflate(tiling: Tiling, cell: Cell, sum_deflate: bool) -> bool: From 5e4fb85f797d0f0fe9ffcbcd2144839395a4a5ae Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 3 May 2022 15:24:28 +0200 Subject: [PATCH 034/100] allow fusing when any assumption covers entire fuse region (#449) --- tilings/algorithms/fusion.py | 49 +++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/tilings/algorithms/fusion.py b/tilings/algorithms/fusion.py index 63a4d00b..33aba358 100644 --- a/tilings/algorithms/fusion.py +++ b/tilings/algorithms/fusion.py @@ -278,13 +278,12 @@ def _can_fuse_assumption( are all contained entirely on the left of the fusion region, entirely on the right, or split in every possible way. """ - if isinstance(assumption, ComponentAssumption): - return self.is_left_sided_assumption( - assumption - ) and self.is_right_sided_assumption(assumption) return self._can_fuse_set_of_gridded_perms(fuse_counter) or ( - all(count == 1 for gp, count in fuse_counter.items()) - and self._is_one_sided_assumption(assumption) + not isinstance(assumption, ComponentAssumption) + and ( + all(count == 1 for count in fuse_counter.values()) + and self._is_one_sided_assumption(assumption) + ) ) def _is_one_sided_assumption(self, assumption: TrackingAssumption) -> bool: @@ -575,21 +574,22 @@ def _can_component_fuse_assumption(self, assumption: TrackingAssumption) -> bool do not touch the fuse region unless it is the correct sum or skew assumption. """ - if ( - not isinstance(assumption, ComponentAssumption) - or ( + gps = [ + GriddedPerm.point_perm(self.first_cell), + GriddedPerm.point_perm(self.second_cell), + ] + return ( # if right type + ( isinstance(assumption, SumComponentAssumption) - and not self.is_sum_component_fusion() + and self.is_sum_component_fusion() ) or ( isinstance(assumption, SkewComponentAssumption) - and self.is_sum_component_fusion() - ) - ): - return self.is_left_sided_assumption( - assumption - ) and self.is_right_sided_assumption(assumption) - return True + and self.is_skew_component_fusion() + ) # or covers whole region or none of it + or all(gp in assumption.gps for gp in gps) + or all(gp not in assumption.gps for gp in gps) + ) def _can_fuse_set_of_gridded_perms( self, fuse_counter: Counter[GriddedPerm] @@ -629,7 +629,8 @@ def new_assumption(self) -> ComponentAssumption: def is_sum_component_fusion(self) -> bool: """ - Return true if is a sum component fusion""" + Return true if is a sum component fusion + """ fcell = self.first_cell scell = self.second_cell if self._fuse_row: @@ -638,6 +639,18 @@ def is_sum_component_fusion(self) -> bool: sum_ob = GriddedPerm((1, 0), (fcell, scell)) return sum_ob in self._tiling.obstructions + def is_skew_component_fusion(self) -> bool: + """ + Return true if is a skew component fusion + """ + fcell = self.first_cell + scell = self.second_cell + if self._fuse_row: + skew_ob = GriddedPerm((0, 1), (fcell, scell)) + else: + skew_ob = GriddedPerm((0, 1), (fcell, scell)) + return skew_ob in self._tiling.obstructions + def __str__(self) -> str: s = "ComponentFusion Algorithm for:\n" s += str(self._tiling) From c345d85065d5388f2f890184e9faa43e2a7b5e36 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 3 May 2022 15:27:23 +0200 Subject: [PATCH 035/100] add backward map to FactorWithInterleavingStrategy (#450) --- CHANGELOG.md | 2 + tilings/strategies/factor.py | 175 ++++++++++++++++++++++++++++++----- 2 files changed, 153 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d61bc615..91765aa9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,8 @@ swapped around a fusable row or column. - `StrategyPack.kitchen_sinkify` to add many experimental strategies to the pack - `SubobstructionInsertionFactory` that inserts subobstructions and the pack `TileScopePack.subobstruction_placements` which uses it. +- `FactorWithInterleavingStrategy.backward_map` so you can now generate permutation + from specifications using interleaving factors. ### Fixed - `ForgetTrackedSearcher` was not retroactively applying strategies that had a `basis`. diff --git a/tilings/strategies/factor.py b/tilings/strategies/factor.py index daba25cc..769c3e3d 100644 --- a/tilings/strategies/factor.py +++ b/tilings/strategies/factor.py @@ -1,8 +1,19 @@ from collections import Counter from functools import reduce -from itertools import chain +from itertools import chain, combinations from operator import mul -from typing import Callable, Dict, Iterable, Iterator, List, Optional, Set, Tuple, cast +from typing import ( + Callable, + Dict, + Iterable, + Iterator, + List, + Optional, + Set, + Tuple, + Union, + cast, +) from sympy import Eq, Function @@ -12,14 +23,7 @@ StrategyFactory, ) from comb_spec_searcher.exception import StrategyDoesNotApply -from comb_spec_searcher.typing import ( - Parameters, - SubObjects, - SubRecs, - SubSamplers, - SubTerms, - Terms, -) +from comb_spec_searcher.typing import SubRecs, SubSamplers, SubTerms, Terms from permuta import Perm from tilings import GriddedPerm, Tiling from tilings.algorithms import ( @@ -40,6 +44,13 @@ "FactorWithMonotoneInterleavingStrategy", ) +TempGP = Tuple[ + Tuple[ + Tuple[Union[float, int], ...], Tuple[Union[float, int], ...], Tuple[Cell, ...] + ], + ..., +] + class FactorStrategy(CartesianProductStrategy[Tiling, GriddedPerm]): def __init__( @@ -255,11 +266,6 @@ def get_terms( interleaved_terms[parameters] += multiplier * value return interleaved_terms - def get_sub_objects( - self, subobjs: SubObjects, n: int - ) -> Iterator[Tuple[Parameters, Tuple[List[Optional[GriddedPerm]], ...]]]: - raise NotImplementedError - def random_sample_sub_objects( self, parent_count: int, @@ -272,6 +278,10 @@ def random_sample_sub_objects( class FactorWithInterleavingStrategy(FactorStrategy): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.cols, self.rows = interleaving_rows_and_cols(self.partition) + def formal_step(self) -> str: return "interleaving " + super().formal_step() @@ -342,15 +352,129 @@ def backward_map( objs: Tuple[Optional[GriddedPerm], ...], children: Optional[Tuple[Tiling, ...]] = None, ) -> Iterator[GriddedPerm]: - raise NotImplementedError + if children is None: + children = self.decomposition_function(comb_class) + gps_to_combine = tuple( + tiling.backward_map.map_gp(cast(GriddedPerm, gp)) + for gp, tiling in zip(objs, children) + ) + all_gps_to_combine: List[TempGP] = [ + tuple( + (tuple(range(len(gp))), tuple(gp.patt), gp.pos) for gp in gps_to_combine + ) + ] + for row in self.rows: + all_gps_to_combine = self._interleave_row(all_gps_to_combine, row) + for col in self.cols: + all_gps_to_combine = self._interleave_col(all_gps_to_combine, col) + + for interleaved_gps_to_combine in all_gps_to_combine: + temp = [ + ((cell[0], idx), (cell[1], val)) + for gp in interleaved_gps_to_combine + for idx, val, cell in zip(*gp) + ] + temp.sort() + new_pos = [(idx[0], val[0]) for idx, val in temp] + new_patt = Perm.to_standard(val for _, val in temp) + assert not GriddedPerm(new_patt, new_pos).contradictory() + yield GriddedPerm(new_patt, new_pos) - def forward_map( + def _interleave_row( self, - comb_class: Tiling, - obj: GriddedPerm, - children: Optional[Tuple[Tiling, ...]] = None, - ) -> Tuple[GriddedPerm, ...]: - raise NotImplementedError + all_gps_to_combine: List[TempGP], + row: int, + ) -> List[TempGP]: + # pylint: disable=too-many-locals + res: List[TempGP] = [] + for gps_to_combine in all_gps_to_combine: + row_points = tuple( + tuple( + (idx, values[idx]) + for idx, cell in enumerate(position) + if cell[1] == row + ) + for _, values, position in gps_to_combine + ) + total = sum(len(points) for points in row_points) + if total == 0: + res.append(gps_to_combine) + continue + min_val = min(val for _, val in chain(*row_points)) + max_val = max(val for _, val in chain(*row_points)) + 1 + temp_values = tuple( + min_val + i * (max_val - min_val) / total for i in range(total) + ) + for partition in self._partitions( + set(temp_values), tuple(len(indices) for indices in row_points) + ): + new_gps_to_combine = [] + for part, (indices, values, position), points in zip( + partition, gps_to_combine, row_points + ): + new_values = list(values) + actual_indices = [ + idx for _, idx in sorted((val, idx) for idx, val in points) + ] + for idx, val in zip(actual_indices, sorted(part)): + new_values[idx] = val + new_gps_to_combine.append((indices, tuple(new_values), position)) + res.append(tuple(new_gps_to_combine)) + return res + + def _interleave_col( + self, + all_gps_to_combine: List[TempGP], + col: int, + ): + # pylint: disable=too-many-locals + res: List[TempGP] = [] + for gps_to_combine in all_gps_to_combine: + col_points = tuple( + tuple( + (idx, indices[idx]) + for idx, cell in enumerate(position) + if cell[0] == col + ) + for indices, _, position in gps_to_combine + ) + total = sum(len(points) for points in col_points) + if total == 0: + res.append(gps_to_combine) + continue + mindex = min(val for _, val in chain(*col_points)) + maxdex = max(val for _, val in chain(*col_points)) + 1 + temp_indices = tuple( + mindex + i * (maxdex - mindex) / total for i in range(total) + ) + for partition in self._partitions( + set(temp_indices), tuple(len(indices) for indices in col_points) + ): + new_gps_to_combine = [] + for part, (indices, values, position), points in zip( + partition, gps_to_combine, col_points + ): + new_indices = list(indices) + for idx, new_idx in zip([idx for idx, _ in points], sorted(part)): + new_indices[idx] = new_idx + new_gps_to_combine.append((tuple(new_indices), values, position)) + res.append(tuple(new_gps_to_combine)) + return res + + @staticmethod + def _partitions( + values: Set[float], size_of_parts: Tuple[int, ...] + ) -> Iterator[Tuple[Tuple[float, ...], ...]]: + if not size_of_parts: + if not values: + yield tuple() + return + size = size_of_parts[0] + for part in combinations(values, size): + for rest in FactorWithInterleavingStrategy._partitions( + values - set(part), size_of_parts[1:] + ): + yield (part,) + rest @staticmethod def get_eq_symbol() -> str: @@ -451,8 +575,11 @@ def _build_strategy( """ interleaving = any(interleaving_rows_and_cols(components)) factor_strat = self.factor_class if interleaving else FactorStrategy - return factor_strat( - components, ignore_parent=self.ignore_parent, workable=workable + return cast( + FactorStrategy, + factor_strat( + components, ignore_parent=self.ignore_parent, workable=workable + ), ) def __str__(self) -> str: From 27506a65550d528ac28e1568b67f47de9ab25b00 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Wed, 4 May 2022 15:49:19 +0200 Subject: [PATCH 036/100] remove AddInterleavingAssumptionsFactory (#451) * remove AddInterleavingAssumptionsFactory * allow untracked interleaving * self. --- CHANGELOG.md | 3 + tilings/strategies/__init__.py | 3 +- tilings/strategies/assumption_insertion.py | 64 +----- tilings/strategies/factor.py | 216 +++++++++++---------- tilings/strategy_pack.py | 11 +- 5 files changed, 120 insertions(+), 177 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91765aa9..c1ef23a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,9 @@ swapped around a fusable row or column. - `TileScopePack.pattern_placements` factors as an initial strategy. - `is_component` method of assumptions updated to consider cell decomposition +### Removed +- `AddInterleavingAssumptionsFactory`. The factor strategy now adds the relevant + assumptions where necessary directly, lowering the number of CVs needed. ### Deprecated - Python 3.7 is no longer supported diff --git a/tilings/strategies/__init__.py b/tilings/strategies/__init__.py index e28957e6..58dc7f84 100644 --- a/tilings/strategies/__init__.py +++ b/tilings/strategies/__init__.py @@ -1,4 +1,4 @@ -from .assumption_insertion import AddAssumptionFactory, AddInterleavingAssumptionFactory +from .assumption_insertion import AddAssumptionFactory from .assumption_splitting import SplittingStrategy from .cell_reduction import CellReductionFactory from .deflation import DeflationFactory @@ -56,7 +56,6 @@ __all__ = [ # Assumptions "AddAssumptionFactory", - "AddInterleavingAssumptionFactory", "DetectComponentsStrategy", "RearrangeAssumptionFactory", "SplittingStrategy", diff --git a/tilings/strategies/assumption_insertion.py b/tilings/strategies/assumption_insertion.py index 409fd06e..21073a98 100644 --- a/tilings/strategies/assumption_insertion.py +++ b/tilings/strategies/assumption_insertion.py @@ -1,5 +1,5 @@ from collections import Counter -from itertools import chain, product +from itertools import product from random import randint from typing import Callable, Dict, Iterable, Iterator, List, Optional, Tuple @@ -19,11 +19,7 @@ Terms, ) from tilings import GriddedPerm, Tiling -from tilings.algorithms import FactorWithInterleaving from tilings.assumptions import TrackingAssumption -from tilings.misc import partitions_iterator - -from .factor import assumptions_to_add, interleaving_rows_and_cols Cell = Tuple[int, int] @@ -44,7 +40,7 @@ def __init__( self.extra_parameters = extra_parameters # the paramater that was added, to count we must sum over all possible values self.new_parameters = tuple(new_parameters) - self._child_param_map = self._build_child_param_map(parent, child) + self.child_param_map = self._build_child_param_map(parent, child) def get_equation(self, lhs_func: Function, rhs_funcs: Tuple[Function, ...]) -> Eq: rhs_func = rhs_funcs[0] @@ -62,7 +58,7 @@ def get_terms( self, parent_terms: Callable[[int], Terms], subterms: SubTerms, n: int ) -> Terms: assert len(subterms) == 1 - return self._push_add_assumption(n, subterms[0], self._child_param_map) + return self._push_add_assumption(n, subterms[0], self.child_param_map) @staticmethod def _push_add_assumption( @@ -94,7 +90,7 @@ def get_sub_objects( self, subobjs: SubObjects, n: int ) -> Iterator[Tuple[Parameters, Tuple[List[Optional[GriddedPerm]], ...]]]: for param, gps in subobjs[0](n).items(): - yield self._child_param_map(param), (gps,) + yield self.child_param_map(param), (gps,) def random_sample_sub_objects( self, @@ -283,55 +279,3 @@ def to_jsonable(self) -> dict: @classmethod def from_dict(cls, d: dict) -> "AddAssumptionFactory": return cls() - - -class AddInterleavingAssumptionFactory(StrategyFactory[Tiling]): - def __init__(self, unions: bool = False): - self.unions = unions - - @staticmethod - def strategy_from_components( - comb_class: Tiling, components: Tuple[Tuple[Cell, ...], ...] - ) -> Iterator[Rule]: - """ - Yield an AddAssumption strategy for the given component if needed. - """ - cols, rows = interleaving_rows_and_cols(components) - assumptions = set( - ass - for ass in chain.from_iterable( - assumptions_to_add(cells, cols, rows) for cells in components - ) - if ass not in comb_class.assumptions - ) - if assumptions: - strategy = AddAssumptionsStrategy(assumptions, workable=True) - yield strategy(comb_class) - - # TODO: monotone? - def __call__(self, comb_class: Tiling) -> Iterator[Rule]: - factor_algo = FactorWithInterleaving(comb_class) - if factor_algo.factorable(): - min_comp = tuple(tuple(part) for part in factor_algo.get_components()) - if self.unions: - for partition in partitions_iterator(min_comp): - components = tuple( - tuple(chain.from_iterable(part)) for part in partition - ) - yield from self.strategy_from_components(comb_class, components) - yield from self.strategy_from_components(comb_class, min_comp) - - def __repr__(self) -> str: - return self.__class__.__name__ + "()" - - def __str__(self) -> str: - return "add interleaving assumptions to factor" - - def to_jsonable(self) -> dict: - d: dict = super().to_jsonable() - d["unions"] = self.unions - return d - - @classmethod - def from_dict(cls, d: dict) -> "AddInterleavingAssumptionFactory": - return cls(**d) diff --git a/tilings/strategies/factor.py b/tilings/strategies/factor.py index 769c3e3d..41845054 100644 --- a/tilings/strategies/factor.py +++ b/tilings/strategies/factor.py @@ -34,6 +34,10 @@ from tilings.assumptions import TrackingAssumption from tilings.exception import InvalidOperationError from tilings.misc import multinomial, partitions_iterator +from tilings.strategies.assumption_insertion import ( + AddAssumptionsConstructor, + AddAssumptionsStrategy, +) Cell = Tuple[int, int] @@ -60,7 +64,9 @@ def __init__( workable: bool = True, ): self.partition = tuple(sorted(tuple(sorted(p)) for p in partition)) - inferrable = any(interleaving_rows_and_cols(self.partition)) + inferrable = any( + FactorWithInterleavingStrategy.interleaving_rows_and_cols(self.partition) + ) super().__init__( ignore_parent=ignore_parent, workable=workable, inferrable=inferrable ) @@ -167,64 +173,6 @@ def from_dict(cls, d: dict) -> "FactorStrategy": # interleavings of a factor. They are also used by AddInterleavingAssumptionStrategy. -def interleaving_rows_and_cols( - partition: Tuple[Tuple[Cell, ...], ...] -) -> Tuple[Set[int], Set[int]]: - """ - Return the set of cols and the set of rows that are being interleaved when - factoring with partition. - """ - cols: Set[int] = set() - rows: Set[int] = set() - x_seen: Set[int] = set() - y_seen: Set[int] = set() - for part in partition: - cols.update(x for x, _ in part if x in x_seen) - rows.update(y for _, y in part if y in y_seen) - x_seen.update(x for x, _ in part) - y_seen.update(y for _, y in part) - return cols, rows - - -def assumptions_to_add( - cells: Tuple[Cell, ...], cols: Set[int], rows: Set[int] -) -> Tuple[TrackingAssumption, ...]: - """ - Return the assumptions that should be tracked in the set of cells if we are - interleaving the given rows and cols. - """ - col_assumptions = [ - TrackingAssumption( - [GriddedPerm.point_perm(cell) for cell in cells if x == cell[0]] - ) - for x in cols - ] - row_assumptions = [ - TrackingAssumption( - [GriddedPerm.point_perm(cell) for cell in cells if y == cell[1]] - ) - for y in rows - ] - return tuple(ass for ass in chain(col_assumptions, row_assumptions) if ass.gps) - - -def contains_interleaving_assumptions( - comb_class: Tiling, partition: Tuple[Tuple[Cell, ...], ...] -) -> bool: - """ - Return True if the parent tiling contains all of the necessary tracking - assumptions needed to count the interleavings, and therefore the - children too. - """ - cols, rows = interleaving_rows_and_cols(partition) - return all( - ass in comb_class.assumptions - for ass in chain.from_iterable( - assumptions_to_add(cells, cols, rows) for cells in partition - ) - ) - - class Interleaving(CartesianProduct[Tiling, GriddedPerm]): def __init__( self, @@ -232,6 +180,7 @@ def __init__( children: Iterable[Tiling], extra_parameters: Tuple[Dict[str, str], ...], interleaving_parameters: Iterable[Tuple[str, ...]], + insertion_constructor: Optional[AddAssumptionsConstructor], ): super().__init__(parent, children, extra_parameters) self.interleaving_parameters = tuple(interleaving_parameters) @@ -239,6 +188,7 @@ def __init__( tuple(parent.extra_parameters.index(k) for k in parameters) for parameters in interleaving_parameters ) + self.insertion_constructor = insertion_constructor @staticmethod def is_equivalence() -> bool: @@ -264,6 +214,11 @@ def get_terms( 1, ) interleaved_terms[parameters] += multiplier * value + if self.insertion_constructor: + new_terms: Terms = Counter() + for param, value in interleaved_terms.items(): + new_terms[self.insertion_constructor.child_param_map(param)] += value + return new_terms return interleaved_terms def random_sample_sub_objects( @@ -278,28 +233,98 @@ def random_sample_sub_objects( class FactorWithInterleavingStrategy(FactorStrategy): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.cols, self.rows = interleaving_rows_and_cols(self.partition) + def __init__( + self, + partition: Iterable[Iterable[Cell]], + ignore_parent: bool = True, + workable: bool = True, + tracked: bool = True, + ): + super().__init__(partition, ignore_parent, workable) + self.tracked = tracked + self.cols, self.rows = self.interleaving_rows_and_cols(self.partition) def formal_step(self) -> str: return "interleaving " + super().formal_step() + def assumptions_to_add(self, comb_class: Tiling) -> Tuple[TrackingAssumption, ...]: + """Return the set of assumptions that need to be added to""" + cols, rows = self.interleaving_rows_and_cols(self.partition) + return tuple( + ass + for ass in chain.from_iterable( + self._assumptions_to_add(cells, cols, rows) for cells in self.partition + ) + if ass not in comb_class.assumptions + ) + + @staticmethod + def interleaving_rows_and_cols( + partition: Tuple[Tuple[Cell, ...], ...] + ) -> Tuple[Set[int], Set[int]]: + """ + Return the set of cols and the set of rows that are being interleaved when + factoring with partition. + """ + cols: Set[int] = set() + rows: Set[int] = set() + x_seen: Set[int] = set() + y_seen: Set[int] = set() + for part in partition: + cols.update(x for x, _ in part if x in x_seen) + rows.update(y for _, y in part if y in y_seen) + x_seen.update(x for x, _ in part) + y_seen.update(y for _, y in part) + return cols, rows + + @staticmethod + def _assumptions_to_add( + cells: Tuple[Cell, ...], cols: Set[int], rows: Set[int] + ) -> Tuple[TrackingAssumption, ...]: + """ + Return the assumptions that should be tracked in the set of cells if we are + interleaving the given rows and cols. + """ + col_assumptions = [ + TrackingAssumption( + [GriddedPerm.point_perm(cell) for cell in cells if x == cell[0]] + ) + for x in cols + ] + row_assumptions = [ + TrackingAssumption( + [GriddedPerm.point_perm(cell) for cell in cells if y == cell[1]] + ) + for y in rows + ] + return tuple(ass for ass in chain(col_assumptions, row_assumptions) if ass.gps) + + def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling, ...]: + if self.tracked: + comb_class = comb_class.add_assumptions(self.assumptions_to_add(comb_class)) + return super().decomposition_function(comb_class) + def constructor( self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None ) -> Interleaving: if children is None: children = self.decomposition_function(comb_class) - try: - interleaving_parameters = self.interleaving_parameters(comb_class) - except ValueError as e: - # must be untracked - raise NotImplementedError("The interleaving factor was not tracked.") from e + assumptions = self.assumptions_to_add(comb_class) + insertion_constructor = None + if assumptions: + insertion_constructor = AddAssumptionsStrategy(assumptions).constructor( + comb_class + ) + comb_class = comb_class.add_assumptions(assumptions) + interleaving_parameters = self.interleaving_parameters(comb_class) + if interleaving_parameters and not self.tracked: + raise NotImplementedError("The interleaving factor was not tracked.") return Interleaving( comb_class, children, self.extra_parameters(comb_class, children), interleaving_parameters, + insertion_constructor, ) def reverse_constructor( @@ -315,7 +340,7 @@ def interleaving_parameters(self, comb_class: Tiling) -> List[Tuple[str, ...]]: Return the parameters on the parent tiling that needed to be interleaved. """ res: List[Tuple[str, ...]] = [] - cols, rows = interleaving_rows_and_cols(self.partition) + cols, rows = self.interleaving_rows_and_cols(self.partition) for x in cols: assumptions = [ TrackingAssumption( @@ -485,29 +510,8 @@ def get_op_symbol() -> str: return "*" -class MonotoneInterleaving(Interleaving): - pass - - class FactorWithMonotoneInterleavingStrategy(FactorWithInterleavingStrategy): - def constructor( - self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None - ) -> MonotoneInterleaving: - if children is None: - children = self.decomposition_function(comb_class) - try: - interleaving_parameters = self.interleaving_parameters(comb_class) - except ValueError as e: - # must be untracked - raise NotImplementedError( - "The monotone interleaving was not tracked." - ) from e - return MonotoneInterleaving( - comb_class, - children, - self.extra_parameters(comb_class, children), - interleaving_parameters, - ) + pass class FactorFactory(StrategyFactory[Tiling]): @@ -556,14 +560,8 @@ def __call__(self, comb_class: Tiling) -> Iterator[FactorStrategy]: components = tuple( tuple(chain.from_iterable(part)) for part in partition ) - if not self.tracked or contains_interleaving_assumptions( - comb_class, components - ): - yield self._build_strategy(components, workable=False) - if not self.tracked or contains_interleaving_assumptions( - comb_class, min_comp - ): - yield self._build_strategy(min_comp, workable=self.workable) + yield self._build_strategy(components, workable=False) + yield self._build_strategy(min_comp, workable=self.workable) def _build_strategy( self, components: Tuple[Tuple[Cell, ...], ...], workable: bool @@ -573,13 +571,21 @@ def _build_strategy( It ensure that a plain factor rule is returned. """ - interleaving = any(interleaving_rows_and_cols(components)) - factor_strat = self.factor_class if interleaving else FactorStrategy - return cast( - FactorStrategy, - factor_strat( - components, ignore_parent=self.ignore_parent, workable=workable - ), + interleaving = any( + FactorWithInterleavingStrategy.interleaving_rows_and_cols(components) + ) + if interleaving: + return cast( + FactorStrategy, + self.factor_class( + components, + ignore_parent=self.ignore_parent, + workable=workable, + tracked=self.tracked, + ), + ) + return FactorStrategy( + components, ignore_parent=self.ignore_parent, workable=workable ) def __str__(self) -> str: diff --git a/tilings/strategy_pack.py b/tilings/strategy_pack.py index 1cd4677d..33b4c973 100644 --- a/tilings/strategy_pack.py +++ b/tilings/strategy_pack.py @@ -126,11 +126,6 @@ def replace_list(strats): res = [] for strategy in strats: d = strategy.to_jsonable() - if "interleaving" in d: - if not d["tracked"] and d["interleaving"] in ("all", "monotone"): - res.append( - strat.AddInterleavingAssumptionFactory(unions=d["unions"]) - ) if not d.get("tracked", True): d["tracked"] = True strategy = AbstractStrategy.from_dict(d) @@ -218,8 +213,7 @@ def make_interleaving( Return a new pack where the factor strategy is replaced with an interleaving factor strategy. - If unions is set to True it will overwrite unions on the strategy, and - also pass the argument to AddInterleavingAssumption method. + If unions is set to True it will overwrite unions on the strategy. """ def replace_list(strats): @@ -251,9 +245,6 @@ def replace_list(strats): ) if tracked: - pack = pack.add_initial( - strat.AddInterleavingAssumptionFactory(unions=unions), apply_first=True - ) pack = pack.add_initial(strat.AddAssumptionFactory(), apply_first=True) return pack From b153a512b81f425d197604a578640d503984061a Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Fri, 6 May 2022 14:55:57 +0200 Subject: [PATCH 037/100] make sure to check both cells are monotone (#457) --- tilings/strategies/monotone_sliding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tilings/strategies/monotone_sliding.py b/tilings/strategies/monotone_sliding.py index 981258ce..c256d556 100644 --- a/tilings/strategies/monotone_sliding.py +++ b/tilings/strategies/monotone_sliding.py @@ -122,7 +122,7 @@ def __call__(self, comb_class: Tiling) -> Iterator[GeneralizedSlidingStrategy]: comb_class.cell_basis()[(col, 0)][0], comb_class.cell_basis()[(col + 1, 0)][0], ) - if len(local_cells[0]) == 1 and len(local_cells[0]) == 1: + if len(local_cells[0]) == 1 and len(local_cells[1]) == 1: if ( local_cells[0][0].is_increasing() and local_cells[1][0].is_increasing() From 1f5371ee83d08447d32753c53dfcb6c0520dd382 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Fri, 6 May 2022 17:29:20 +0200 Subject: [PATCH 038/100] pointing strategy, unfusion strategy, disjoint fusion strategy, a dummy strategy, and allowing directionless pp (#453) * pointing strategy, a dummy strategy, and allowing directionless pp * add json encoding test * mypy and pylint * CHANGELOG.md * unfusion strategy * factory * the disjoint fusion strategy * changelog and add to kitchen sink * added counting code * implement counting with params for unfusion and pointing * pointing subtracts already placed cells --- CHANGELOG.md | 7 + tests/strategies/test_encoding.py | 22 +- tilings/algorithms/requirement_placement.py | 5 +- tilings/strategies/__init__.py | 11 +- tilings/strategies/dummy_strategy.py | 86 +++++ tilings/strategies/fusion/__init__.py | 3 + tilings/strategies/fusion/disjoint_fusion.py | 95 ++++++ tilings/strategies/pointing.py | 132 ++++++++ tilings/strategies/unfusion.py | 317 +++++++++++++++++++ tilings/strategy_pack.py | 8 + tilings/tiling.py | 2 +- 11 files changed, 684 insertions(+), 4 deletions(-) create mode 100644 tilings/strategies/dummy_strategy.py create mode 100644 tilings/strategies/fusion/disjoint_fusion.py create mode 100644 tilings/strategies/pointing.py create mode 100644 tilings/strategies/unfusion.py diff --git a/CHANGELOG.md b/CHANGELOG.md index c1ef23a3..86b8d5d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,13 @@ swapped around a fusable row or column. `TileScopePack.subobstruction_placements` which uses it. - `FactorWithInterleavingStrategy.backward_map` so you can now generate permutation from specifications using interleaving factors. +- `DummyStrategy` that gives a quick template for making strategies. +- `PointingStrategy` that places points directionless in non-point cells. This is + a non-productive strategy so should be used with `RuleDBForest`. +- `UnfusionFactory` that unfuses either all the rows or columns. Also non-productive. +- `DisjointFusionFactory` that produces disjoint fusion rules that come from placing + points directionless. + ### Fixed - `ForgetTrackedSearcher` was not retroactively applying strategies that had a `basis`. diff --git a/tests/strategies/test_encoding.py b/tests/strategies/test_encoding.py index b6664c7d..fdcb517b 100644 --- a/tests/strategies/test_encoding.py +++ b/tests/strategies/test_encoding.py @@ -17,6 +17,8 @@ ComponentFusionFactory, DatabaseVerificationStrategy, DeflationFactory, + DisjointFusionFactory, + DummyStrategy, ElementaryVerificationStrategy, EmptyCellInferralFactory, FactorFactory, @@ -32,6 +34,7 @@ ObstructionTransitivityFactory, OneByOneVerificationStrategy, PatternPlacementFactory, + PointingStrategy, RearrangeAssumptionFactory, RequirementCorroborationFactory, RequirementExtensionFactory, @@ -48,6 +51,9 @@ SubobstructionInsertionFactory, SymmetriesFactory, TargetedCellInsertionFactory, + UnfusionColumnStrategy, + UnfusionFactory, + UnfusionRowStrategy, ) from tilings.strategies.cell_reduction import CellReductionStrategy from tilings.strategies.deflation import DeflationStrategy @@ -57,7 +63,11 @@ FactorWithInterleavingStrategy, FactorWithMonotoneInterleavingStrategy, ) -from tilings.strategies.fusion import ComponentFusionStrategy, FusionStrategy +from tilings.strategies.fusion import ( + ComponentFusionStrategy, + DisjointFusionStrategy, + FusionStrategy, +) from tilings.strategies.monotone_sliding import GeneralizedSlidingStrategy from tilings.strategies.obstruction_inferral import ObstructionInferralStrategy from tilings.strategies.point_jumping import ( @@ -465,6 +475,16 @@ def indices_and_row(strategy): + indices_and_row(PointJumpingStrategy) + [TargetedCellInsertionFactory()] + ignoreparent(SubobstructionInsertionFactory) + + [ + DummyStrategy(), + PointingStrategy(), + UnfusionRowStrategy(), + UnfusionColumnStrategy(), + UnfusionFactory(), + DisjointFusionFactory(), + DisjointFusionStrategy(row_idx=1), + DisjointFusionStrategy(col_idx=1), + ] ) __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) diff --git a/tilings/algorithms/requirement_placement.py b/tilings/algorithms/requirement_placement.py index a30f409a..fc04cda7 100644 --- a/tilings/algorithms/requirement_placement.py +++ b/tilings/algorithms/requirement_placement.py @@ -1,7 +1,7 @@ from itertools import chain from typing import TYPE_CHECKING, Dict, FrozenSet, Iterable, List, Tuple -from permuta.misc import DIR_EAST, DIR_NORTH, DIR_SOUTH, DIR_WEST, DIRS +from permuta.misc import DIR_EAST, DIR_NONE, DIR_NORTH, DIR_SOUTH, DIR_WEST, DIRS from tilings import GriddedPerm from tilings.assumptions import TrackingAssumption @@ -362,6 +362,9 @@ def place_point_of_req( for cell in sorted(cells): stretched = self._stretched_obstructions_requirements_and_assumptions(cell) (obs, reqs, ass) = stretched + if direction == DIR_NONE: + res.append(self._tiling.__class__(obs, reqs, ass)) + continue forced_obs = self.forced_obstructions_from_requirement( gps, indices, cell, direction ) diff --git a/tilings/strategies/__init__.py b/tilings/strategies/__init__.py index 58dc7f84..2c3c2b43 100644 --- a/tilings/strategies/__init__.py +++ b/tilings/strategies/__init__.py @@ -3,13 +3,14 @@ from .cell_reduction import CellReductionFactory from .deflation import DeflationFactory from .detect_components import DetectComponentsStrategy +from .dummy_strategy import DummyStrategy from .experimental_verification import ( NoRootCellVerificationStrategy, ShortObstructionVerificationStrategy, SubclassVerificationFactory, ) from .factor import FactorFactory -from .fusion import ComponentFusionFactory, FusionFactory +from .fusion import ComponentFusionFactory, DisjointFusionFactory, FusionFactory from .monotone_sliding import MonotoneSlidingFactory from .obstruction_inferral import ( EmptyCellInferralFactory, @@ -18,6 +19,7 @@ SubobstructionInferralFactory, ) from .point_jumping import AssumptionAndPointJumpingFactory +from .pointing import PointingStrategy from .rearrange_assumption import RearrangeAssumptionFactory from .requirement_insertion import ( CellInsertionFactory, @@ -41,6 +43,7 @@ from .row_and_col_separation import RowColumnSeparationStrategy from .sliding import SlidingFactory from .symmetry import SymmetriesFactory +from .unfusion import UnfusionColumnStrategy, UnfusionFactory, UnfusionRowStrategy from .verification import ( BasicVerificationStrategy, ComponentVerificationStrategy, @@ -84,9 +87,15 @@ "SlidingFactory", # Experimental "AssumptionAndPointJumpingFactory", + "DummyStrategy", + "PointingStrategy", # Fusion "ComponentFusionFactory", + "DisjointFusionFactory", "FusionFactory", + "UnfusionColumnStrategy", + "UnfusionRowStrategy", + "UnfusionFactory", # Inferral "EmptyCellInferralFactory", "ObstructionInferralFactory", diff --git a/tilings/strategies/dummy_strategy.py b/tilings/strategies/dummy_strategy.py new file mode 100644 index 00000000..766fb6f9 --- /dev/null +++ b/tilings/strategies/dummy_strategy.py @@ -0,0 +1,86 @@ +from typing import Dict, Iterator, Optional, Tuple + +from comb_spec_searcher import Strategy +from tilings import GriddedPerm, Tiling + +from .dummy_constructor import DummyConstructor + + +class DummyStrategy(Strategy[Tiling, GriddedPerm]): + def can_be_equivalent(self) -> bool: + raise NotImplementedError + + def is_two_way(self, comb_class: Tiling) -> bool: + raise NotImplementedError + + def is_reversible(self, comb_class: Tiling) -> bool: + raise NotImplementedError + + def shifts( + self, + comb_class: Tiling, + children: Optional[Tuple[Tiling, ...]], + ) -> Tuple[int, ...]: + if children is None: + children = self.decomposition_function(comb_class) + raise NotImplementedError + + def decomposition_function( + self, comb_class: Tiling + ) -> Optional[Tuple[Tiling, ...]]: + raise NotImplementedError + + def formal_step(self) -> str: + return "dummy strategy" + + def constructor( + self, + comb_class: Tiling, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> DummyConstructor: + if children is None: + children = self.decomposition_function(comb_class) + return DummyConstructor() + + def reverse_constructor( + self, + idx: int, + comb_class: Tiling, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> DummyConstructor: + if children is None: + children = self.decomposition_function(comb_class) + return DummyConstructor() + + def backward_map( + self, + comb_class: Tiling, + objs: Tuple[Optional[GriddedPerm], ...], + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Iterator[GriddedPerm]: + if children is None: + children = self.decomposition_function(comb_class) + raise NotImplementedError + + def forward_map( + self, + comb_class: Tiling, + obj: GriddedPerm, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Tuple[Optional[GriddedPerm], ...]: + if children is None: + children = self.decomposition_function(comb_class) + raise NotImplementedError + + def extra_parameters( + self, + comb_class: Tiling, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Tuple[Dict[str, str], ...]: + if children is None: + children = self.decomposition_function(comb_class) + raise NotImplementedError + + @classmethod + def from_dict(cls, d: dict) -> "DummyStrategy": + return cls(**d) diff --git a/tilings/strategies/fusion/__init__.py b/tilings/strategies/fusion/__init__.py index ff2ec87b..c4664e3e 100644 --- a/tilings/strategies/fusion/__init__.py +++ b/tilings/strategies/fusion/__init__.py @@ -1,10 +1,13 @@ from .component import ComponentFusionFactory, ComponentFusionStrategy from .constructor import FusionConstructor +from .disjoint_fusion import DisjointFusionFactory, DisjointFusionStrategy from .fusion import FusionFactory, FusionRule, FusionStrategy __all__ = [ "ComponentFusionFactory", "ComponentFusionStrategy", + "DisjointFusionFactory", + "DisjointFusionStrategy", "FusionFactory", "FusionStrategy", "FusionRule", diff --git a/tilings/strategies/fusion/disjoint_fusion.py b/tilings/strategies/fusion/disjoint_fusion.py new file mode 100644 index 00000000..e35164bc --- /dev/null +++ b/tilings/strategies/fusion/disjoint_fusion.py @@ -0,0 +1,95 @@ +from typing import Iterator, Optional, Tuple + +from comb_spec_searcher import DisjointUnionStrategy, StrategyFactory +from comb_spec_searcher.exception import StrategyDoesNotApply +from permuta.misc import DIR_NONE +from tilings import GriddedPerm, Tiling +from tilings.algorithms import Fusion + + +class DisjointFusionStrategy(DisjointUnionStrategy[Tiling, GriddedPerm]): + def __init__(self, row_idx=None, col_idx=None): + self.col_idx = col_idx + self.row_idx = row_idx + super().__init__( + ignore_parent=False, inferrable=True, possibly_empty=False, workable=True + ) + + def fusion_algorithm(self, tiling: Tiling) -> Fusion: + return Fusion(tiling, row_idx=self.row_idx, col_idx=self.col_idx, tracked=False) + + def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling, ...]: + algo = self.fusion_algorithm(comb_class) + if algo.fusable(): + fused_tiling = algo.fused_tiling() + point_count = sum(algo.min_left_right_points()) + if point_count == 0: + res = [fused_tiling] + elif point_count == 1: + res = [] + else: + raise StrategyDoesNotApply("Can't handle case with both sides positive") + for gp in algo.new_assumption().gps: + cell = gp.pos[0] + res.append(fused_tiling.place_point_in_cell(cell, DIR_NONE)) + return tuple(res) + + def formal_step(self) -> str: + if self.row_idx is not None: + return f"fuse rows {self.row_idx} and {self.row_idx + 1}" + return f"fuse cols {self.col_idx} and {self.col_idx + 1}" + + def backward_map( + self, + comb_class: Tiling, + objs: Tuple[Optional[GriddedPerm], ...], + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Iterator[GriddedPerm]: + if children is None: + children = self.decomposition_function(comb_class) + raise NotImplementedError + + def forward_map( + self, + comb_class: Tiling, + obj: GriddedPerm, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Tuple[Optional[GriddedPerm], ...]: + if children is None: + children = self.decomposition_function(comb_class) + raise NotImplementedError + + def to_jsonable(self) -> dict: + d = super().to_jsonable() + d["row"] = self.row_idx + d["col"] = self.col_idx + return d + + @classmethod + def from_dict(cls, d: dict) -> "DisjointFusionStrategy": + return cls(row_idx=d["row"], col_idx=d["col"]) + + def __repr__(self) -> str: + return ( + self.__class__.__name__ + + f"(row_idx={self.row_idx}, col_idx={self.col_idx})" + ) + + +class DisjointFusionFactory(StrategyFactory[Tiling]): + def __call__(self, comb_class: Tiling) -> Iterator[DisjointFusionStrategy]: + cols, rows = comb_class.dimensions + for row_idx in range(rows - 1): + yield DisjointFusionStrategy(row_idx=row_idx) + for col_idx in range(cols - 1): + yield DisjointFusionStrategy(col_idx=col_idx) + + def __str__(self) -> str: + return "disjoint fusion strategy" + + def __repr__(self) -> str: + return self.__class__.__name__ + "()" + + @classmethod + def from_dict(cls, d: dict) -> "DisjointFusionFactory": + return cls() diff --git a/tilings/strategies/pointing.py b/tilings/strategies/pointing.py new file mode 100644 index 00000000..272e8a49 --- /dev/null +++ b/tilings/strategies/pointing.py @@ -0,0 +1,132 @@ +""" +The directionless point placement strategy that is counted +by the 'pointing' constructor. +""" +from typing import Dict, FrozenSet, Iterator, Optional, Tuple + +from comb_spec_searcher import Strategy +from comb_spec_searcher.exception import StrategyDoesNotApply +from permuta.misc import DIR_NONE +from tilings import GriddedPerm, Tiling +from tilings.algorithms import RequirementPlacement +from tilings.tiling import Cell + +from .unfusion import DivideByN, ReverseDivideByN + + +class PointingStrategy(Strategy[Tiling, GriddedPerm]): + def can_be_equivalent(self) -> bool: + return False + + def is_two_way(self, comb_class: Tiling) -> bool: + return True + + def is_reversible(self, comb_class: Tiling) -> bool: + return True + + def shifts( + self, + comb_class: Tiling, + children: Optional[Tuple[Tiling, ...]], + ) -> Tuple[int, ...]: + if children is None: + children = self.decomposition_function(comb_class) + assert children is not None + return tuple(0 for _ in children) + + @staticmethod + def already_placed_cells(comb_class: Tiling) -> FrozenSet[Cell]: + return frozenset( + cell + for cell in comb_class.point_cells + if comb_class.only_cell_in_row_and_col(cell) + ) + + def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling, ...]: + cells = comb_class.active_cells - self.already_placed_cells(comb_class) + if cells: + return tuple( + comb_class.place_point_in_cell(cell, DIR_NONE) for cell in sorted(cells) + ) + raise StrategyDoesNotApply("The tiling is just point cells!") + + def formal_step(self) -> str: + return "directionless point placement" + + def constructor( + self, + comb_class: Tiling, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> DivideByN: + if children is None: + children = self.decomposition_function(comb_class) + return DivideByN( + comb_class, + children, + -len(self.already_placed_cells(comb_class)), + self.extra_parameters(comb_class, children), + ) + + def reverse_constructor( + self, + idx: int, + comb_class: Tiling, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> ReverseDivideByN: + if children is None: + children = self.decomposition_function(comb_class) + return ReverseDivideByN( + comb_class, + children, + idx, + -len(self.already_placed_cells(comb_class)), + self.extra_parameters(comb_class, children), + ) + + def backward_map( + self, + comb_class: Tiling, + objs: Tuple[Optional[GriddedPerm], ...], + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Iterator[GriddedPerm]: + if children is None: + children = self.decomposition_function(comb_class) + raise NotImplementedError + + def forward_map( + self, + comb_class: Tiling, + obj: GriddedPerm, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Tuple[Optional[GriddedPerm], ...]: + if children is None: + children = self.decomposition_function(comb_class) + raise NotImplementedError + + def extra_parameters( + self, + comb_class: Tiling, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Tuple[Dict[str, str], ...]: + if children is None: + children = self.decomposition_function(comb_class) + cells = comb_class.active_cells - self.already_placed_cells(comb_class) + algo = RequirementPlacement(comb_class, True, True) + res = [] + for child, cell in zip(children, sorted(cells)): + params: Dict[str, str] = {} + mapped_assumptions = [ + child.forward_map.map_assumption(ass).avoiding(child.obstructions) + for ass in algo.stretched_assumptions(cell) + ] + for ass, mapped_ass in zip(comb_class.assumptions, mapped_assumptions): + if mapped_ass.gps: + params[ + comb_class.get_assumption_parameter(ass) + ] = child.get_assumption_parameter(mapped_ass) + res.append(params) + return tuple(res) + + @classmethod + def from_dict(cls, d: dict) -> "PointingStrategy": + return cls(**d) diff --git a/tilings/strategies/unfusion.py b/tilings/strategies/unfusion.py new file mode 100644 index 00000000..62452a30 --- /dev/null +++ b/tilings/strategies/unfusion.py @@ -0,0 +1,317 @@ +from collections import Counter +from itertools import chain +from typing import Callable, Dict, Iterator, List, Optional, Tuple + +import sympy + +from comb_spec_searcher import Strategy +from comb_spec_searcher.strategies import Constructor +from comb_spec_searcher.strategies.constructor import Complement, DisjointUnion +from comb_spec_searcher.strategies.strategy import StrategyFactory +from comb_spec_searcher.typing import ( + CombinatorialClassType, + CombinatorialObjectType, + Parameters, + SubObjects, + SubRecs, + SubSamplers, + SubTerms, + Terms, +) +from tilings import GriddedPerm, Tiling +from tilings.algorithms import Fusion + + +class DivideByN(DisjointUnion[CombinatorialClassType, CombinatorialObjectType]): + """ + A constructor that works as disjoint union + but divides the values by n + shift. + """ + + def __init__( + self, + parent: CombinatorialClassType, + children: Tuple[CombinatorialClassType, ...], + shift: int, + extra_parameters: Optional[Tuple[Dict[str, str], ...]] = None, + ): + self.shift = shift + self.initial_conditions = { + n: parent.get_terms(n) for n in range(1 - self.shift) + } + super().__init__(parent, children, extra_parameters) + + def get_equation( + self, lhs_func: sympy.Function, rhs_funcs: Tuple[sympy.Function, ...] + ) -> sympy.Eq: + # TODO: d/dx [ x**shift * lhsfun ] / x**(shift - 1) = A + B + ... + raise NotImplementedError + + def get_terms( + self, parent_terms: Callable[[int], Terms], subterms: SubTerms, n: int + ) -> Terms: + if n + self.shift <= 0: + return self.initial_conditions[n] + terms = super().get_terms(parent_terms, subterms, n) + return Counter({key: value // (n + self.shift) for key, value in terms.items()}) + + def get_sub_objects( + self, subobjs: SubObjects, n: int + ) -> Iterator[ + Tuple[Parameters, Tuple[List[Optional[CombinatorialObjectType]], ...]] + ]: + raise NotImplementedError + + def random_sample_sub_objects( + self, + parent_count: int, + subsamplers: SubSamplers, + subrecs: SubRecs, + n: int, + **parameters: int, + ) -> Tuple[Optional[CombinatorialObjectType], ...]: + raise NotImplementedError + + @staticmethod + def get_eq_symbol() -> str: + return "?" + + def __str__(self): + return "divide by n" + + def equiv( + self, other: Constructor, data: Optional[object] = None + ) -> Tuple[bool, Optional[object]]: + raise NotImplementedError + + +class ReverseDivideByN(Complement[CombinatorialClassType, CombinatorialObjectType]): + """ + The complement version of DivideByN. + It works as Complement, but multiplies by n + shift the original left hand side. + """ + + def __init__( + self, + parent: CombinatorialClassType, + children: Tuple[CombinatorialClassType, ...], + idx: int, + shift: int, + extra_parameters: Optional[Tuple[Dict[str, str], ...]] = None, + ): + self.shift = shift + self.initial_conditions = { + n: children[idx].get_terms(n) for n in range(1 - self.shift) + } + super().__init__(parent, children, idx, extra_parameters) + + def get_equation( + self, lhs_func: sympy.Function, rhs_funcs: Tuple[sympy.Function, ...] + ) -> sympy.Eq: + # TODO: rhs_funcs[0] should be a derivative etc, see DivideByN.get_equation. + raise NotImplementedError + + def get_terms( + self, parent_terms: Callable[[int], Terms], subterms: SubTerms, n: int + ) -> Terms: + if n + self.shift <= 0: + return self.initial_conditions[n] + parent_terms_mapped: Terms = Counter() + for param, value in subterms[0](n).items(): + if value: + # This is the only change from complement + N = n + self.shift + assert N > 0 + parent_terms_mapped[self._parent_param_map(param)] += value * N + children_terms = subterms[1:] + for child_terms, param_map in zip(children_terms, self._children_param_maps): + # we subtract from total + for param, value in child_terms(n).items(): + mapped_param = self._parent_param_map(param_map(param)) + parent_terms_mapped[mapped_param] -= value + assert parent_terms_mapped[mapped_param] >= 0 + if parent_terms_mapped[mapped_param] == 0: + parent_terms_mapped.pop(mapped_param) + + return parent_terms_mapped + + def __str__(self): + return "reverse divide by n" + + +class UnfusionColumnStrategy(Strategy[Tiling, GriddedPerm]): + def __init__( + self, + ignore_parent: bool = False, + inferrable: bool = True, + possibly_empty: bool = True, + workable: bool = True, + cols: bool = True, + ): + self.cols = cols + super().__init__(ignore_parent, inferrable, possibly_empty, workable) + + def can_be_equivalent(self) -> bool: + return False + + def is_two_way(self, comb_class: Tiling) -> bool: + return True + + def is_reversible(self, comb_class: Tiling) -> bool: + return True + + def shifts( + self, + comb_class: Tiling, + children: Optional[Tuple[Tiling, ...]], + ) -> Tuple[int, ...]: + if children is None: + children = self.decomposition_function(comb_class) + return tuple(0 for _ in range(self.width(comb_class))) + + def width(self, comb_class: Tiling) -> int: + if self.cols: + return comb_class.dimensions[0] + return comb_class.dimensions[1] + + def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling, ...]: + res = [] + for idx in range(self.width(comb_class)): + if self.cols: + algo = Fusion(comb_class, col_idx=idx) + else: + algo = Fusion(comb_class, row_idx=idx) + obs = chain( + *[algo.unfuse_gridded_perm(ob) for ob in comb_class.obstructions] + ) + reqs = [ + [gp for req_gp in req_list for gp in algo.unfuse_gridded_perm(req_gp)] + for req_list in comb_class.requirements + ] + ass = [ + ass.__class__( + [ + gp + for ass_gp in ass.gps + for gp in algo.unfuse_gridded_perm(ass_gp) + ] + ) + for ass in comb_class.assumptions + ] + res.append(Tiling(obs, reqs, ass)) + return tuple(res) + + def formal_step(self) -> str: + if self.cols: + return "unfuse columns strategy" + return "unfuse rows strategy" + + def constructor( + self, + comb_class: Tiling, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> DivideByN: + if children is None: + children = self.decomposition_function(comb_class) + return DivideByN( + comb_class, + children, + len(children), + self.extra_parameters(comb_class, children), + ) + + def reverse_constructor( + self, + idx: int, + comb_class: Tiling, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> ReverseDivideByN: + if children is None: + children = self.decomposition_function(comb_class) + return ReverseDivideByN( + comb_class, + children, + idx, + len(children), + self.extra_parameters(comb_class, children), + ) + + def backward_map( + self, + comb_class: Tiling, + objs: Tuple[Optional[GriddedPerm], ...], + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Iterator[GriddedPerm]: + if children is None: + children = self.decomposition_function(comb_class) + raise NotImplementedError + + def forward_map( + self, + comb_class: Tiling, + obj: GriddedPerm, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Tuple[Optional[GriddedPerm], ...]: + if children is None: + children = self.decomposition_function(comb_class) + raise NotImplementedError + + def extra_parameters( + self, + comb_class: Tiling, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Tuple[Dict[str, str], ...]: + if children is None: + children = self.decomposition_function(comb_class) + res = [] + for idx in range(self.width(comb_class)): + if self.cols: + algo = Fusion(comb_class, col_idx=idx) + else: + algo = Fusion(comb_class, row_idx=idx) + params: Dict[str, str] = {} + for ass in comb_class.assumptions: + mapped_ass = ass.__class__( + [ + children[idx].forward_map.map_gp(gp) + for ass_gp in ass.gps + for gp in algo.unfuse_gridded_perm(ass_gp) + ] + ) + params[comb_class.get_assumption_parameter(ass)] = children[ + idx + ].get_assumption_parameter(mapped_ass) + res.append(params) + return tuple(res) + + @classmethod + def from_dict(cls, d: dict) -> "UnfusionColumnStrategy": + return cls(**d) + + +class UnfusionRowStrategy(UnfusionColumnStrategy): + def __init__( + self, + ignore_parent: bool = False, + inferrable: bool = True, + possibly_empty: bool = True, + workable: bool = True, + cols: bool = False, + ): + super().__init__(ignore_parent, inferrable, possibly_empty, workable, cols) + + +class UnfusionFactory(StrategyFactory[Tiling]): + def __call__(self, comb_class: Tiling) -> Iterator[UnfusionColumnStrategy]: + yield UnfusionColumnStrategy() + yield UnfusionRowStrategy() + + def __str__(self) -> str: + return "unfusion strategy" + + def __repr__(self) -> str: + return self.__class__.__name__ + "()" + + @classmethod + def from_dict(cls, d: dict) -> "UnfusionFactory": + return cls() diff --git a/tilings/strategy_pack.py b/tilings/strategy_pack.py index 33b4c973..d8f04e58 100644 --- a/tilings/strategy_pack.py +++ b/tilings/strategy_pack.py @@ -393,6 +393,14 @@ def kitchen_sinkify( # pylint: disable=R0912 if tracked: ks_pack = ks_pack.make_tracked() + ks_pack.expansion_strats = ks_pack.expansion_strats + ( + ( + strat.PointingStrategy(), + strat.UnfusionFactory(), + strat.DisjointFusionFactory(), + ), + ) + ks_pack.name += "_kitchen_sink" return ks_pack diff --git a/tilings/tiling.py b/tilings/tiling.py index b6427599..bccd36a3 100644 --- a/tilings/tiling.py +++ b/tilings/tiling.py @@ -756,7 +756,7 @@ def only_cell_in_row(self, cell: Cell) -> bool: return sum(1 for (x, y) in self.active_cells if y == cell[1]) == 1 def only_cell_in_row_and_col(self, cell: Cell) -> bool: - """Checks if the cell is the only active cell in the row.""" + """Checks if the cell is the only active cell in the row and column.""" return ( sum(1 for (x, y) in self.active_cells if y == cell[1] or x == cell[0]) == 1 ) From 1a29f91c30c079a80ffb903f802662b538dd4ff2 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Fri, 6 May 2022 17:29:37 +0200 Subject: [PATCH 039/100] reversible if parent has all the assumptions for interleaving (#456) * reversible if parent has all the assumptions for interleaving * unused import --- tilings/strategies/factor.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tilings/strategies/factor.py b/tilings/strategies/factor.py index 41845054..ae5471b1 100644 --- a/tilings/strategies/factor.py +++ b/tilings/strategies/factor.py @@ -244,6 +244,12 @@ def __init__( self.tracked = tracked self.cols, self.rows = self.interleaving_rows_and_cols(self.partition) + def is_two_way(self, comb_class: Tiling) -> bool: # type: ignore + return self.is_reversible(comb_class) + + def is_reversible(self, comb_class: Tiling) -> bool: # type: ignore + return not bool(self.assumptions_to_add(comb_class)) + def formal_step(self) -> str: return "interleaving " + super().formal_step() From f0374ca830d634cc3efb5080d2b5d6a0a4acfaa3 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Fri, 6 May 2022 17:30:07 +0200 Subject: [PATCH 040/100] small bugs in interleaving factors (#458) * fix the param when generating objects * remove empty cells when mapping assumption in interleaving factors * remove outdated todo --- tilings/strategies/factor.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/tilings/strategies/factor.py b/tilings/strategies/factor.py index ae5471b1..694cc83b 100644 --- a/tilings/strategies/factor.py +++ b/tilings/strategies/factor.py @@ -23,7 +23,14 @@ StrategyFactory, ) from comb_spec_searcher.exception import StrategyDoesNotApply -from comb_spec_searcher.typing import SubRecs, SubSamplers, SubTerms, Terms +from comb_spec_searcher.typing import ( + Parameters, + SubObjects, + SubRecs, + SubSamplers, + SubTerms, + Terms, +) from permuta import Perm from tilings import GriddedPerm, Tiling from tilings.algorithms import ( @@ -86,8 +93,9 @@ def extra_parameters( comb_class.extra_parameters, comb_class.assumptions ): for idx, child in enumerate(children): - # TODO: consider skew/sum - new_assumption = child.forward_map.map_assumption(assumption) + new_assumption = child.forward_map.map_assumption(assumption).avoiding( + child.obstructions + ) if new_assumption.gps: child_var = child.get_assumption_parameter(new_assumption) extra_parameters[idx][parent_var] = child_var @@ -221,6 +229,14 @@ def get_terms( return new_terms return interleaved_terms + def get_sub_objects( + self, subobjs: SubObjects, n: int + ) -> Iterator[Tuple[Parameters, Tuple[List[Optional[GriddedPerm]], ...]]]: + for param, objs in super().get_sub_objects(subobjs, n): + if self.insertion_constructor: + param = self.insertion_constructor.child_param_map(param) + yield param, objs + def random_sample_sub_objects( self, parent_count: int, From be81c6b170c4d295b54c38d6bfb79a314f61ebd7 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Mon, 9 May 2022 15:08:14 +0200 Subject: [PATCH 041/100] count component behave like disjoint in shift algo (#460) --- tilings/algorithms/locally_factorable_shift.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tilings/algorithms/locally_factorable_shift.py b/tilings/algorithms/locally_factorable_shift.py index cb95b136..08ac165a 100644 --- a/tilings/algorithms/locally_factorable_shift.py +++ b/tilings/algorithms/locally_factorable_shift.py @@ -14,6 +14,7 @@ from comb_spec_searcher.typing import CSSstrategy from permuta import Av, Perm from tilings import GriddedPerm, Tiling +from tilings.strategies.detect_components import CountComponent __all__ = ["shift_from_spec"] @@ -87,7 +88,9 @@ def traverse(t: Tiling) -> Optional[int]: res = 0 elif isinstance(rule, VerificationRule): res = shift_from_spec(tiling, rule.pack(), symmetries) - elif isinstance(rule, Rule) and isinstance(rule.constructor, DisjointUnion): + elif isinstance(rule, Rule) and isinstance( + rule.constructor, (DisjointUnion, CountComponent) + ): children_reliance = [traverse(c) for c in rule.children] res = min([r for r in children_reliance if r is not None], default=None) elif isinstance(rule, Rule) and isinstance(rule.constructor, CartesianProduct): From 6a99c28a9deb5ccc336d4a65a07033aba973ce64 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Mon, 9 May 2022 15:08:32 +0200 Subject: [PATCH 042/100] removing the disjoint fusion strategy (#459) --- CHANGELOG.md | 2 - tests/strategies/test_encoding.py | 10 +-- tilings/strategies/__init__.py | 3 +- tilings/strategies/fusion/__init__.py | 3 - tilings/strategies/fusion/disjoint_fusion.py | 95 -------------------- tilings/strategy_pack.py | 1 - 6 files changed, 2 insertions(+), 112 deletions(-) delete mode 100644 tilings/strategies/fusion/disjoint_fusion.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 86b8d5d7..75a96949 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,8 +47,6 @@ swapped around a fusable row or column. - `PointingStrategy` that places points directionless in non-point cells. This is a non-productive strategy so should be used with `RuleDBForest`. - `UnfusionFactory` that unfuses either all the rows or columns. Also non-productive. -- `DisjointFusionFactory` that produces disjoint fusion rules that come from placing - points directionless. ### Fixed diff --git a/tests/strategies/test_encoding.py b/tests/strategies/test_encoding.py index fdcb517b..3de2f852 100644 --- a/tests/strategies/test_encoding.py +++ b/tests/strategies/test_encoding.py @@ -17,7 +17,6 @@ ComponentFusionFactory, DatabaseVerificationStrategy, DeflationFactory, - DisjointFusionFactory, DummyStrategy, ElementaryVerificationStrategy, EmptyCellInferralFactory, @@ -63,11 +62,7 @@ FactorWithInterleavingStrategy, FactorWithMonotoneInterleavingStrategy, ) -from tilings.strategies.fusion import ( - ComponentFusionStrategy, - DisjointFusionStrategy, - FusionStrategy, -) +from tilings.strategies.fusion import ComponentFusionStrategy, FusionStrategy from tilings.strategies.monotone_sliding import GeneralizedSlidingStrategy from tilings.strategies.obstruction_inferral import ObstructionInferralStrategy from tilings.strategies.point_jumping import ( @@ -481,9 +476,6 @@ def indices_and_row(strategy): UnfusionRowStrategy(), UnfusionColumnStrategy(), UnfusionFactory(), - DisjointFusionFactory(), - DisjointFusionStrategy(row_idx=1), - DisjointFusionStrategy(col_idx=1), ] ) diff --git a/tilings/strategies/__init__.py b/tilings/strategies/__init__.py index 2c3c2b43..35bf43c7 100644 --- a/tilings/strategies/__init__.py +++ b/tilings/strategies/__init__.py @@ -10,7 +10,7 @@ SubclassVerificationFactory, ) from .factor import FactorFactory -from .fusion import ComponentFusionFactory, DisjointFusionFactory, FusionFactory +from .fusion import ComponentFusionFactory, FusionFactory from .monotone_sliding import MonotoneSlidingFactory from .obstruction_inferral import ( EmptyCellInferralFactory, @@ -91,7 +91,6 @@ "PointingStrategy", # Fusion "ComponentFusionFactory", - "DisjointFusionFactory", "FusionFactory", "UnfusionColumnStrategy", "UnfusionRowStrategy", diff --git a/tilings/strategies/fusion/__init__.py b/tilings/strategies/fusion/__init__.py index c4664e3e..ff2ec87b 100644 --- a/tilings/strategies/fusion/__init__.py +++ b/tilings/strategies/fusion/__init__.py @@ -1,13 +1,10 @@ from .component import ComponentFusionFactory, ComponentFusionStrategy from .constructor import FusionConstructor -from .disjoint_fusion import DisjointFusionFactory, DisjointFusionStrategy from .fusion import FusionFactory, FusionRule, FusionStrategy __all__ = [ "ComponentFusionFactory", "ComponentFusionStrategy", - "DisjointFusionFactory", - "DisjointFusionStrategy", "FusionFactory", "FusionStrategy", "FusionRule", diff --git a/tilings/strategies/fusion/disjoint_fusion.py b/tilings/strategies/fusion/disjoint_fusion.py deleted file mode 100644 index e35164bc..00000000 --- a/tilings/strategies/fusion/disjoint_fusion.py +++ /dev/null @@ -1,95 +0,0 @@ -from typing import Iterator, Optional, Tuple - -from comb_spec_searcher import DisjointUnionStrategy, StrategyFactory -from comb_spec_searcher.exception import StrategyDoesNotApply -from permuta.misc import DIR_NONE -from tilings import GriddedPerm, Tiling -from tilings.algorithms import Fusion - - -class DisjointFusionStrategy(DisjointUnionStrategy[Tiling, GriddedPerm]): - def __init__(self, row_idx=None, col_idx=None): - self.col_idx = col_idx - self.row_idx = row_idx - super().__init__( - ignore_parent=False, inferrable=True, possibly_empty=False, workable=True - ) - - def fusion_algorithm(self, tiling: Tiling) -> Fusion: - return Fusion(tiling, row_idx=self.row_idx, col_idx=self.col_idx, tracked=False) - - def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling, ...]: - algo = self.fusion_algorithm(comb_class) - if algo.fusable(): - fused_tiling = algo.fused_tiling() - point_count = sum(algo.min_left_right_points()) - if point_count == 0: - res = [fused_tiling] - elif point_count == 1: - res = [] - else: - raise StrategyDoesNotApply("Can't handle case with both sides positive") - for gp in algo.new_assumption().gps: - cell = gp.pos[0] - res.append(fused_tiling.place_point_in_cell(cell, DIR_NONE)) - return tuple(res) - - def formal_step(self) -> str: - if self.row_idx is not None: - return f"fuse rows {self.row_idx} and {self.row_idx + 1}" - return f"fuse cols {self.col_idx} and {self.col_idx + 1}" - - def backward_map( - self, - comb_class: Tiling, - objs: Tuple[Optional[GriddedPerm], ...], - children: Optional[Tuple[Tiling, ...]] = None, - ) -> Iterator[GriddedPerm]: - if children is None: - children = self.decomposition_function(comb_class) - raise NotImplementedError - - def forward_map( - self, - comb_class: Tiling, - obj: GriddedPerm, - children: Optional[Tuple[Tiling, ...]] = None, - ) -> Tuple[Optional[GriddedPerm], ...]: - if children is None: - children = self.decomposition_function(comb_class) - raise NotImplementedError - - def to_jsonable(self) -> dict: - d = super().to_jsonable() - d["row"] = self.row_idx - d["col"] = self.col_idx - return d - - @classmethod - def from_dict(cls, d: dict) -> "DisjointFusionStrategy": - return cls(row_idx=d["row"], col_idx=d["col"]) - - def __repr__(self) -> str: - return ( - self.__class__.__name__ - + f"(row_idx={self.row_idx}, col_idx={self.col_idx})" - ) - - -class DisjointFusionFactory(StrategyFactory[Tiling]): - def __call__(self, comb_class: Tiling) -> Iterator[DisjointFusionStrategy]: - cols, rows = comb_class.dimensions - for row_idx in range(rows - 1): - yield DisjointFusionStrategy(row_idx=row_idx) - for col_idx in range(cols - 1): - yield DisjointFusionStrategy(col_idx=col_idx) - - def __str__(self) -> str: - return "disjoint fusion strategy" - - def __repr__(self) -> str: - return self.__class__.__name__ + "()" - - @classmethod - def from_dict(cls, d: dict) -> "DisjointFusionFactory": - return cls() diff --git a/tilings/strategy_pack.py b/tilings/strategy_pack.py index d8f04e58..75239186 100644 --- a/tilings/strategy_pack.py +++ b/tilings/strategy_pack.py @@ -397,7 +397,6 @@ def kitchen_sinkify( # pylint: disable=R0912 ( strat.PointingStrategy(), strat.UnfusionFactory(), - strat.DisjointFusionFactory(), ), ) From 71e2aa829052e99d2d736434f571927b7efad289 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Mon, 9 May 2022 16:46:14 +0200 Subject: [PATCH 043/100] remove the remove_strategy defined on css (#461) --- CHANGELOG.md | 1 - tilings/strategy_pack.py | 22 ---------------------- 2 files changed, 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75a96949..2de4cd26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,7 +32,6 @@ swapped around a fusable row or column. - `PositiveCorroborationFactory` that inserts into cells which if positive makes another cell empty. Also, the `PointCorroborationFactory`, which does this for point or empty cells which is added to most packs. -- `TileScopePack.remove_strategy` method that removes a strategy from a pack. - `TargetedCellInsertionFactory` which inserts factors of gridded perms if it can lead to factoring out a verified sub tiling. - `ComponentVerificationStrategy` which is added to component fusion packs. diff --git a/tilings/strategy_pack.py b/tilings/strategy_pack.py index 75239186..c635b3fb 100644 --- a/tilings/strategy_pack.py +++ b/tilings/strategy_pack.py @@ -280,28 +280,6 @@ def add_all_symmetry(self) -> "TileScopePack": raise ValueError("Symmetries already turned on.") return super().add_symmetry(strat.SymmetriesFactory(), "symmetries") - def remove_strategy(self, strategy: CSSstrategy): - """remove the strategy from the pack""" - d = strategy.to_jsonable() - - def replace_list(strats): - """Return a new list with the replaced fusion strat.""" - res = [] - for strategy in strats: - if not strategy.to_jsonable() == d: - res.append(strategy) - return res - - return self.__class__( - ver_strats=replace_list(self.ver_strats), - inferral_strats=replace_list(self.inferral_strats), - initial_strats=replace_list(self.initial_strats), - expansion_strats=list(map(replace_list, self.expansion_strats)), - name=self.name, - symmetries=replace_list(self.symmetries), - iterative=self.iterative, - ) - def kitchen_sinkify( # pylint: disable=R0912 self, short_obs_len: int, obs_inferral_len: int, tracked: bool ) -> "TileScopePack": From 8683ea84867110979187cb637242e4c99ed6b38d Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 10 May 2022 17:41:14 +0200 Subject: [PATCH 044/100] add assumption reversible when assumption covers whole tiling (#463) --- CHANGELOG.md | 2 ++ tilings/strategies/assumption_insertion.py | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2de4cd26..52bdb8f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -76,6 +76,8 @@ swapped around a fusable row or column. - `GuidedSearcher` expands every symmetry - `TileScopePack.pattern_placements` factors as an initial strategy. - `is_component` method of assumptions updated to consider cell decomposition +- `AddAssumptionsStrategy.is_reverible` is now True when the assumption covers the + whole tiling. ### Removed - `AddInterleavingAssumptionsFactory`. The factor strategy now adds the relevant diff --git a/tilings/strategies/assumption_insertion.py b/tilings/strategies/assumption_insertion.py index 21073a98..78d3faa0 100644 --- a/tilings/strategies/assumption_insertion.py +++ b/tilings/strategies/assumption_insertion.py @@ -19,7 +19,7 @@ Terms, ) from tilings import GriddedPerm, Tiling -from tilings.assumptions import TrackingAssumption +from tilings.assumptions import ComponentAssumption, TrackingAssumption Cell = Tuple[int, int] @@ -145,7 +145,11 @@ def is_two_way(comb_class: Tiling): @staticmethod def is_reversible(comb_class: Tiling) -> bool: - return False + return all( + not isinstance(assumption, ComponentAssumption) + and frozenset(gp.pos[0] for gp in assumption.gps) == comb_class.active_cells + for assumption in comb_class.assumptions + ) @staticmethod def shifts( From ce22e5428842354f73bdd37ff337e4b4ca45402f Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 10 May 2022 18:47:37 +0200 Subject: [PATCH 045/100] a strategy for placing points of assumptions (#462) * a strategy for placing points of assumptions * make sure to do this on point tracking assumptions --- CHANGELOG.md | 5 +- tests/strategies/test_encoding.py | 10 ++ tilings/strategies/__init__.py | 12 +- tilings/strategies/pointing.py | 213 +++++++++++++++++++++++++++++- tilings/strategy_pack.py | 1 + 5 files changed, 231 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52bdb8f7..2aaf9a04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,8 +43,9 @@ swapped around a fusable row or column. - `FactorWithInterleavingStrategy.backward_map` so you can now generate permutation from specifications using interleaving factors. - `DummyStrategy` that gives a quick template for making strategies. -- `PointingStrategy` that places points directionless in non-point cells. This is - a non-productive strategy so should be used with `RuleDBForest`. +- `PointingStrategy` and `AssumptionPointingFactory` that place points directionless + in non-point cells. This are a non-productive strategy so should be used with + `RuleDBForest`. - `UnfusionFactory` that unfuses either all the rows or columns. Also non-productive. diff --git a/tests/strategies/test_encoding.py b/tests/strategies/test_encoding.py index 3de2f852..1af8db5e 100644 --- a/tests/strategies/test_encoding.py +++ b/tests/strategies/test_encoding.py @@ -11,6 +11,7 @@ from tilings.strategies import ( AllPlacementsFactory, AssumptionAndPointJumpingFactory, + AssumptionPointingFactory, BasicVerificationStrategy, CellInsertionFactory, CellReductionFactory, @@ -70,6 +71,7 @@ AssumptionJumpingStrategy, PointJumpingStrategy, ) +from tilings.strategies.pointing import AssumptionPointingStrategy from tilings.strategies.rearrange_assumption import RearrangeAssumptionStrategy from tilings.strategies.requirement_insertion import RequirementInsertionStrategy from tilings.strategies.requirement_placement import RequirementPlacementStrategy @@ -471,12 +473,20 @@ def indices_and_row(strategy): + [TargetedCellInsertionFactory()] + ignoreparent(SubobstructionInsertionFactory) + [ + AssumptionPointingFactory(), DummyStrategy(), PointingStrategy(), UnfusionRowStrategy(), UnfusionColumnStrategy(), UnfusionFactory(), ] + + [ + AssumptionPointingStrategy( + TrackingAssumption( + [GriddedPerm((0,), [(0, 0)]), GriddedPerm((0,), [(1, 0)])] + ) + ) + ] ) __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) diff --git a/tilings/strategies/__init__.py b/tilings/strategies/__init__.py index 35bf43c7..870515ef 100644 --- a/tilings/strategies/__init__.py +++ b/tilings/strategies/__init__.py @@ -19,7 +19,7 @@ SubobstructionInferralFactory, ) from .point_jumping import AssumptionAndPointJumpingFactory -from .pointing import PointingStrategy +from .pointing import AssumptionPointingFactory, PointingStrategy from .rearrange_assumption import RearrangeAssumptionFactory from .requirement_insertion import ( CellInsertionFactory, @@ -81,6 +81,12 @@ "FactorFactory", # Deflation "DeflationFactory", + # Derivatives + "AssumptionPointingFactory", + "PointingStrategy", + "UnfusionColumnStrategy", + "UnfusionRowStrategy", + "UnfusionFactory", # Equivalence "MonotoneSlidingFactory", "PatternPlacementFactory", @@ -88,13 +94,9 @@ # Experimental "AssumptionAndPointJumpingFactory", "DummyStrategy", - "PointingStrategy", # Fusion "ComponentFusionFactory", "FusionFactory", - "UnfusionColumnStrategy", - "UnfusionRowStrategy", - "UnfusionFactory", # Inferral "EmptyCellInferralFactory", "ObstructionInferralFactory", diff --git a/tilings/strategies/pointing.py b/tilings/strategies/pointing.py index 272e8a49..9607dbe2 100644 --- a/tilings/strategies/pointing.py +++ b/tilings/strategies/pointing.py @@ -2,13 +2,19 @@ The directionless point placement strategy that is counted by the 'pointing' constructor. """ -from typing import Dict, FrozenSet, Iterator, Optional, Tuple +from collections import Counter +from typing import Callable, Dict, FrozenSet, Iterator, Optional, Tuple from comb_spec_searcher import Strategy from comb_spec_searcher.exception import StrategyDoesNotApply +from comb_spec_searcher.strategies.constructor.disjoint import DisjointUnion +from comb_spec_searcher.strategies.strategy import StrategyFactory +from comb_spec_searcher.typing import CombinatorialClassType, SubTerms, Terms from permuta.misc import DIR_NONE from tilings import GriddedPerm, Tiling from tilings.algorithms import RequirementPlacement +from tilings.assumptions import ComponentAssumption, TrackingAssumption +from tilings.strategies.obstruction_inferral import ObstructionInferralStrategy from tilings.tiling import Cell from .unfusion import DivideByN, ReverseDivideByN @@ -42,8 +48,11 @@ def already_placed_cells(comb_class: Tiling) -> FrozenSet[Cell]: if comb_class.only_cell_in_row_and_col(cell) ) + def cells_to_place(self, comb_class: Tiling) -> FrozenSet[Cell]: + return comb_class.active_cells - self.already_placed_cells(comb_class) + def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling, ...]: - cells = comb_class.active_cells - self.already_placed_cells(comb_class) + cells = self.cells_to_place(comb_class) if cells: return tuple( comb_class.place_point_in_cell(cell, DIR_NONE) for cell in sorted(cells) @@ -110,7 +119,7 @@ def extra_parameters( ) -> Tuple[Dict[str, str], ...]: if children is None: children = self.decomposition_function(comb_class) - cells = comb_class.active_cells - self.already_placed_cells(comb_class) + cells = self.cells_to_place(comb_class) algo = RequirementPlacement(comb_class, True, True) res = [] for child, cell in zip(children, sorted(cells)): @@ -130,3 +139,201 @@ def extra_parameters( @classmethod def from_dict(cls, d: dict) -> "PointingStrategy": return cls(**d) + + +class DivideByK(DivideByN): + """ + A constructor that works as disjoint union + but divides the values by k + shift. + """ + + def __init__( + self, + parent: CombinatorialClassType, + children: Tuple[CombinatorialClassType, ...], + shift: int, + parameter: str, + extra_parameters: Optional[Tuple[Dict[str, str], ...]] = None, + ): + self.parameter = parameter + self.division_index = parent.extra_parameters.index(parameter) + super().__init__(parent, children, shift, extra_parameters) + + def get_terms( + self, parent_terms: Callable[[int], Terms], subterms: SubTerms, n: int + ) -> Terms: + if n + self.shift <= 0: + return self.initial_conditions[n] + terms = DisjointUnion.get_terms(self, parent_terms, subterms, n) + return Counter( + { + key: value // (key[self.division_index] + self.shift) + if (key[self.division_index] + self.shift) != 0 + else value + for key, value in terms.items() + } + ) + + def __str__(self): + return f"divide by {self.parameter}" + + +class ReverseDivideByK(ReverseDivideByN): + """ + The complement version of DivideByK. + It works as Complement, but multiplies by k + shift the original left hand side. + """ + + def __init__( + self, + parent: CombinatorialClassType, + children: Tuple[CombinatorialClassType, ...], + idx: int, + shift: int, + parameter: str, + extra_parameters: Optional[Tuple[Dict[str, str], ...]] = None, + ): + self.parameter = parameter + self.division_index = parent.extra_parameters.index(parameter) + super().__init__(parent, children, idx, shift, extra_parameters) + + def get_terms( + self, parent_terms: Callable[[int], Terms], subterms: SubTerms, n: int + ) -> Terms: + if n + self.shift <= 0: + return self.initial_conditions[n] + parent_terms_mapped: Terms = Counter() + for param, value in subterms[0](n).items(): + if value: + # This is the only change from complement + K = param[self.division_index] + self.shift + assert K >= 0 + if K == 0: + parent_terms_mapped[self._parent_param_map(param)] += value + else: + parent_terms_mapped[self._parent_param_map(param)] += value * K + children_terms = subterms[1:] + for child_terms, param_map in zip(children_terms, self._children_param_maps): + # we subtract from total + for param, value in child_terms(n).items(): + mapped_param = self._parent_param_map(param_map(param)) + parent_terms_mapped[mapped_param] -= value + assert parent_terms_mapped[mapped_param] >= 0 + if parent_terms_mapped[mapped_param] == 0: + parent_terms_mapped.pop(mapped_param) + + return parent_terms_mapped + + def __str__(self): + return f"reverse divide by {self.parameter}" + + +class AssumptionPointingStrategy(PointingStrategy): + def __init__( + self, + assumption: TrackingAssumption, + ignore_parent: bool = False, + inferrable: bool = True, + possibly_empty: bool = True, + workable: bool = True, + ): + self.assumption = assumption + assert not isinstance(assumption, ComponentAssumption) + self.cells = frozenset(gp.pos[0] for gp in assumption.gps) + super().__init__(ignore_parent, inferrable, possibly_empty, workable) + + def cells_to_place(self, comb_class: Tiling) -> FrozenSet[Cell]: + return super().cells_to_place(comb_class).intersection(self.cells) + + def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling, ...]: + if self.assumption not in comb_class.assumptions: + raise StrategyDoesNotApply("The assumption is not on tiling") + cells = self.cells_to_place(comb_class) + if cells: + return ( + comb_class.add_obstructions( + [GriddedPerm.point_perm(cell) for cell in cells] + ), + ) + tuple( + comb_class.place_point_in_cell(cell, DIR_NONE) for cell in sorted(cells) + ) + raise StrategyDoesNotApply("The assumption is just point cells!") + + def formal_step(self) -> str: + return super().formal_step() + f" in cells {set(self.cells)}~~~" + + def constructor( + self, + comb_class: Tiling, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> DivideByN: + if children is None: + children = self.decomposition_function(comb_class) + return DivideByK( + comb_class, + children, + -len(self.cells.intersection(self.already_placed_cells(comb_class))), + comb_class.get_assumption_parameter(self.assumption), + self.extra_parameters(comb_class, children), + ) + + def reverse_constructor( + self, + idx: int, + comb_class: Tiling, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> ReverseDivideByN: + if children is None: + children = self.decomposition_function(comb_class) + return ReverseDivideByK( + comb_class, + children, + idx, + -len(self.cells.intersection(self.already_placed_cells(comb_class))), + comb_class.get_assumption_parameter(self.assumption), + self.extra_parameters(comb_class, children), + ) + + def extra_parameters( + self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None + ) -> Tuple[Dict[str, str], ...]: + if children is None: + children = self.decomposition_function(comb_class) + empty_params = ObstructionInferralStrategy( + [GriddedPerm.point_perm(cell) for cell in self.cells_to_place(comb_class)] + ).extra_parameters(comb_class) + return empty_params + super().extra_parameters(comb_class, children[1:]) + + def to_jsonable(self) -> dict: + d = super().to_jsonable() + d["assumption"] = self.assumption.to_jsonable() + return d + + @classmethod + def from_dict(cls, d: dict) -> "AssumptionPointingStrategy": + assumption = TrackingAssumption.from_dict(d.pop("assumption")) + return cls(assumption=assumption, **d) + + def __repr__(self) -> str: + return ( + self.__class__.__name__ + + f"({repr(self.assumption)}, {self.ignore_parent}, " + f"{self.inferrable}, {self.possibly_empty}, {self.workable})" + ) + + +class AssumptionPointingFactory(StrategyFactory[Tiling]): + def __call__(self, comb_class: Tiling) -> Iterator[AssumptionPointingStrategy]: + for assumption in comb_class.assumptions: + if not isinstance(assumption, ComponentAssumption): + yield AssumptionPointingStrategy(assumption) + + def __str__(self) -> str: + return "assumption pointing strategy" + + def __repr__(self) -> str: + return self.__class__.__name__ + "()" + + @classmethod + def from_dict(cls, d: dict) -> "AssumptionPointingFactory": + return cls() diff --git a/tilings/strategy_pack.py b/tilings/strategy_pack.py index c635b3fb..6ca709b7 100644 --- a/tilings/strategy_pack.py +++ b/tilings/strategy_pack.py @@ -373,6 +373,7 @@ def kitchen_sinkify( # pylint: disable=R0912 ks_pack.expansion_strats = ks_pack.expansion_strats + ( ( + strat.AssumptionPointingFactory(), strat.PointingStrategy(), strat.UnfusionFactory(), ), From 55a6ea984dc14a25018063a681ec50dfce50d687 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 10 May 2022 18:48:26 +0200 Subject: [PATCH 046/100] a factory for placing into rows and cols that are fusable (#464) * a factory for placing into rows and cols that are fusable. * __str__ * test encoding * adds to kitchen sink Co-authored-by: Jay Pantone --- CHANGELOG.md | 1 + tests/strategies/test_encoding.py | 2 + tilings/strategies/__init__.py | 2 + tilings/strategies/requirement_placement.py | 45 +++++++++++++++++++++ tilings/strategy_pack.py | 4 ++ 5 files changed, 54 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2aaf9a04..ea7e4472 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ swapped around a fusable row or column. in non-point cells. This are a non-productive strategy so should be used with `RuleDBForest`. - `UnfusionFactory` that unfuses either all the rows or columns. Also non-productive. +- `FusableRowAndColumnPlacementFactory` places fusable rows and columns. ### Fixed diff --git a/tests/strategies/test_encoding.py b/tests/strategies/test_encoding.py index 1af8db5e..b5584edc 100644 --- a/tests/strategies/test_encoding.py +++ b/tests/strategies/test_encoding.py @@ -23,6 +23,7 @@ EmptyCellInferralFactory, FactorFactory, FactorInsertionFactory, + FusableRowAndColumnPlacementFactory, FusionFactory, InsertionEncodingVerificationStrategy, LocallyFactorableVerificationStrategy, @@ -475,6 +476,7 @@ def indices_and_row(strategy): + [ AssumptionPointingFactory(), DummyStrategy(), + FusableRowAndColumnPlacementFactory(), PointingStrategy(), UnfusionRowStrategy(), UnfusionColumnStrategy(), diff --git a/tilings/strategies/__init__.py b/tilings/strategies/__init__.py index 870515ef..a745cadf 100644 --- a/tilings/strategies/__init__.py +++ b/tilings/strategies/__init__.py @@ -36,6 +36,7 @@ ) from .requirement_placement import ( AllPlacementsFactory, + FusableRowAndColumnPlacementFactory, PatternPlacementFactory, RequirementPlacementFactory, RowAndColumnPlacementFactory, @@ -66,6 +67,7 @@ "AllPlacementsFactory", "CellInsertionFactory", "FactorInsertionFactory", + "FusableRowAndColumnPlacementFactory", "PointCorroborationFactory", "PositiveCorroborationFactory", "RemoveRequirementFactory", diff --git a/tilings/strategies/requirement_placement.py b/tilings/strategies/requirement_placement.py index 47777781..27a2aff7 100644 --- a/tilings/strategies/requirement_placement.py +++ b/tilings/strategies/requirement_placement.py @@ -10,6 +10,7 @@ from permuta.misc import DIR_EAST, DIR_NORTH, DIR_SOUTH, DIR_WEST, DIRS from tilings import GriddedPerm, Tiling from tilings.algorithms import RequirementPlacement +from tilings.algorithms.fusion import Fusion __all__ = [ "PatternPlacementFactory", @@ -634,6 +635,50 @@ def from_dict(cls, d: dict) -> "RowAndColumnPlacementFactory": return cls(**d) +class FusableRowAndColumnPlacementFactory(RowAndColumnPlacementFactory): + def req_indices_and_directions_to_place( + self, tiling: Tiling + ) -> Iterator[Tuple[Tuple[GriddedPerm, ...], Tuple[int, ...], int]]: + """ + For each row, yield the gps with size one req for each cell in a row. + """ + cols: Dict[int, Set[GriddedPerm]] = defaultdict(set) + rows: Dict[int, Set[GriddedPerm]] = defaultdict(set) + for cell in tiling.active_cells: + gp = GriddedPerm((0,), (cell,)) + cols[cell[0]].add(gp) + rows[cell[1]].add(gp) + if self.place_col: + fusable_indices = set( + chain.from_iterable( + (idx, idx + 1) + for idx in range(tiling.dimensions[0] - 1) + if Fusion(tiling, col_idx=idx).fusable() + ) + ) + fusable_cols = [cols[idx] for idx in fusable_indices] + col_dirs = tuple(d for d in self.dirs if d in (DIR_EAST, DIR_WEST)) + for gps, direction in product(fusable_cols, col_dirs): + indices = tuple(0 for _ in gps) + yield tuple(gps), indices, direction + if self.place_row: + fusable_indices = set( + chain.from_iterable( + (idx, idx + 1) + for idx in range(tiling.dimensions[1] - 1) + if Fusion(tiling, row_idx=idx).fusable() + ) + ) + fusable_rows = [rows[idx] for idx in fusable_indices] + row_dirs = tuple(d for d in self.dirs if d in (DIR_NORTH, DIR_SOUTH)) + for gps, direction in product(fusable_rows, row_dirs): + indices = tuple(0 for _ in gps) + yield tuple(gps), indices, direction + + def __str__(self) -> str: + return "fusable " + super().__str__() + + class AllPlacementsFactory(AbstractRequirementPlacementFactory): PLACEMENT_STRATS: Tuple[AbstractRequirementPlacementFactory, ...] = ( diff --git a/tilings/strategy_pack.py b/tilings/strategy_pack.py index 6ca709b7..4809a0b2 100644 --- a/tilings/strategy_pack.py +++ b/tilings/strategy_pack.py @@ -295,6 +295,9 @@ def kitchen_sinkify( # pylint: disable=R0912 Requirement corroboration Obstruction Inferral (unless obs_inferral_len = 0) Symmetries + Point Pointing + Unfusion + Targeted Row/Col Placements when fusable Will be made tracked or not, depending on preference. Note that nothing is done with positive / point corroboration, requirement corroboration, or database verification. @@ -376,6 +379,7 @@ def kitchen_sinkify( # pylint: disable=R0912 strat.AssumptionPointingFactory(), strat.PointingStrategy(), strat.UnfusionFactory(), + strat.FusableRowAndColumnPlacementFactory(), ), ) From 244371dc498344f859942b160ca99bbba24b884f Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 10 May 2022 19:08:10 +0200 Subject: [PATCH 047/100] Remove assumptions (#465) * add assumption reversible when assumption covers whole tiling * check the assumptions being added * add a test for is reversible on assumption insertion --- tests/strategies/test_assumption_insertion.py | 23 +++++++++++++++++++ tilings/strategies/assumption_insertion.py | 5 ++-- 2 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 tests/strategies/test_assumption_insertion.py diff --git a/tests/strategies/test_assumption_insertion.py b/tests/strategies/test_assumption_insertion.py new file mode 100644 index 00000000..f5efac4c --- /dev/null +++ b/tests/strategies/test_assumption_insertion.py @@ -0,0 +1,23 @@ +from tilings import GriddedPerm, Tiling +from tilings.assumptions import ComponentAssumption, TrackingAssumption +from tilings.strategies.assumption_insertion import AddAssumptionsStrategy + + +def test_component_not_reversible(): + ass = ComponentAssumption.from_cells([(0, 0)]) + tiling = Tiling.from_string("123") + assert not AddAssumptionsStrategy([ass]).is_reversible(tiling) + + +def test_cover_all_cells_reversible(): + tiling = Tiling( + [ + GriddedPerm.single_cell((0, 1), (0, 0)), + GriddedPerm.single_cell((0, 1), (0, 1)), + ] + ) + ass1 = TrackingAssumption.from_cells([(0, 0), (0, 1)]) + ass2 = TrackingAssumption.from_cells([(0, 0)]) + assert AddAssumptionsStrategy([ass1]).is_reversible(tiling) + assert not AddAssumptionsStrategy([ass2]).is_reversible(tiling) + assert not AddAssumptionsStrategy([ass1, ass2]).is_reversible(tiling) diff --git a/tilings/strategies/assumption_insertion.py b/tilings/strategies/assumption_insertion.py index 78d3faa0..1b2cdbe2 100644 --- a/tilings/strategies/assumption_insertion.py +++ b/tilings/strategies/assumption_insertion.py @@ -143,12 +143,11 @@ def can_be_equivalent() -> bool: def is_two_way(comb_class: Tiling): return False - @staticmethod - def is_reversible(comb_class: Tiling) -> bool: + def is_reversible(self, comb_class: Tiling) -> bool: return all( not isinstance(assumption, ComponentAssumption) and frozenset(gp.pos[0] for gp in assumption.gps) == comb_class.active_cells - for assumption in comb_class.assumptions + for assumption in self.assumptions ) @staticmethod From aa7b79c25a28db35e4c24f3a670dd5b457e21bab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20Nadeau?= Date: Thu, 12 May 2022 17:00:30 +0000 Subject: [PATCH 048/100] fix json and repr encoding of interleaving factor strategy (#469) * fix json and repr encoding * remove dead code --- tests/strategies/test_encoding.py | 24 ++++++++++++++++++++++-- tilings/strategies/factor.py | 16 ++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/tests/strategies/test_encoding.py b/tests/strategies/test_encoding.py index b5584edc..d46b7258 100644 --- a/tests/strategies/test_encoding.py +++ b/tests/strategies/test_encoding.py @@ -244,6 +244,26 @@ def partition_ignoreparent_workable(strategy): ] +def partition_ignoreparent_workable_tracked(strategy): + return [ + strategy( + partition=partition, + ignore_parent=ignore_parent, + workable=workable, + tracked=tracked, + ) + for partition, ignore_parent, workable, tracked in product( + ( + [[(2, 1), (0, 1)], [(1, 0)]], + (((0, 0), (0, 2)), ((0, 1),), ((3, 3), (4, 3))), + ), + (True, False), + (True, False), + (True, False), + ) + ] + + def gps_ignoreparent(strategy): return [ strategy(gps=gps, ignore_parent=ignore_parent) @@ -376,8 +396,8 @@ def indices_and_row(strategy): + subreqs_partial_ignoreparent_dirs(RequirementPlacementFactory) + [SymmetriesFactory(), BasicVerificationStrategy(), EmptyCellInferralFactory()] + partition_ignoreparent_workable(FactorStrategy) - + partition_ignoreparent_workable(FactorWithInterleavingStrategy) - + partition_ignoreparent_workable(FactorWithMonotoneInterleavingStrategy) + + partition_ignoreparent_workable_tracked(FactorWithInterleavingStrategy) + + partition_ignoreparent_workable_tracked(FactorWithMonotoneInterleavingStrategy) + ignoreparent(DatabaseVerificationStrategy) + ignoreparent(LocallyFactorableVerificationStrategy) + ignoreparent(ElementaryVerificationStrategy) diff --git a/tilings/strategies/factor.py b/tilings/strategies/factor.py index 694cc83b..1b139552 100644 --- a/tilings/strategies/factor.py +++ b/tilings/strategies/factor.py @@ -260,6 +260,22 @@ def __init__( self.tracked = tracked self.cols, self.rows = self.interleaving_rows_and_cols(self.partition) + def to_jsonable(self) -> dict: + d = super().to_jsonable() + d["tracked"] = self.tracked + return d + + def __repr__(self) -> str: + args = ", ".join( + [ + f"partition={self.partition}", + f"ignore_parent={self.ignore_parent}", + f"workable={self.workable}", + f"tracked={self.tracked}", + ] + ) + return f"{self.__class__.__name__}({args})" + def is_two_way(self, comb_class: Tiling) -> bool: # type: ignore return self.is_reversible(comb_class) From 6499428a98ba424879d4abfa49dac15b863511ef Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 17 May 2022 16:33:17 +0200 Subject: [PATCH 049/100] a requirement pointing strategy (#466) * a requirement pointing strategy * allow for directionless placement of gp into all cells, so divide by N * only point at reqs on tilings --- CHANGELOG.md | 6 +- tests/strategies/test_encoding.py | 24 ++- tilings/algorithms/requirement_placement.py | 27 ++- tilings/strategies/__init__.py | 7 +- tilings/strategies/pointing.py | 191 +++++++++++++++++++- 5 files changed, 239 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea7e4472..e17f1fd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,9 +43,9 @@ swapped around a fusable row or column. - `FactorWithInterleavingStrategy.backward_map` so you can now generate permutation from specifications using interleaving factors. - `DummyStrategy` that gives a quick template for making strategies. -- `PointingStrategy` and `AssumptionPointingFactory` that place points directionless - in non-point cells. This are a non-productive strategy so should be used with - `RuleDBForest`. +- `PointingStrategy`, `AssumptionPointingFactory` and `RequirementPointingFactory` + that place points directionless in non-point cells. This are a non-productive + strategy so should be used with `RuleDBForest`. - `UnfusionFactory` that unfuses either all the rows or columns. Also non-productive. - `FusableRowAndColumnPlacementFactory` places fusable rows and columns. diff --git a/tests/strategies/test_encoding.py b/tests/strategies/test_encoding.py index d46b7258..21f2ab81 100644 --- a/tests/strategies/test_encoding.py +++ b/tests/strategies/test_encoding.py @@ -41,6 +41,7 @@ RequirementExtensionFactory, RequirementInsertionFactory, RequirementPlacementFactory, + RequirementPointingFactory, RootInsertionFactory, RowAndColumnPlacementFactory, RowColumnSeparationStrategy, @@ -72,7 +73,11 @@ AssumptionJumpingStrategy, PointJumpingStrategy, ) -from tilings.strategies.pointing import AssumptionPointingStrategy +from tilings.strategies.pointing import ( + AssumptionPointingStrategy, + RequirementAssumptionPointingStrategy, + RequirementPointingStrategy, +) from tilings.strategies.rearrange_assumption import RearrangeAssumptionStrategy from tilings.strategies.requirement_insertion import RequirementInsertionStrategy from tilings.strategies.requirement_placement import RequirementPlacementStrategy @@ -498,6 +503,7 @@ def indices_and_row(strategy): DummyStrategy(), FusableRowAndColumnPlacementFactory(), PointingStrategy(), + RequirementPointingFactory(), UnfusionRowStrategy(), UnfusionColumnStrategy(), UnfusionFactory(), @@ -509,6 +515,22 @@ def indices_and_row(strategy): ) ) ] + + [ + RequirementPointingStrategy( + ( + GriddedPerm.single_cell((0, 1), (0, 0)), + GriddedPerm.single_cell((0, 1), (0, 0)), + ), + (0, 1), + ), + RequirementAssumptionPointingStrategy( + ( + GriddedPerm.single_cell((0, 1), (0, 0)), + GriddedPerm.single_cell((0, 1), (0, 0)), + ), + (0, 1), + ), + ] ) __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) diff --git a/tilings/algorithms/requirement_placement.py b/tilings/algorithms/requirement_placement.py index fc04cda7..458e0625 100644 --- a/tilings/algorithms/requirement_placement.py +++ b/tilings/algorithms/requirement_placement.py @@ -1,5 +1,5 @@ from itertools import chain -from typing import TYPE_CHECKING, Dict, FrozenSet, Iterable, List, Tuple +from typing import TYPE_CHECKING, Dict, FrozenSet, Iterable, List, Optional, Tuple from permuta.misc import DIR_EAST, DIR_NONE, DIR_NORTH, DIR_SOUTH, DIR_WEST, DIRS from tilings import GriddedPerm @@ -350,33 +350,42 @@ def place_point_of_gridded_permutation( return self.place_point_of_req((gp,), (idx,), direction)[0] def place_point_of_req( - self, gps: Iterable[GriddedPerm], indices: Iterable[int], direction: Dir + self, + gps: Iterable[GriddedPerm], + indices: Iterable[int], + direction: Dir, + include_not: bool = False, + cells: Optional[Iterable[Cell]] = None, ) -> Tuple["Tiling", ...]: """ Return the tilings, where the placed point corresponds to the directionmost (the furtest in the given direction, ex: leftmost point) of an occurrence of any point idx, gp(idx) for gridded perms in gp, and idx in indices """ - cells = frozenset(gp.pos[idx] for idx, gp in zip(indices, gps)) + if cells is not None: + cells = frozenset(cells) + else: + cells = frozenset(gp.pos[idx] for idx, gp in zip(indices, gps)) res = [] for cell in sorted(cells): stretched = self._stretched_obstructions_requirements_and_assumptions(cell) (obs, reqs, ass) = stretched + rem_req = self._remaining_requirement_from_requirement(gps, indices, cell) + if direction == DIR_NONE: - res.append(self._tiling.__class__(obs, reqs, ass)) + res.append(self._tiling.__class__(obs, reqs + [rem_req], ass)) + if include_not: + res.append(self._tiling.__class__(obs + rem_req, reqs, ass)) continue + forced_obs = self.forced_obstructions_from_requirement( gps, indices, cell, direction ) - reduced_obs = [o1 for o1 in obs if not any(o2 in o1 for o2 in forced_obs)] - new_obs = reduced_obs + forced_obs - - rem_req = self._remaining_requirement_from_requirement(gps, indices, cell) res.append( self._tiling.__class__( - new_obs, + reduced_obs + forced_obs, reqs + [rem_req], assumptions=ass, already_minimized_obs=True, diff --git a/tilings/strategies/__init__.py b/tilings/strategies/__init__.py index a745cadf..9e59df4f 100644 --- a/tilings/strategies/__init__.py +++ b/tilings/strategies/__init__.py @@ -19,7 +19,11 @@ SubobstructionInferralFactory, ) from .point_jumping import AssumptionAndPointJumpingFactory -from .pointing import AssumptionPointingFactory, PointingStrategy +from .pointing import ( + AssumptionPointingFactory, + PointingStrategy, + RequirementPointingFactory, +) from .rearrange_assumption import RearrangeAssumptionFactory from .requirement_insertion import ( CellInsertionFactory, @@ -86,6 +90,7 @@ # Derivatives "AssumptionPointingFactory", "PointingStrategy", + "RequirementPointingFactory", "UnfusionColumnStrategy", "UnfusionRowStrategy", "UnfusionFactory", diff --git a/tilings/strategies/pointing.py b/tilings/strategies/pointing.py index 9607dbe2..ddb85478 100644 --- a/tilings/strategies/pointing.py +++ b/tilings/strategies/pointing.py @@ -3,17 +3,30 @@ by the 'pointing' constructor. """ from collections import Counter -from typing import Callable, Dict, FrozenSet, Iterator, Optional, Tuple +from itertools import product +from typing import ( + Callable, + Dict, + FrozenSet, + Iterator, + List, + Optional, + Tuple, + Union, + cast, +) from comb_spec_searcher import Strategy from comb_spec_searcher.exception import StrategyDoesNotApply from comb_spec_searcher.strategies.constructor.disjoint import DisjointUnion +from comb_spec_searcher.strategies.rule import Rule from comb_spec_searcher.strategies.strategy import StrategyFactory from comb_spec_searcher.typing import CombinatorialClassType, SubTerms, Terms from permuta.misc import DIR_NONE from tilings import GriddedPerm, Tiling from tilings.algorithms import RequirementPlacement from tilings.assumptions import ComponentAssumption, TrackingAssumption +from tilings.strategies.assumption_insertion import AddAssumptionsStrategy from tilings.strategies.obstruction_inferral import ObstructionInferralStrategy from tilings.tiling import Cell @@ -260,7 +273,7 @@ def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling, ...]: raise StrategyDoesNotApply("The assumption is just point cells!") def formal_step(self) -> str: - return super().formal_step() + f" in cells {set(self.cells)}~~~" + return super().formal_step() + f" in cells {set(self.cells)}" def constructor( self, @@ -337,3 +350,177 @@ def __repr__(self) -> str: @classmethod def from_dict(cls, d: dict) -> "AssumptionPointingFactory": return cls() + + +class RequirementPointingStrategy(PointingStrategy): + def __init__( + self, + gps: Tuple[GriddedPerm, ...], + indices: Tuple[int, ...], + ignore_parent: bool = False, + inferrable: bool = True, + possibly_empty: bool = True, + workable: bool = True, + ): + assert len(gps) == len(indices) + self.gps = gps + self.indices = indices + super().__init__(ignore_parent, inferrable, possibly_empty, workable) + + def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling, ...]: + cells = self.cells_to_place(comb_class) + algo = RequirementPlacement(comb_class) + if cells: + return algo.place_point_of_req( + self.gps, self.indices, DIR_NONE, include_not=True, cells=cells + ) + raise StrategyDoesNotApply("The assumption is just point cells!") + + def formal_step(self) -> str: + return super().formal_step() + f" in {self.gps} at indices {self.indices}" + + def extra_parameters( + self: Union[ + "RequirementPointingStrategy", "RequirementAssumptionPointingStrategy" + ], + comb_class: Tiling, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Tuple[Dict[str, str], ...]: + if children is None: + children = self.decomposition_function(comb_class) + even_index = tuple(child for idx, child in enumerate(children) if idx % 2 == 0) + odd_index = tuple(child for idx, child in enumerate(children) if idx % 2 == 1) + res: List[Optional[Dict[str, str]]] = [None for _ in children] + for idx, param in enumerate( + PointingStrategy.extra_parameters(self, comb_class, even_index) + ): + res[2 * idx] = param + for idx, param in enumerate( + PointingStrategy.extra_parameters(self, comb_class, odd_index) + ): + res[2 * idx + 1] = param + cast(List[Dict[str, str]], res) + return tuple(cast(List[Dict[str, str]], res)) + + def to_jsonable(self) -> dict: + d = super().to_jsonable() + d["gps"] = [gp.to_jsonable() for gp in self.gps] + d["indices"] = self.indices + return d + + @classmethod + def from_dict(cls, d: dict) -> "RequirementPointingStrategy": + return cls( + tuple(GriddedPerm.from_dict(gp) for gp in d.pop("gps")), + tuple(d.pop("indices")), + **d, + ) + + def __repr__(self) -> str: + return ( + self.__class__.__name__ + + f"({self.gps}, {self.indices}, {self.ignore_parent}, " + f"{self.inferrable}, {self.possibly_empty}, {self.workable})" + ) + + +class RequirementAssumptionPointingStrategy(AssumptionPointingStrategy): + def __init__( + self, + gps: Tuple[GriddedPerm, ...], + indices: Tuple[int, ...], + ignore_parent: bool = False, + inferrable: bool = True, + possibly_empty: bool = True, + workable: bool = True, + ): + assert len(gps) == len(indices) + self.gps = gps + self.indices = indices + self.cells = frozenset(gp.pos[idx] for gp, idx in zip(gps, indices)) + self.assumption = TrackingAssumption.from_cells(self.cells) + + super().__init__( + self.assumption, ignore_parent, inferrable, possibly_empty, workable + ) + + def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling, ...]: + if self.assumption not in comb_class.assumptions: + raise StrategyDoesNotApply("The assumption is not on tiling") + cells = self.cells_to_place(comb_class) + algo = RequirementPlacement(comb_class) + if cells: + return ( + comb_class.add_obstructions( + [GriddedPerm.point_perm(cell) for cell in cells] + ), + ) + algo.place_point_of_req( + self.gps, self.indices, DIR_NONE, include_not=True + ) + raise StrategyDoesNotApply("The assumption is just point cells!") + + def formal_step(self) -> str: + return super().formal_step() + f" in {self.gps} at indices {self.indices}" + + def extra_parameters( + self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None + ) -> Tuple[Dict[str, str], ...]: + if children is None: + children = self.decomposition_function(comb_class) + empty_params = ObstructionInferralStrategy( + [GriddedPerm.point_perm(cell) for cell in self.cells_to_place(comb_class)] + ).extra_parameters(comb_class) + rest = RequirementPointingStrategy.extra_parameters( + self, comb_class, children[1:] + ) + return empty_params + tuple(rest) + + def to_jsonable(self) -> dict: + d = super().to_jsonable() + d["gps"] = [gp.to_jsonable() for gp in self.gps] + d["indices"] = self.indices + d.pop("assumption") + return d + + @classmethod + def from_dict(cls, d: dict) -> "RequirementAssumptionPointingStrategy": + return cls( + tuple(GriddedPerm.from_dict(gp) for gp in d.pop("gps")), + tuple(d.pop("indices")), + **d, + ) + + def __repr__(self) -> str: + return ( + self.__class__.__name__ + + f"({self.gps}, {self.indices}, {self.ignore_parent}, " + f"{self.inferrable}, {self.possibly_empty}, {self.workable})" + ) + + +class RequirementPointingFactory(StrategyFactory[Tiling]): + def __call__(self, comb_class: Tiling) -> Iterator[Rule]: + for gps in comb_class.requirements: + for indices in product(*[range(len(gp)) for gp in gps]): + untracked_strategy = RequirementPointingStrategy(gps, indices) + yield untracked_strategy(comb_class) + strategy = RequirementAssumptionPointingStrategy(gps, indices) + if untracked_strategy.cells_to_place( + comb_class + ) != strategy.cells_to_place(comb_class): + parent = comb_class + if strategy.assumption not in comb_class.assumptions: + rule = AddAssumptionsStrategy([strategy.assumption])(comb_class) + yield rule + parent = rule.children[0] + yield strategy(parent) + + def __str__(self) -> str: + return "requirement pointing strategy" + + def __repr__(self) -> str: + return self.__class__.__name__ + "()" + + @classmethod + def from_dict(cls, d: dict) -> "RequirementPointingFactory": + return cls() From 565243e6e84da1522657f0254eb8850143c13872 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 17 May 2022 16:33:30 +0200 Subject: [PATCH 050/100] add remove constructor (#470) * add remove constructor * fix equation * remove unused import * remove print --- tilings/strategies/assumption_insertion.py | 108 ++++++++++++++++++++- 1 file changed, 107 insertions(+), 1 deletion(-) diff --git a/tilings/strategies/assumption_insertion.py b/tilings/strategies/assumption_insertion.py index 1b2cdbe2..1274066e 100644 --- a/tilings/strategies/assumption_insertion.py +++ b/tilings/strategies/assumption_insertion.py @@ -125,6 +125,96 @@ def equiv( ) +class RemoveAssumptionsConstructor(Constructor): + """ + The constructor used to count when a variable the same as n is removed. + """ + + def __init__( + self, + parent: Tiling, + child: Tiling, + parameter: str, + extra_parameters: Dict[str, str], + ): + # parent parameter -> child parameter mapping + self.extra_parameters = extra_parameters + # the paramater that was added, to count we must sum over all possible values + self.parameter = parameter + self.parameter_idx = parent.extra_parameters.index(self.parameter) + self.child_param_map = self._build_child_param_map(parent, child) + + def get_equation(self, lhs_func: Function, rhs_funcs: Tuple[Function, ...]) -> Eq: + rhs_func = rhs_funcs[0] + subs: Dict[Symbol, Expr] = { + var(child): var(parent) for parent, child in self.extra_parameters.items() + } + subs[var("x")] = var("x") * var(self.parameter) + return Eq(lhs_func, rhs_func.subs(subs, simultaneous=True)) + + def reliance_profile(self, n: int, **parameters: int) -> RelianceProfile: + raise NotImplementedError + + def get_terms( + self, parent_terms: Callable[[int], Terms], subterms: SubTerms, n: int + ) -> Terms: + assert len(subterms) == 1 + return self._push_add_assumption(n, subterms[0], self.child_param_map) + + def _push_add_assumption( + self, + n: int, + child_terms: Callable[[int], Terms], + child_param_map: ParametersMap, + ) -> Terms: + new_terms: Terms = Counter() + for param, value in child_terms(n).items(): + new_param = list(child_param_map(param)) + new_param[self.parameter_idx] = n + new_terms[tuple(new_param)] += value + return new_terms + + def _build_child_param_map(self, parent: Tiling, child: Tiling) -> ParametersMap: + parent_param_to_pos = { + param: pos for pos, param in enumerate(parent.extra_parameters) + } + child_param_to_parent_param = {v: k for k, v in self.extra_parameters.items()} + child_pos_to_parent_pos: Tuple[Tuple[int, ...], ...] = tuple( + (parent_param_to_pos[child_param_to_parent_param[param]],) + for param in child.extra_parameters + ) + return self.build_param_map( + child_pos_to_parent_pos, len(parent.extra_parameters) + ) + + def get_sub_objects( + self, subobjs: SubObjects, n: int + ) -> Iterator[Tuple[Parameters, Tuple[List[Optional[GriddedPerm]], ...]]]: + raise NotImplementedError + + def random_sample_sub_objects( + self, + parent_count: int, + subsamplers: SubSamplers, + subrecs: SubRecs, + n: int, + **parameters: int, + ): + raise NotImplementedError + + def equiv( + self, other: "Constructor", data: Optional[object] = None + ) -> Tuple[bool, Optional[object]]: + return ( + isinstance(other, type(self)) + and len(other.parameter) == len(self.parameter) + and AddAssumptionsConstructor.extra_params_equiv( + (self.extra_parameters,), (other.extra_parameters,) + ), + None, + ) + + class AddAssumptionsStrategy(Strategy[Tiling, GriddedPerm]): def __init__(self, assumptions: Iterable[TrackingAssumption], workable=False): self.assumptions = tuple(set(assumptions)) @@ -184,7 +274,23 @@ def reverse_constructor( comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None, ) -> Constructor: - raise NotImplementedError + if children is None: + children = self.decomposition_function(comb_class) + assert idx == 0 + child = children[idx] + assert len(self.assumptions) == 1 + assumption = self.assumptions[0] + assert len(assumption.gps) == len(comb_class.active_cells) + parameter = child.get_assumption_parameter(self.assumptions[0]) + extra_params = { + child.get_assumption_parameter(ass): comb_class.get_assumption_parameter( + ass + ) + for ass in child.assumptions + if ass != assumption + } + + return RemoveAssumptionsConstructor(child, comb_class, parameter, extra_params) def extra_parameters( self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None From af6b774d8dc405dbf68decbfc143d1c03e5dbef3 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 24 May 2022 15:17:00 +0200 Subject: [PATCH 051/100] add flags to unfusion and pointing strategies (#471) --- tilings/strategies/pointing.py | 15 +++++++++++---- tilings/strategies/unfusion.py | 12 ++++++++++-- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/tilings/strategies/pointing.py b/tilings/strategies/pointing.py index ddb85478..be07c046 100644 --- a/tilings/strategies/pointing.py +++ b/tilings/strategies/pointing.py @@ -499,15 +499,22 @@ def __repr__(self) -> str: class RequirementPointingFactory(StrategyFactory[Tiling]): + def __init__(self, max_cells: int = 4) -> None: + self.max_cells = max_cells + super().__init__() + def __call__(self, comb_class: Tiling) -> Iterator[Rule]: for gps in comb_class.requirements: for indices in product(*[range(len(gp)) for gp in gps]): untracked_strategy = RequirementPointingStrategy(gps, indices) - yield untracked_strategy(comb_class) + if len(comb_class.active_cells) <= self.max_cells: + yield untracked_strategy(comb_class) strategy = RequirementAssumptionPointingStrategy(gps, indices) - if untracked_strategy.cells_to_place( - comb_class - ) != strategy.cells_to_place(comb_class): + cells_to_place = strategy.cells_to_place(comb_class) + if ( + untracked_strategy.cells_to_place(comb_class) != cells_to_place + and len(cells_to_place) <= self.max_cells + ): parent = comb_class if strategy.assumption not in comb_class.assumptions: rule = AddAssumptionsStrategy([strategy.assumption])(comb_class) diff --git a/tilings/strategies/unfusion.py b/tilings/strategies/unfusion.py index 62452a30..041c1ea7 100644 --- a/tilings/strategies/unfusion.py +++ b/tilings/strategies/unfusion.py @@ -302,9 +302,17 @@ def __init__( class UnfusionFactory(StrategyFactory[Tiling]): + def __init__(self, max_width: int = 4, max_height: int = 4) -> None: + self.max_height = max_height + self.max_width = max_width + super().__init__() + def __call__(self, comb_class: Tiling) -> Iterator[UnfusionColumnStrategy]: - yield UnfusionColumnStrategy() - yield UnfusionRowStrategy() + width, height = comb_class.dimensions + if width <= self.max_width: + yield UnfusionColumnStrategy() + if height <= self.max_height: + yield UnfusionRowStrategy() def __str__(self) -> str: return "unfusion strategy" From ffdd84c6cb69565d65e28b34c17d999dcd1764e8 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Wed, 1 Jun 2022 11:41:05 +0000 Subject: [PATCH 052/100] All at once obstruction inferral (#467) * first draft, mostly copying SubclassVerification.compute_subclasses * remove prints and tidy * update test * Obs inf early yield (#472) * adds yield_non_minimal flag to obs inf * adds typing and sets to True Co-authored-by: jaypantone --- tests/algorithms/test_obstruction_inferral.py | 1 + tests/strategies/test_inferral.py | 5 ++- tilings/algorithms/gridded_perm_generation.py | 7 ++-- tilings/algorithms/obstruction_inferral.py | 33 +++++++++++++++---- tilings/strategies/obstruction_inferral.py | 4 ++- 5 files changed, 39 insertions(+), 11 deletions(-) diff --git a/tests/algorithms/test_obstruction_inferral.py b/tests/algorithms/test_obstruction_inferral.py index b29db55a..bb82953a 100644 --- a/tests/algorithms/test_obstruction_inferral.py +++ b/tests/algorithms/test_obstruction_inferral.py @@ -173,6 +173,7 @@ def test_new_obs(self, obs_not_inf, obs_inf1, obs_inf2): assert obs_not_inf.new_obs() == [] assert obs_inf2.new_obs() == [ GriddedPerm((0, 1), ((0, 0), (2, 0))), + GriddedPerm((0, 2, 1), ((0, 0), (0, 0), (2, 0))), ] def test_obstruction_inferral(self, obs_inf2): diff --git a/tests/strategies/test_inferral.py b/tests/strategies/test_inferral.py index 6504fa65..db57ec29 100644 --- a/tests/strategies/test_inferral.py +++ b/tests/strategies/test_inferral.py @@ -161,7 +161,10 @@ def test_obstruction_inferral(tiling1, tiling_not_inf): requirements=((GriddedPerm((1, 0), ((1, 0), (1, 0))),),), ) assert isinstance(rule.constructor, DisjointUnion) - assert rule.formal_step == "added the obstructions {01: (0, 0), (0, 0)}" + assert ( + rule.formal_step == "added the obstructions {01: (0, 0), (0, 0)," + " 021: (0, 0), (0, 0), (1, 0), 120: (0, 0), (0, 0), (1, 0)}" + ) assert rule.inferrable assert not rule.possibly_empty assert rule.ignore_parent diff --git a/tilings/algorithms/gridded_perm_generation.py b/tilings/algorithms/gridded_perm_generation.py index 8110d4d6..bd39106c 100644 --- a/tilings/algorithms/gridded_perm_generation.py +++ b/tilings/algorithms/gridded_perm_generation.py @@ -32,17 +32,20 @@ class GriddedPermsOnTiling: built by inserting points into the minimal gridded permutations. """ - def __init__(self, tiling: "Tiling"): + def __init__(self, tiling: "Tiling", yield_non_minimal: bool = False): self._tiling = tiling self._minimal_gps = MinimalGriddedPerms( tiling.obstructions, tiling.requirements ) + self._yield_non_minimal = yield_non_minimal self._yielded_gridded_perms: Set[GriddedPerm] = set() def prepare_queue(self, size: int) -> List[QueuePacket]: queue: List[QueuePacket] = [] heapify(queue) - for mgp in self._minimal_gps.minimal_gridded_perms(): + for mgp in self._minimal_gps.minimal_gridded_perms( + yield_non_minimal=self._yield_non_minimal + ): if len(mgp) <= size: packet = QueuePacket(mgp, (-1, -1), {}, 0) heappush(queue, packet) diff --git a/tilings/algorithms/obstruction_inferral.py b/tilings/algorithms/obstruction_inferral.py index 5a401b38..cab6fd21 100644 --- a/tilings/algorithms/obstruction_inferral.py +++ b/tilings/algorithms/obstruction_inferral.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING, Iterable, List, Optional, Set, Tuple from tilings import GriddedPerm +from tilings.algorithms.gridded_perm_generation import GriddedPermsOnTiling if TYPE_CHECKING: from tilings import Tiling @@ -26,18 +27,36 @@ def potential_new_obs(self) -> Iterable[GriddedPerm]: tiling if possible. """ - def new_obs(self) -> List[GriddedPerm]: + def new_obs(self, yield_non_minimal: bool = False) -> List[GriddedPerm]: """ Returns the list of new obstructions that can be added to the tiling. """ if self._new_obs is not None: return self._new_obs - newobs: List[GriddedPerm] = [] - for ob in sorted(self.potential_new_obs(), key=len): - cont_newob = any(newob in ob for newob in newobs) - if not cont_newob and self.can_add_obstruction(ob, self._tiling): - newobs.append(ob) - self._new_obs = newobs + + perms_to_check = tuple(self.potential_new_obs()) + if not perms_to_check: + self._new_obs = [] + return self._new_obs + + max_len_of_perms_to_check = max(map(len, perms_to_check)) + max_length = ( + self._tiling.maximum_length_of_minimum_gridded_perm() + + max_len_of_perms_to_check + ) + GP = GriddedPermsOnTiling( + self._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: + to_remove: List[GriddedPerm] = [] + for perm in perms_left: + if gp.contains(perm): + to_remove.append(perm) + perms_left.difference_update(to_remove) + if not perms_left: + break + self._new_obs = sorted(perms_left) return self._new_obs @staticmethod diff --git a/tilings/strategies/obstruction_inferral.py b/tilings/strategies/obstruction_inferral.py index 27b24707..a0704f89 100644 --- a/tilings/strategies/obstruction_inferral.py +++ b/tilings/strategies/obstruction_inferral.py @@ -117,7 +117,9 @@ def new_obs(self, tiling: Tiling) -> Sequence[GriddedPerm]: """ Returns the list of new obstructions that can be added to the tiling. """ - return AllObstructionInferral(tiling, self.maxlen).new_obs() + return AllObstructionInferral(tiling, self.maxlen).new_obs( + yield_non_minimal=True + ) def __call__(self, comb_class: Tiling) -> Iterator[ObstructionInferralStrategy]: gps = self.new_obs(comb_class) From 3fab1c958e7df45aa4ca7d90b0e25d41fd80897f Mon Sep 17 00:00:00 2001 From: jaypantone Date: Wed, 1 Jun 2022 07:08:55 -0500 Subject: [PATCH 053/100] full-tiling assumptions do not count against the max_assumptions cap (#474) * full-tiling assumptions do not count against the max_assumptions cap * changelog --- CHANGELOG.md | 31 ++++++++++++++++--------------- tilings/tilescope.py | 10 +++++++++- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e17f1fd4..03de8e98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,23 +29,23 @@ swapped around a fusable row or column. cell. - `CellReductionFactory` which changes a cell to monotone if at most one point of any crossing gp touches that cell. -- `PositiveCorroborationFactory` that inserts into cells which if positive makes - another cell empty. Also, the `PointCorroborationFactory`, which does this for +- `PositiveCorroborationFactory` that inserts into cells which if positive makes + another cell empty. Also, the `PointCorroborationFactory`, which does this for point or empty cells which is added to most packs. -- `TargetedCellInsertionFactory` which inserts factors of gridded perms if it can - lead to factoring out a verified sub tiling. -- `ComponentVerificationStrategy` which is added to component fusion packs. -- `ComponentToPointAssumptionStrategy` that changes component assumptions to point +- `TargetedCellInsertionFactory` which inserts factors of gridded perms if it can + lead to factoring out a verified sub tiling. +- `ComponentVerificationStrategy` which is added to component fusion packs. +- `ComponentToPointAssumptionStrategy` that changes component assumptions to point assumptions. These strategies are yielded in `RearrangeAssumptionFactory`. - `StrategyPack.kitchen_sinkify` to add many experimental strategies to the pack - `SubobstructionInsertionFactory` that inserts subobstructions and the pack - `TileScopePack.subobstruction_placements` which uses it. -- `FactorWithInterleavingStrategy.backward_map` so you can now generate permutation + `TileScopePack.subobstruction_placements` which uses it. +- `FactorWithInterleavingStrategy.backward_map` so you can now generate permutation from specifications using interleaving factors. -- `DummyStrategy` that gives a quick template for making strategies. -- `PointingStrategy`, `AssumptionPointingFactory` and `RequirementPointingFactory` - that place points directionless in non-point cells. This are a non-productive - strategy so should be used with `RuleDBForest`. +- `DummyStrategy` that gives a quick template for making strategies. +- `PointingStrategy`, `AssumptionPointingFactory` and `RequirementPointingFactory` + that place points directionless in non-point cells. This are a non-productive + strategy so should be used with `RuleDBForest`. - `UnfusionFactory` that unfuses either all the rows or columns. Also non-productive. - `FusableRowAndColumnPlacementFactory` places fusable rows and columns. @@ -78,12 +78,13 @@ swapped around a fusable row or column. - `GuidedSearcher` expands every symmetry - `TileScopePack.pattern_placements` factors as an initial strategy. - `is_component` method of assumptions updated to consider cell decomposition -- `AddAssumptionsStrategy.is_reverible` is now True when the assumption covers the +- `AddAssumptionsStrategy.is_reverible` is now True when the assumption covers the whole tiling. +- Assumptions that cover a whole tiling do not count against the max_assumptions limit ### Removed -- `AddInterleavingAssumptionsFactory`. The factor strategy now adds the relevant - assumptions where necessary directly, lowering the number of CVs needed. +- `AddInterleavingAssumptionsFactory`. The factor strategy now adds the relevant + assumptions where necessary directly, lowering the number of CVs needed. ### Deprecated - Python 3.7 is no longer supported diff --git a/tilings/tilescope.py b/tilings/tilescope.py index 897bc6ba..57829e31 100644 --- a/tilings/tilescope.py +++ b/tilings/tilescope.py @@ -108,6 +108,14 @@ def _expand( strategies, but only add rules whose children all satisfy the max_assumptions requirement. """ + + def num_child_assumptions(child: Tiling) -> int: + return sum( + 1 + for ass in child.assumptions + if len(ass.gps) != len(child.active_cells) + ) + if inferral: self._inferral_expand(comb_class, label, strategies) else: @@ -116,7 +124,7 @@ def _expand( comb_class, strategy_generator, label ): if all( - len(child.assumptions) <= self.max_assumptions + num_child_assumptions(child.assumptions) <= self.max_assumptions for child in rule.children ): self.add_rule(start_label, end_labels, rule) From 0b9056fe79921979ace47a2b20fe3377c983127d Mon Sep 17 00:00:00 2001 From: jaypantone Date: Tue, 7 Jun 2022 08:48:10 -0500 Subject: [PATCH 054/100] more flags for pointing (#475) * adds flags to PointingStrategy and AssumptionPointingStrategy; also adds RequirementPointingStrategy to kitchen sink * fixes reprs, from_dicts, to_jsonables * fixes from_dict bugs from max_cell argument * Bug fixes * better __str__s --- tilings/strategies/pointing.py | 86 ++++++++++++++++++++++++++-------- tilings/strategy_pack.py | 1 + tilings/tilescope.py | 2 +- 3 files changed, 69 insertions(+), 20 deletions(-) diff --git a/tilings/strategies/pointing.py b/tilings/strategies/pointing.py index be07c046..c9f60a29 100644 --- a/tilings/strategies/pointing.py +++ b/tilings/strategies/pointing.py @@ -34,6 +34,17 @@ class PointingStrategy(Strategy[Tiling, GriddedPerm]): + def __init__( + self, + max_cells: Optional[int] = 4, + ignore_parent: bool = False, + inferrable: bool = True, + possibly_empty: bool = True, + workable: bool = True, + ) -> None: + self.max_cells = max_cells + super().__init__(ignore_parent, inferrable, possibly_empty, workable) + def can_be_equivalent(self) -> bool: return False @@ -65,15 +76,19 @@ def cells_to_place(self, comb_class: Tiling) -> FrozenSet[Cell]: return comb_class.active_cells - self.already_placed_cells(comb_class) def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling, ...]: - cells = self.cells_to_place(comb_class) - if cells: - return tuple( - comb_class.place_point_in_cell(cell, DIR_NONE) for cell in sorted(cells) - ) - raise StrategyDoesNotApply("The tiling is just point cells!") + assert self.max_cells is not None + if len(comb_class.active_cells) <= self.max_cells: + cells = self.cells_to_place(comb_class) + if cells: + return tuple( + comb_class.place_point_in_cell(cell, DIR_NONE) + for cell in sorted(cells) + ) + raise StrategyDoesNotApply("The tiling is just point cells!") + raise StrategyDoesNotApply("Too many active cells.") def formal_step(self) -> str: - return "directionless point placement" + return f"directionless point placement (<= {self.max_cells} cells)" def constructor( self, @@ -149,6 +164,17 @@ def extra_parameters( res.append(params) return tuple(res) + def __repr__(self) -> str: + return ( + self.__class__.__name__ + f"({self.max_cells}, {self.ignore_parent}, " + f"{self.inferrable}, {self.possibly_empty}, {self.workable})" + ) + + def to_jsonable(self) -> dict: + d = super().to_jsonable() + d["max_cells"] = self.max_cells + return d + @classmethod def from_dict(cls, d: dict) -> "PointingStrategy": return cls(**d) @@ -253,7 +279,7 @@ def __init__( self.assumption = assumption assert not isinstance(assumption, ComponentAssumption) self.cells = frozenset(gp.pos[0] for gp in assumption.gps) - super().__init__(ignore_parent, inferrable, possibly_empty, workable) + super().__init__(None, ignore_parent, inferrable, possibly_empty, workable) def cells_to_place(self, comb_class: Tiling) -> FrozenSet[Cell]: return super().cells_to_place(comb_class).intersection(self.cells) @@ -324,6 +350,8 @@ def to_jsonable(self) -> dict: @classmethod def from_dict(cls, d: dict) -> "AssumptionPointingStrategy": + if "max_cells" in d: + d.pop("max_cells") assumption = TrackingAssumption.from_dict(d.pop("assumption")) return cls(assumption=assumption, **d) @@ -336,20 +364,31 @@ def __repr__(self) -> str: class AssumptionPointingFactory(StrategyFactory[Tiling]): + def __init__(self, max_cells: int = 4) -> None: + self.max_cells = max_cells + super().__init__() + def __call__(self, comb_class: Tiling) -> Iterator[AssumptionPointingStrategy]: - for assumption in comb_class.assumptions: - if not isinstance(assumption, ComponentAssumption): - yield AssumptionPointingStrategy(assumption) + if len(comb_class.active_cells) <= self.max_cells: + for assumption in comb_class.assumptions: + if not isinstance(assumption, ComponentAssumption): + yield AssumptionPointingStrategy(assumption) def __str__(self) -> str: - return "assumption pointing strategy" + return f"assumption pointing strategy (<= {self.max_cells} cells)" def __repr__(self) -> str: - return self.__class__.__name__ + "()" + return self.__class__.__name__ + f"({self.max_cells})" + + def to_jsonable(self) -> dict: + d = super().to_jsonable() + d["max_cells"] = self.max_cells + return d @classmethod def from_dict(cls, d: dict) -> "AssumptionPointingFactory": - return cls() + + return cls(**d) class RequirementPointingStrategy(PointingStrategy): @@ -365,7 +404,7 @@ def __init__( assert len(gps) == len(indices) self.gps = gps self.indices = indices - super().__init__(ignore_parent, inferrable, possibly_empty, workable) + super().__init__(None, ignore_parent, inferrable, possibly_empty, workable) def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling, ...]: cells = self.cells_to_place(comb_class) @@ -410,6 +449,8 @@ def to_jsonable(self) -> dict: @classmethod def from_dict(cls, d: dict) -> "RequirementPointingStrategy": + if "max_cells" in d: + d.pop("max_cells") return cls( tuple(GriddedPerm.from_dict(gp) for gp in d.pop("gps")), tuple(d.pop("indices")), @@ -484,6 +525,8 @@ def to_jsonable(self) -> dict: @classmethod def from_dict(cls, d: dict) -> "RequirementAssumptionPointingStrategy": + if "max_cells" in d: + d.pop("max_cells") return cls( tuple(GriddedPerm.from_dict(gp) for gp in d.pop("gps")), tuple(d.pop("indices")), @@ -513,7 +556,7 @@ def __call__(self, comb_class: Tiling) -> Iterator[Rule]: cells_to_place = strategy.cells_to_place(comb_class) if ( untracked_strategy.cells_to_place(comb_class) != cells_to_place - and len(cells_to_place) <= self.max_cells + and 0 < len(cells_to_place) <= self.max_cells ): parent = comb_class if strategy.assumption not in comb_class.assumptions: @@ -523,11 +566,16 @@ def __call__(self, comb_class: Tiling) -> Iterator[Rule]: yield strategy(parent) def __str__(self) -> str: - return "requirement pointing strategy" + return f"requirement pointing strategy (<= {self.max_cells} cells)" def __repr__(self) -> str: - return self.__class__.__name__ + "()" + return self.__class__.__name__ + f"({self.max_cells})" + + def to_jsonable(self) -> dict: + d = super().to_jsonable() + d["max_cells"] = self.max_cells + return d @classmethod def from_dict(cls, d: dict) -> "RequirementPointingFactory": - return cls() + return cls(**d) diff --git a/tilings/strategy_pack.py b/tilings/strategy_pack.py index 4809a0b2..7bc21a9e 100644 --- a/tilings/strategy_pack.py +++ b/tilings/strategy_pack.py @@ -377,6 +377,7 @@ def kitchen_sinkify( # pylint: disable=R0912 ks_pack.expansion_strats = ks_pack.expansion_strats + ( ( strat.AssumptionPointingFactory(), + strat.RequirementPointingFactory(), strat.PointingStrategy(), strat.UnfusionFactory(), strat.FusableRowAndColumnPlacementFactory(), diff --git a/tilings/tilescope.py b/tilings/tilescope.py index 57829e31..23071559 100644 --- a/tilings/tilescope.py +++ b/tilings/tilescope.py @@ -124,7 +124,7 @@ def num_child_assumptions(child: Tiling) -> int: comb_class, strategy_generator, label ): if all( - num_child_assumptions(child.assumptions) <= self.max_assumptions + num_child_assumptions(child) <= self.max_assumptions for child in rule.children ): self.add_rule(start_label, end_labels, rule) From 1804639a676757800cb7f476091c98845f192ad5 Mon Sep 17 00:00:00 2001 From: jaypantone Date: Tue, 7 Jun 2022 09:00:15 -0500 Subject: [PATCH 055/100] adds flag to allow factorable requirement insertions, default = False (#473) * adds flag to allow factorable requirement insertions, default = False * changelog and default set to True --- CHANGELOG.md | 43 ++++++++++++--------- tilings/strategies/requirement_insertion.py | 8 +++- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03de8e98..8efeca5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,17 +5,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] - -## [3.1.0] - 2022-01-17 ### Added -- `Tiling.remove_requirement` method that removes a requirement from a tiling. -- `RemoveRequirementFactory` which adds the rules where we insert a requirement to a -tiling after we first remove that requirement. This is added to -`LocallyFactorableVerificationStrategy.pack`. -- The tiling initialiser will now add factors of obstructions if it is implied by -multiple different obs and one requirement list of size possibly greater than one. -Previously it was only doing the case where a single ob's factor is implied by a -requirement. - added `TileScopePack.requirement_and_row_and_col_placements` - `AssumptionAndPointJumpingFactory` which adds rules where requirements and/or assumptions are swapped around a fusable row or column. @@ -49,21 +39,13 @@ swapped around a fusable row or column. - `UnfusionFactory` that unfuses either all the rows or columns. Also non-productive. - `FusableRowAndColumnPlacementFactory` places fusable rows and columns. - ### Fixed -- `ForgetTrackedSearcher` was not retroactively applying strategies that had a `basis`. -- Bug with sliding symmetries -- The tiling initialiser was not removing duplicate/redundant requirements. - `Factor` was not factoring correctly with respect to component assumptions. - `ComponentAssumption` are flipped when taking symmetries - `Tiling.get_minimum_value` fixed for component assumptions - `RearrangeAssumptionFactory` will ignore component assumptions ### Changed -- One by one verification will now only verify subclasses of the given basis. -- Verification strategies no longer ignore parent -- `TrackedSearcher` now uses a `TrackedQueue` and is able to work with all packs - and new future strategies. - `TileScopePack.make_tracked` will add the appropriate tracking methods for interleaving factors and make strategies tracked if it can be. - The `GriddedPermReduction` limits the size of obstructions it tries to infer in @@ -80,12 +62,37 @@ swapped around a fusable row or column. - `is_component` method of assumptions updated to consider cell decomposition - `AddAssumptionsStrategy.is_reverible` is now True when the assumption covers the whole tiling. +- The default behavior for `RequirementInsertion` is to allow insertion of factorable + requirements - Assumptions that cover a whole tiling do not count against the max_assumptions limit ### Removed - `AddInterleavingAssumptionsFactory`. The factor strategy now adds the relevant assumptions where necessary directly, lowering the number of CVs needed. + +## [3.1.0] - 2022-01-17 +### Added +- `Tiling.remove_requirement` method that removes a requirement from a tiling. +- `RemoveRequirementFactory` which adds the rules where we insert a requirement to a +tiling after we first remove that requirement. This is added to +`LocallyFactorableVerificationStrategy.pack`. +- The tiling initialiser will now add factors of obstructions if it is implied by +multiple different obs and one requirement list of size possibly greater than one. +Previously it was only doing the case where a single ob's factor is implied by a +requirement. + +### Fixed +- `ForgetTrackedSearcher` was not retroactively applying strategies that had a `basis`. +- Bug with sliding symmetries +- The tiling initialiser was not removing duplicate/redundant requirements. + +### Changed +- One by one verification will now only verify subclasses of the given basis. +- Verification strategies no longer ignore parent +- `TrackedSearcher` now uses a `TrackedQueue` and is able to work with all packs + and new future strategies. + ### Deprecated - Python 3.7 is no longer supported diff --git a/tilings/strategies/requirement_insertion.py b/tilings/strategies/requirement_insertion.py index 65d830da..d96767e7 100644 --- a/tilings/strategies/requirement_insertion.py +++ b/tilings/strategies/requirement_insertion.py @@ -400,8 +400,10 @@ def __init__( extra_basis: Optional[List[Perm]] = None, limited_insertion: bool = True, ignore_parent: bool = False, + allow_factorable_insertions: bool = True, ) -> None: self.limited_insertion = limited_insertion + self.allow_factorable_insertions = allow_factorable_insertions super().__init__(maxreqlen, extra_basis, ignore_parent) def req_lists_to_insert(self, tiling: Tiling) -> Iterator[ListRequirement]: @@ -414,7 +416,7 @@ def req_lists_to_insert(self, tiling: Tiling) -> Iterator[ListRequirement]: ) for length in range(1, self.maxreqlen + 1): for gp in obs_tiling.gridded_perms_of_length(length): - if len(gp.factors()) == 1 and all( + if (self.allow_factorable_insertions or len(gp.factors()) == 1) and all( p not in gp.patt for p in self.extra_basis ): yield (GriddedPerm(gp.patt, gp.pos),) @@ -440,6 +442,7 @@ def __repr__(self) -> str: f"extra_basis={self.extra_basis!r}", f"limited_insertion={self.limited_insertion}", f"ignore_parent={self.ignore_parent}", + f"allow_factorable_insertions={self.allow_factorable_insertions}", ] ) return f"{self.__class__.__name__}({args})" @@ -447,6 +450,7 @@ def __repr__(self) -> str: def to_jsonable(self) -> dict: d: dict = super().to_jsonable() d["limited_insertion"] = self.limited_insertion + d["allow_factorable_insertions"] = self.allow_factorable_insertions return d @classmethod @@ -458,10 +462,12 @@ def from_dict(cls, d: dict) -> "RequirementInsertionWithRestrictionFactory": d.pop("extra_basis") limited_insertion = d.pop("limited_insertion") maxreqlen = d.pop("maxreqlen") + allow_factorable_insertions = d.pop("allow_factorable_insertions") return cls( maxreqlen=maxreqlen, extra_basis=extra_basis, limited_insertion=limited_insertion, + allow_factorable_insertions=allow_factorable_insertions, **d, ) From 9c66582f2499745332be87e06fed8d7cc314f16b Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Mon, 13 Jun 2022 21:31:55 -0500 Subject: [PATCH 056/100] Small bug (#480) * small bug in initialiser * add a test --- CHANGELOG.md | 2 + .../algorithms/test_simplify_gridded_perms.py | 138 ++++++++++++++++++ tilings/algorithms/gridded_perm_reduction.py | 2 +- 3 files changed, 141 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8efeca5e..90d8de51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,8 @@ swapped around a fusable row or column. - `ComponentAssumption` are flipped when taking symmetries - `Tiling.get_minimum_value` fixed for component assumptions - `RearrangeAssumptionFactory` will ignore component assumptions +- `GriddedPermReduction.minimal_reqs` was removing requirements if they + were duplicates. ### Changed - `TileScopePack.make_tracked` will add the appropriate tracking methods for diff --git a/tests/algorithms/test_simplify_gridded_perms.py b/tests/algorithms/test_simplify_gridded_perms.py index 5a432793..a67e025f 100644 --- a/tests/algorithms/test_simplify_gridded_perms.py +++ b/tests/algorithms/test_simplify_gridded_perms.py @@ -254,3 +254,141 @@ def test_reduce_to_join_of_subobs(): ), ) assert t == expected + + +def test_minimal_req_duplicate(): + assert Tiling( + obstructions=( + GriddedPerm((0, 2, 3, 1), ((0, 1), (0, 1), (0, 1), (1, 1))), + GriddedPerm((0, 2, 3, 1), ((0, 1), (0, 1), (1, 1), (1, 1))), + GriddedPerm((0, 2, 3, 1), ((0, 1), (1, 1), (1, 1), (1, 1))), + GriddedPerm((0, 3, 2, 1), ((0, 1), (0, 1), (0, 1), (1, 1))), + GriddedPerm((0, 3, 2, 1), ((0, 1), (0, 1), (1, 1), (1, 1))), + GriddedPerm((0, 3, 2, 1), ((0, 1), (1, 1), (1, 1), (1, 1))), + GriddedPerm((1, 0, 2, 3), ((1, 0), (1, 0), (1, 1), (1, 1))), + GriddedPerm((1, 0, 3, 2), ((1, 0), (1, 0), (1, 1), (1, 1))), + GriddedPerm((1, 2, 0, 3), ((1, 0), (1, 1), (1, 0), (1, 1))), + GriddedPerm((1, 2, 3, 0), ((1, 0), (1, 1), (1, 1), (1, 0))), + GriddedPerm((1, 3, 0, 2), ((1, 0), (1, 1), (1, 0), (1, 1))), + GriddedPerm((1, 3, 2, 0), ((1, 0), (1, 1), (1, 1), (1, 0))), + GriddedPerm((2, 0, 3, 1), ((0, 1), (0, 1), (0, 1), (1, 1))), + GriddedPerm((2, 0, 3, 1), ((0, 1), (0, 1), (1, 1), (1, 1))), + GriddedPerm((2, 1, 0, 3), ((0, 1), (1, 0), (1, 0), (1, 1))), + GriddedPerm((2, 1, 0, 3), ((1, 1), (1, 0), (1, 0), (1, 1))), + GriddedPerm((2, 1, 3, 0), ((0, 1), (1, 0), (1, 1), (1, 0))), + GriddedPerm((2, 1, 3, 0), ((1, 1), (1, 0), (1, 1), (1, 0))), + GriddedPerm((1, 0, 3, 4, 2), ((0, 1), (0, 1), (0, 1), (0, 1), (0, 1))), + GriddedPerm((1, 0, 3, 4, 2), ((1, 0), (1, 0), (1, 0), (1, 0), (1, 0))), + GriddedPerm((1, 0, 3, 4, 2), ((1, 0), (1, 0), (1, 0), (1, 1), (1, 0))), + GriddedPerm((1, 0, 3, 4, 2), ((1, 1), (1, 0), (1, 1), (1, 1), (1, 1))), + GriddedPerm((1, 0, 3, 4, 2), ((1, 1), (1, 1), (1, 1), (1, 1), (1, 1))), + GriddedPerm((1, 0, 4, 3, 2), ((0, 1), (0, 1), (0, 1), (0, 1), (0, 1))), + GriddedPerm((1, 0, 4, 3, 2), ((1, 0), (1, 0), (1, 0), (1, 0), (1, 0))), + GriddedPerm((1, 0, 4, 3, 2), ((1, 0), (1, 0), (1, 1), (1, 0), (1, 0))), + GriddedPerm((1, 0, 4, 3, 2), ((1, 1), (1, 0), (1, 1), (1, 1), (1, 1))), + GriddedPerm((1, 0, 4, 3, 2), ((1, 1), (1, 1), (1, 1), (1, 1), (1, 1))), + GriddedPerm((1, 3, 0, 4, 2), ((0, 1), (0, 1), (0, 1), (0, 1), (0, 1))), + GriddedPerm((1, 3, 0, 4, 2), ((1, 0), (1, 0), (1, 0), (1, 0), (1, 0))), + GriddedPerm((1, 3, 0, 4, 2), ((1, 0), (1, 0), (1, 0), (1, 1), (1, 0))), + GriddedPerm((1, 3, 0, 4, 2), ((1, 1), (1, 1), (1, 0), (1, 1), (1, 1))), + GriddedPerm((1, 3, 0, 4, 2), ((1, 1), (1, 1), (1, 1), (1, 1), (1, 1))), + GriddedPerm((1, 3, 4, 0, 2), ((0, 1), (0, 1), (0, 1), (0, 1), (0, 1))), + GriddedPerm((1, 3, 4, 0, 2), ((1, 0), (1, 0), (1, 0), (1, 0), (1, 0))), + GriddedPerm((1, 3, 4, 0, 2), ((1, 0), (1, 0), (1, 1), (1, 0), (1, 0))), + GriddedPerm((1, 3, 4, 0, 2), ((1, 1), (1, 1), (1, 1), (1, 0), (1, 1))), + GriddedPerm((1, 3, 4, 0, 2), ((1, 1), (1, 1), (1, 1), (1, 1), (1, 1))), + GriddedPerm((1, 4, 0, 3, 2), ((0, 1), (0, 1), (0, 1), (0, 1), (0, 1))), + GriddedPerm((1, 4, 0, 3, 2), ((1, 0), (1, 0), (1, 0), (1, 0), (1, 0))), + GriddedPerm((1, 4, 0, 3, 2), ((1, 0), (1, 1), (1, 0), (1, 0), (1, 0))), + GriddedPerm((1, 4, 0, 3, 2), ((1, 1), (1, 1), (1, 0), (1, 1), (1, 1))), + GriddedPerm((1, 4, 0, 3, 2), ((1, 1), (1, 1), (1, 1), (1, 1), (1, 1))), + GriddedPerm((1, 4, 3, 0, 2), ((0, 1), (0, 1), (0, 1), (0, 1), (0, 1))), + GriddedPerm((1, 4, 3, 0, 2), ((1, 0), (1, 0), (1, 0), (1, 0), (1, 0))), + GriddedPerm((1, 4, 3, 0, 2), ((1, 0), (1, 1), (1, 0), (1, 0), (1, 0))), + GriddedPerm((1, 4, 3, 0, 2), ((1, 1), (1, 1), (1, 1), (1, 0), (1, 1))), + GriddedPerm((1, 4, 3, 0, 2), ((1, 1), (1, 1), (1, 1), (1, 1), (1, 1))), + GriddedPerm((3, 1, 0, 4, 2), ((0, 1), (0, 1), (0, 1), (0, 1), (0, 1))), + GriddedPerm((3, 1, 0, 4, 2), ((0, 1), (1, 1), (1, 0), (1, 1), (1, 1))), + GriddedPerm((3, 1, 0, 4, 2), ((0, 1), (1, 1), (1, 1), (1, 1), (1, 1))), + GriddedPerm((3, 1, 0, 4, 2), ((1, 0), (1, 0), (1, 0), (1, 0), (1, 0))), + GriddedPerm((3, 1, 0, 4, 2), ((1, 0), (1, 0), (1, 0), (1, 1), (1, 0))), + GriddedPerm((3, 1, 0, 4, 2), ((1, 1), (1, 1), (1, 0), (1, 1), (1, 1))), + GriddedPerm((3, 1, 0, 4, 2), ((1, 1), (1, 1), (1, 1), (1, 1), (1, 1))), + GriddedPerm((3, 1, 4, 0, 2), ((0, 1), (0, 1), (0, 1), (0, 1), (0, 1))), + GriddedPerm((3, 1, 4, 0, 2), ((0, 1), (1, 1), (1, 1), (1, 0), (1, 1))), + GriddedPerm((3, 1, 4, 0, 2), ((0, 1), (1, 1), (1, 1), (1, 1), (1, 1))), + GriddedPerm((3, 1, 4, 0, 2), ((1, 0), (1, 0), (1, 0), (1, 0), (1, 0))), + GriddedPerm((3, 1, 4, 0, 2), ((1, 0), (1, 0), (1, 1), (1, 0), (1, 0))), + GriddedPerm((3, 1, 4, 0, 2), ((1, 1), (1, 1), (1, 1), (1, 0), (1, 1))), + GriddedPerm((3, 1, 4, 0, 2), ((1, 1), (1, 1), (1, 1), (1, 1), (1, 1))), + ), + requirements=((GriddedPerm((1, 0), ((1, 0), (1, 0))),),), + assumptions=(), + ) == Tiling( + obstructions=( + GriddedPerm((0, 2, 3, 1), ((0, 1), (0, 1), (0, 1), (1, 1))), + GriddedPerm((0, 2, 3, 1), ((0, 1), (0, 1), (1, 1), (1, 1))), + GriddedPerm((0, 2, 3, 1), ((0, 1), (1, 1), (1, 1), (1, 1))), + GriddedPerm((0, 3, 2, 1), ((0, 1), (0, 1), (0, 1), (1, 1))), + GriddedPerm((0, 3, 2, 1), ((0, 1), (0, 1), (1, 1), (1, 1))), + GriddedPerm((0, 3, 2, 1), ((0, 1), (1, 1), (1, 1), (1, 1))), + GriddedPerm((1, 0, 2, 3), ((1, 0), (1, 0), (1, 1), (1, 1))), + GriddedPerm((1, 0, 3, 2), ((1, 0), (1, 0), (1, 1), (1, 1))), + GriddedPerm((1, 2, 0, 3), ((1, 0), (1, 1), (1, 0), (1, 1))), + GriddedPerm((1, 2, 3, 0), ((1, 0), (1, 1), (1, 1), (1, 0))), + GriddedPerm((1, 3, 0, 2), ((1, 0), (1, 1), (1, 0), (1, 1))), + GriddedPerm((1, 3, 2, 0), ((1, 0), (1, 1), (1, 1), (1, 0))), + GriddedPerm((2, 0, 3, 1), ((0, 1), (0, 1), (0, 1), (1, 1))), + GriddedPerm((2, 0, 3, 1), ((0, 1), (0, 1), (1, 1), (1, 1))), + GriddedPerm((2, 1, 0, 3), ((0, 1), (1, 0), (1, 0), (1, 1))), + GriddedPerm((2, 1, 0, 3), ((1, 1), (1, 0), (1, 0), (1, 1))), + GriddedPerm((2, 1, 3, 0), ((0, 1), (1, 0), (1, 1), (1, 0))), + GriddedPerm((2, 1, 3, 0), ((1, 1), (1, 0), (1, 1), (1, 0))), + GriddedPerm((1, 0, 3, 4, 2), ((0, 1), (0, 1), (0, 1), (0, 1), (0, 1))), + GriddedPerm((1, 0, 3, 4, 2), ((1, 0), (1, 0), (1, 0), (1, 0), (1, 0))), + GriddedPerm((1, 0, 3, 4, 2), ((1, 0), (1, 0), (1, 0), (1, 1), (1, 0))), + GriddedPerm((1, 0, 3, 4, 2), ((1, 1), (1, 0), (1, 1), (1, 1), (1, 1))), + GriddedPerm((1, 0, 3, 4, 2), ((1, 1), (1, 1), (1, 1), (1, 1), (1, 1))), + GriddedPerm((1, 0, 4, 3, 2), ((0, 1), (0, 1), (0, 1), (0, 1), (0, 1))), + GriddedPerm((1, 0, 4, 3, 2), ((1, 0), (1, 0), (1, 0), (1, 0), (1, 0))), + GriddedPerm((1, 0, 4, 3, 2), ((1, 0), (1, 0), (1, 1), (1, 0), (1, 0))), + GriddedPerm((1, 0, 4, 3, 2), ((1, 1), (1, 0), (1, 1), (1, 1), (1, 1))), + GriddedPerm((1, 0, 4, 3, 2), ((1, 1), (1, 1), (1, 1), (1, 1), (1, 1))), + GriddedPerm((1, 3, 0, 4, 2), ((0, 1), (0, 1), (0, 1), (0, 1), (0, 1))), + GriddedPerm((1, 3, 0, 4, 2), ((1, 0), (1, 0), (1, 0), (1, 0), (1, 0))), + GriddedPerm((1, 3, 0, 4, 2), ((1, 0), (1, 0), (1, 0), (1, 1), (1, 0))), + GriddedPerm((1, 3, 0, 4, 2), ((1, 1), (1, 1), (1, 0), (1, 1), (1, 1))), + GriddedPerm((1, 3, 0, 4, 2), ((1, 1), (1, 1), (1, 1), (1, 1), (1, 1))), + GriddedPerm((1, 3, 4, 0, 2), ((0, 1), (0, 1), (0, 1), (0, 1), (0, 1))), + GriddedPerm((1, 3, 4, 0, 2), ((1, 0), (1, 0), (1, 0), (1, 0), (1, 0))), + GriddedPerm((1, 3, 4, 0, 2), ((1, 0), (1, 0), (1, 1), (1, 0), (1, 0))), + GriddedPerm((1, 3, 4, 0, 2), ((1, 1), (1, 1), (1, 1), (1, 0), (1, 1))), + GriddedPerm((1, 3, 4, 0, 2), ((1, 1), (1, 1), (1, 1), (1, 1), (1, 1))), + GriddedPerm((1, 4, 0, 3, 2), ((0, 1), (0, 1), (0, 1), (0, 1), (0, 1))), + GriddedPerm((1, 4, 0, 3, 2), ((1, 0), (1, 0), (1, 0), (1, 0), (1, 0))), + GriddedPerm((1, 4, 0, 3, 2), ((1, 0), (1, 1), (1, 0), (1, 0), (1, 0))), + GriddedPerm((1, 4, 0, 3, 2), ((1, 1), (1, 1), (1, 0), (1, 1), (1, 1))), + GriddedPerm((1, 4, 0, 3, 2), ((1, 1), (1, 1), (1, 1), (1, 1), (1, 1))), + GriddedPerm((1, 4, 3, 0, 2), ((0, 1), (0, 1), (0, 1), (0, 1), (0, 1))), + GriddedPerm((1, 4, 3, 0, 2), ((1, 0), (1, 0), (1, 0), (1, 0), (1, 0))), + GriddedPerm((1, 4, 3, 0, 2), ((1, 0), (1, 1), (1, 0), (1, 0), (1, 0))), + GriddedPerm((1, 4, 3, 0, 2), ((1, 1), (1, 1), (1, 1), (1, 0), (1, 1))), + GriddedPerm((1, 4, 3, 0, 2), ((1, 1), (1, 1), (1, 1), (1, 1), (1, 1))), + GriddedPerm((3, 1, 0, 4, 2), ((0, 1), (0, 1), (0, 1), (0, 1), (0, 1))), + GriddedPerm((3, 1, 0, 4, 2), ((0, 1), (1, 1), (1, 0), (1, 1), (1, 1))), + GriddedPerm((3, 1, 0, 4, 2), ((0, 1), (1, 1), (1, 1), (1, 1), (1, 1))), + GriddedPerm((3, 1, 0, 4, 2), ((1, 0), (1, 0), (1, 0), (1, 0), (1, 0))), + GriddedPerm((3, 1, 0, 4, 2), ((1, 0), (1, 0), (1, 0), (1, 1), (1, 0))), + GriddedPerm((3, 1, 0, 4, 2), ((1, 1), (1, 1), (1, 0), (1, 1), (1, 1))), + GriddedPerm((3, 1, 0, 4, 2), ((1, 1), (1, 1), (1, 1), (1, 1), (1, 1))), + GriddedPerm((3, 1, 4, 0, 2), ((0, 1), (0, 1), (0, 1), (0, 1), (0, 1))), + GriddedPerm((3, 1, 4, 0, 2), ((0, 1), (1, 1), (1, 1), (1, 0), (1, 1))), + GriddedPerm((3, 1, 4, 0, 2), ((0, 1), (1, 1), (1, 1), (1, 1), (1, 1))), + GriddedPerm((3, 1, 4, 0, 2), ((1, 0), (1, 0), (1, 0), (1, 0), (1, 0))), + GriddedPerm((3, 1, 4, 0, 2), ((1, 0), (1, 0), (1, 1), (1, 0), (1, 0))), + GriddedPerm((3, 1, 4, 0, 2), ((1, 1), (1, 1), (1, 1), (1, 0), (1, 1))), + GriddedPerm((3, 1, 4, 0, 2), ((1, 1), (1, 1), (1, 1), (1, 1), (1, 1))), + ), + requirements=((GriddedPerm((1, 0), ((1, 0), (1, 0))),),), + assumptions=(), + ) diff --git a/tilings/algorithms/gridded_perm_reduction.py b/tilings/algorithms/gridded_perm_reduction.py index 070cc178..990b0f6e 100644 --- a/tilings/algorithms/gridded_perm_reduction.py +++ b/tilings/algorithms/gridded_perm_reduction.py @@ -169,7 +169,7 @@ def remove_redundant(self, requirements: List[Requirement]) -> List[Requirement] ( gps for gps in islice(requirements, idx + 1, None) - if gps != requirement + if sorted(gps) != sorted(requirement) ), ), ): From 1fce66026d4ff4e102274df9d385f6de0eb0ec36 Mon Sep 17 00:00:00 2001 From: jaypantone Date: Wed, 15 Jun 2022 07:03:09 -0500 Subject: [PATCH 057/100] adds default option (#481) --- tilings/strategies/requirement_insertion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tilings/strategies/requirement_insertion.py b/tilings/strategies/requirement_insertion.py index d96767e7..82e5b29b 100644 --- a/tilings/strategies/requirement_insertion.py +++ b/tilings/strategies/requirement_insertion.py @@ -462,7 +462,7 @@ def from_dict(cls, d: dict) -> "RequirementInsertionWithRestrictionFactory": d.pop("extra_basis") limited_insertion = d.pop("limited_insertion") maxreqlen = d.pop("maxreqlen") - allow_factorable_insertions = d.pop("allow_factorable_insertions") + allow_factorable_insertions = d.pop("allow_factorable_insertions", False) return cls( maxreqlen=maxreqlen, extra_basis=extra_basis, From 947e19aa751b8edabc74358ce6b5701e56605d8c Mon Sep 17 00:00:00 2001 From: jaypantone Date: Wed, 15 Jun 2022 07:03:40 -0500 Subject: [PATCH 058/100] makes an option for whether full tiling assumptions count against cap (#482) --- CHANGELOG.md | 4 +++- tilings/tilescope.py | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90d8de51..5d1bc096 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,9 @@ swapped around a fusable row or column. strategy so should be used with `RuleDBForest`. - `UnfusionFactory` that unfuses either all the rows or columns. Also non-productive. - `FusableRowAndColumnPlacementFactory` places fusable rows and columns. +- added the option `ignore_full_tiling_assumptions` to `LimitedAssumptionTileScope` + and therefore also to `TrackedSearcher`. If set to `True`, then full-tiling + assumptions do not count against the `max_assumptions` cap. The default is `False`. ### Fixed - `Factor` was not factoring correctly with respect to component assumptions. @@ -66,7 +69,6 @@ swapped around a fusable row or column. whole tiling. - The default behavior for `RequirementInsertion` is to allow insertion of factorable requirements -- Assumptions that cover a whole tiling do not count against the max_assumptions limit ### Removed - `AddInterleavingAssumptionsFactory`. The factor strategy now adds the relevant diff --git a/tilings/tilescope.py b/tilings/tilescope.py index 23071559..25b4e906 100644 --- a/tilings/tilescope.py +++ b/tilings/tilescope.py @@ -91,10 +91,12 @@ def __init__( start_class: Union[str, Iterable[Perm], Tiling], strategy_pack: TileScopePack, max_assumptions: int, + ignore_full_tiling_assumptions: bool = False, **kwargs, ) -> None: super().__init__(start_class, strategy_pack, **kwargs) self.max_assumptions = max_assumptions + self.ignore_full_tiling_assumptions = ignore_full_tiling_assumptions def _expand( self, @@ -113,7 +115,8 @@ def num_child_assumptions(child: Tiling) -> int: return sum( 1 for ass in child.assumptions - if len(ass.gps) != len(child.active_cells) + if (not self.ignore_full_tiling_assumptions) + or len(ass.gps) != len(child.active_cells) ) if inferral: From d43284ce8e1007934822d8bbb44046f12e981fd9 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Wed, 15 Jun 2022 13:46:52 -0500 Subject: [PATCH 059/100] one by one looks up permpal (#483) * one by one looks up permpal * remove cached property * more detailed error messages --- CHANGELOG.md | 2 + tilings/algorithms/enumeration.py | 28 +++++------ tilings/strategies/verification.py | 81 +++++++++++++++++++++++++++++- 3 files changed, 95 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d1bc096..32d2c694 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,8 @@ swapped around a fusable row or column. whole tiling. - The default behavior for `RequirementInsertion` is to allow insertion of factorable requirements +- `OneByOneVerificationStrategy` will look up permpal.com to find the generating + functions and min polys. ### Removed - `AddInterleavingAssumptionsFactory`. The factor strategy now adds the relevant diff --git a/tilings/algorithms/enumeration.py b/tilings/algorithms/enumeration.py index b402b8e4..d289c030 100644 --- a/tilings/algorithms/enumeration.py +++ b/tilings/algorithms/enumeration.py @@ -7,6 +7,8 @@ from sympy import Expr, Function, Symbol, diff, simplify, sympify, var from comb_spec_searcher.utils import taylor_expand +from permuta import Av +from permuta.permutils.symmetry import lex_min from tilings.exception import InvalidOperationError from tilings.griddedperm import GriddedPerm from tilings.misc import is_tree @@ -119,20 +121,18 @@ def get_genf(self, **kwargs) -> Expr: return 1 if self.tiling == self.tiling.__class__.from_string("01_10"): return 1 + x - if self.tiling in ( - self.tiling.__class__.from_string("01"), - self.tiling.__class__.from_string("10"), - ): - return 1 / (1 - x) - if self.tiling in ( - self.tiling.__class__.from_string("123"), - self.tiling.__class__.from_string("321"), - ): - return sympify("-1/2*(sqrt(-4*x + 1) - 1)/x") - # TODO: should this create a spec as in the strategy? - raise NotImplementedError( - f"Look up the combopal database for:\n{self.tiling}" - ) + basis = [ob.patt for ob in self.tiling.obstructions] + basis_str = "_".join(map(str, lex_min(basis))) + uri = f"https://permpal.com/perms/raw_data_json/basis/{basis_str}" + request = requests.get(uri) + if request.status_code == 404: + raise NotImplementedError(f"No entry on permpal for {Av(basis)}") + data = request.json() + if data["generating_function_sympy"] is None: + raise NotImplementedError( + f"No explicit generating function on permpal for {Av(basis)}" + ) + return sympify(data["generating_function_sympy"]) gf = None if MonotoneTreeEnumeration(self.tiling).verified(): gf = MonotoneTreeEnumeration(self.tiling).get_genf() diff --git a/tilings/strategies/verification.py b/tilings/strategies/verification.py index 42c94106..9f246a74 100644 --- a/tilings/strategies/verification.py +++ b/tilings/strategies/verification.py @@ -2,9 +2,10 @@ from functools import reduce from itertools import chain from operator import mul -from typing import Dict, Iterator, Optional, Tuple, cast +from typing import Callable, Dict, Iterator, Optional, Tuple, cast -from sympy import Expr, Function, var +import requests +from sympy import Eq, Expr, Function, solve, sympify, var from comb_spec_searcher import ( AtomStrategy, @@ -13,11 +14,13 @@ VerificationStrategy, ) from comb_spec_searcher.exception import InvalidOperationError, StrategyDoesNotApply +from comb_spec_searcher.strategies import VerificationRule from comb_spec_searcher.typing import Objects, Terms from permuta import Av, Perm from permuta.permutils import ( is_insertion_encodable_maximum, is_insertion_encodable_rightmost, + lex_min, ) from tilings import GriddedPerm, Tiling from tilings.algorithms import locally_factorable_shift @@ -125,6 +128,69 @@ def __repr__(self) -> str: return f"{self.__class__.__name__}()" +class OneByOneVerificationRule(VerificationRule[Tiling, GriddedPerm]): + def get_equation( + self, + get_function: Callable[[Tiling], Function], + funcs: Optional[Dict[Tiling, Function]] = None, + ) -> Eq: + # Find the minimal polynomial for the underlying class + basis = [ob.patt for ob in self.comb_class.obstructions] + basis_str = "_".join(map(str, lex_min(basis))) + uri = f"https://permpal.com/perms/raw_data_json/basis/{basis_str}" + request = requests.get(uri) + if request.status_code == 404: + return super().get_equation(get_function, funcs) + data = request.json() + min_poly = data["min_poly_maple"] + if min_poly is None: + raise NotImplementedError(f"No min poly on permpal for {Av(basis)}") + min_poly = min_poly.replace("^", "**").replace("F(x)", "F") + lhs, _ = min_poly.split("=") + # We now need to worry about the requirements. The min poly we got is + # for the class with requirements. + eq = Eq(self.without_req_genf(self.comb_class), get_function(self.comb_class)) + subs = solve([eq], var("F"), dict=True)[0] + if self.comb_class.assumptions: + subs["x"] = var("x") * var("k_0") + res, _ = sympify(lhs).subs(subs, simultaneous=True).as_numer_denom() + # Pick the unique factor that contains F + for factor in res.as_ordered_factors(): + if factor.atoms(Function): + res = factor + return Eq(res, 0) + + @property + def no_req_tiling(self) -> Tiling: + return self.comb_class.__class__( + self.comb_class.obstructions, tuple(), self.comb_class.assumptions + ) + + def without_req_genf(self, tiling: Tiling): + """ + Find the equation for the tiling in terms of F, the generating + function where the reqs are reomoved from tiling. + """ + if tiling == self.no_req_tiling: + return var("F") + if tiling.requirements: + reqs = tiling.requirements[0] + avoided = tiling.__class__( + tiling.obstructions + reqs, + tiling.requirements[1:], + tiling.assumptions, + ) + without = tiling.__class__( + tiling.obstructions, + tiling.requirements[1:], + tiling.assumptions, + ) + avgf = self.without_req_genf(avoided) + wogf = self.without_req_genf(without) + return wogf - avgf + return LocalEnumeration(tiling).get_genf() + + class OneByOneVerificationStrategy(BasisAwareVerificationStrategy): @staticmethod def pack(comb_class: Tiling) -> StrategyPack: @@ -176,6 +242,17 @@ def pack(comb_class: Tiling) -> StrategyPack: f"subclass Av({basis})" ) + def __call__( + self, + comb_class: Tiling, + children: Tuple[Tiling, ...] = None, + ) -> OneByOneVerificationRule: + if children is None: + children = self.decomposition_function(comb_class) + if children is None: + raise StrategyDoesNotApply("The combinatorial class is not verified") + return OneByOneVerificationRule(self, comb_class, children) + def verified(self, comb_class: Tiling) -> bool: if not comb_class.dimensions == (1, 1): return False From 49105423255ccca38e7de4681f84226100cdec1a Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Wed, 15 Jun 2022 13:51:00 -0500 Subject: [PATCH 060/100] reverse fusion uses divide by k (#484) * reverse fusion uses divide by k * mypy --- tilings/strategies/fusion/fusion.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/tilings/strategies/fusion/fusion.py b/tilings/strategies/fusion/fusion.py index 3f9837ac..932b117f 100644 --- a/tilings/strategies/fusion/fusion.py +++ b/tilings/strategies/fusion/fusion.py @@ -1,5 +1,5 @@ from collections import defaultdict -from itertools import islice +from itertools import chain, islice from random import randint from typing import Dict, Iterator, List, Optional, Set, Tuple, cast @@ -10,6 +10,7 @@ from tilings import GriddedPerm, Tiling from tilings.algorithms import Fusion +from ..pointing import DivideByK from .constructor import FusionConstructor, ReverseFusionConstructor @@ -157,7 +158,7 @@ def fusion_algorithm(self, tiling: Tiling) -> Fusion: tiling, row_idx=self.row_idx, col_idx=self.col_idx, tracked=self.tracked ) - def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling, ...]: + def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling]: algo = self.fusion_algorithm(comb_class) if algo.fusable(): return (algo.fused_tiling(),) @@ -217,6 +218,8 @@ def reverse_constructor( # pylint: disable=no-self-use 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) # Need to recompute some info to count, so ignoring passed in children algo = self.fusion_algorithm(comb_class) if not algo.fusable(): @@ -232,6 +235,21 @@ def reverse_constructor( # pylint: disable=no-self-use right_sided_params, _, ) = self.left_right_both_sided_parameters(comb_class) + if not left_sided_params and not right_sided_params: + fused_assumption = algo.new_assumption() + unfused_assumption = fused_assumption.__class__( + chain.from_iterable( + algo.unfuse_gridded_perm(gp) for gp in fused_assumption.gps + ) + ) + assert unfused_assumption in comb_class.assumptions + return DivideByK( + comb_class, + children, + 1, + comb_class.get_assumption_parameter(unfused_assumption), + self.extra_parameters(comb_class, children), + ) return ReverseFusionConstructor( comb_class, child, From 31fcbc59eecee2ec05a94f85d166e37ab583e735 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Thu, 16 Jun 2022 14:24:34 -0500 Subject: [PATCH 061/100] forbid fusing if assumption not entirely on left or right (#485) --- tilings/algorithms/fusion.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/tilings/algorithms/fusion.py b/tilings/algorithms/fusion.py index 33aba358..6e471e3c 100644 --- a/tilings/algorithms/fusion.py +++ b/tilings/algorithms/fusion.py @@ -3,7 +3,16 @@ """ import collections from itertools import chain -from typing import TYPE_CHECKING, Counter, Iterable, Iterator, List, Optional, Tuple +from typing import ( + TYPE_CHECKING, + Counter, + FrozenSet, + Iterable, + Iterator, + List, + Optional, + Tuple, +) from tilings.assumptions import ( ComponentAssumption, @@ -278,6 +287,12 @@ def _can_fuse_assumption( are all contained entirely on the left of the fusion region, entirely on the right, or split in every possible way. """ + left, right = self._left_fuse_region(), self._right_fuse_region() + cells = set(gp.pos[0] for gp in assumption.gps) + if (left.intersection(cells) and not left.issubset(cells)) or ( + right.intersection(cells) and not right.issubset(cells) + ): + return False return self._can_fuse_set_of_gridded_perms(fuse_counter) or ( not isinstance(assumption, ComponentAssumption) and ( @@ -286,6 +301,16 @@ def _can_fuse_assumption( ) ) + def _left_fuse_region(self) -> FrozenSet[Cell]: + if self._fuse_row: + return self._tiling.cells_in_row(self._row_idx) + return self._tiling.cells_in_col(self._col_idx) + + def _right_fuse_region(self) -> FrozenSet[Cell]: + if self._fuse_row: + return self._tiling.cells_in_row(self._row_idx + 1) + return self._tiling.cells_in_col(self._col_idx + 1) + def _is_one_sided_assumption(self, assumption: TrackingAssumption) -> bool: """ Return True if all of the assumption is contained either entirely on From 28a61f868c55f4944014529f728039f68f2a0ed9 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Wed, 29 Jun 2022 13:12:54 +0000 Subject: [PATCH 062/100] stop expanding av(012), and return invalid op for comp ver (#487) * stop expanding av(012), and return invalid op for comp ver * remove outdated test * flake Co-authored-by: Jay Pantone --- tests/strategies/test_verification.py | 12 ++++++++---- tilings/strategies/verification.py | 15 +++++---------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/tests/strategies/test_verification.py b/tests/strategies/test_verification.py index d80536ee..1add7e22 100644 --- a/tests/strategies/test_verification.py +++ b/tests/strategies/test_verification.py @@ -1018,15 +1018,19 @@ def test_pack(self, strategy, enum_verified): [Perm((0, 1, 2)), Perm((2, 3, 0, 1))] ) - assert strategy.pack(enum_verified[5]) == TileScopePack.row_and_col_placements( - row_only=True - ).make_fusion(tracked=True).add_basis([Perm((0, 1, 2))]) + # assert strategy.pack( + # enum_verified[5] + # ) == TileScopePack.row_and_col_placements(row_only=True).make_fusion( + # tracked=True + # ).add_basis( + # [Perm((0, 1, 2))] + # ) with pytest.raises(InvalidOperationError): strategy.pack(enum_verified[6]) @pytest.mark.timeout(120) def test_get_specification(self, strategy, enum_verified): - for tiling in enum_verified[:-1]: + for tiling in enum_verified[:-2]: spec = strategy.get_specification(tiling) assert isinstance(spec, CombinatorialSpecification) with pytest.raises(InvalidOperationError): diff --git a/tilings/strategies/verification.py b/tilings/strategies/verification.py index 9f246a74..f5c0591d 100644 --- a/tilings/strategies/verification.py +++ b/tilings/strategies/verification.py @@ -224,15 +224,10 @@ def pack(comb_class: Tiling) -> StrategyPack: and len(comb_class.requirements[0]) == 1 and len(comb_class.requirements[0][0]) <= 2 ): - if basis in ([Perm((0, 1, 2))], [Perm((2, 1, 0))]): - # Av(123) or Av(321) - use fusion! - return ( - TileScopePack.row_and_col_placements(row_only=True) - .make_fusion(tracked=True) - .add_basis(basis) - ) - if (Perm((0, 1, 2)) in basis or Perm((2, 1, 0)) in basis) and all( - len(p) <= 4 for p in basis + if ( + (Perm((0, 1, 2)) in basis or Perm((2, 1, 0)) in basis) + and all(len(p) <= 4 for p in basis) + and len(basis) > 1 ): # is a subclass of Av(123) avoiding patterns of length <= 4 # experimentally showed that such clsses always terminates @@ -316,7 +311,7 @@ class ComponentVerificationStrategy(TileScopeVerificationStrategy): @staticmethod def pack(comb_class: Tiling) -> StrategyPack: - raise NotImplementedError("No pack for removing component assumption") + raise InvalidOperationError("No pack for removing component assumption") @staticmethod def verified(comb_class: Tiling) -> bool: From 8b7827f733ebe4568a816a1826be5c2b34eb9a89 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Fri, 15 Jul 2022 18:27:28 +0000 Subject: [PATCH 063/100] change error to logging check (#489) * change error to logging check * Update test_reverse_fusion.py --- tests/strategies/test_reverse_fusion.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/tests/strategies/test_reverse_fusion.py b/tests/strategies/test_reverse_fusion.py index 04970c62..44247b16 100644 --- a/tests/strategies/test_reverse_fusion.py +++ b/tests/strategies/test_reverse_fusion.py @@ -1,8 +1,13 @@ +import logging + import pytest +from logzero import logger from tilings import GriddedPerm, Tiling, TrackingAssumption from tilings.strategies.fusion import FusionStrategy +LOGGER = logging.getLogger(__name__) + def reverse_fusion_rules(): t = Tiling( @@ -89,7 +94,7 @@ def test_sanity_check(rule): assert rule.sanity_check(length) -def test_test_positive_reverse_fusion(): +def test_positive_reverse_fusion(caplog): t = Tiling( obstructions=[ GriddedPerm((0, 1), [(0, 0), (0, 0)]), @@ -102,5 +107,11 @@ def test_test_positive_reverse_fusion(): rule = FusionStrategy(col_idx=0, tracked=True)(t) assert rule.is_reversible() reverse_rule = rule.to_reverse_rule(0) - with pytest.raises(NotImplementedError): + logger.propagate = True + with caplog.at_level(logging.WARNING): reverse_rule.sanity_check(4) + assert len(caplog.records) == 2 + assert ( + "Skipping sanity checking counts" in caplog.text + and "Skipping sanity checking generation" in caplog.text + ) From cc9927af80acec3dab0da719b3e39c06f79a400f Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Fri, 22 Jul 2022 16:42:44 +0000 Subject: [PATCH 064/100] need to pass is empty to fusion is equiv now (#491) --- tilings/strategies/factor.py | 2 +- tilings/strategies/fusion/fusion.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tilings/strategies/factor.py b/tilings/strategies/factor.py index 1b139552..0a36dfff 100644 --- a/tilings/strategies/factor.py +++ b/tilings/strategies/factor.py @@ -199,7 +199,7 @@ def __init__( self.insertion_constructor = insertion_constructor @staticmethod - def is_equivalence() -> bool: + def is_equivalence(is_empty: Optional[Callable[[Tiling], bool]] = None) -> bool: return False @staticmethod diff --git a/tilings/strategies/fusion/fusion.py b/tilings/strategies/fusion/fusion.py index 932b117f..faea799e 100644 --- a/tilings/strategies/fusion/fusion.py +++ b/tilings/strategies/fusion/fusion.py @@ -1,7 +1,7 @@ from collections import defaultdict from itertools import chain, islice from random import randint -from typing import Dict, Iterator, List, Optional, Set, Tuple, cast +from typing import Callable, Dict, Iterator, List, Optional, Set, Tuple, cast from comb_spec_searcher import Constructor, Strategy, StrategyFactory from comb_spec_searcher.exception import StrategyDoesNotApply @@ -30,7 +30,7 @@ def constructor(self) -> FusionConstructor: return cast(FusionConstructor, super().constructor) @staticmethod - def is_equivalence() -> bool: + def is_equivalence(is_empty: Optional[Callable[[Tiling], bool]] = None) -> bool: return False def _ensure_level_objects(self, n: int) -> None: From 9ac581489c51d0213d1ba202f31472cbffb20b7d Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Wed, 27 Jul 2022 16:12:21 +0000 Subject: [PATCH 065/100] ensure obs are minimised in point placements (#494) * ensure obs are minimised in point placements * changelog --- CHANGELOG.md | 2 ++ tilings/algorithms/requirement_placement.py | 12 ++++++++---- tilings/tilescope.py | 5 +++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32d2c694..71b1ea1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,8 @@ swapped around a fusable row or column. - `RearrangeAssumptionFactory` will ignore component assumptions - `GriddedPermReduction.minimal_reqs` was removing requirements if they were duplicates. +- `RequirementPlacement` algorithm didn't minimise obstructions correctly when + placing size 2 or higher gridded perms. ### Changed - `TileScopePack.make_tracked` will add the appropriate tracking methods for diff --git a/tilings/algorithms/requirement_placement.py b/tilings/algorithms/requirement_placement.py index 458e0625..a3997518 100644 --- a/tilings/algorithms/requirement_placement.py +++ b/tilings/algorithms/requirement_placement.py @@ -1,4 +1,4 @@ -from itertools import chain +from itertools import chain, filterfalse from typing import TYPE_CHECKING, Dict, FrozenSet, Iterable, List, Optional, Tuple from permuta.misc import DIR_EAST, DIR_NONE, DIR_NORTH, DIR_SOUTH, DIR_WEST, DIRS @@ -377,15 +377,19 @@ def place_point_of_req( if include_not: res.append(self._tiling.__class__(obs + rem_req, reqs, ass)) continue - forced_obs = self.forced_obstructions_from_requirement( gps, indices, cell, direction ) + forced_obs = [ + o1 + for o1 in forced_obs + if not any(o2 in o1 for o2 in filterfalse(o1.__eq__, forced_obs)) + ] reduced_obs = [o1 for o1 in obs if not any(o2 in o1 for o2 in forced_obs)] - + reduced_obs.extend(filterfalse(reduced_obs.__contains__, forced_obs)) res.append( self._tiling.__class__( - reduced_obs + forced_obs, + reduced_obs, reqs + [rem_req], assumptions=ass, already_minimized_obs=True, diff --git a/tilings/tilescope.py b/tilings/tilescope.py index 25b4e906..ead482de 100644 --- a/tilings/tilescope.py +++ b/tilings/tilescope.py @@ -356,11 +356,12 @@ def status(self) -> str: f"Queue {idx}" for idx in range(len(self.queues) - 1) ) underlying = ("underlying",) + tuple( - self._underlyng_labels_per_level[level] for level in range(len(self.queues)) + str(self._underlyng_labels_per_level[level]) + for level in range(len(self.queues)) ) table.append(underlying) all_labels = ("all labels",) + tuple( - self._all_labels_per_level[level] for level in range(len(self.queues)) + str(self._all_labels_per_level[level]) for level in range(len(self.queues)) ) table.append(all_labels) table = [headers] + table From 8a230e39325cfd87cf72277d49631cf611195c14 Mon Sep 17 00:00:00 2001 From: Henning Ulfarsson Date: Mon, 28 Nov 2022 14:27:25 +0000 Subject: [PATCH 066/100] Work in process getting rid of the new warnings (#497) * Work in process getting rid of the new warnings * Updating black version in the pre-commit file * fixing pylint * Fixing typing errors * Still fixing typing issues * No more typing today, thank you * A few more typing issues taken care of * A bit of linting * Pylint is now happy * Mypy is now happy * Quick fix for typing error of array --- .pre-commit-config.yaml | 2 +- mypy.ini | 1 + pylintrc | 83 +---------------- tests/test_bijections.py | 3 +- tests/test_griddedperm.py | 3 +- tilings/algorithms/enumeration.py | 23 +++-- tilings/algorithms/fusion.py | 6 +- tilings/algorithms/gridded_perm_generation.py | 4 +- .../algorithms/locally_factorable_shift.py | 4 +- tilings/algorithms/obstruction_inferral.py | 4 +- .../algorithms/obstruction_transitivity.py | 16 ++-- tilings/algorithms/row_col_separation.py | 9 +- tilings/assumptions.py | 24 ++--- tilings/griddedperm.py | 25 +++-- tilings/misc.py | 22 ++++- tilings/strategies/assumption_insertion.py | 13 +-- tilings/strategies/assumption_splitting.py | 18 ++-- tilings/strategies/cell_reduction.py | 12 +-- tilings/strategies/deflation.py | 12 +-- tilings/strategies/detect_components.py | 19 ++-- tilings/strategies/factor.py | 10 +- tilings/strategies/fusion/constructor.py | 4 +- tilings/strategies/fusion/fusion.py | 20 ++-- tilings/strategies/obstruction_inferral.py | 2 +- tilings/strategies/point_jumping.py | 36 ++------ tilings/strategies/rearrange_assumption.py | 12 +-- tilings/strategies/row_and_col_separation.py | 8 +- tilings/strategies/symmetry.py | 23 ++--- tilings/strategies/verification.py | 91 ++++++++----------- tilings/strategy_pack.py | 15 ++- tilings/tilescope.py | 3 +- tilings/tiling.py | 5 +- tox.ini | 18 ++-- 33 files changed, 212 insertions(+), 338 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e3d11388..fe30c3b4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ repos: - repo: https://github.com/psf/black - rev: 22.3.0 + rev: 22.10.0 hooks: - id: black diff --git a/mypy.ini b/mypy.ini index 34a4b00a..31d8ccf7 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,4 +1,5 @@ [mypy] +check_untyped_defs = True warn_return_any = True warn_unused_configs = True warn_no_return = False diff --git a/pylintrc b/pylintrc index a3471211..7ef81862 100644 --- a/pylintrc +++ b/pylintrc @@ -28,7 +28,7 @@ limit-inference-results=100 # List of plugins (as comma separated values of python module names) to load, # usually to register additional checkers. -load-plugins= +load-plugins=pylint.extensions.no_self_use # Pickle collected data for later comparisons. persistent=yes @@ -76,19 +76,8 @@ disable=missing-class-docstring, inconsistent-return-statements, invalid-name, unused-argument, - bad-continuation, missing-function-docstring, missing-module-docstring, - print-statement, - parameter-unpacking, - unpacking-in-except, - old-raise-syntax, - backtick, - long-suffix, - old-ne-operator, - old-octal-literal, - import-star-module-level, - non-ascii-bytes-literal, raw-checker-failed, bad-inline-option, locally-disabled, @@ -96,68 +85,7 @@ disable=missing-class-docstring, suppressed-message, useless-suppression, deprecated-pragma, - use-symbolic-message-instead, - apply-builtin, - basestring-builtin, - buffer-builtin, - cmp-builtin, - coerce-builtin, - execfile-builtin, - file-builtin, - long-builtin, - raw_input-builtin, - reduce-builtin, - standarderror-builtin, - unicode-builtin, - xrange-builtin, - coerce-method, - delslice-method, - getslice-method, - setslice-method, - no-absolute-import, - old-division, - dict-iter-method, - dict-view-method, - next-method-called, - metaclass-assignment, - indexing-exception, - raising-string, - reload-builtin, - oct-method, - hex-method, - nonzero-method, - cmp-method, - input-builtin, - round-builtin, - intern-builtin, - unichr-builtin, - map-builtin-not-iterating, - zip-builtin-not-iterating, - range-builtin-not-iterating, - filter-builtin-not-iterating, - using-cmp-argument, - eq-without-hash, - div-method, - idiv-method, - rdiv-method, - exception-message-attribute, - invalid-str-codec, - sys-max-int, - bad-python3-import, - deprecated-string-function, - deprecated-str-translate-call, - deprecated-itertools-function, - deprecated-types-field, - next-method-defined, - dict-items-not-iterating, - dict-keys-not-iterating, - dict-values-not-iterating, - deprecated-operator-function, - deprecated-urllib-function, - xreadlines-attribute, - deprecated-sys-function, - exception-escape, - comprehension-escape + use-symbolic-message-instead # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option @@ -224,13 +152,6 @@ max-line-length=100 # Maximum number of lines in a module. max-module-lines=1000 -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma, - dict-separator - # Allow the body of a class to be on the same line as the declaration if body # contains single statement. single-line-class-stmt=no diff --git a/tests/test_bijections.py b/tests/test_bijections.py index bbe1bc41..ad608090 100644 --- a/tests/test_bijections.py +++ b/tests/test_bijections.py @@ -82,7 +82,8 @@ def _tester(basis1: str, basis2: str, max_size=7): def _import_css_example(): r = requests.get( "https://raw.githubusercontent.com/PermutaTriangle" - "/comb_spec_searcher/develop/example.py" + "/comb_spec_searcher/develop/example.py", + timeout=5, ) r.raise_for_status() exec(r.text[: r.text.find("pack = StrategyPack(")], globals()) diff --git a/tests/test_griddedperm.py b/tests/test_griddedperm.py index 36db022d..0d8666db 100644 --- a/tests/test_griddedperm.py +++ b/tests/test_griddedperm.py @@ -184,7 +184,8 @@ def test_is_isolated(simpleob, isolatedob): def test_forced_point_index(singlecellob): - assert singlecellob.forced_point_index((1, 1), DIR_SOUTH) is None + with pytest.raises(ValueError): + singlecellob.forced_point_index((1, 1), DIR_SOUTH) assert singlecellob.forced_point_index((2, 2), DIR_WEST) == 0 assert singlecellob.forced_point_index((2, 2), DIR_SOUTH) == 1 assert singlecellob.forced_point_index((2, 2), DIR_NORTH) == 2 diff --git a/tilings/algorithms/enumeration.py b/tilings/algorithms/enumeration.py index d289c030..c1bec536 100644 --- a/tilings/algorithms/enumeration.py +++ b/tilings/algorithms/enumeration.py @@ -1,10 +1,10 @@ import abc from collections import deque from itertools import chain -from typing import TYPE_CHECKING, Dict, FrozenSet, Iterable, Optional +from typing import TYPE_CHECKING, Any, Dict, FrozenSet, Iterable, Optional import requests -from sympy import Expr, Function, Symbol, diff, simplify, sympify, var +from sympy import Expr, Symbol, diff, simplify, sympify, var from comb_spec_searcher.utils import taylor_expand from permuta import Av @@ -42,6 +42,7 @@ def get_genf(self, **kwargs) -> Expr: """ if not self.verified(): raise InvalidOperationError("The tiling is not verified") + raise NotImplementedError def __repr__(self) -> str: return "Enumeration for:\n" + str(self.tiling) @@ -90,12 +91,12 @@ def _req_is_single_cell(req: Iterable[GriddedPerm]) -> bool: all_cells = chain.from_iterable(gp.pos for gp in req_iter) return all(c == cell for c in all_cells) - def get_genf(self, **kwargs) -> Expr: + def get_genf(self, **kwargs) -> Any: # pylint: disable=too-many-return-statements if not self.verified(): raise InvalidOperationError("The tiling is not verified") - funcs: Optional[Dict["Tiling", Function]] = kwargs.get("funcs") + funcs: Optional[Dict["Tiling", Any]] = kwargs.get("funcs") if funcs is None: funcs = {} if self.tiling.requirements: @@ -124,7 +125,7 @@ def get_genf(self, **kwargs) -> Expr: basis = [ob.patt for ob in self.tiling.obstructions] basis_str = "_".join(map(str, lex_min(basis))) uri = f"https://permpal.com/perms/raw_data_json/basis/{basis_str}" - request = requests.get(uri) + request = requests.get(uri, timeout=10) if request.status_code == 404: raise NotImplementedError(f"No entry on permpal for {Av(basis)}") data = request.json() @@ -203,7 +204,7 @@ def _visted_cells_aligned(self, cell, visited): col_cells = self.tiling.cells_in_col(cell[0]) return (c for c in visited if (c in row_cells or c in col_cells)) - def get_genf(self, **kwargs) -> Expr: + def get_genf(self, **kwargs) -> Any: # pylint: disable=too-many-locals if not self.verified(): raise InvalidOperationError("The tiling is not verified") @@ -242,7 +243,9 @@ def get_genf(self, **kwargs) -> Expr: else: F = self._interleave_fixed_lengths(F_tracked, cell, minlen, maxlen) visited.add(cell) - F = simplify(F.subs({v: 1 for v in F.free_symbols if v != x})) + F = simplify( + F.subs({v: 1 for v in F.free_symbols if v != x}) + ) # type: ignore[operator] # A simple test to warn us if the code is wrong if __debug__: lhs = taylor_expand(F, n=6) @@ -345,7 +348,7 @@ def load_verified_tiling(cls): """ if not DatabaseEnumeration.all_verified_tilings: uri = f"{cls.API_ROOT_URL}/all_verified_tilings" - response = requests.get(uri) + response = requests.get(uri, timeout=10) response.raise_for_status() compressed_tilings = map(bytes.fromhex, response.json()) cls.all_verified_tilings = frozenset(compressed_tilings) @@ -357,7 +360,7 @@ def _get_tiling_entry(self): """ key = self.tiling.to_bytes().hex() search_url = f"{DatabaseEnumeration.API_ROOT_URL}/verified_tiling/key/{key}" - r = requests.get(search_url) + r = requests.get(search_url, timeout=10) if r.status_code == 404: return None r.raise_for_status() @@ -377,7 +380,7 @@ def verified(self): DatabaseEnumeration.load_verified_tiling() return self._get_tiling_entry() is not None - def get_genf(self, **kwargs) -> Expr: + def get_genf(self, **kwargs) -> Any: if not self.verified(): raise InvalidOperationError("The tiling is not verified") return sympify(self._get_tiling_entry()["genf"]) diff --git a/tilings/algorithms/fusion.py b/tilings/algorithms/fusion.py index 6e471e3c..f5092357 100644 --- a/tilings/algorithms/fusion.py +++ b/tilings/algorithms/fusion.py @@ -457,7 +457,7 @@ def __init__( ): if tiling.requirements: raise NotImplementedError( - "Component fusion does not handle " "requirements at the moment" + "Component fusion does not handle requirements at the moment" ) super().__init__( tiling, @@ -518,7 +518,7 @@ def first_cell(self) -> Cell: return self._first_cell if not self._pre_check(): raise RuntimeError( - "Pre-check failed. No component fusion " "possible and no first cell" + "Pre-check failed. No component fusion possible and no first cell" ) assert self._first_cell is not None return self._first_cell @@ -533,7 +533,7 @@ def second_cell(self) -> Cell: return self._second_cell if not self._pre_check(): raise RuntimeError( - "Pre-check failed. No component fusion " "possible and no second cell" + "Pre-check failed. No component fusion possible and no second cell" ) assert self._second_cell is not None return self._second_cell diff --git a/tilings/algorithms/gridded_perm_generation.py b/tilings/algorithms/gridded_perm_generation.py index bd39106c..bb8e4aa7 100644 --- a/tilings/algorithms/gridded_perm_generation.py +++ b/tilings/algorithms/gridded_perm_generation.py @@ -1,5 +1,5 @@ from heapq import heapify, heappop, heappush -from typing import TYPE_CHECKING, Dict, List, Set, Tuple +from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple from tilings.griddedperm import GriddedPerm @@ -53,7 +53,7 @@ def prepare_queue(self, size: int) -> List[QueuePacket]: break return queue - def gridded_perms(self, size: int, place_at_most: int = None): + def gridded_perms(self, size: int, place_at_most: Optional[int] = None): if place_at_most is None: place_at_most = size queue = self.prepare_queue(size) diff --git a/tilings/algorithms/locally_factorable_shift.py b/tilings/algorithms/locally_factorable_shift.py index 08ac165a..9b612545 100644 --- a/tilings/algorithms/locally_factorable_shift.py +++ b/tilings/algorithms/locally_factorable_shift.py @@ -92,14 +92,14 @@ def traverse(t: Tiling) -> Optional[int]: rule.constructor, (DisjointUnion, CountComponent) ): children_reliance = [traverse(c) for c in rule.children] - res = min([r for r in children_reliance if r is not None], default=None) + res = min((r for r in children_reliance if r is not None), default=None) elif isinstance(rule, Rule) and isinstance(rule.constructor, CartesianProduct): min_points = [len(next(c.minimal_gridded_perms())) for c in rule.children] point_sum = sum(min_points) shifts = [point_sum - mpoint for mpoint in min_points] children_reliance = [traverse(c) for c in rule.children] res = min( - [r + s for r, s in zip(children_reliance, shifts) if r is not None], + (r + s for r, s in zip(children_reliance, shifts) if r is not None), default=None, ) else: diff --git a/tilings/algorithms/obstruction_inferral.py b/tilings/algorithms/obstruction_inferral.py index cab6fd21..aa90f8fe 100644 --- a/tilings/algorithms/obstruction_inferral.py +++ b/tilings/algorithms/obstruction_inferral.py @@ -100,12 +100,12 @@ class AllObstructionInferral(ObstructionInferral): obstruction of length up to obstruction_length which can be added. """ - def __init__(self, tiling: "Tiling", obstruction_length: int) -> None: + def __init__(self, tiling: "Tiling", obstruction_length: Optional[int]) -> None: super().__init__(tiling) self._obs_len = obstruction_length @property - def obstruction_length(self) -> int: + def obstruction_length(self) -> Optional[int]: return self._obs_len def not_required(self, gp: GriddedPerm) -> bool: diff --git a/tilings/algorithms/obstruction_transitivity.py b/tilings/algorithms/obstruction_transitivity.py index b32e390e..61246108 100644 --- a/tilings/algorithms/obstruction_transitivity.py +++ b/tilings/algorithms/obstruction_transitivity.py @@ -28,7 +28,7 @@ def __init__(self, tiling: "Tiling") -> None: self._rowineq: Optional[Dict[int, Set[Tuple[int, int]]]] = None self._positive_cells_col: Optional[Dict[int, List[int]]] = None self._positive_cells_row: Optional[Dict[int, List[int]]] = None - self._new_ineq = None + self._new_ineq: Optional[List[Tuple[Tuple[int, int], Tuple[int, int]]]] = None def positive_cells_col(self, col_index: int) -> List[int]: """ @@ -136,9 +136,7 @@ def ineq_ob(ineq) -> GriddedPerm: ) @staticmethod - def ineq_closure( - positive_cells: Iterable[Cell], ineqs: Set[Tuple[Cell, Cell]] - ) -> Set[Tuple[Cell, Cell]]: + def ineq_closure(positive_cells: Iterable[int], ineqs: Set[Cell]) -> Set[Cell]: """ Computes the transitive closure over positive cells. @@ -148,8 +146,8 @@ def ineq_closure( The list of new inequalities is returned. """ - gtlist: Dict[Cell, List[Cell]] = defaultdict(list) - ltlist: Dict[Cell, List[Cell]] = defaultdict(list) + gtlist: Dict[int, List[int]] = defaultdict(list) + ltlist: Dict[int, List[int]] = defaultdict(list) for left, right in ineqs: ltlist[left].append(right) gtlist[right].append(left) @@ -172,13 +170,15 @@ def ineq_closure( to_analyse.add(gt) return newineqs - def new_ineq(self): + def new_ineq( + self, + ) -> List[Tuple[Tuple[int, int], Tuple[int, int]]]: """ Compute the new inequalities. """ if self._new_ineq is not None: return self._new_ineq - newineqs = [] + newineqs: List[Tuple[Tuple[int, int], Tuple[int, int]]] = [] ncol, nrow = self._tiling.dimensions for col in range(ncol): ineqs = self.ineq_col(col) diff --git a/tilings/algorithms/row_col_separation.py b/tilings/algorithms/row_col_separation.py index 606edb6e..bcf2ec43 100644 --- a/tilings/algorithms/row_col_separation.py +++ b/tilings/algorithms/row_col_separation.py @@ -398,7 +398,7 @@ def _separates_tiling(self, row_order, col_order): ) @staticmethod - def _get_cell_map(row_order, col_order): + def _get_cell_map(row_order, col_order) -> Dict[Cell, Cell]: """ Return the position of the according to the given row_order and col_order. @@ -406,13 +406,14 @@ def _get_cell_map(row_order, col_order): This method does not account for any cleaning occuring in the initializer. For the complete cell map use `get_cell_map`. """ - cell_map = {} + cell_map: Dict[Cell, Cell] = {} + row_map: Dict[Cell, int] = {} for i, row in enumerate(row_order): for cell in row: - cell_map[cell] = (None, i) + row_map[cell] = i for i, col in enumerate(col_order): for cell in col: - cell_map[cell] = (i, cell_map[cell][1]) + cell_map[cell] = (i, row_map[cell]) return cell_map def map_obstructions(self, cell_map): diff --git a/tilings/assumptions.py b/tilings/assumptions.py index f476a5d6..22275e10 100644 --- a/tilings/assumptions.py +++ b/tilings/assumptions.py @@ -218,20 +218,16 @@ def __hash__(self) -> int: class SumComponentAssumption(ComponentAssumption): - @staticmethod - def decomposition(perm: Perm) -> List[Perm]: + def decomposition(self, perm: Perm) -> List[Perm]: return perm.sum_decomposition() # type: ignore - @staticmethod - def tiling_decomposition(tiling: "Tiling") -> List[List[Cell]]: + def tiling_decomposition(self, tiling: "Tiling") -> List[List[Cell]]: return tiling.sum_decomposition() - @staticmethod - def opposite_tiling_decomposition(tiling: "Tiling") -> List[List[Cell]]: + def opposite_tiling_decomposition(self, tiling: "Tiling") -> List[List[Cell]]: return tiling.skew_decomposition() - @staticmethod - def one_or_fewer_components(tiling: "Tiling", cell: Cell) -> bool: + def one_or_fewer_components(self, tiling: "Tiling", cell: Cell) -> bool: return GriddedPerm.single_cell(Perm((0, 1)), cell) in tiling.obstructions def __str__(self): @@ -242,20 +238,16 @@ def __hash__(self) -> int: class SkewComponentAssumption(ComponentAssumption): - @staticmethod - def decomposition(perm: Perm) -> List[Perm]: + def decomposition(self, perm: Perm) -> List[Perm]: return perm.skew_decomposition() # type: ignore - @staticmethod - def tiling_decomposition(tiling: "Tiling") -> List[List[Cell]]: + def tiling_decomposition(self, tiling: "Tiling") -> List[List[Cell]]: return tiling.skew_decomposition() - @staticmethod - def opposite_tiling_decomposition(tiling: "Tiling") -> List[List[Cell]]: + def opposite_tiling_decomposition(self, tiling: "Tiling") -> List[List[Cell]]: return tiling.sum_decomposition() - @staticmethod - def one_or_fewer_components(tiling: "Tiling", cell: Cell) -> bool: + def one_or_fewer_components(self, tiling: "Tiling", cell: Cell) -> bool: return GriddedPerm.single_cell(Perm((1, 0)), cell) in tiling.obstructions def __str__(self): diff --git a/tilings/griddedperm.py b/tilings/griddedperm.py index 212f65a6..fb4697fc 100644 --- a/tilings/griddedperm.py +++ b/tilings/griddedperm.py @@ -1,4 +1,5 @@ import json +from array import array from itertools import chain, combinations, islice, product, tee from typing import Callable, Dict, FrozenSet, Iterable, Iterator, List, Optional, Tuple @@ -121,6 +122,7 @@ def forced_point_index(self, cell: Cell, direction: int) -> int: if direction == DIR_SOUTH: return min((self._patt[idx], idx) for idx in indices)[1] raise ValueError("You're lost, no valid direction") + raise ValueError("The gridded perm does not occupy the cell") def forced_point_of_requirement( self, gps: Tuple["GriddedPerm", ...], indices: Tuple[int, ...], direction: int @@ -272,8 +274,8 @@ def all_subperms(self, proper: bool = True) -> Iterator["GriddedPerm"]: ) def extend(self, c: int, r: int) -> Iterator["GriddedPerm"]: - """Add n+1 to all possible positions in perm and all allowed positions given that - placement.""" + """Add n+1 to all possible positions in perm and all allowed positions given + that placement.""" n = len(self) if n == 0: yield from ( @@ -355,10 +357,10 @@ def compress(self) -> List[int]: return list(chain(self._patt, chain.from_iterable(self._pos))) @classmethod - def decompress(cls, array: List[int]) -> "GriddedPerm": + def decompress(cls, arr: array) -> "GriddedPerm": """Decompresses a list of integers in the form outputted by the compress method and constructs an Obstruction.""" - n, it = len(array) // 3, iter(array) + n, it = len(arr) // 3, iter(arr) return cls( Perm(next(it) for _ in range(n)), ((next(it), next(it)) for _ in range(n)) ) @@ -617,6 +619,10 @@ def to_svg(self, image_scale: float = 10.0) -> str: """Return the svg code to plot the GriddedPerm.""" i_scale = int(image_scale * 10) val_to_pos, (m_x, m_y) = self._get_plot_pos() + points = " ".join( + f"{x * 10},{(m_y - y) * 10}" + for x, y in (val_to_pos[val] for val in self.patt) + ) return "".join( [ ( @@ -641,15 +647,8 @@ def to_svg(self, image_scale: float = 10.0) -> str: ), "\n", ( - lambda path: ( - f'\n' - ) - )( - " ".join( - f"{x * 10},{(m_y - y) * 10}" - for x, y in (val_to_pos[val] for val in self.patt) - ) + f'\n' ), "\n".join( ( diff --git a/tilings/misc.py b/tilings/misc.py index 4958db5d..a28ed316 100644 --- a/tilings/misc.py +++ b/tilings/misc.py @@ -3,7 +3,17 @@ useful. """ from functools import reduce -from typing import Dict, Iterable, Iterator, Sequence, Set, Tuple, TypeVar +from typing import ( + Collection, + Dict, + Iterable, + Iterator, + List, + Sequence, + Set, + Tuple, + TypeVar, +) Vertex = TypeVar("Vertex") T = TypeVar("T") @@ -35,7 +45,9 @@ def intersection_reduce(iterables: Iterable[Iterable[T]]) -> Set[T]: return set() -def is_tree(vertices: Sequence[Vertex], edges: Sequence[Tuple[Vertex, Vertex]]) -> bool: +def is_tree( + vertices: Collection[Vertex], edges: Collection[Tuple[Vertex, Vertex]] +) -> bool: """ Return True if the undirected graph is a tree. @@ -48,7 +60,7 @@ def is_tree(vertices: Sequence[Vertex], edges: Sequence[Tuple[Vertex, Vertex]]) def adjacency_table( - vertices: Sequence[Vertex], edges: Sequence[Tuple[Vertex, Vertex]] + vertices: Collection[Vertex], edges: Collection[Tuple[Vertex, Vertex]] ) -> AdjTable: """Return adjacency table of edges.""" adj_table = {v: set() for v in vertices} # type: AdjTable @@ -113,10 +125,10 @@ def partitions_iterator(lst: Sequence[T]) -> Iterator[Tuple[Tuple[T, ...], ...]] yield tuple(map(tuple, part)) -def algorithm_u(ns, m): +def algorithm_u(ns: Sequence[T], m: int): # pylint: disable=too-many-statements,too-many-branches def visit(n, a): - ps = [[] for i in range(m)] + ps: List[List[T]] = [[] for i in range(m)] for j in range(n): ps[a[j + 1]].append(ns[j]) return ps diff --git a/tilings/strategies/assumption_insertion.py b/tilings/strategies/assumption_insertion.py index 1274066e..524f69f0 100644 --- a/tilings/strategies/assumption_insertion.py +++ b/tilings/strategies/assumption_insertion.py @@ -1,7 +1,7 @@ from collections import Counter from itertools import product from random import randint -from typing import Callable, Dict, Iterable, Iterator, List, Optional, Tuple +from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Tuple from sympy import Eq, Expr, Function, Number, Symbol, var @@ -44,7 +44,7 @@ def __init__( def get_equation(self, lhs_func: Function, rhs_funcs: Tuple[Function, ...]) -> Eq: rhs_func = rhs_funcs[0] - subs: Dict[Symbol, Expr] = { + subs: Dict[Any, Expr] = { var(child): var(parent) for parent, child in self.extra_parameters.items() } for k in self.new_parameters: @@ -225,12 +225,10 @@ def __init__(self, assumptions: Iterable[TrackingAssumption], workable=False): workable=workable, ) - @staticmethod - def can_be_equivalent() -> bool: + def can_be_equivalent(self) -> bool: return False - @staticmethod - def is_two_way(comb_class: Tiling): + def is_two_way(self, comb_class: Tiling): return False def is_reversible(self, comb_class: Tiling) -> bool: @@ -240,9 +238,8 @@ def is_reversible(self, comb_class: Tiling) -> bool: for assumption in self.assumptions ) - @staticmethod def shifts( - comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None + self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None ) -> Tuple[int, ...]: return (0,) diff --git a/tilings/strategies/assumption_splitting.py b/tilings/strategies/assumption_splitting.py index d45ba87c..e82cb1bc 100644 --- a/tilings/strategies/assumption_splitting.py +++ b/tilings/strategies/assumption_splitting.py @@ -2,7 +2,7 @@ from functools import reduce from itertools import product from operator import mul -from typing import Callable, Dict, Iterable, Iterator, List, Optional, Set, Tuple +from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Set, Tuple from sympy import Eq, Function, var @@ -31,7 +31,7 @@ class Split(Constructor): """ - The constructor used to cound when a variable is counted by some multiple + The constructor used to count when a variable is counted by some multiple disjoint subvariables. """ @@ -40,7 +40,7 @@ def __init__(self, split_parameters: Dict[str, Tuple[str, ...]]): def get_equation(self, lhs_func: Function, rhs_funcs: Tuple[Function, ...]) -> Eq: rhs_func = rhs_funcs[0] - subs: Dict[var, List[var]] = defaultdict(list) + subs: Dict[Any, List[Any]] = defaultdict(list) for parent, children in self.split_parameters.items(): for child in children: subs[var(child)].append(var(parent)) @@ -156,16 +156,13 @@ def __init__( workable=workable, ) - @staticmethod - def can_be_equivalent() -> bool: + def can_be_equivalent(self) -> bool: return False - @staticmethod - def is_two_way(comb_class: Tiling): + def is_two_way(self, comb_class: Tiling): return False - @staticmethod - def is_reversible(comb_class: Tiling): + def is_reversible(self, comb_class: Tiling): return False def shifts( @@ -313,8 +310,7 @@ def reverse_constructor( ) -> Constructor: raise NotImplementedError - @staticmethod - def formal_step() -> str: + def formal_step(self) -> str: return "splitting the assumptions" def backward_map( diff --git a/tilings/strategies/cell_reduction.py b/tilings/strategies/cell_reduction.py index 899729f2..9ff68ea9 100644 --- a/tilings/strategies/cell_reduction.py +++ b/tilings/strategies/cell_reduction.py @@ -73,21 +73,17 @@ def __init__( self.increasing = increasing super().__init__() - @staticmethod - def can_be_equivalent() -> bool: + def can_be_equivalent(self) -> bool: return False - @staticmethod - def is_two_way(comb_class: Tiling) -> bool: + def is_two_way(self, comb_class: Tiling) -> bool: return False - @staticmethod - def is_reversible(comb_class: Tiling) -> bool: + def is_reversible(self, comb_class: Tiling) -> bool: return False - @staticmethod def shifts( - comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None + self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None ) -> Tuple[int, ...]: return (0, 0) diff --git a/tilings/strategies/deflation.py b/tilings/strategies/deflation.py index 458ff911..4a8a2f6a 100644 --- a/tilings/strategies/deflation.py +++ b/tilings/strategies/deflation.py @@ -71,21 +71,17 @@ def __init__( self.sum_deflate = sum_deflate super().__init__() - @staticmethod - def can_be_equivalent() -> bool: + def can_be_equivalent(self) -> bool: return False - @staticmethod - def is_two_way(comb_class: Tiling) -> bool: + def is_two_way(self, comb_class: Tiling) -> bool: return False - @staticmethod - def is_reversible(comb_class: Tiling) -> bool: + def is_reversible(self, comb_class: Tiling) -> bool: return False - @staticmethod def shifts( - comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None + self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None ) -> Tuple[int, ...]: return (0, 0) diff --git a/tilings/strategies/detect_components.py b/tilings/strategies/detect_components.py index 10f023ee..4b06d77d 100644 --- a/tilings/strategies/detect_components.py +++ b/tilings/strategies/detect_components.py @@ -99,16 +99,13 @@ def equiv( class DetectComponentsStrategy(Strategy[Tiling, GriddedPerm]): - @staticmethod - def can_be_equivalent() -> bool: + def can_be_equivalent(self) -> bool: return False - @staticmethod - def is_two_way(comb_class: Tiling): + def is_two_way(self, comb_class: Tiling): return False - @staticmethod - def is_reversible(comb_class: Tiling): + def is_reversible(self, comb_class: Tiling): return False def shifts( @@ -120,8 +117,7 @@ def shifts( raise StrategyDoesNotApply return (0,) - @staticmethod - def decomposition_function(comb_class: Tiling) -> Optional[Tuple[Tiling]]: + def decomposition_function(self, comb_class: Tiling) -> Optional[Tuple[Tiling]]: if not comb_class.assumptions: return None return (comb_class.remove_components_from_assumptions(),) @@ -172,12 +168,11 @@ def extra_parameters( ] = child.get_assumption_parameter(mapped_assumption) return (extra_parameters,) - @staticmethod - def formal_step() -> str: + def formal_step(self) -> str: return "removing exact components" - @staticmethod def backward_map( + self, comb_class: Tiling, objs: Tuple[Optional[GriddedPerm], ...], children: Optional[Tuple[Tiling, ...]] = None, @@ -189,8 +184,8 @@ def backward_map( assert isinstance(objs[0], GriddedPerm) yield objs[0] - @staticmethod def forward_map( + self, comb_class: Tiling, obj: GriddedPerm, children: Optional[Tuple[Tiling, ...]] = None, diff --git a/tilings/strategies/factor.py b/tilings/strategies/factor.py index 0a36dfff..f256b85f 100644 --- a/tilings/strategies/factor.py +++ b/tilings/strategies/factor.py @@ -202,8 +202,7 @@ def __init__( def is_equivalence(is_empty: Optional[Callable[[Tiling], bool]] = None) -> bool: return False - @staticmethod - def get_equation(lhs_func: Function, rhs_funcs: Tuple[Function, ...]) -> Eq: + def get_equation(self, lhs_func: Function, rhs_funcs: Tuple[Function, ...]) -> Eq: raise NotImplementedError def get_terms( @@ -276,10 +275,10 @@ def __repr__(self) -> str: ) return f"{self.__class__.__name__}({args})" - def is_two_way(self, comb_class: Tiling) -> bool: # type: ignore + def is_two_way(self, comb_class: Tiling) -> bool: # pylint: disable=W0221 return self.is_reversible(comb_class) - def is_reversible(self, comb_class: Tiling) -> bool: # type: ignore + def is_reversible(self, comb_class: Tiling) -> bool: # pylint: disable=W0221 return not bool(self.assumptions_to_add(comb_class)) def formal_step(self) -> str: @@ -613,8 +612,9 @@ def _build_strategy( FactorWithInterleavingStrategy.interleaving_rows_and_cols(components) ) if interleaving: + # pylint: disable=E1123 return cast( - FactorStrategy, + FactorWithInterleavingStrategy, self.factor_class( components, ignore_parent=self.ignore_parent, diff --git a/tilings/strategies/fusion/constructor.py b/tilings/strategies/fusion/constructor.py index 6e9d0592..6c443593 100644 --- a/tilings/strategies/fusion/constructor.py +++ b/tilings/strategies/fusion/constructor.py @@ -17,7 +17,7 @@ from collections import Counter, defaultdict from functools import reduce from operator import mul -from typing import Callable, Dict, Iterable, Iterator, List, Optional, Set, Tuple +from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Set, Tuple import sympy @@ -176,7 +176,7 @@ def get_equation( "left or right containing more than one point" ) rhs_func = rhs_funcs[0] - subs: Dict[str, sympy.Expr] = { + subs: Dict[str, Any] = { child: reduce(mul, [sympy.var(k) for k in parent_vars], 1) for child, parent_vars in self.reversed_extra_parameters.items() } diff --git a/tilings/strategies/fusion/fusion.py b/tilings/strategies/fusion/fusion.py index faea799e..8f8027f4 100644 --- a/tilings/strategies/fusion/fusion.py +++ b/tilings/strategies/fusion/fusion.py @@ -29,8 +29,9 @@ def strategy(self) -> "FusionStrategy": def constructor(self) -> FusionConstructor: return cast(FusionConstructor, super().constructor) - @staticmethod - def is_equivalence(is_empty: Optional[Callable[[Tiling], bool]] = None) -> bool: + def is_equivalence( + self, is_empty: Optional[Callable[[Tiling], bool]] = None + ) -> bool: return False def _ensure_level_objects(self, n: int) -> None: @@ -110,6 +111,7 @@ def random_sample_object_of_size(self, n: int, **parameters: int) -> GriddedPerm ) except StopIteration: assert 0, "something went wrong" + raise RuntimeError("The for-loop for randomly sampling objects was empty") def _forward_order( self, @@ -145,7 +147,7 @@ def __init__(self, row_idx=None, col_idx=None, tracked: bool = False): def __call__( self, comb_class: Tiling, - children: Tuple[Tiling, ...] = None, + children: Optional[Tuple[Tiling, ...]] = None, ) -> FusionRule: if children is None: children = self.decomposition_function(comb_class) @@ -162,13 +164,12 @@ def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling]: algo = self.fusion_algorithm(comb_class) if algo.fusable(): return (algo.fused_tiling(),) + raise AttributeError("Trying to fuse a tiling that does not fuse") - @staticmethod - def can_be_equivalent() -> bool: + def can_be_equivalent(self) -> bool: return False - @staticmethod - def is_two_way(comb_class: Tiling): + def is_two_way(self, comb_class: Tiling): return False def is_reversible(self, comb_class: Tiling) -> bool: @@ -180,9 +181,8 @@ def is_reversible(self, comb_class: Tiling) -> bool: ) return new_ass in fused_assumptions - @staticmethod def shifts( - comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None + self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None ) -> Tuple[int, ...]: return (0,) @@ -321,7 +321,7 @@ def backward_map( comb_class: Tiling, objs: Tuple[Optional[GriddedPerm], ...], children: Optional[Tuple[Tiling, ...]] = None, - left_points: int = None, + left_points: Optional[int] = None, ) -> Iterator[GriddedPerm]: """ The backward direction of the underlying bijection used for object diff --git a/tilings/strategies/obstruction_inferral.py b/tilings/strategies/obstruction_inferral.py index a0704f89..26d39e79 100644 --- a/tilings/strategies/obstruction_inferral.py +++ b/tilings/strategies/obstruction_inferral.py @@ -109,7 +109,7 @@ class ObstructionInferralFactory(StrategyFactory[Tiling]): recompute new_obs which is needed for the strategy. """ - def __init__(self, maxlen: int = 3): + def __init__(self, maxlen: Optional[int] = 3): self.maxlen = maxlen super().__init__() diff --git a/tilings/strategies/point_jumping.py b/tilings/strategies/point_jumping.py index 6869944a..c4be4e4b 100644 --- a/tilings/strategies/point_jumping.py +++ b/tilings/strategies/point_jumping.py @@ -153,7 +153,7 @@ def extra_parameters( self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None ) -> Tuple[Dict[str, str], ...]: if not comb_class.extra_parameters: - return super().extra_parameters(comb_class, children) + raise ValueError("This tiling does not have assumptions") if children is None: children = self.decomposition_function(comb_class) if children is None: @@ -188,20 +188,17 @@ def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling]: ), ) - @staticmethod - def can_be_equivalent() -> bool: + def can_be_equivalent(self) -> bool: return False - @staticmethod - def is_two_way(comb_class: Tiling) -> bool: + def is_two_way(self, comb_class: Tiling) -> bool: return True - @staticmethod - def is_reversible(comb_class: Tiling) -> bool: + def is_reversible(self, comb_class: Tiling) -> bool: return True - @staticmethod def shifts( + self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]], ) -> Tuple[int, ...]: @@ -241,12 +238,6 @@ def forward_map( def extra_parameters( self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None ) -> Tuple[Dict[str, str], ...]: - if not comb_class.extra_parameters: - return super().extra_parameters(comb_class, children) - if children is None: - children = self.decomposition_function(comb_class) - if children is None: - raise StrategyDoesNotApply("Strategy does not apply") raise NotImplementedError def formal_step(self) -> str: @@ -267,20 +258,17 @@ def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling]: ), ) - @staticmethod - def can_be_equivalent() -> bool: + def can_be_equivalent(self) -> bool: return False - @staticmethod - def is_two_way(comb_class: Tiling) -> bool: + def is_two_way(self, comb_class: Tiling) -> bool: return True - @staticmethod - def is_reversible(comb_class: Tiling) -> bool: + def is_reversible(self, comb_class: Tiling) -> bool: return True - @staticmethod def shifts( + self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]], ) -> Tuple[int, ...]: @@ -320,12 +308,6 @@ def forward_map( def extra_parameters( self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None ) -> Tuple[Dict[str, str], ...]: - if not comb_class.extra_parameters: - return super().extra_parameters(comb_class, children) - if children is None: - children = self.decomposition_function(comb_class) - if children is None: - raise StrategyDoesNotApply("Strategy does not apply") raise NotImplementedError def formal_step(self) -> str: diff --git a/tilings/strategies/rearrange_assumption.py b/tilings/strategies/rearrange_assumption.py index 6cb9969b..a8353443 100644 --- a/tilings/strategies/rearrange_assumption.py +++ b/tilings/strategies/rearrange_assumption.py @@ -285,21 +285,17 @@ def __init__( self.sub_assumption = sub_assumption super().__init__() - @staticmethod - def can_be_equivalent() -> bool: + def can_be_equivalent(self) -> bool: return False - @staticmethod - def is_two_way(comb_class: Tiling) -> bool: + def is_two_way(self, comb_class: Tiling) -> bool: return True - @staticmethod - def is_reversible(comb_class: Tiling) -> bool: + def is_reversible(self, comb_class: Tiling) -> bool: return True - @staticmethod def shifts( - comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None + self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None ) -> Tuple[int, ...]: return (0,) diff --git a/tilings/strategies/row_and_col_separation.py b/tilings/strategies/row_and_col_separation.py index 36a6b719..7ac3ce9c 100644 --- a/tilings/strategies/row_and_col_separation.py +++ b/tilings/strategies/row_and_col_separation.py @@ -44,11 +44,14 @@ def _get_cell_maps(self, tiling: Tiling) -> Tuple[CellMap, CellMap]: forward_cell_map, backward_cell_map = res return forward_cell_map, backward_cell_map - def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling, ...]: + def decomposition_function( + self, comb_class: Tiling + ) -> Optional[Tuple[Tiling, ...]]: """Return the separated tiling if it separates, otherwise None.""" rcs = self.row_col_sep_algorithm(comb_class) if rcs.separable(): return (rcs.separated_tiling(),) + return None def extra_parameters( self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None @@ -87,8 +90,7 @@ def row_col_sep_algorithm(tiling: Tiling) -> RowColSeparation: """Return the algorithm class using tiling.""" return RowColSeparation(tiling) - @staticmethod - def formal_step() -> str: + def formal_step(self) -> str: """Return formal step.""" return "row and column separation" diff --git a/tilings/strategies/symmetry.py b/tilings/strategies/symmetry.py index 2fa88e68..7e051a87 100644 --- a/tilings/strategies/symmetry.py +++ b/tilings/strategies/symmetry.py @@ -30,7 +30,7 @@ def inverse_gp_transform(self, tiling: Tiling, gp: GriddedPerm) -> GriddedPerm: def assumption_type_transform( assumption: TrackingAssumption, ) -> Type[TrackingAssumption]: - pass + raise NotImplementedError @staticmethod def _assumption_type_swap( @@ -139,8 +139,7 @@ def inverse_gp_transform(self, tiling: Tiling, gp: GriddedPerm) -> GriddedPerm: assumption_type_transform = TilingSymmetryStrategy._assumption_type_swap - @staticmethod - def formal_step() -> str: + def formal_step(self) -> str: return "reverse of the tiling" def __str__(self) -> str: @@ -163,8 +162,7 @@ def inverse_gp_transform(self, tiling: Tiling, gp: GriddedPerm) -> GriddedPerm: assumption_type_transform = TilingSymmetryStrategy._assumption_type_swap - @staticmethod - def formal_step() -> str: + def formal_step(self) -> str: return "complement of the tiling" def __str__(self) -> str: @@ -187,8 +185,7 @@ def inverse_gp_transform(self, tiling: Tiling, gp: GriddedPerm) -> GriddedPerm: assumption_type_transform = TilingSymmetryStrategy._assumption_type_identity - @staticmethod - def formal_step() -> str: + def formal_step(self) -> str: return "inverse of the tiling" def __str__(self) -> str: @@ -220,8 +217,7 @@ def antidiagonal_cell(cell): assumption_type_transform = TilingSymmetryStrategy._assumption_type_identity - @staticmethod - def formal_step() -> str: + def formal_step(self) -> str: return "antidiagonal of the tiling" def __str__(self) -> str: @@ -247,8 +243,7 @@ def rotate270_cell(cell): assumption_type_transform = TilingSymmetryStrategy._assumption_type_swap - @staticmethod - def formal_step() -> str: + def formal_step(self) -> str: return "rotate the tiling 90 degrees clockwise" def __str__(self) -> str: @@ -274,8 +269,7 @@ def inverse_gp_transform(self, tiling: Tiling, gp: GriddedPerm) -> GriddedPerm: assumption_type_transform = TilingSymmetryStrategy._assumption_type_identity - @staticmethod - def formal_step() -> str: + def formal_step(self) -> str: return "rotate the tiling 180 degrees clockwise" def __str__(self) -> str: @@ -301,8 +295,7 @@ def rotate90_cell(cell): assumption_type_transform = TilingSymmetryStrategy._assumption_type_swap - @staticmethod - def formal_step() -> str: + def formal_step(self) -> str: return "rotate the tiling 270 degrees clockwise" def __str__(self) -> str: diff --git a/tilings/strategies/verification.py b/tilings/strategies/verification.py index f5c0591d..026af932 100644 --- a/tilings/strategies/verification.py +++ b/tilings/strategies/verification.py @@ -2,7 +2,7 @@ from functools import reduce from itertools import chain from operator import mul -from typing import Callable, Dict, Iterator, Optional, Tuple, cast +from typing import Any, Callable, Dict, Iterator, Optional, Tuple, cast import requests from sympy import Eq, Expr, Function, solve, sympify, var @@ -61,8 +61,7 @@ class BasicVerificationStrategy(AtomStrategy): TODO: can this be moved to the CSS atom strategy? """ - @staticmethod - def get_terms(comb_class: CombinatorialClass, n: int) -> Terms: + def get_terms(self, comb_class: CombinatorialClass, n: int) -> Terms: if not isinstance(comb_class, Tiling): raise NotImplementedError gp = next(comb_class.minimal_gridded_perms()) @@ -73,8 +72,7 @@ def get_terms(comb_class: CombinatorialClass, n: int) -> Terms: return Counter([parameters]) return Counter() - @staticmethod - def get_objects(comb_class: CombinatorialClass, n: int) -> Objects: + def get_objects(self, comb_class: CombinatorialClass, n: int) -> Objects: if not isinstance(comb_class, Tiling): raise NotImplementedError res: Objects = defaultdict(list) @@ -95,22 +93,26 @@ def generate_objects_of_size( """ yield from comb_class.objects_of_size(n, **parameters) - @staticmethod def random_sample_object_of_size( - comb_class: CombinatorialClass, n: int, **parameters: int + self, comb_class: CombinatorialClass, n: int, **parameters: int ) -> GriddedPerm: """ Verification strategies must contain a method to sample the objects. """ key = tuple(y for _, y in sorted(parameters.items())) - if BasicVerificationStrategy.get_terms(comb_class, n).get(key): + if BasicVerificationStrategy().get_terms(comb_class, n).get(key): return cast(GriddedPerm, next(comb_class.objects_of_size(n, **parameters))) + raise ( + NotImplementedError( + "Verification strategy did not contain a method to sample the objects" + ) + ) def get_genf( self, comb_class: CombinatorialClass, funcs: Optional[Dict[CombinatorialClass, Function]] = None, - ) -> Expr: + ) -> Any: if not self.verified(comb_class): raise StrategyDoesNotApply("Can't find generating functon for non-atom.") if not isinstance(comb_class, Tiling): @@ -138,7 +140,7 @@ def get_equation( basis = [ob.patt for ob in self.comb_class.obstructions] basis_str = "_".join(map(str, lex_min(basis))) uri = f"https://permpal.com/perms/raw_data_json/basis/{basis_str}" - request = requests.get(uri) + request = requests.get(uri, timeout=10) if request.status_code == 404: return super().get_equation(get_function, funcs) data = request.json() @@ -192,10 +194,9 @@ def without_req_genf(self, tiling: Tiling): class OneByOneVerificationStrategy(BasisAwareVerificationStrategy): - @staticmethod - def pack(comb_class: Tiling) -> StrategyPack: + def pack(self, comb_class: Tiling) -> StrategyPack: if any(isinstance(ass, ComponentAssumption) for ass in comb_class.assumptions): - return ComponentVerificationStrategy.pack(comb_class) + return ComponentVerificationStrategy().pack(comb_class) # pylint: disable=import-outside-toplevel from tilings.tilescope import TileScopePack @@ -240,7 +241,7 @@ def pack(comb_class: Tiling) -> StrategyPack: def __call__( self, comb_class: Tiling, - children: Tuple[Tiling, ...] = None, + children: Optional[Tuple[Tiling, ...]] = None, ) -> OneByOneVerificationRule: if children is None: children = self.decomposition_function(comb_class) @@ -262,7 +263,7 @@ def verified(self, comb_class: Tiling) -> bool: def get_genf( self, comb_class: Tiling, funcs: Optional[Dict[Tiling, Function]] = None - ) -> Expr: + ) -> Any: if not self.verified(comb_class): raise StrategyDoesNotApply("tiling not 1x1 verified") if len(comb_class.obstructions) == 1 and comb_class.obstructions[0] in ( @@ -275,12 +276,10 @@ def get_genf( except InvalidOperationError: return LocalEnumeration(comb_class).get_genf(funcs=funcs) - @staticmethod - def formal_step() -> str: + def formal_step(self) -> str: return "tiling is a subclass of the original tiling" - @staticmethod - def get_terms(comb_class: Tiling, n: int) -> Terms: + def get_terms(self, comb_class: Tiling, n: int) -> Terms: raise NotImplementedError( "Not implemented method to count objects for one by one verified tilings" ) @@ -309,12 +308,10 @@ def __str__(self) -> str: class ComponentVerificationStrategy(TileScopeVerificationStrategy): """Enumeration strategy for verifying 1x1s with component assumptions.""" - @staticmethod - def pack(comb_class: Tiling) -> StrategyPack: + def pack(self, comb_class: Tiling) -> StrategyPack: raise InvalidOperationError("No pack for removing component assumption") - @staticmethod - def verified(comb_class: Tiling) -> bool: + def verified(self, comb_class: Tiling) -> bool: return comb_class.dimensions == (1, 1) and any( isinstance(ass, ComponentAssumption) for ass in comb_class.assumptions ) @@ -334,8 +331,7 @@ def shifts( ) -> Tuple[int, ...]: return (0,) - @staticmethod - def formal_step() -> str: + def formal_step(self) -> str: return "component verified" def get_genf( @@ -380,30 +376,26 @@ class DatabaseVerificationStrategy(TileScopeVerificationStrategy): can always find the generating function by looking up the database. """ - @staticmethod - def pack(comb_class: Tiling) -> StrategyPack: + def pack(self, comb_class: Tiling) -> StrategyPack: # TODO: check database for tiling raise InvalidOperationError( "Cannot get a specification for a tiling in the database" ) - @staticmethod - def verified(comb_class: Tiling): + def verified(self, comb_class: Tiling): return DatabaseEnumeration(comb_class).verified() - @staticmethod - def formal_step() -> str: + def formal_step(self) -> str: return "tiling is in the database" def get_genf( self, comb_class: Tiling, funcs: Optional[Dict[Tiling, Function]] = None - ) -> Expr: + ) -> Any: if not self.verified(comb_class): raise StrategyDoesNotApply("tiling is not in the database") return DatabaseEnumeration(comb_class).get_genf() - @staticmethod - def get_terms(comb_class: Tiling, n: int) -> Terms: + def get_terms(self, comb_class: Tiling, n: int) -> Terms: raise NotImplementedError( "Not implemented method to count objects for database verified tilings" ) @@ -550,8 +542,7 @@ def shifts( assert shift is not None return (shift,) - @staticmethod - def formal_step() -> str: + def formal_step(self) -> str: return "tiling is locally factorable" def __str__(self) -> str: @@ -572,8 +563,7 @@ class ElementaryVerificationStrategy(LocallyFactorableVerificationStrategy): verified tiling. """ - @staticmethod - def verified(comb_class: Tiling): + def verified(self, comb_class: Tiling): return ( comb_class.fully_isolated() and not comb_class.dimensions == (1, 1) @@ -584,8 +574,7 @@ def verified(comb_class: Tiling): ) ) - @staticmethod - def formal_step() -> str: + def formal_step(self) -> str: return "tiling is elementary verified" @classmethod @@ -647,8 +636,7 @@ def verified(self, comb_class: Tiling) -> bool: ) ) - @staticmethod - def formal_step() -> str: + def formal_step(self) -> str: return "tiling is locally enumerable" @classmethod @@ -657,7 +645,7 @@ def from_dict(cls, d: dict) -> "LocalVerificationStrategy": def get_genf( self, comb_class: Tiling, funcs: Optional[Dict[Tiling, Function]] = None - ) -> Expr: + ) -> Any: if not self.verified(comb_class): raise StrategyDoesNotApply("tiling not locally verified") if len(comb_class.obstructions) == 1 and comb_class.obstructions[0] in ( @@ -670,8 +658,7 @@ def get_genf( except InvalidOperationError: return LocalEnumeration(comb_class).get_genf(funcs=funcs) - @staticmethod - def get_terms(comb_class: Tiling, n: int) -> Terms: + def get_terms(self, comb_class: Tiling, n: int) -> Terms: raise NotImplementedError( "Not implemented method to count objects for locally verified tilings" ) @@ -735,16 +722,14 @@ def verified(self, comb_class: Tiling) -> bool: comb_class ) or self.has_topmost_insertion_encoding(comb_class) - @staticmethod - def formal_step() -> str: + def formal_step(self) -> str: return "tiling has a regular insertion encoding" @classmethod def from_dict(cls, d: dict) -> "InsertionEncodingVerificationStrategy": return cls(**d) - @staticmethod - def get_terms(comb_class: Tiling, n: int) -> Terms: + def get_terms(self, comb_class: Tiling, n: int) -> Terms: raise NotImplementedError( "Not implemented method to count objects for insertion encoding " "verified tilings" @@ -803,8 +788,7 @@ def verified(self, comb_class: Tiling) -> bool: not self.no_factors or len(comb_class.find_factors()) == 1 ) and MonotoneTreeEnumeration(comb_class).verified() - @staticmethod - def formal_step() -> str: + def formal_step(self) -> str: return "tiling is a monotone tree" @classmethod @@ -813,7 +797,7 @@ def from_dict(cls, d: dict) -> "MonotoneTreeVerificationStrategy": def get_genf( self, comb_class: Tiling, funcs: Optional[Dict[Tiling, Function]] = None - ) -> Expr: + ) -> Any: if not self.verified(comb_class): raise StrategyDoesNotApply("tiling not locally verified") try: @@ -821,8 +805,7 @@ def get_genf( except InvalidOperationError: return MonotoneTreeEnumeration(comb_class).get_genf(funcs=funcs) - @staticmethod - def get_terms(comb_class: Tiling, n: int) -> Terms: + def get_terms(self, comb_class: Tiling, n: int) -> Terms: raise NotImplementedError( "Not implemented method to count objects for monotone tree " "verified tilings" diff --git a/tilings/strategy_pack.py b/tilings/strategy_pack.py index 7bc21a9e..3ab43d72 100644 --- a/tilings/strategy_pack.py +++ b/tilings/strategy_pack.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Iterable, List, Optional, Union +from typing import TYPE_CHECKING, Iterable, List, Optional, Tuple, Union from logzero import logger @@ -32,9 +32,11 @@ def add_basis(self, basis: Iterable[Perm]) -> "TileScopePack": basis = tuple(basis) symmetry = bool(self.symmetries) - def replace_list(strats): + def replace_list( + strats: Tuple[Union[AbstractStrategy, StrategyFactory], ...] + ) -> List[Union[AbstractStrategy, StrategyFactory]]: """Return a new list with the replaced 1x1 strat.""" - res = [] + res: List[Union[AbstractStrategy, StrategyFactory]] = [] for strategy in strats: if isinstance(strategy, BasisAwareVerificationStrategy): if strategy.basis: @@ -68,11 +70,13 @@ def setup_subclass_verification(self, start_tiling: "Tiling") -> "TileScopePack" has length strictly smaller than the maximum length cell basis element. """ - def replace_list(strats): + def replace_list( + strats: Tuple[Union[AbstractStrategy, StrategyFactory], ...] + ) -> List[Union[AbstractStrategy, StrategyFactory]]: """ Find subclass verification and alter its perms_to_check variable. """ - res = [] + res: List[Union[AbstractStrategy, StrategyFactory]] = [] for strategy in strats: if isinstance(strategy, strat.SubclassVerificationFactory): printed_log = False @@ -99,6 +103,7 @@ def replace_list(strats): ) printed_log = True if not printed_log: + assert strategy.perms_to_check is not None logger.info( "SubclassVerification set up to check the subclasses: " "Av(%s)", diff --git a/tilings/tilescope.py b/tilings/tilescope.py index ead482de..80a78f0e 100644 --- a/tilings/tilescope.py +++ b/tilings/tilescope.py @@ -184,7 +184,7 @@ def from_spec( @classmethod def from_uri(cls, URI: str) -> "GuidedSearcher": - response = requests.get(URI) + response = requests.get(URI, timeout=10) spec = CombinatorialSpecification.from_dict(response.json()["specification"]) pack = TileScopePack.from_dict(response.json()["pack"]).make_tracked() return cls.from_spec(spec, pack) @@ -387,3 +387,4 @@ def __next__(self) -> WorkPacket: return next(queue) except StopIteration: continue + raise StopIteration("No elements in queue") diff --git a/tilings/tiling.py b/tilings/tiling.py index bccd36a3..5d49dc8c 100644 --- a/tilings/tiling.py +++ b/tilings/tiling.py @@ -6,6 +6,7 @@ from itertools import chain, filterfalse, product from operator import mul, xor from typing import ( + Any, Callable, Dict, FrozenSet, @@ -1514,7 +1515,7 @@ def gridded_perms_of_length(self, length: int) -> Iterator[GriddedPerm]: if len(gp) == length: yield gp - def initial_conditions(self, check: int = 6) -> List[sympy.Expr]: + def initial_conditions(self, check: int = 6) -> List[Any]: """ Returns a list with the initial conditions to size `check` of the CombinatorialClass. @@ -1834,7 +1835,7 @@ def tiling_from_perm(cls, p: Perm) -> "Tiling": requirements=[[GriddedPerm((0,), ((i, p[i]),))] for i in range(len(p))] ) - def get_genf(self, *args, **kwargs) -> sympy.Expr: + def get_genf(self, *args, **kwargs) -> Any: # pylint: disable=import-outside-toplevel if self.is_empty(): return sympy.sympify(0) diff --git a/tox.ini b/tox.ini index 0f503082..0fad5b68 100644 --- a/tox.ini +++ b/tox.ini @@ -20,8 +20,8 @@ basepython = py310: python3.10 pypy38: pypy3 deps = - pytest==6.2.5 - pytest-timeout==2.0.1 + pytest==7.2.0 + pytest-timeout==2.1.0 commands = pytest [pytest] @@ -35,8 +35,8 @@ description = run flake8 (linter) basepython = {[default]basepython} skip_install = True deps = - flake8==4.0.1 - flake8-isort==4.1.1 + flake8==5.0.4 + flake8-isort==5.0.0 commands = flake8 --isort-show-traceback tilings tests setup.py @@ -44,21 +44,21 @@ commands = description = run pylint (static code analysis) basepython = {[default]basepython} deps = - pylint==2.11.1 + pylint==2.15.5 commands = pylint tilings [testenv:mypy] description = run mypy (static type checker) basepython = {[default]basepython} deps = - mypy==0.910 - types-requests==2.26.0 - types-tabulate==0.8.3 + mypy==0.990 + types-requests==2.28.11.4 + types-tabulate==0.9.0 commands = mypy [testenv:black] description = check that comply with autoformating basepython = {[default]basepython} deps = - black==22.3.0 + black==22.10.0 commands = black --check --diff . From 6fb6d388621b1dd6d6c28d32256fdafb0e590a61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20Nadeau?= Date: Thu, 12 Jan 2023 15:16:12 +0000 Subject: [PATCH 067/100] better exception message for point jumping (#498) --- tilings/strategies/point_jumping.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/tilings/strategies/point_jumping.py b/tilings/strategies/point_jumping.py index c4be4e4b..c2ad0799 100644 --- a/tilings/strategies/point_jumping.py +++ b/tilings/strategies/point_jumping.py @@ -209,7 +209,9 @@ def constructor( comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None, ) -> Constructor: - raise NotImplementedError + raise NotImplementedError( + "Constructor for assumption jumping is not implemented" + ) def reverse_constructor( self, @@ -217,7 +219,9 @@ def reverse_constructor( comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None, ) -> Constructor: - raise NotImplementedError + raise NotImplementedError( + "Reverse constructor for assumption jumping is not implemented." + ) def backward_map( self, @@ -238,7 +242,9 @@ def forward_map( def extra_parameters( self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None ) -> Tuple[Dict[str, str], ...]: - raise NotImplementedError + raise NotImplementedError( + "extra_parameters not implemented for assumption jumping" + ) def formal_step(self) -> str: row_or_col = "rows" if self.row else "cols" @@ -279,7 +285,7 @@ def constructor( comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None, ) -> Constructor: - raise NotImplementedError + raise NotImplementedError("Constructor not implemented for point jumping") def reverse_constructor( self, @@ -287,7 +293,9 @@ def reverse_constructor( comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None, ) -> Constructor: - raise NotImplementedError + raise NotImplementedError( + "Reverse contructor not implemented for point jumping" + ) def backward_map( self, @@ -308,7 +316,7 @@ def forward_map( def extra_parameters( self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None ) -> Tuple[Dict[str, str], ...]: - raise NotImplementedError + raise NotImplementedError("not implemented extra_parameters for point jumping") def formal_step(self) -> str: row_or_col = "rows" if self.row else "cols" From e002bec777d0e0c51f318c880acab5df79dae1b5 Mon Sep 17 00:00:00 2001 From: jaypantone Date: Wed, 18 Jan 2023 16:59:58 -0600 Subject: [PATCH 068/100] Fix and upgrade Github Actions (#500) * "pypy3" -> "pypy3.8" * change default from cpython 3.8 to 3.10 and add pypy3.9 * add quotes * add more quotes * MOAR QUOTES * oops too many quotes --- .github/workflows/build-and-deploy.yml | 2 +- .github/workflows/test.yml | 22 ++++++++++++++-------- tox.ini | 10 ++++++---- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index aab82b89..974a25c1 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -12,7 +12,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 with: - python-version: 3.8 + python-version: 3.10 - name: install dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c8bf50d1..dfa225f2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,16 +9,16 @@ jobs: fail-fast: false matrix: include: - - python: 3.8 + - python: "3.10" toxenv: flake8 os: ubuntu-latest - - python: 3.8 + - python: "3.10" toxenv: mypy os: ubuntu-latest - - python: 3.8 + - python: "3.10" toxenv: pylint os: ubuntu-latest - - python: 3.8 + - python: "3.10" toxenv: black os: ubuntu-latest @@ -31,15 +31,21 @@ jobs: - python: "3.10" toxenv: py310 os: ubuntu-latest + - python: "3.11" + toxenv: py311 + os: ubuntu-latest - python: pypy-3.8 toxenv: pypy38 os: ubuntu-latest + - python: pypy-3.9 + toxenv: pypy39 + os: ubuntu-latest - - python: 3.8 - toxenv: py38 + - python: "3.10" + toxenv: py310 os: macos-latest - - python: 3.8 - toxenv: py38 + - python: "3.10" + toxenv: py310 os: windows-latest runs-on: ${{ matrix.os }} diff --git a/tox.ini b/tox.ini index 0fad5b68..add7e7d8 100644 --- a/tox.ini +++ b/tox.ini @@ -6,11 +6,11 @@ [tox] envlist = flake8, mypy, pylint, black - py{38,39,310}, - pypy38 + py{38,39,310,311}, + pypy{38,39} [default] -basepython=python3.8 +basepython=python3.10 [testenv] description = run test @@ -18,7 +18,9 @@ basepython = py38: python3.8 py39: python3.9 py310: python3.10 - pypy38: pypy3 + py311: python3.11 + pypy38: pypy3.8 + pypy39: pypy3.9 deps = pytest==7.2.0 pytest-timeout==2.1.0 From 0c64e3879b6b664614e68452c25fb5c30a5be6eb Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Thu, 19 Jan 2023 13:56:21 +0000 Subject: [PATCH 069/100] Sliding bug (#501) * add consecutive value condition for sliding * tidy up --- CHANGELOG.md | 1 + tests/strategies/test_sliding.py | 72 ++++++++++++++++++++ tilings/strategies/monotone_sliding.py | 94 ++++++++++++++++++-------- 3 files changed, 139 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71b1ea1d..bd776d53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ swapped around a fusable row or column. were duplicates. - `RequirementPlacement` algorithm didn't minimise obstructions correctly when placing size 2 or higher gridded perms. +- added missing condition in `MonotoneSlidingFactory` for consecutive values ### Changed - `TileScopePack.make_tracked` will add the appropriate tracking methods for diff --git a/tests/strategies/test_sliding.py b/tests/strategies/test_sliding.py index dcf72df5..beec54da 100644 --- a/tests/strategies/test_sliding.py +++ b/tests/strategies/test_sliding.py @@ -1,5 +1,6 @@ from tilings import GriddedPerm, Tiling from tilings.assumptions import TrackingAssumption +from tilings.strategies.monotone_sliding import MonotoneSlidingFactory from tilings.strategies.sliding import SlidingFactory tiling = Tiling( @@ -31,6 +32,70 @@ ), ) +noslidetiling1 = Tiling( + obstructions=( + GriddedPerm((0, 1), ((0, 0), (0, 0))), + GriddedPerm((0, 1, 2), ((1, 0), (1, 0), (1, 0))), + GriddedPerm((0, 1, 2, 3), ((2, 0), (2, 0), (2, 0), (2, 0))), + GriddedPerm((0, 1, 3, 2), ((2, 0), (2, 0), (2, 0), (2, 0))), + GriddedPerm((0, 1, 2, 3, 4), ((3, 0), (3, 0), (3, 0), (3, 0), (3, 0))), + GriddedPerm((0, 1, 2, 4, 3), ((3, 0), (3, 0), (3, 0), (3, 0), (3, 0))), + GriddedPerm((0, 1, 3, 2, 4), ((3, 0), (3, 0), (3, 0), (3, 0), (3, 0))), + GriddedPerm((0, 1, 3, 4, 2), ((3, 0), (3, 0), (3, 0), (3, 0), (3, 0))), + GriddedPerm((0, 1, 4, 2, 3), ((3, 0), (3, 0), (3, 0), (3, 0), (3, 0))), + GriddedPerm((0, 1, 4, 3, 2), ((3, 0), (3, 0), (3, 0), (3, 0), (3, 0))), + GriddedPerm((0, 2, 3, 4, 1), ((3, 0), (3, 0), (3, 0), (3, 0), (3, 0))), + GriddedPerm((0, 2, 4, 3, 1), ((3, 0), (3, 0), (3, 0), (3, 0), (3, 0))), + GriddedPerm((0, 1, 2), ((0, 0), (1, 0), (1, 0))), + GriddedPerm((0, 1, 2), ((0, 0), (1, 0), (2, 0))), + GriddedPerm((0, 1, 2), ((0, 0), (1, 0), (3, 0))), + GriddedPerm((0, 1, 2), ((1, 0), (1, 0), (2, 0))), + GriddedPerm((0, 1, 2), ((1, 0), (1, 0), (3, 0))), + GriddedPerm((0, 2, 1), ((0, 0), (1, 0), (3, 0))), + GriddedPerm((0, 2, 1), ((1, 0), (1, 0), (3, 0))), + GriddedPerm((0, 1, 2, 3), ((0, 0), (2, 0), (2, 0), (2, 0))), + GriddedPerm((0, 1, 2, 3), ((0, 0), (2, 0), (2, 0), (3, 0))), + GriddedPerm((0, 1, 2, 3), ((0, 0), (2, 0), (3, 0), (3, 0))), + GriddedPerm((0, 1, 2, 3), ((1, 0), (2, 0), (2, 0), (2, 0))), + GriddedPerm((0, 1, 2, 3), ((1, 0), (2, 0), (2, 0), (3, 0))), + GriddedPerm((0, 1, 2, 3), ((1, 0), (2, 0), (3, 0), (3, 0))), + ), + requirements=(), + assumptions=[TrackingAssumption((GriddedPerm((0,), ((0, 0),)),))], +) + +noslidetiling2 = Tiling( + obstructions=( + GriddedPerm((0, 1), ((1, 0), (1, 0))), + GriddedPerm((0, 1, 2), ((0, 0), (0, 0), (0, 0))), + GriddedPerm((0, 1, 2, 3), ((2, 0), (2, 0), (2, 0), (2, 0))), + GriddedPerm((0, 1, 3, 2), ((2, 0), (2, 0), (2, 0), (2, 0))), + GriddedPerm((0, 1, 2, 3, 4), ((3, 0), (3, 0), (3, 0), (3, 0), (3, 0))), + GriddedPerm((0, 1, 2, 4, 3), ((3, 0), (3, 0), (3, 0), (3, 0), (3, 0))), + GriddedPerm((0, 1, 3, 2, 4), ((3, 0), (3, 0), (3, 0), (3, 0), (3, 0))), + GriddedPerm((0, 1, 3, 4, 2), ((3, 0), (3, 0), (3, 0), (3, 0), (3, 0))), + GriddedPerm((0, 1, 4, 2, 3), ((3, 0), (3, 0), (3, 0), (3, 0), (3, 0))), + GriddedPerm((0, 1, 4, 3, 2), ((3, 0), (3, 0), (3, 0), (3, 0), (3, 0))), + GriddedPerm((0, 2, 3, 4, 1), ((3, 0), (3, 0), (3, 0), (3, 0), (3, 0))), + GriddedPerm((0, 2, 4, 3, 1), ((3, 0), (3, 0), (3, 0), (3, 0), (3, 0))), + GriddedPerm((0, 1, 2), ((0, 0), (0, 0), (1, 0))), + GriddedPerm((0, 1, 2), ((0, 0), (0, 0), (2, 0))), + GriddedPerm((0, 1, 2), ((0, 0), (0, 0), (3, 0))), + GriddedPerm((0, 1, 2), ((0, 0), (1, 0), (2, 0))), + GriddedPerm((0, 1, 2), ((0, 0), (1, 0), (3, 0))), + GriddedPerm((0, 2, 1), ((0, 0), (0, 0), (3, 0))), + GriddedPerm((0, 2, 1), ((0, 0), (1, 0), (3, 0))), + GriddedPerm((0, 1, 2, 3), ((0, 0), (2, 0), (2, 0), (2, 0))), + GriddedPerm((0, 1, 2, 3), ((0, 0), (2, 0), (2, 0), (3, 0))), + GriddedPerm((0, 1, 2, 3), ((0, 0), (2, 0), (3, 0), (3, 0))), + GriddedPerm((0, 1, 2, 3), ((1, 0), (2, 0), (2, 0), (2, 0))), + GriddedPerm((0, 1, 2, 3), ((1, 0), (2, 0), (2, 0), (3, 0))), + GriddedPerm((0, 1, 2, 3), ((1, 0), (2, 0), (3, 0), (3, 0))), + ), + requirements=(), + assumptions=[TrackingAssumption((GriddedPerm((0,), ((1, 0),)),))], +) + def sanity_checker(rules): found_some = False @@ -46,3 +111,10 @@ def test_sliding_factory(): assert sanity_checker(SlidingFactory(True)(tiling.reverse())) assert sanity_checker(SlidingFactory(True)(tiling.rotate90())) assert sanity_checker(SlidingFactory(True)(tiling.rotate90().reverse())) + + +def test_monotone_sliding_factory(): + assert list(MonotoneSlidingFactory()(noslidetiling1)) == [] + assert list(MonotoneSlidingFactory()(noslidetiling2)) == [] + for strategy in MonotoneSlidingFactory()(tiling): + assert sanity_checker([strategy(tiling)]) diff --git a/tilings/strategies/monotone_sliding.py b/tilings/strategies/monotone_sliding.py index c256d556..82ab22cc 100644 --- a/tilings/strategies/monotone_sliding.py +++ b/tilings/strategies/monotone_sliding.py @@ -1,8 +1,9 @@ from itertools import chain -from typing import Dict, Iterator, Optional, Tuple +from typing import Dict, Iterator, List, Optional, Tuple from comb_spec_searcher import DisjointUnionStrategy, StrategyFactory from comb_spec_searcher.exception import StrategyDoesNotApply +from permuta import Perm from tilings import GriddedPerm, Tiling from tilings.algorithms import Fusion @@ -116,40 +117,77 @@ def __call__(self, comb_class: Tiling) -> Iterator[GeneralizedSlidingStrategy]: ): comb_class = comb_class.rotate270() rotate = True + + def valid_monotone_sliding_region( + local_cells: Tuple[List[Perm], List[Perm]], comb_class: Tiling + ) -> bool: + """ + Return True if the region is a possible valid monotone sliding region. + + That is: + - neighbouring cells are both increasing or decreasing. + - the values of non-local obstructions in sliding region are + monotone and consecutive in value. + """ + return (len(local_cells[0]) == 1 and len(local_cells[1]) == 1) and ( + ( # both cells are increasing, and consecutive values are increasing + local_cells[0][0].is_increasing() + and local_cells[1][0].is_increasing() + and consecutive_value(col, comb_class) + ) + or ( # both cells are decreasing, and consecutive values are decreasing + ( + local_cells[0][0].is_decreasing() + and local_cells[1][0].is_decreasing() + and consecutive_value(col, comb_class, False) + ) + ) + ) + + def consecutive_value(col: int, tiling: Tiling, incr: bool = True) -> bool: + """ + Return True if the values in the column are consecutive, + and increasing or decreasing if incr is True or False. + """ + for gp in tiling.obstructions: + if any(x not in (col, col + 1) for x, _ in gp.pos): + points = chain(gp.get_points_col(col), gp.get_points_col(col + 1)) + values = [y for _, y in points] + if incr and not all(x + 1 == y for x, y in zip(values, values[1:])): + return False + if not incr and not all( + x - 1 == y for x, y in zip(values, values[1:]) + ): + return False + return True + if comb_class.dimensions[1] == 1 and not comb_class.requirements: + # TODO: allow requirements outside of sliding region for col in range(comb_class.dimensions[0] - 1): local_cells = ( comb_class.cell_basis()[(col, 0)][0], comb_class.cell_basis()[(col + 1, 0)][0], ) - if len(local_cells[0]) == 1 and len(local_cells[1]) == 1: - if ( - local_cells[0][0].is_increasing() - and local_cells[1][0].is_increasing() - ) or ( - ( - local_cells[0][0].is_decreasing() - and local_cells[1][0].is_decreasing() - ) - ): - shortest = ( - col - if len(local_cells[0][0]) <= len(local_cells[1][0]) - else col + 1 - ) - algo = Fusion(comb_class, col_idx=col) - fused_obs = tuple( - algo.fuse_gridded_perm(gp) - for gp in comb_class.obstructions - if not all(x == shortest for x, _ in gp.pos) - ) - unfused_obs = tuple( - chain.from_iterable( - algo.unfuse_gridded_perm(gp) for gp in fused_obs - ) + if valid_monotone_sliding_region(local_cells, comb_class): + # Check the fusability condition + shortest = ( + col + if len(local_cells[0][0]) <= len(local_cells[1][0]) + else col + 1 + ) + algo = Fusion(comb_class, col_idx=col) + fused_obs = tuple( + algo.fuse_gridded_perm(gp) + for gp in comb_class.obstructions + if not all(x == shortest for x, _ in gp.pos) + ) + unfused_obs = tuple( + chain.from_iterable( + algo.unfuse_gridded_perm(gp) for gp in fused_obs ) - if comb_class == comb_class.add_obstructions(unfused_obs): - yield GeneralizedSlidingStrategy(col, rotate) + ) + if comb_class == comb_class.add_obstructions(unfused_obs): + yield GeneralizedSlidingStrategy(col, rotate) def __repr__(self): return f"{self.__class__.__name__}()" From 0e1e659f932cf389303c36feb0290bee6abe8697 Mon Sep 17 00:00:00 2001 From: jaypantone Date: Thu, 19 Jan 2023 09:36:08 -0600 Subject: [PATCH 070/100] add quotes and set new CSS version in setup.py (#502) --- .github/workflows/build-and-deploy.yml | 2 +- setup.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 974a25c1..67de9fc6 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -12,7 +12,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 with: - python-version: 3.10 + python-version: "3.10" - name: install dependencies run: | python -m pip install --upgrade pip diff --git a/setup.py b/setup.py index eba8b042..50e6d8b3 100755 --- a/setup.py +++ b/setup.py @@ -34,10 +34,10 @@ def get_version(rel_path): packages=find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests"]), long_description=read("README.rst"), install_requires=[ - "comb-spec-searcher==4.1.0", + "comb-spec-searcher==4.2.0", "permuta==2.2.0", ], - python_requires=">=3.7", + python_requires=">=3.8", include_package_data=True, classifiers=[ "Development Status :: 5 - Production/Stable", From 3e16919d0f0df9ca7977534455fd8430bb20f093 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Fri, 20 Jan 2023 14:45:43 +0000 Subject: [PATCH 071/100] check in decomp func of sliding (#503) * check in decomp func of sliding * changelog --- CHANGELOG.md | 4 +- tests/strategies/test_sliding.py | 6 +- tilings/strategies/monotone_sliding.py | 126 +++++++++++++++---------- 3 files changed, 81 insertions(+), 55 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd776d53..966cea96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,7 +51,9 @@ swapped around a fusable row or column. were duplicates. - `RequirementPlacement` algorithm didn't minimise obstructions correctly when placing size 2 or higher gridded perms. -- added missing condition in `MonotoneSlidingFactory` for consecutive values +- added missing condition in `MonotoneSlidingFactory` for consecutive + values. Previous rules failing this condition will now raise + `StrategyDoesNotApply` if it fails this condition. ### Changed - `TileScopePack.make_tracked` will add the appropriate tracking methods for diff --git a/tests/strategies/test_sliding.py b/tests/strategies/test_sliding.py index beec54da..a0ef22c1 100644 --- a/tests/strategies/test_sliding.py +++ b/tests/strategies/test_sliding.py @@ -116,5 +116,7 @@ def test_sliding_factory(): def test_monotone_sliding_factory(): assert list(MonotoneSlidingFactory()(noslidetiling1)) == [] assert list(MonotoneSlidingFactory()(noslidetiling2)) == [] - for strategy in MonotoneSlidingFactory()(tiling): - assert sanity_checker([strategy(tiling)]) + assert sanity_checker(MonotoneSlidingFactory()(tiling)) + assert sanity_checker(MonotoneSlidingFactory()(tiling.rotate90())) + assert sanity_checker(MonotoneSlidingFactory()(tiling.rotate180())) + assert sanity_checker(MonotoneSlidingFactory()(tiling.rotate270())) diff --git a/tilings/strategies/monotone_sliding.py b/tilings/strategies/monotone_sliding.py index 82ab22cc..dd749134 100644 --- a/tilings/strategies/monotone_sliding.py +++ b/tilings/strategies/monotone_sliding.py @@ -3,6 +3,7 @@ from comb_spec_searcher import DisjointUnionStrategy, StrategyFactory from comb_spec_searcher.exception import StrategyDoesNotApply +from comb_spec_searcher.strategies import Rule from permuta import Perm from tilings import GriddedPerm, Tiling from tilings.algorithms import Fusion @@ -19,6 +20,16 @@ def __init__(self, idx: int, rotate: bool = False): self.rotate = rotate def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling]: + if self.can_slide(comb_class): + return (self.slide_tiling(comb_class),) + raise StrategyDoesNotApply(f"Sliding idx {self.idx} does not apply") + + def can_slide(self, comb_class: Tiling) -> bool: + return MonotoneSlidingFactory.can_slide_col( + self.idx, comb_class.rotate270() if self.rotate else comb_class + ) + + def slide_tiling(self, comb_class: Tiling) -> Tiling: if self.rotate: comb_class = comb_class.rotate270() child = Tiling( @@ -28,7 +39,7 @@ def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling]: ) if self.rotate: child = child.rotate90() - return (child,) + return child def formal_step(self) -> str: return f"Sliding index {self.idx}" @@ -108,7 +119,8 @@ class MonotoneSlidingFactory(StrategyFactory[Tiling]): This is only looks at n x 1 and 1 x n tilings. """ - def __call__(self, comb_class: Tiling) -> Iterator[GeneralizedSlidingStrategy]: + def __call__(self, comb_class: Tiling) -> Iterator[Rule]: + parent = comb_class rotate = False if ( not comb_class.dimensions[1] == 1 @@ -118,31 +130,54 @@ def __call__(self, comb_class: Tiling) -> Iterator[GeneralizedSlidingStrategy]: comb_class = comb_class.rotate270() rotate = True - def valid_monotone_sliding_region( - local_cells: Tuple[List[Perm], List[Perm]], comb_class: Tiling - ) -> bool: - """ - Return True if the region is a possible valid monotone sliding region. - - That is: - - neighbouring cells are both increasing or decreasing. - - the values of non-local obstructions in sliding region are - monotone and consecutive in value. - """ - return (len(local_cells[0]) == 1 and len(local_cells[1]) == 1) and ( - ( # both cells are increasing, and consecutive values are increasing - local_cells[0][0].is_increasing() - and local_cells[1][0].is_increasing() - and consecutive_value(col, comb_class) - ) - or ( # both cells are decreasing, and consecutive values are decreasing - ( - local_cells[0][0].is_decreasing() - and local_cells[1][0].is_decreasing() - and consecutive_value(col, comb_class, False) - ) - ) + if comb_class.dimensions[1] == 1 and not comb_class.requirements: + # TODO: allow requirements outside of sliding region + for col in range(comb_class.dimensions[0] - 1): + if self.can_slide_col(col, comb_class): + strategy = GeneralizedSlidingStrategy(col, rotate) + child = strategy.slide_tiling(parent) + yield strategy(parent, (child,)) + + @staticmethod + def can_slide_col(col: int, comb_class: Tiling) -> bool: + """ + Return True if the column can be slid. + """ + local_cells = ( + comb_class.cell_basis()[(col, 0)][0], + comb_class.cell_basis()[(col + 1, 0)][0], + ) + if MonotoneSlidingFactory.valid_monotone_sliding_region( + col, local_cells, comb_class + ): + # Check the fusability condition + shortest = ( + col if len(local_cells[0][0]) <= len(local_cells[1][0]) else col + 1 + ) + algo = Fusion(comb_class, col_idx=col) + fused_obs = tuple( + algo.fuse_gridded_perm(gp) + for gp in comb_class.obstructions + if not all(x == shortest for x, _ in gp.pos) ) + unfused_obs = tuple( + chain.from_iterable(algo.unfuse_gridded_perm(gp) for gp in fused_obs) + ) + return comb_class == comb_class.add_obstructions(unfused_obs) + return False + + @staticmethod + def valid_monotone_sliding_region( + col: int, local_cells: Tuple[List[Perm], List[Perm]], comb_class: Tiling + ) -> bool: + """ + Return True if the region is a possible valid monotone sliding region. + + That is: + - neighbouring cells are both increasing or decreasing. + - the values of non-local obstructions in sliding region are + monotone and consecutive in value. + """ def consecutive_value(col: int, tiling: Tiling, incr: bool = True) -> bool: """ @@ -161,33 +196,20 @@ def consecutive_value(col: int, tiling: Tiling, incr: bool = True) -> bool: return False return True - if comb_class.dimensions[1] == 1 and not comb_class.requirements: - # TODO: allow requirements outside of sliding region - for col in range(comb_class.dimensions[0] - 1): - local_cells = ( - comb_class.cell_basis()[(col, 0)][0], - comb_class.cell_basis()[(col + 1, 0)][0], + return (len(local_cells[0]) == 1 and len(local_cells[1]) == 1) and ( + ( # both cells are increasing, and consecutive values are increasing + local_cells[0][0].is_increasing() + and local_cells[1][0].is_increasing() + and consecutive_value(col, comb_class) + ) + or ( # both cells are decreasing, and consecutive values are decreasing + ( + local_cells[0][0].is_decreasing() + and local_cells[1][0].is_decreasing() + and consecutive_value(col, comb_class, False) ) - if valid_monotone_sliding_region(local_cells, comb_class): - # Check the fusability condition - shortest = ( - col - if len(local_cells[0][0]) <= len(local_cells[1][0]) - else col + 1 - ) - algo = Fusion(comb_class, col_idx=col) - fused_obs = tuple( - algo.fuse_gridded_perm(gp) - for gp in comb_class.obstructions - if not all(x == shortest for x, _ in gp.pos) - ) - unfused_obs = tuple( - chain.from_iterable( - algo.unfuse_gridded_perm(gp) for gp in fused_obs - ) - ) - if comb_class == comb_class.add_obstructions(unfused_obs): - yield GeneralizedSlidingStrategy(col, rotate) + ) + ) def __repr__(self): return f"{self.__class__.__name__}()" From 9649292aa32c6ce6b601ca476180182bd73622de Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 24 Jan 2023 17:32:38 +0000 Subject: [PATCH 072/100] a new pack (#486) * a new pack * add json encoding test --- CHANGELOG.md | 2 + tests/test_strategy_pack.py | 1 + tilings/strategies/__init__.py | 2 + tilings/strategies/requirement_insertion.py | 60 +++++++++++++++++++++ tilings/strategy_pack.py | 32 +++++++++++ 5 files changed, 97 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 966cea96..4493ef50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,8 @@ swapped around a fusable row or column. point or empty cells which is added to most packs. - `TargetedCellInsertionFactory` which inserts factors of gridded perms if it can lead to factoring out a verified sub tiling. +- `BasisPatternInsertionFactory` which inserts permutations which are contained in + every pattern in the basis - `ComponentVerificationStrategy` which is added to component fusion packs. - `ComponentToPointAssumptionStrategy` that changes component assumptions to point assumptions. These strategies are yielded in `RearrangeAssumptionFactory`. diff --git a/tests/test_strategy_pack.py b/tests/test_strategy_pack.py index 77754090..98e1924a 100644 --- a/tests/test_strategy_pack.py +++ b/tests/test_strategy_pack.py @@ -85,6 +85,7 @@ def length_row_col_partial(pack): length(TileScopePack.all_the_strategies) + partial(TileScopePack.insertion_point_placements) + partial(TileScopePack.subobstruction_placements) + + partial(TileScopePack.basis_pattern_insertions) + row_col_partial(TileScopePack.insertion_row_and_col_placements) + row_col_partial(TileScopePack.insertion_point_row_and_col_placements) + length_maxnumreq_partial(TileScopePack.only_root_placements) diff --git a/tilings/strategies/__init__.py b/tilings/strategies/__init__.py index 9e59df4f..1c9155ec 100644 --- a/tilings/strategies/__init__.py +++ b/tilings/strategies/__init__.py @@ -26,6 +26,7 @@ ) from .rearrange_assumption import RearrangeAssumptionFactory from .requirement_insertion import ( + BasisPatternInsertionFactory, CellInsertionFactory, FactorInsertionFactory, PointCorroborationFactory, @@ -69,6 +70,7 @@ "SplittingStrategy", # Batch "AllPlacementsFactory", + "BasisPatternInsertionFactory", "CellInsertionFactory", "FactorInsertionFactory", "FusableRowAndColumnPlacementFactory", diff --git a/tilings/strategies/requirement_insertion.py b/tilings/strategies/requirement_insertion.py index 82e5b29b..332de52d 100644 --- a/tilings/strategies/requirement_insertion.py +++ b/tilings/strategies/requirement_insertion.py @@ -692,3 +692,63 @@ def req_lists_to_insert(self, tiling: Tiling) -> Iterator[ListRequirement]: def __str__(self) -> str: return "subobstruction insertion" + + +class BasisPatternInsertionFactory(AbstractRequirementInsertionFactory): + """ + Insert all requirements that are a subpattern of every pattern in the basis. + """ + + def __init__( + self, + basis: Optional[Iterable[Perm]] = None, + ignore_parent: bool = False, + ): + self.basis: Tuple[Perm, ...] = tuple(basis) if basis is not None else tuple() + self.perms = self.get_patterns() + self.maxreqlen = max(map(len, self.perms), default=0) + super().__init__(ignore_parent=ignore_parent) + + def get_patterns(self) -> Set[Perm]: + res: Set[Perm] = set() + to_process: Iterable[Perm] = self.basis + while to_process: + to_process = set( + ( + perm.remove(idx) + for perm in to_process + for idx in perm + if len(perm) > 1 + ) + ) + res.update(to_process) + return set( + perm for perm in res if all(patt.contains(perm) for patt in self.basis) + ) + + def change_basis( + self, + basis: Iterable[Perm], + ) -> "BasisPatternInsertionFactory": + """ + Return the version of the strategy with the given basis instead + of the current one. + """ + return self.__class__(tuple(basis)) + + def req_lists_to_insert(self, tiling: Tiling) -> Iterator[ListRequirement]: + obs_tiling = Tiling( + tiling.obstructions, + remove_empty_rows_and_cols=False, + derive_empty=False, + simplify=False, + sorted_input=True, + ) + for length in range(1, self.maxreqlen + 1): + for gp in obs_tiling.gridded_perms_of_length(length): + if gp.patt in self.perms: + yield (gp,) + + def __str__(self) -> str: + patts = "{" + ", ".join(map(str, sorted(self.perms))) + "}" + return f"insertions with permutations {patts}" diff --git a/tilings/strategy_pack.py b/tilings/strategy_pack.py index 3ab43d72..715ac567 100644 --- a/tilings/strategy_pack.py +++ b/tilings/strategy_pack.py @@ -46,6 +46,9 @@ def replace_list( if strategy.basis: logger.warning("Basis changed in %s", strategy) res.append(strategy.change_basis(basis)) + elif hasattr(strategy, "change_basis"): + logger.warning("Basis changed in %s", strategy) + res.append(strategy.change_basis(basis)) else: res.append(strategy) return res @@ -738,6 +741,35 @@ def subobstruction_placements(cls, partial: bool = False) -> "TileScopePack": name=name, ) + @classmethod + def basis_pattern_insertions(cls, partial: bool = False) -> "TileScopePack": + name = "partial_" if partial else "" + name += "basis_pattern_insertions" + return TileScopePack( + initial_strats=[ + strat.FactorFactory(), + strat.PointCorroborationFactory(), + strat.RequirementCorroborationFactory(), + ], + ver_strats=[ + strat.BasicVerificationStrategy(), + strat.InsertionEncodingVerificationStrategy(), + strat.OneByOneVerificationStrategy(), + strat.LocallyFactorableVerificationStrategy(), + ], + inferral_strats=[ + strat.RowColumnSeparationStrategy(), + strat.ObstructionTransitivityFactory(), + ], + expansion_strats=[ + [ + strat.BasisPatternInsertionFactory(), + strat.PatternPlacementFactory(partial=partial), + ], + ], + name=name, + ) + @classmethod def point_and_row_and_col_placements( cls, From 3ac3f90c197d26cebc610fd4088fc7028d975699 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 24 Jan 2023 17:33:21 +0000 Subject: [PATCH 073/100] Trackedclassdb (#431) * a first draft of a tracked classdb * make default for guided and limited searcher * adds flags to PointingStrategy and AssumptionPointingStrategy; also adds RequirementPointingStrategy to kitchen sink * fixes reprs, from_dicts, to_jsonables * fixes from_dict bugs from max_cell argument * Bug fixes * lots of improvements to trackedclassdb * mypy * mypy * mypy * black * css now takes classdb flag * use classqueue flag * fixes the fact that assumption types were completely ignored * adds one test * removes some comments * ignore pylint error Co-authored-by: Jay Pantone --- CHANGELOG.md | 4 +- tests/test_classdb.py | 24 ++++ tilings/assumptions.py | 4 + tilings/tilescope.py | 316 ++++++++++++++++++++++++++++++++++++----- tilings/tiling.py | 9 +- 5 files changed, 318 insertions(+), 39 deletions(-) create mode 100644 tests/test_classdb.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 4493ef50..7a933a93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,9 +40,7 @@ swapped around a fusable row or column. strategy so should be used with `RuleDBForest`. - `UnfusionFactory` that unfuses either all the rows or columns. Also non-productive. - `FusableRowAndColumnPlacementFactory` places fusable rows and columns. -- added the option `ignore_full_tiling_assumptions` to `LimitedAssumptionTileScope` - and therefore also to `TrackedSearcher`. If set to `True`, then full-tiling - assumptions do not count against the `max_assumptions` cap. The default is `False`. +- `TrackedClassDB` used by `TrackedSearcher` ### Fixed - `Factor` was not factoring correctly with respect to component assumptions. diff --git a/tests/test_classdb.py b/tests/test_classdb.py new file mode 100644 index 00000000..d66a2508 --- /dev/null +++ b/tests/test_classdb.py @@ -0,0 +1,24 @@ +from tilings.assumptions import SkewComponentAssumption, TrackingAssumption +from tilings.griddedperm import GriddedPerm +from tilings.tilescope import TrackedClassDB +from tilings.tiling import Tiling + + +def test_tracked_classdb(): + tiling = Tiling( + obstructions=( + GriddedPerm((1, 0, 2), ((1, 0), (1, 0), (1, 0))), + GriddedPerm((0, 2, 1, 3), ((0, 0), (0, 0), (0, 0), (0, 0))), + GriddedPerm((0, 2, 1, 3), ((0, 0), (0, 0), (0, 0), (1, 0))), + GriddedPerm((0, 2, 1, 3), ((0, 0), (0, 0), (1, 0), (1, 0))), + ), + requirements=(), + assumptions=( + SkewComponentAssumption((GriddedPerm((0,), ((1, 0),)),)), + TrackingAssumption((GriddedPerm((0,), ((1, 0),)),)), + ), + ) + tracked_classdb = TrackedClassDB() + tracked_classdb.add(tiling) + new_tiling = tracked_classdb.get_class(0) + assert tiling == new_tiling diff --git a/tilings/assumptions.py b/tilings/assumptions.py index 22275e10..93ed2b9e 100644 --- a/tilings/assumptions.py +++ b/tilings/assumptions.py @@ -27,6 +27,10 @@ def from_cells(cls, cells: Iterable[Cell]) -> "TrackingAssumption": gps = [GriddedPerm.single_cell((0,), cell) for cell in cells] return cls(gps) + def get_cells(self) -> Tuple[Cell, ...]: + assert all(len(gp) == 1 for gp in self.gps) + return tuple(gp.pos[0] for gp in self.gps) + def avoiding( self, obstructions: Iterable[GriddedPerm], diff --git a/tilings/tilescope.py b/tilings/tilescope.py index 80a78f0e..238563a7 100644 --- a/tilings/tilescope.py +++ b/tilings/tilescope.py @@ -1,3 +1,6 @@ +import itertools +import math +from array import array from collections import Counter from typing import Counter as CounterType from typing import ( @@ -9,6 +12,7 @@ Optional, Set, Tuple, + Type, Union, cast, ) @@ -21,15 +25,22 @@ CombinatorialSpecification, CombinatorialSpecificationSearcher, ) +from comb_spec_searcher.class_db import ClassDB, ClassKey, Info, Key from comb_spec_searcher.class_queue import CSSQueue, DefaultQueue, WorkPacket from comb_spec_searcher.rule_db.abstract import RuleDBAbstract +from comb_spec_searcher.strategies.rule import AbstractRule from comb_spec_searcher.typing import CombinatorialClassType, CSSstrategy from permuta import Basis, Perm from tilings import GriddedPerm, Tiling +from tilings.assumptions import TrackingAssumption from tilings.strategy_pack import TileScopePack __all__ = ("TileScope", "TileScopePack", "LimitedAssumptionTileScope", "GuidedSearcher") +Cell = Tuple[int, int] +TrackedClassAssumption = Tuple[int, Tuple[Cell, ...]] +TrackedClassDBKey = Tuple[int, Tuple[TrackedClassAssumption, ...]] + class TileScope(CombinatorialSpecificationSearcher): """ @@ -42,6 +53,8 @@ def __init__( start_class: Union[str, Iterable[Perm], Tiling], strategy_pack: TileScopePack, ruledb: Optional[RuleDBAbstract] = None, + classdb: Optional[ClassDB] = None, + classqueue: Optional[CSSQueue] = None, expand_verified: bool = False, debug: bool = False, ) -> None: @@ -74,7 +87,9 @@ def __init__( super().__init__( start_class=start_tiling, strategy_pack=strategy_pack, + classdb=classdb, ruledb=ruledb, + classqueue=classqueue, expand_verified=expand_verified, debug=debug, ) @@ -94,23 +109,23 @@ def __init__( ignore_full_tiling_assumptions: bool = False, **kwargs, ) -> None: - super().__init__(start_class, strategy_pack, **kwargs) self.max_assumptions = max_assumptions + super().__init__( + start_class, + strategy_pack, + classdb=TrackedClassDB(), + **kwargs, + ) self.ignore_full_tiling_assumptions = ignore_full_tiling_assumptions - def _expand( - self, - comb_class: CombinatorialClassType, - label: int, - strategies: Tuple[CSSstrategy, ...], - inferral: bool, - ) -> None: + def _rules_from_strategy( # type: ignore + self, comb_class: CombinatorialClassType, strategy: CSSstrategy + ) -> Iterator[AbstractRule]: """ - Will expand the combinatorial class with given label using the given - strategies, but only add rules whose children all satisfy the max_assumptions - requirement. + Yield all the rules given by a strategy/strategy factory whose children all + satisfy the max_assumptions constraint. """ - + # pylint: disable=arguments-differ def num_child_assumptions(child: Tiling) -> int: return sum( 1 @@ -119,18 +134,12 @@ def num_child_assumptions(child: Tiling) -> int: or len(ass.gps) != len(child.active_cells) ) - if inferral: - self._inferral_expand(comb_class, label, strategies) - else: - for strategy_generator in strategies: - for start_label, end_labels, rule in self._expand_class_with_strategy( - comb_class, strategy_generator, label - ): - if all( - num_child_assumptions(child) <= self.max_assumptions - for child in rule.children - ): - self.add_rule(start_label, end_labels, rule) + for rule in super()._rules_from_strategy(comb_class, strategy): + if all( + num_child_assumptions(child) <= self.max_assumptions + for child in rule.children + ): + yield rule class GuidedSearcher(TileScope): @@ -139,11 +148,15 @@ def __init__( tilings: Iterable[Tiling], basis: Tiling, pack: TileScopePack, - *args, **kwargs, ): self.tilings = frozenset(t.remove_assumptions() for t in tilings) - super().__init__(basis, pack, *args, **kwargs) + super().__init__( + basis, + pack, + classdb=TrackedClassDB(), + **kwargs, + ) for t in self.tilings: class_label = self.classdb.get_label(t) is_empty = self.classdb.is_empty(t, class_label) @@ -212,14 +225,14 @@ def __init__( **kwargs, ) -> None: super().__init__( - start_class, strategy_pack, max_assumptions=max_assumptions, **kwargs + start_class, + strategy_pack, + max_assumptions=max_assumptions, + classqueue=TrackedQueue( + cast(TileScopePack, strategy_pack), self, delay_next + ), + **kwargs, ) - # reset to the trackedqueue! - self.classqueue = cast( - DefaultQueue, - TrackedQueue(cast(TileScopePack, self.strategy_pack), self, delay_next), - ) # TODO: make CSS accept a CSSQueue as a kwarg - self.classqueue.add(self.start_label) class TrackedDefaultQueue(DefaultQueue): @@ -388,3 +401,240 @@ def __next__(self) -> WorkPacket: except StopIteration: continue raise StopIteration("No elements in queue") + + +class TrackedClassDB(ClassDB[Tiling]): + def __init__(self) -> None: + super().__init__(Tiling) + self.classdb = ClassDB(Tiling) + self.label_to_tilings: List[bytes] = [] + self.tilings_to_label: Dict[bytes, int] = {} + self.assumption_type_to_int: Dict[Type[TrackingAssumption], int] = {} + self.int_to_assumption_type: List[Type[TrackingAssumption]] = [] + + def __iter__(self) -> Iterator[int]: + for key in self.label_to_info: + yield key + + def __contains__(self, key: Key) -> bool: + if isinstance(key, Tiling): + actual_key = self.tiling_to_key(key) + compressed_key = self._compress_key(actual_key) + return self.tilings_to_label.get(compressed_key) is not None + if isinstance(key, int): + return 0 <= key < len(self.label_to_tilings) + raise ValueError("Invalid key") + + def __eq__(self, other: object) -> bool: + if not isinstance(other, TrackedClassDB): + return NotImplemented + return bool( + self.classdb == other.classdb + and self.label_to_tilings == other.label_to_tilings + and self.tilings_to_label == other.tilings_to_label + ) + + def tiling_to_key(self, tiling: Tiling) -> TrackedClassDBKey: + """ + Converts a tiling to its corresponding key. + """ + underlying_label = self.classdb.get_label(tiling.remove_assumptions()) + assumption_keys = tuple( + self.assumption_to_key(ass) for ass in tiling.assumptions + ) + return (underlying_label, assumption_keys) + + def assumption_to_key(self, ass: TrackingAssumption) -> TrackedClassAssumption: + """ + Determines the type of the assumption and retrieves the int representing + that type from the appropriate class variables, and then apprends the cells. + """ + try: + ass_type_int = self.assumption_type_to_int[type(ass)] + except KeyError: + ass_type_int = len(self.int_to_assumption_type) + assert ass_type_int < 256 + self.int_to_assumption_type.append(type(ass)) + self.assumption_type_to_int[type(ass)] = ass_type_int + return (ass_type_int, ass.get_cells()) + + def key_to_tiling(self, key: TrackedClassDBKey) -> Tiling: + """ + Converts a key back to a Tiling. + """ + return self.classdb.get_class(key[0]).add_assumptions( + ( + self.int_to_assumption_type[ass_key[0]].from_cells(ass_key[1]) + for ass_key in key[1] + ), + clean=False, + ) + + @staticmethod + def _compress_key(key: TrackedClassDBKey) -> bytes: + # Assumes there are fewer than 256 assumptions + # Assumes every assumption covers fewer than 256 cells + # Assumes the positions in an assumption have value < 256 + + def int_to_bytes(n: int) -> List[int]: + """ + Converts an int to a list of ints all in [0 .. 255] ready for + byte compression. First entry is the number of bytes needed (assumes < 256), + remaining entries the bytes composing the int from lowest byte up to largest + byte. + """ + bytes_needed = max(math.ceil(n.bit_length() / 8), 1) + result: List[int] = [bytes_needed] + while n >= 2**8: + result.append(n & 0xFF) + n = n >> 8 + result.append(n) + return result + + def _compress_assumption(ass_key: TrackedClassAssumption) -> List[int]: + type_int, cells = ass_key + assert type_int < 256 + assert len(cells) < 256 + assert all(cell[0] < 256 and cell[1] < 256 for cell in cells) + + result = [type_int] + result.append(len(cells)) + result.extend(itertools.chain(*cells)) + return result + + result: List[int] = int_to_bytes(key[0]) + result.extend( + itertools.chain.from_iterable( + _compress_assumption(ass_key) for ass_key in key[1] + ) + ) + compressed_key = array("B", result).tobytes() + return compressed_key + + @staticmethod + def _decompress_key(compressed_key: bytes) -> TrackedClassDBKey: + def int_from_bytes(n: array) -> int: + """ + Converts a list of ints to a single int assuming the first entry is the + lowest byte and so on. + """ + result = n[0] + for idx in range(1, len(n)): + result |= n[idx] << (8 * idx) + return cast(int, result) + + def _decompress_tuple_of_cells( + compressed_cells: array, + ) -> Tuple[Cell, ...]: + """ + compressed_cells is a list of 2*i bytes, each of which is a coordinates + """ + vals = iter(compressed_cells) + return tuple( + (next(vals), next(vals)) for _ in range(len(compressed_cells) // 2) + ) + + vals = array("B", compressed_key) + offset = 0 + + num_bytes_int = vals[offset] + offset += 1 + label = int_from_bytes(vals[offset : offset + num_bytes_int]) + offset += num_bytes_int + + tuples_of_cells = [] + while offset < len(vals): + type_int, num_cells = vals[offset : offset + 2] + offset += 2 + tuples_of_cells.append( + ( + type_int, + _decompress_tuple_of_cells(vals[offset : offset + 2 * num_cells]), + ) + ) + offset += 2 * num_cells + + return (label, tuple(tuples_of_cells)) + + def add(self, comb_class: ClassKey, compressed: bool = False) -> None: + """ + Adds a Tiling to the classdb + """ + if compressed: + raise NotImplementedError + if isinstance(comb_class, Tiling): + key = self.tiling_to_key(comb_class) + compressed_key = self._compress_key(key) + if compressed_key not in self.tilings_to_label: + self.label_to_tilings.append(compressed_key) + self.tilings_to_label[compressed_key] = len(self.tilings_to_label) + + def _get_info(self, key: Key) -> Info: + """ + Return the "Info" object corresponding to the key, which is + either a Tiling or an integer + """ + # pylint: disable=protected-access + if isinstance(key, Tiling): + actual_key = self.tiling_to_key(key) + compressed_key = self._compress_key(actual_key) + if compressed_key not in self.tilings_to_label: + self.add(key) + info: Optional[Info] = self.classdb._get_info(actual_key[0]) + if info is None: + raise ValueError("Invalid key") + info = Info( + key, + self.tilings_to_label[compressed_key], + info.empty, + ) + elif isinstance(key, int): + if not 0 <= key < len(self.label_to_tilings): + raise KeyError("Key not in ClassDB") + tiling_key = self._decompress_key(self.label_to_tilings[key]) + info = self.classdb.label_to_info.get(tiling_key[0]) + if info is None: + raise ValueError("Invalid key") + info = Info( + self.key_to_tiling(tiling_key), + key, + info.empty, + ) + else: + raise TypeError() + return info + + def get_class(self, key: Key) -> Tiling: + """ + Return combinatorial class of key. + """ + info = self._get_info(key) + return cast(Tiling, info.comb_class) + + def is_empty(self, comb_class: Tiling, label: Optional[int] = None) -> bool: + """ + Return True if combinatorial class is set to be empty, False if not. + """ + return bool(self.classdb.is_empty(comb_class.remove_assumptions())) + + def set_empty(self, key: Key, empty: bool = True) -> None: + """ + Set a class to be empty. + """ + if isinstance(key, int): + if 0 <= key < len(self.label_to_tilings): + underlying_label, _ = self._decompress_key(self.label_to_tilings[key]) + if isinstance(key, Tiling): + underlying_label = self.classdb.get_label(key.remove_assumptions()) + self.classdb.set_empty(underlying_label, empty) + + def status(self) -> str: + """ + Return a string with the current status of the run. + """ + status = self.classdb.status() + status = status.replace("combinatorial classes", "underlying tilings") + tilings = "\n\tTotal number of tilings found is" + tilings += f" {len(self.label_to_tilings):,d}" + status = status.replace("ClassDB status:", "TrackedClassDB status:" + tilings) + return status + "\n" diff --git a/tilings/tiling.py b/tilings/tiling.py index 5d49dc8c..4e32ebad 100644 --- a/tilings/tiling.py +++ b/tilings/tiling.py @@ -647,7 +647,9 @@ def add_assumption(self, assumption: TrackingAssumption) -> "Tiling": """Returns a new tiling with the added assumption.""" return self.add_assumptions((assumption,)) - def add_assumptions(self, assumptions: Iterable[TrackingAssumption]) -> "Tiling": + def add_assumptions( + self, assumptions: Iterable[TrackingAssumption], clean: bool = True + ) -> "Tiling": """Returns a new tiling with the added assumptions.""" tiling = Tiling( self._obstructions, @@ -658,7 +660,8 @@ def add_assumptions(self, assumptions: Iterable[TrackingAssumption]) -> "Tiling" simplify=False, sorted_input=True, ) - tiling.clean_assumptions() + if clean: + tiling.clean_assumptions() return tiling def remove_assumption(self, assumption: TrackingAssumption): @@ -681,7 +684,7 @@ def remove_assumption(self, assumption: TrackingAssumption): tiling.clean_assumptions() return tiling - def remove_assumptions(self): + def remove_assumptions(self) -> "Tiling": """ Return the tiling with all assumptions removed. """ From 943263d320da5eb23e1c68f8dcc4dff5f489475d Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 24 Jan 2023 17:55:59 +0000 Subject: [PATCH 074/100] one by one uses permpal for counting etc (#499) * one by one uses permpal for counting etc * fixed small bug getting rational gfs * changelog * only storing 1x1 specs without ass and reqs * ensure not over correcting for reqs * simplfy req correction line * eqs print F_Av(basis) --- CHANGELOG.md | 3 +- tilings/strategies/verification.py | 169 ++++++++++++++++++++++++++--- 2 files changed, 153 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a933a93..9d67077f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,7 +75,8 @@ swapped around a fusable row or column. - The default behavior for `RequirementInsertion` is to allow insertion of factorable requirements - `OneByOneVerificationStrategy` will look up permpal.com to find the generating - functions and min polys. + functions and min polys, and also use permpal specs for counting, sampling and + generating objects. ### Removed - `AddInterleavingAssumptionsFactory`. The factor strategy now adds the relevant diff --git a/tilings/strategies/verification.py b/tilings/strategies/verification.py index 026af932..b0e33fb2 100644 --- a/tilings/strategies/verification.py +++ b/tilings/strategies/verification.py @@ -2,14 +2,15 @@ from functools import reduce from itertools import chain from operator import mul -from typing import Any, Callable, Dict, Iterator, Optional, Tuple, cast +from typing import Any, Callable, Dict, Iterable, Iterator, Optional, Tuple, cast import requests -from sympy import Eq, Expr, Function, solve, sympify, var +from sympy import Eq, Expr, Function, Symbol, collect, degree, solve, sympify, var from comb_spec_searcher import ( AtomStrategy, CombinatorialClass, + CombinatorialSpecification, StrategyPack, VerificationStrategy, ) @@ -29,13 +30,14 @@ LocalEnumeration, MonotoneTreeEnumeration, ) -from tilings.assumptions import ComponentAssumption +from tilings.assumptions import ComponentAssumption, TrackingAssumption from tilings.strategies import ( DetectComponentsStrategy, FactorFactory, FactorInsertionFactory, RemoveRequirementFactory, RequirementCorroborationFactory, + SymmetriesFactory, ) from .abstract import BasisAwareVerificationStrategy @@ -146,7 +148,10 @@ def get_equation( data = request.json() min_poly = data["min_poly_maple"] if min_poly is None: - raise NotImplementedError(f"No min poly on permpal for {Av(basis)}") + return Eq( + get_function(self.comb_class), + self.tiling_to_symbol_eq(self.comb_class), + ) min_poly = min_poly.replace("^", "**").replace("F(x)", "F") lhs, _ = min_poly.split("=") # We now need to worry about the requirements. The min poly we got is @@ -160,7 +165,41 @@ def get_equation( for factor in res.as_ordered_factors(): if factor.atoms(Function): res = factor - return Eq(res, 0) + # currently we have 0 = rhs, + lhs = get_function(self.comb_class) + if degree(res, lhs) == 1: + # solve for rational gf + rhs = solve([Eq(res, 0)], lhs, dict=True)[0][lhs] + else: + # or add F to both sides + rhs = collect(res + lhs, lhs) + return Eq(lhs, rhs) + + def tiling_to_symbol_eq(self, tiling: Tiling) -> Any: + """ + Find the equation for the tiling in terms of F_C's, where C are + permutation classes. + """ + if tiling.requirements: + reqs = tiling.requirements[0] + avoided = tiling.__class__( + tiling.obstructions + reqs, + tiling.requirements[1:], + tiling.assumptions, + ) + without = tiling.__class__( + tiling.obstructions, + tiling.requirements[1:], + tiling.assumptions, + ) + return self.tiling_to_symbol_eq(without) - self.tiling_to_symbol_eq(avoided) + params = self.comb_class.extra_parameters + x_var = "x" + if params: + assert len(params) == 1 + x_var += "*" + params[0].replace("_", "") + basis = [ob.patt for ob in tiling.obstructions] + return Symbol(f"F_{Av(basis)}({x_var})") @property def no_req_tiling(self) -> Tiling: @@ -194,6 +233,59 @@ def without_req_genf(self, tiling: Tiling): class OneByOneVerificationStrategy(BasisAwareVerificationStrategy): + def __init__( + self, + basis: Optional[Iterable[Perm]] = None, + symmetry: bool = False, + ignore_parent: bool = False, + ): + super().__init__(basis, symmetry, ignore_parent) + self._spec: Dict[Tiling, CombinatorialSpecification] = {} + + @staticmethod + def _spec_from_permpal(tiling: Tiling) -> CombinatorialSpecification: + basis = [ob.patt for ob in tiling.obstructions] + basis_str = "_".join(map(str, lex_min(basis))) + uri = f"https://permpal.com/perms/raw_data_json/basis/{basis_str}" + request = requests.get(uri, timeout=10) + if request.status_code == 404: + raise InvalidOperationError("Can't find spec for one by one verified rule.") + data = request.json() + spec_json = data["specs_and_eqs"][0]["spec_json"] + spec = cast( + CombinatorialSpecification, CombinatorialSpecification.from_dict(spec_json) + ) + if spec.root != Tiling(tiling.obstructions): + for strategy in SymmetriesFactory()(tiling.remove_assumptions()): + rule = strategy(tiling.remove_assumptions()) + if rule.children[0] == spec.root: + break + else: + raise InvalidOperationError("Error fixing sym in 1x1") + rules = [rule] + list(spec.rules_dict.values()) + spec = CombinatorialSpecification(rule.comb_class, rules) + assert spec.root == Tiling(tiling.obstructions) + return spec + + def get_specification( + self, comb_class: Tiling + ) -> CombinatorialSpecification[Tiling, GriddedPerm]: + if comb_class not in self._spec: + try: + self._spec[comb_class] = super().get_specification(comb_class) + except InvalidOperationError as e: + if len(comb_class.requirements) > 1 or comb_class.dimensions != (1, 1): + raise e + self._spec[comb_class] = self._spec_from_permpal(comb_class) + return self._spec[comb_class] + + def get_complement_spec(self, tiling: Tiling) -> CombinatorialSpecification: + assert len(tiling.requirements) == 1 + complement = tiling.remove_requirement(tiling.requirements[0]).add_obstructions( + tiling.requirements[0] + ) + return self.get_specification(complement) + def pack(self, comb_class: Tiling) -> StrategyPack: if any(isinstance(ass, ComponentAssumption) for ass in comb_class.assumptions): return ComponentVerificationStrategy().pack(comb_class) @@ -280,24 +372,65 @@ def formal_step(self) -> str: return "tiling is a subclass of the original tiling" def get_terms(self, comb_class: Tiling, n: int) -> Terms: - raise NotImplementedError( - "Not implemented method to count objects for one by one verified tilings" - ) + terms = super().get_terms(comb_class.remove_assumptions(), n) + if ( + comb_class.requirements + and self.get_specification(comb_class).root != comb_class + ): + if len(comb_class.requirements) == 1: + comp_spec = self.get_complement_spec(comb_class.remove_assumptions()) + else: + raise NotImplementedError( + "Not implemented counting for one by one with two or more reqs" + ) + comp_terms = comp_spec.get_terms(n) + terms = Counter({tuple(): terms[tuple()] - comp_terms[tuple()]}) + if comb_class.assumptions: + assert comb_class.assumptions == (TrackingAssumption.from_cells([(0, 0)]),) + terms = Counter({(n,): terms[tuple()]}) + return terms + + def get_objects(self, comb_class: Tiling, n: int) -> Objects: + objects = super().get_objects(comb_class, n) + if comb_class.requirements: + if len(comb_class.requirements) == 1: + comp_spec = self.get_complement_spec(comb_class.remove_assumptions()) + else: + raise NotImplementedError( + "Not implemented objects for one by one with two or more reqs" + ) + comp_objects = comp_spec.get_objects(n) + objects = defaultdict( + list, + { + a: list(set(b).difference(comp_objects[a])) + for a, b in objects.items() + }, + ) - def generate_objects_of_size( - self, comb_class: Tiling, n: int, **parameters: int - ) -> Iterator[GriddedPerm]: - raise NotImplementedError( - "Not implemented method to generate objects for one by one " - "verified tilings" - ) + if comb_class.assumptions: + assert comb_class.assumptions == (TrackingAssumption.from_cells([(0, 0)]),) + objects = defaultdict(list, {(n,): objects[tuple()]}) + return objects def random_sample_object_of_size( self, comb_class: Tiling, n: int, **parameters: int ) -> GriddedPerm: - raise NotImplementedError( - "Not implemented random sample for one by one verified tilings" - ) + if comb_class.assumptions: + assert ( + len(comb_class.assumptions) == 1 + and parameters[ + comb_class.get_assumption_parameter(comb_class.assumptions[0]) + ] + == n + ) + while True: + # Rejection sampling + gp = super().random_sample_object_of_size( + Tiling(comb_class.obstructions), n + ) + if gp in comb_class: + return gp def __str__(self) -> str: if not self.basis: From 76bb128dcf0444302833dae3319418bd384f8e80 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 31 Jan 2023 15:41:33 +0000 Subject: [PATCH 075/100] fix k0 / k_0 typo in 1x1 eqs (#505) --- tilings/strategies/verification.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tilings/strategies/verification.py b/tilings/strategies/verification.py index b0e33fb2..3cd27dd8 100644 --- a/tilings/strategies/verification.py +++ b/tilings/strategies/verification.py @@ -197,7 +197,7 @@ def tiling_to_symbol_eq(self, tiling: Tiling) -> Any: x_var = "x" if params: assert len(params) == 1 - x_var += "*" + params[0].replace("_", "") + x_var += "*" + params[0] basis = [ob.patt for ob in tiling.obstructions] return Symbol(f"F_{Av(basis)}({x_var})") From ec7bf101da792cea268af78a701526a944cd7aa0 Mon Sep 17 00:00:00 2001 From: jaypantone Date: Wed, 1 Feb 2023 15:13:49 -0600 Subject: [PATCH 076/100] fixes bug in sanity checking new 1x1 verification (#506) * fixes bug in sanity checking new 1x1 verification * reuse variable --- tilings/strategies/verification.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tilings/strategies/verification.py b/tilings/strategies/verification.py index 3cd27dd8..ca51bef4 100644 --- a/tilings/strategies/verification.py +++ b/tilings/strategies/verification.py @@ -255,9 +255,10 @@ def _spec_from_permpal(tiling: Tiling) -> CombinatorialSpecification: spec = cast( CombinatorialSpecification, CombinatorialSpecification.from_dict(spec_json) ) - if spec.root != Tiling(tiling.obstructions): - for strategy in SymmetriesFactory()(tiling.remove_assumptions()): - rule = strategy(tiling.remove_assumptions()) + actual_class = Tiling(tiling.obstructions) + if spec.root != actual_class: + for strategy in SymmetriesFactory()(actual_class): + rule = strategy(actual_class) if rule.children[0] == spec.root: break else: From 5368c86dfd19557421eee1bbd818f66d84db8517 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 7 Feb 2023 14:41:06 +0000 Subject: [PATCH 077/100] make local verification basis aware (#507) --- CHANGELOG.md | 1 + tilings/strategies/verification.py | 16 +++++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d67077f..cbe6619c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,7 @@ swapped around a fusable row or column. - added missing condition in `MonotoneSlidingFactory` for consecutive values. Previous rules failing this condition will now raise `StrategyDoesNotApply` if it fails this condition. +- `LocalVerificationStrategy` needs to be a `BasisAwareVerificationStrategy` ### Changed - `TileScopePack.make_tracked` will add the appropriate tracking methods for diff --git a/tilings/strategies/verification.py b/tilings/strategies/verification.py index ca51bef4..063d1439 100644 --- a/tilings/strategies/verification.py +++ b/tilings/strategies/verification.py @@ -719,7 +719,7 @@ def __str__(self) -> str: return "elementary verification" -class LocalVerificationStrategy(TileScopeVerificationStrategy): +class LocalVerificationStrategy(BasisAwareVerificationStrategy): """ The local verified strategy. @@ -727,9 +727,15 @@ class LocalVerificationStrategy(TileScopeVerificationStrategy): localized, i.e. in a single cell and the tiling is not 1x1. """ - def __init__(self, ignore_parent: bool = False, no_factors: bool = False): + def __init__( + self, + basis: Optional[Iterable[Perm]] = None, + symmetry: bool = False, + ignore_parent: bool = False, + no_factors: bool = False, + ): self.no_factors = no_factors - super().__init__(ignore_parent=ignore_parent) + super().__init__(basis, symmetry, ignore_parent) def pack(self, comb_class: Tiling) -> StrategyPack: try: @@ -746,11 +752,11 @@ def pack(self, comb_class: Tiling) -> StrategyPack: expansion_strats=[], ver_strats=[ BasicVerificationStrategy(), - OneByOneVerificationStrategy(), + OneByOneVerificationStrategy(self.basis, self._symmetry), ComponentVerificationStrategy(), InsertionEncodingVerificationStrategy(), MonotoneTreeVerificationStrategy(no_factors=True), - LocalVerificationStrategy(no_factors=True), + LocalVerificationStrategy(self.basis, self._symmetry, no_factors=True), ], name="factor pack", ) From b401cba6098c34a110e70f25c3da710253280697 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 14 Feb 2023 19:50:58 +0000 Subject: [PATCH 078/100] implement positive reverse fusion counting (#509) * implement positive reverse fusion counting * If left only and right is positive, then add one to every left parameter * tidy up tests --- tests/strategies/test_reverse_fusion.py | 36 ++++++++++++++++++++---- tilings/strategies/fusion/constructor.py | 16 +++++++++++ tilings/strategies/fusion/fusion.py | 11 +++++--- 3 files changed, 53 insertions(+), 10 deletions(-) diff --git a/tests/strategies/test_reverse_fusion.py b/tests/strategies/test_reverse_fusion.py index 44247b16..6b10775c 100644 --- a/tests/strategies/test_reverse_fusion.py +++ b/tests/strategies/test_reverse_fusion.py @@ -70,6 +70,33 @@ def reverse_fusion_rules(): yield FusionStrategy(col_idx=1, tracked=True)(t3).to_reverse_rule(0) yield FusionStrategy(row_idx=1, tracked=True)(t3.rotate270()).to_reverse_rule(0) + left_requirement = [GriddedPerm.point_perm((1, 0))] + right_requirement = [GriddedPerm.point_perm((2, 0))] + t4 = t.add_assumptions([left_overlap, left]).add_list_requirement(right_requirement) + yield FusionStrategy(col_idx=1, tracked=True)(t4).to_reverse_rule(0) + t5 = t.add_assumptions([left_overlap, left]).add_list_requirement(left_requirement) + yield FusionStrategy(col_idx=1, tracked=True)(t5).to_reverse_rule(0) + + # # FOR A MORE EXHAUSTIVE SET OF TESTS UNCOMMENT THE FOLLOWING + # from itertools import chain, combinations + # + # for assumptions in chain.from_iterable( + # combinations([left, right, left_overlap, right_overlap], i) for i in range(5) + # ): + # for reqs in chain.from_iterable( + # combinations([left_requirement, right_requirement], i) for i in range(2) + # ): + # tiling = t.add_assumptions(assumptions) + # for req in reqs: + # tiling = tiling.add_list_requirement(req) + # rule = FusionStrategy(col_idx=1, tracked=True)(tiling) + # rotate_tiling = tiling.rotate270() + # rotate_rule = FusionStrategy(row_idx=1, tracked=True)(rotate_tiling) + # yield rule + # if left in assumptions or right in assumptions: + # yield rule.to_reverse_rule(0) + # yield rotate_rule.to_reverse_rule(0) + @pytest.fixture def both_reverse_fusion_rule(): @@ -109,9 +136,6 @@ def test_positive_reverse_fusion(caplog): reverse_rule = rule.to_reverse_rule(0) logger.propagate = True with caplog.at_level(logging.WARNING): - reverse_rule.sanity_check(4) - assert len(caplog.records) == 2 - assert ( - "Skipping sanity checking counts" in caplog.text - and "Skipping sanity checking generation" in caplog.text - ) + assert reverse_rule.sanity_check(4) + assert len(caplog.records) == 1 + assert "Skipping sanity checking generation" in caplog.text diff --git a/tilings/strategies/fusion/constructor.py b/tilings/strategies/fusion/constructor.py index 6c443593..5cf811de 100644 --- a/tilings/strategies/fusion/constructor.py +++ b/tilings/strategies/fusion/constructor.py @@ -711,7 +711,11 @@ def __init__( extra_parameters: Dict[str, str], left_sided_parameters: Tuple[str, ...], right_sided_parameters: Tuple[str, ...], + left_points: int, + right_points: int, ): + self.left_points = left_points + self.right_points = right_points left_fuse_index = self.get_left_fuse_index( left_sided_parameters, fuse_parameter, extra_parameters, t_unfuse ) @@ -818,6 +822,18 @@ def forward_map(self, param: Parameters) -> Parameters: for pvalue, fuse_idxs in zip(param, self.unfuse_pos_to_fuse_pos): for fuse_idx in fuse_idxs: new_param[fuse_idx] += pvalue + if ( + self.type == ReverseFusionConstructor.Type.LEFT_ONLY + and self.right_points + and fuse_idx in self.left_sided_index + ): + new_param[fuse_idx] += 1 + elif ( + self.type == ReverseFusionConstructor.Type.RIGHT_ONLY + and self.left_points + and fuse_idx in self.right_sided_index + ): + new_param[fuse_idx] += 1 return tuple(new_param) def a_map(self, param: Parameters) -> Parameters: diff --git a/tilings/strategies/fusion/fusion.py b/tilings/strategies/fusion/fusion.py index 8f8027f4..18644cfd 100644 --- a/tilings/strategies/fusion/fusion.py +++ b/tilings/strategies/fusion/fusion.py @@ -224,10 +224,6 @@ def reverse_constructor( # pylint: disable=no-self-use algo = self.fusion_algorithm(comb_class) 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" - ) child = algo.fused_tiling() assert children is None or children == (child,) ( @@ -236,6 +232,10 @@ def reverse_constructor( # pylint: disable=no-self-use _, ) = self.left_right_both_sided_parameters(comb_class) if not left_sided_params and not right_sided_params: + if algo.min_left_right_points() != (0, 0): + raise NotImplementedError( + "Reverse positive fusion counting not implemented" + ) fused_assumption = algo.new_assumption() unfused_assumption = fused_assumption.__class__( chain.from_iterable( @@ -250,6 +250,7 @@ def reverse_constructor( # pylint: disable=no-self-use comb_class.get_assumption_parameter(unfused_assumption), self.extra_parameters(comb_class, children), ) + left_points, right_points = algo.min_left_right_points() return ReverseFusionConstructor( comb_class, child, @@ -257,6 +258,8 @@ def reverse_constructor( # pylint: disable=no-self-use self.extra_parameters(comb_class, children)[0], tuple(left_sided_params), tuple(right_sided_params), + left_points, + right_points, ) def extra_parameters( From 31c3c5c4a5654a88a03d77b59479e781313a2b36 Mon Sep 17 00:00:00 2001 From: jaypantone Date: Wed, 22 Feb 2023 11:46:19 -0600 Subject: [PATCH 079/100] make one-based (#512) --- tilings/strategies/verification.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tilings/strategies/verification.py b/tilings/strategies/verification.py index 063d1439..faa4e667 100644 --- a/tilings/strategies/verification.py +++ b/tilings/strategies/verification.py @@ -199,7 +199,8 @@ def tiling_to_symbol_eq(self, tiling: Tiling) -> Any: assert len(params) == 1 x_var += "*" + params[0] basis = [ob.patt for ob in tiling.obstructions] - return Symbol(f"F_{Av(basis)}({x_var})") + one_based_basis = ",".join(("".join(str(i + 1) for i in b) for b in basis)) + return Symbol(f"F_Av({one_based_basis})({x_var})") @property def no_req_tiling(self) -> Tiling: From 6c9a4f34ecebfea86410c58298c2b6140871e336 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 28 Feb 2023 09:56:09 +0000 Subject: [PATCH 080/100] remove unnecessary error (#514) --- tilings/strategies/point_jumping.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tilings/strategies/point_jumping.py b/tilings/strategies/point_jumping.py index c2ad0799..05ea4ffe 100644 --- a/tilings/strategies/point_jumping.py +++ b/tilings/strategies/point_jumping.py @@ -152,8 +152,6 @@ def forward_map( def extra_parameters( self, comb_class: Tiling, children: Optional[Tuple[Tiling, ...]] = None ) -> Tuple[Dict[str, str], ...]: - if not comb_class.extra_parameters: - raise ValueError("This tiling does not have assumptions") if children is None: children = self.decomposition_function(comb_class) if children is None: From dd3d2ede85cb7cb1a103e005129617c476a98d69 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 28 Feb 2023 15:21:05 +0000 Subject: [PATCH 081/100] a test for duplicated mapped params in complement (#510) * a test for duplicated mapped params in complement * skip test for incr with assumption --- tests/strategies/test_constructor_equiv.py | 29 ++++++++++++++++++++++ tests/strategies/test_verification.py | 6 ----- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/tests/strategies/test_constructor_equiv.py b/tests/strategies/test_constructor_equiv.py index 812cc259..fa4ee902 100644 --- a/tests/strategies/test_constructor_equiv.py +++ b/tests/strategies/test_constructor_equiv.py @@ -1,7 +1,11 @@ +import pytest + +from comb_spec_searcher.strategies.rule import EquivalencePathRule from tilings import GriddedPerm, Tiling, TrackingAssumption from tilings.bijections import _TermCacher from tilings.strategies.factor import FactorStrategy from tilings.strategies.fusion import FusionStrategy +from tilings.strategies.obstruction_inferral import ObstructionInferralFactory from tilings.strategies.requirement_placement import RequirementPlacementStrategy @@ -644,3 +648,28 @@ def test_req_placement_equiv_with_assumptions(): ) ).constructor )[0] + + +def test_complement_multiple_params(): + t = Tiling( + [ + GriddedPerm.single_cell((0, 1, 2), (0, 0)), + GriddedPerm.single_cell((0, 1, 2), (1, 0)), + GriddedPerm((0, 1), ((0, 0), (1, 0))), + GriddedPerm((1, 0), ((0, 0), (1, 0))), + ], + [[GriddedPerm.point_perm((0, 0))]], + [ + TrackingAssumption.from_cells([(0, 0), (1, 0)]), + TrackingAssumption.from_cells([(0, 0)]), + ], + ) + for strategy in ObstructionInferralFactory()(t): + rule = strategy(t) + assert not rule.to_reverse_rule(0).is_equivalence() + with pytest.raises(AssertionError): + EquivalencePathRule([rule.to_reverse_rule(0)]) + + for i in range(4): + assert rule.sanity_check(i) + assert rule.to_reverse_rule(0).sanity_check(i) diff --git a/tests/strategies/test_verification.py b/tests/strategies/test_verification.py index 1add7e22..e605a037 100644 --- a/tests/strategies/test_verification.py +++ b/tests/strategies/test_verification.py @@ -1110,12 +1110,6 @@ def test_132_with_two_points(self, strategy): == -sympy.var("x") - 1 ) - def test_with_assumptions(self, strategy): - ass = TrackingAssumption([GriddedPerm.point_perm((0, 0))]) - t = Tiling.from_string("01").add_assumption(ass) - assert strategy.verified(t) - assert strategy.get_genf(t) == sympy.sympify("-1/(k_0*x - 1)") - def test_with_123_subclass_12req(self, strategy): t2 = Tiling( obstructions=[ From 3d6f5349d9a7841cdd24a65ce491b55b16a47341 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Fri, 17 Mar 2023 13:05:01 +0000 Subject: [PATCH 082/100] fix misc bugs in kitchen sink strategies (#517) --- CHANGELOG.md | 2 ++ tilings/strategies/monotone_sliding.py | 14 +++++++++++++- tilings/strategies/point_jumping.py | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cbe6619c..3c3e7e55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ swapped around a fusable row or column. - `UnfusionFactory` that unfuses either all the rows or columns. Also non-productive. - `FusableRowAndColumnPlacementFactory` places fusable rows and columns. - `TrackedClassDB` used by `TrackedSearcher` +- counting for `GeneralizedSlidingStrategy` of rows (i.e., `rotate=True`) ### Fixed - `Factor` was not factoring correctly with respect to component assumptions. @@ -55,6 +56,7 @@ swapped around a fusable row or column. values. Previous rules failing this condition will now raise `StrategyDoesNotApply` if it fails this condition. - `LocalVerificationStrategy` needs to be a `BasisAwareVerificationStrategy` +- `PointJumping` maps component assumption to component assumptions. ### Changed - `TileScopePack.make_tracked` will add the appropriate tracking methods for diff --git a/tilings/strategies/monotone_sliding.py b/tilings/strategies/monotone_sliding.py index dd749134..e40d2651 100644 --- a/tilings/strategies/monotone_sliding.py +++ b/tilings/strategies/monotone_sliding.py @@ -4,10 +4,13 @@ from comb_spec_searcher import DisjointUnionStrategy, StrategyFactory from comb_spec_searcher.exception import StrategyDoesNotApply from comb_spec_searcher.strategies import Rule +from comb_spec_searcher.strategies.rule import EquivalencePathRule from permuta import Perm from tilings import GriddedPerm, Tiling from tilings.algorithms import Fusion +from .symmetry import TilingRotate90, TilingRotate270 + class GeneralizedSlidingStrategy(DisjointUnionStrategy[Tiling, GriddedPerm]): """ @@ -70,7 +73,16 @@ def extra_parameters( if children is None: raise StrategyDoesNotApply("Strategy does not apply") if self.rotate: - raise NotImplementedError("Not implemented counting for columns") + rules: List[Rule[Tiling, GriddedPerm]] = [] + parent = comb_class + for strategy in [ + TilingRotate270(), + GeneralizedSlidingStrategy(self.idx, False), + TilingRotate90(), + ]: + rules.append(strategy(parent)) + parent = rules[-1].children[0] + return EquivalencePathRule(rules).constructor.extra_parameters child = children[0] return ( { diff --git a/tilings/strategies/point_jumping.py b/tilings/strategies/point_jumping.py index 05ea4ffe..8f15c61c 100644 --- a/tilings/strategies/point_jumping.py +++ b/tilings/strategies/point_jumping.py @@ -71,7 +71,7 @@ def _swap_cell(self, cell: Cell) -> Cell: return x, y def _swap_assumption(self, assumption: TrackingAssumption) -> TrackingAssumption: - return TrackingAssumption(self._swapped_gp(gp) for gp in assumption.gps) + return assumption.__class__(self._swapped_gp(gp) for gp in assumption.gps) @abc.abstractmethod def backward_map( From 92ec3a65fbc88a3df226e0aa2ed14662a682e5ff Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Mon, 20 Mar 2023 13:28:18 +0000 Subject: [PATCH 083/100] a strategy for forcing assumptions to be empty (#513) * a strategy for forcing assumptions to be empty * add relax assumption factory to kitchen sink * tidy up kitchen sink method --------- Co-authored-by: jaypantone --- tests/strategies/test_encoding.py | 2 + tilings/strategies/__init__.py | 2 + tilings/strategies/relax_assumption.py | 120 +++++++++++++++++++++++++ tilings/strategy_pack.py | 74 +++++++-------- 4 files changed, 157 insertions(+), 41 deletions(-) create mode 100644 tilings/strategies/relax_assumption.py diff --git a/tests/strategies/test_encoding.py b/tests/strategies/test_encoding.py index 21f2ab81..28274614 100644 --- a/tests/strategies/test_encoding.py +++ b/tests/strategies/test_encoding.py @@ -37,6 +37,7 @@ PatternPlacementFactory, PointingStrategy, RearrangeAssumptionFactory, + RelaxAssumptionFactory, RequirementCorroborationFactory, RequirementExtensionFactory, RequirementInsertionFactory, @@ -507,6 +508,7 @@ def indices_and_row(strategy): UnfusionRowStrategy(), UnfusionColumnStrategy(), UnfusionFactory(), + RelaxAssumptionFactory(), ] + [ AssumptionPointingStrategy( diff --git a/tilings/strategies/__init__.py b/tilings/strategies/__init__.py index 1c9155ec..00108ea9 100644 --- a/tilings/strategies/__init__.py +++ b/tilings/strategies/__init__.py @@ -25,6 +25,7 @@ RequirementPointingFactory, ) from .rearrange_assumption import RearrangeAssumptionFactory +from .relax_assumption import RelaxAssumptionFactory from .requirement_insertion import ( BasisPatternInsertionFactory, CellInsertionFactory, @@ -102,6 +103,7 @@ "SlidingFactory", # Experimental "AssumptionAndPointJumpingFactory", + "RelaxAssumptionFactory", "DummyStrategy", # Fusion "ComponentFusionFactory", diff --git a/tilings/strategies/relax_assumption.py b/tilings/strategies/relax_assumption.py new file mode 100644 index 00000000..c519c6c0 --- /dev/null +++ b/tilings/strategies/relax_assumption.py @@ -0,0 +1,120 @@ +from typing import Dict, Iterator, Optional, Tuple + +from comb_spec_searcher import Strategy, StrategyFactory +from comb_spec_searcher.exception import StrategyDoesNotApply +from comb_spec_searcher.strategies import Rule +from tilings import GriddedPerm, Tiling +from tilings.strategies.dummy_constructor import DummyConstructor + + +class RelaxAssumptionStrategy(Strategy[Tiling, GriddedPerm]): + def __init__(self, child: Tiling, assumption_idx: int, **kwargs): + self.child = child + self.assumption_idx = assumption_idx + super().__init__(**kwargs) + + def can_be_equivalent(self) -> bool: + return False + + def is_two_way(self, comb_class: Tiling) -> bool: + return False + + def is_reversible(self, comb_class: Tiling) -> bool: + return False + + def shifts( + self, + comb_class: Tiling, + children: Optional[Tuple[Tiling, ...]], + ) -> Tuple[int, ...]: + if children is None: + children = self.decomposition_function(comb_class) + return (0,) + + def decomposition_function(self, comb_class: Tiling) -> Tuple[Tiling]: + try: + assumption = self.child.assumptions[self.assumption_idx] + except IndexError as e: + raise StrategyDoesNotApply from e + parent = self.child.add_obstructions(assumption.gps) + if parent != comb_class: + raise StrategyDoesNotApply + return (self.child,) + + def formal_step(self) -> str: + return f"the assumption at index {self.assumption_idx} is relaxed" + + def constructor( + self, + comb_class: Tiling, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> DummyConstructor: + if children is None: + children = self.decomposition_function(comb_class) + return DummyConstructor() + + def reverse_constructor( + self, + idx: int, + comb_class: Tiling, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> DummyConstructor: + if children is None: + children = self.decomposition_function(comb_class) + return DummyConstructor() + + def backward_map( + self, + comb_class: Tiling, + objs: Tuple[Optional[GriddedPerm], ...], + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Iterator[GriddedPerm]: + if children is None: + children = self.decomposition_function(comb_class) + raise NotImplementedError + + def forward_map( + self, + comb_class: Tiling, + obj: GriddedPerm, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Tuple[Optional[GriddedPerm], ...]: + if children is None: + children = self.decomposition_function(comb_class) + raise NotImplementedError + + def extra_parameters( + self, + comb_class: Tiling, + children: Optional[Tuple[Tiling, ...]] = None, + ) -> Tuple[Dict[str, str], ...]: + if children is None: + children = self.decomposition_function(comb_class) + raise NotImplementedError + + def to_jsonable(self) -> dict: + d = super().to_jsonable() + d["child"] = self.child.to_jsonable() + d["assumption_idx"] = self.assumption_idx + return d + + @classmethod + def from_dict(cls, d: dict) -> "RelaxAssumptionStrategy": + return cls(d.pop("child"), d.pop("assumption_idx"), **d) + + +class RelaxAssumptionFactory(StrategyFactory[Tiling]): + def __call__(self, comb_class: Tiling) -> Iterator[Rule]: + for idx, assumption in enumerate(comb_class.assumptions): + parent = comb_class.add_obstructions(assumption.gps) + yield RelaxAssumptionStrategy(comb_class, idx)(parent, (comb_class,)) + + @classmethod + def from_dict(cls, d: dict) -> "RelaxAssumptionFactory": + return cls(**d) + + def __str__(self) -> str: + return "Relax assumption" + + def __repr__(self) -> str: + return f"{self.__class__.__name__}()" diff --git a/tilings/strategy_pack.py b/tilings/strategy_pack.py index 715ac567..ad1d6514 100644 --- a/tilings/strategy_pack.py +++ b/tilings/strategy_pack.py @@ -306,6 +306,7 @@ def kitchen_sinkify( # pylint: disable=R0912 Point Pointing Unfusion Targeted Row/Col Placements when fusable + Relax assumptions Will be made tracked or not, depending on preference. Note that nothing is done with positive / point corroboration, requirement corroboration, or database verification. @@ -320,55 +321,46 @@ def kitchen_sinkify( # pylint: disable=R0912 symmetries=self.symmetries, iterative=self.iterative, ) - if short_obs_len > 0: + ver_strats: List[CSSstrategy] = [ + strat.ShortObstructionVerificationStrategy(short_obs_len) + ] + else: + ver_strats = [] + ver_strats += [ + strat.NoRootCellVerificationStrategy(), + strat.DatabaseVerificationStrategy(), + ] + + for strategy in ver_strats: try: - ks_pack = ks_pack.add_verification( - strat.ShortObstructionVerificationStrategy(short_obs_len) - ) + ks_pack = ks_pack.add_verification(strategy) except ValueError: pass - try: - ks_pack = ks_pack.add_verification(strat.NoRootCellVerificationStrategy()) - except ValueError: - pass - - try: - ks_pack = ks_pack.add_verification(strat.DatabaseVerificationStrategy()) - except ValueError: - pass - - try: - ks_pack = ks_pack.add_initial(strat.DeflationFactory(tracked)) - except ValueError: - pass - - try: - ks_pack = ks_pack.add_initial(strat.AssumptionAndPointJumpingFactory()) - except ValueError: - pass - - try: - ks_pack = ks_pack.add_initial(strat.MonotoneSlidingFactory()) - except ValueError: - pass - - try: - ks_pack = ks_pack.add_initial(strat.CellReductionFactory(tracked)) - except ValueError: - pass - - try: - ks_pack = ks_pack.add_initial(strat.RequirementCorroborationFactory()) - except ValueError: - pass + initial_strats: List[CSSstrategy] = [ + strat.DeflationFactory(tracked), + strat.AssumptionAndPointJumpingFactory(), + strat.MonotoneSlidingFactory(), + strat.CellReductionFactory(tracked), + strat.RequirementCorroborationFactory(), + strat.RelaxAssumptionFactory(), + ] + for strategy in initial_strats: + try: + ks_pack = ks_pack.add_initial(strategy) + except ValueError: + pass if obs_inferral_len > 0: + inf_strats: List[CSSstrategy] = [ + strat.ObstructionInferralFactory(obs_inferral_len) + ] + else: + inf_strats = [] + for strategy in inf_strats: try: - ks_pack = ks_pack.add_inferral( - strat.ObstructionInferralFactory(obs_inferral_len) - ) + ks_pack = ks_pack.add_inferral(strategy) except ValueError: pass From 606cb691c5fc10260763710b215099079b45635f Mon Sep 17 00:00:00 2001 From: jaypantone Date: Tue, 25 Apr 2023 11:31:14 -0500 Subject: [PATCH 084/100] remove break (#518) * remove break * changelog --- CHANGELOG.md | 1 + tilings/tiling.py | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c3e7e55..09b3116e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,7 @@ swapped around a fusable row or column. `StrategyDoesNotApply` if it fails this condition. - `LocalVerificationStrategy` needs to be a `BasisAwareVerificationStrategy` - `PointJumping` maps component assumption to component assumptions. +- `Tiling.all_symmetries` had a premature break statement that was removed ### Changed - `TileScopePack.make_tracked` will add the appropriate tracking methods for diff --git a/tilings/tiling.py b/tilings/tiling.py index 4e32ebad..9269a162 100644 --- a/tilings/tiling.py +++ b/tilings/tiling.py @@ -998,8 +998,6 @@ def all_symmetries(self) -> Set["Tiling"]: symmetries.add(t) symmetries.add(t.inverse()) t = t.rotate90() - if t in symmetries: - break return symmetries def column_reverse(self, column: int) -> "Tiling": From d484c14c3e148d98efaad344bad156bdc2993c19 Mon Sep 17 00:00:00 2001 From: jaypantone Date: Tue, 9 May 2023 11:55:13 -0500 Subject: [PATCH 085/100] adds kitchen sink levels (#519) * adds kitchen sink levels * change log and tes --- CHANGELOG.md | 2 + tests/test_strategy_pack.py | 5 +- tilings/strategies/unfusion.py | 15 ++++-- tilings/strategy_pack.py | 95 +++++++++++++++++++++++++--------- 4 files changed, 88 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09b3116e..9f541fa3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -81,6 +81,8 @@ swapped around a fusable row or column. - `OneByOneVerificationStrategy` will look up permpal.com to find the generating functions and min polys, and also use permpal specs for counting, sampling and generating objects. +- The `kitchen_sinkify` function on `TileScopePack` now takes a level between 1 and 5 + as input, which is used to determine how crazy the added strategies should be. ### Removed - `AddInterleavingAssumptionsFactory`. The factor strategy now adds the relevant diff --git a/tests/test_strategy_pack.py b/tests/test_strategy_pack.py index 98e1924a..a80975a4 100644 --- a/tests/test_strategy_pack.py +++ b/tests/test_strategy_pack.py @@ -117,8 +117,11 @@ def length_row_col_partial(pack): + [pack.add_initial(SlidingFactory(use_symmetries=True)) for pack in packs] + [pack.add_initial(AssumptionAndPointJumpingFactory()) for pack in packs] + [ - pack.kitchen_sinkify(short_obs_len=4, obs_inferral_len=2, tracked=True) + pack.kitchen_sinkify( + short_obs_len=4, obs_inferral_len=2, tracked=True, level=level + ) for pack in packs + for level in (1, 2, 3, 4, 5) ] ) diff --git a/tilings/strategies/unfusion.py b/tilings/strategies/unfusion.py index 041c1ea7..3b9be3a9 100644 --- a/tilings/strategies/unfusion.py +++ b/tilings/strategies/unfusion.py @@ -303,8 +303,8 @@ def __init__( class UnfusionFactory(StrategyFactory[Tiling]): def __init__(self, max_width: int = 4, max_height: int = 4) -> None: - self.max_height = max_height self.max_width = max_width + self.max_height = max_height super().__init__() def __call__(self, comb_class: Tiling) -> Iterator[UnfusionColumnStrategy]: @@ -318,8 +318,17 @@ def __str__(self) -> str: return "unfusion strategy" def __repr__(self) -> str: - return self.__class__.__name__ + "()" + return ( + self.__class__.__name__ + + f"(max_width={self.max_width}, max_height={self.max_height})" + ) + + def to_jsonable(self) -> dict: + d = super().to_jsonable() + d["max_width"] = self.max_width + d["max_height"] = self.max_height + return d @classmethod def from_dict(cls, d: dict) -> "UnfusionFactory": - return cls() + return cls(max_width=d["max_width"], max_height=d["max_height"]) diff --git a/tilings/strategy_pack.py b/tilings/strategy_pack.py index ad1d6514..c1bf9112 100644 --- a/tilings/strategy_pack.py +++ b/tilings/strategy_pack.py @@ -289,7 +289,11 @@ def add_all_symmetry(self) -> "TileScopePack": return super().add_symmetry(strat.SymmetriesFactory(), "symmetries") def kitchen_sinkify( # pylint: disable=R0912 - self, short_obs_len: int, obs_inferral_len: int, tracked: bool + self, + short_obs_len: int, + obs_inferral_len: int, + tracked: bool, + level: int, ) -> "TileScopePack": """ Create a new pack with the following added: @@ -310,8 +314,26 @@ def kitchen_sinkify( # pylint: disable=R0912 Will be made tracked or not, depending on preference. Note that nothing is done with positive / point corroboration, requirement corroboration, or database verification. + + Different stratgies will be added at different levels + Level 1: short obs, no root cell, database verification, symmetries, + obs inferral, interleaving factor without unions + Level 2: deflation, point/assumption jumping, sliding, free cell reduction, + req corrob, targeted row/col placements, relax assumptions, + interleaving factor with unions + Level 3: unfusion 1,1 + Level 4: unfusion 2,2, pointing mc=4, assumption mc=8 + Level 5: unfusion 4,4, pointing mc=6, assumption mc=8, requirement pt, mc=4 """ + assert level in ( + 1, + 2, + 3, + 4, + 5, + ), "Level must be an int between 1 and 5 inclusive" + ks_pack = self.__class__( ver_strats=self.ver_strats, inferral_strats=self.inferral_strats, @@ -327,6 +349,7 @@ def kitchen_sinkify( # pylint: disable=R0912 ] else: ver_strats = [] + ver_strats += [ strat.NoRootCellVerificationStrategy(), strat.DatabaseVerificationStrategy(), @@ -338,19 +361,20 @@ def kitchen_sinkify( # pylint: disable=R0912 except ValueError: pass - initial_strats: List[CSSstrategy] = [ - strat.DeflationFactory(tracked), - strat.AssumptionAndPointJumpingFactory(), - strat.MonotoneSlidingFactory(), - strat.CellReductionFactory(tracked), - strat.RequirementCorroborationFactory(), - strat.RelaxAssumptionFactory(), - ] - for strategy in initial_strats: - try: - ks_pack = ks_pack.add_initial(strategy) - except ValueError: - pass + if level >= 2: + initial_strats: List[CSSstrategy] = [ + strat.DeflationFactory(tracked), + strat.AssumptionAndPointJumpingFactory(), + strat.MonotoneSlidingFactory(), + strat.CellReductionFactory(tracked), + strat.RequirementCorroborationFactory(), + strat.RelaxAssumptionFactory(), + ] + for strategy in initial_strats: + try: + ks_pack = ks_pack.add_initial(strategy) + except ValueError: + pass if obs_inferral_len > 0: inf_strats: List[CSSstrategy] = [ @@ -364,7 +388,7 @@ def kitchen_sinkify( # pylint: disable=R0912 except ValueError: pass - ks_pack = ks_pack.make_interleaving(tracked=tracked, unions=True) + ks_pack = ks_pack.make_interleaving(tracked=tracked, unions=(level > 1)) try: ks_pack = ks_pack.add_all_symmetry() @@ -374,17 +398,38 @@ def kitchen_sinkify( # pylint: disable=R0912 if tracked: ks_pack = ks_pack.make_tracked() - ks_pack.expansion_strats = ks_pack.expansion_strats + ( - ( - strat.AssumptionPointingFactory(), - strat.RequirementPointingFactory(), - strat.PointingStrategy(), - strat.UnfusionFactory(), - strat.FusableRowAndColumnPlacementFactory(), - ), - ) + if level == 2: + ks_pack.expansion_strats = ks_pack.expansion_strats + ( + (strat.FusableRowAndColumnPlacementFactory(),), + ) + elif level == 3: + ks_pack.expansion_strats = ks_pack.expansion_strats + ( + ( + strat.UnfusionFactory(1, 1), + strat.FusableRowAndColumnPlacementFactory(), + ), + ) + elif level == 4: + ks_pack.expansion_strats = ks_pack.expansion_strats + ( + ( + strat.AssumptionPointingFactory(8), + strat.PointingStrategy(4), + strat.UnfusionFactory(2, 2), + strat.FusableRowAndColumnPlacementFactory(), + ), + ) + elif level == 5: + ks_pack.expansion_strats = ks_pack.expansion_strats + ( + ( + strat.AssumptionPointingFactory(8), + strat.RequirementPointingFactory(4), + strat.PointingStrategy(6), + strat.UnfusionFactory(4, 4), + strat.FusableRowAndColumnPlacementFactory(), + ), + ) - ks_pack.name += "_kitchen_sink" + ks_pack.name += f"_kitchen_sink_level_{level}" return ks_pack From a91c628049762fe2bf79fafd9aeab5ade2f3e631 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Tue, 6 Jun 2023 17:57:56 +0100 Subject: [PATCH 086/100] Shift from spec (#520) * the shift fix discussed on 16/05/23 * changelog * fix imports --- CHANGELOG.md | 2 + .../algorithms/locally_factorable_shift.py | 45 +++++++++++++++---- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f541fa3..25dbdd73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,8 @@ swapped around a fusable row or column. - `LocalVerificationStrategy` needs to be a `BasisAwareVerificationStrategy` - `PointJumping` maps component assumption to component assumptions. - `Tiling.all_symmetries` had a premature break statement that was removed +- `shift_from_spec` method would previously fail if any tiling had two or + more interleaving cells. ### Changed - `TileScopePack.make_tracked` will add the appropriate tracking methods for diff --git a/tilings/algorithms/locally_factorable_shift.py b/tilings/algorithms/locally_factorable_shift.py index 9b612545..99452d1a 100644 --- a/tilings/algorithms/locally_factorable_shift.py +++ b/tilings/algorithms/locally_factorable_shift.py @@ -7,7 +7,7 @@ CombinatorialSpecification, CombinatorialSpecificationSearcher, ) -from comb_spec_searcher.strategies.constructor import CartesianProduct, DisjointUnion +from comb_spec_searcher.strategies.constructor import DisjointUnion from comb_spec_searcher.strategies.rule import Rule, VerificationRule from comb_spec_searcher.strategies.strategy import VerificationStrategy from comb_spec_searcher.strategies.strategy_pack import StrategyPack @@ -15,6 +15,7 @@ from permuta import Av, Perm from tilings import GriddedPerm, Tiling from tilings.strategies.detect_components import CountComponent +from tilings.strategies.factor import FactorStrategy __all__ = ["shift_from_spec"] @@ -60,7 +61,31 @@ def expanded_spec( """ Return a spec where any tiling that does not have the basis in one cell is verified. + + A locally factorable tiling can always result in a spec where the + verified leaves are one by one if we remove the local verification + and monotone tree verification strategies and instead use the + interleaving factors. As we only care about the shift, we can + tailor our packs to find this. """ + # pylint: disable=import-outside-toplevel + from tilings.strategies.verification import ( + LocalVerificationStrategy, + MonotoneTreeVerificationStrategy, + ) + from tilings.tilescope import TileScopePack + + pack = TileScopePack( + initial_strats=pack.initial_strats, + inferral_strats=pack.inferral_strats, + expansion_strats=pack.expansion_strats, + ver_strats=pack.ver_strats, + name=pack.name, + ) + pack = pack.remove_strategy(MonotoneTreeVerificationStrategy()).make_interleaving( + tracked=False, unions=False + ) + pack = pack.remove_strategy(LocalVerificationStrategy()) pack = pack.add_verification(NoBasisVerification(symmetries), apply_first=True) with TmpLoggingLevel(logging.WARN): css = CombinatorialSpecificationSearcher(tiling, pack) @@ -87,13 +112,11 @@ def traverse(t: Tiling) -> Optional[int]: elif t.dimensions == (1, 1): res = 0 elif isinstance(rule, VerificationRule): - res = shift_from_spec(tiling, rule.pack(), symmetries) - elif isinstance(rule, Rule) and isinstance( - rule.constructor, (DisjointUnion, CountComponent) - ): - children_reliance = [traverse(c) for c in rule.children] - res = min((r for r in children_reliance if r is not None), default=None) - elif isinstance(rule, Rule) and isinstance(rule.constructor, CartesianProduct): + raise ValueError( + "this should be unreachable, looks like JP, HU " + "and CB misunderstood the code." + ) + elif isinstance(rule, Rule) and isinstance(rule.strategy, FactorStrategy): min_points = [len(next(c.minimal_gridded_perms())) for c in rule.children] point_sum = sum(min_points) shifts = [point_sum - mpoint for mpoint in min_points] @@ -102,6 +125,12 @@ def traverse(t: Tiling) -> Optional[int]: (r + s for r, s in zip(children_reliance, shifts) if r is not None), default=None, ) + elif isinstance(rule, Rule) and isinstance( + rule.constructor, (DisjointUnion, CountComponent) + ): + children_reliance = [traverse(c) for c in rule.children] + res = min((r for r in children_reliance if r is not None), default=None) + else: raise NotImplementedError(rule) traverse_cache[t] = res From c3764f1e24d906f4a11a15832e1d9cde23e0ed4a Mon Sep 17 00:00:00 2001 From: jaypantone Date: Tue, 6 Jun 2023 12:32:49 -0500 Subject: [PATCH 087/100] sets allow_factorable_insertions default to False (#521) --- tilings/strategies/requirement_insertion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tilings/strategies/requirement_insertion.py b/tilings/strategies/requirement_insertion.py index 332de52d..4a8fd257 100644 --- a/tilings/strategies/requirement_insertion.py +++ b/tilings/strategies/requirement_insertion.py @@ -400,7 +400,7 @@ def __init__( extra_basis: Optional[List[Perm]] = None, limited_insertion: bool = True, ignore_parent: bool = False, - allow_factorable_insertions: bool = True, + allow_factorable_insertions: bool = False, ) -> None: self.limited_insertion = limited_insertion self.allow_factorable_insertions = allow_factorable_insertions From ee896800d1f290d8d5f01329f102a7032c318a68 Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Mon, 4 Mar 2024 13:41:08 +0000 Subject: [PATCH 088/100] prepare v4.0.0 --- CHANGELOG.md | 5 ++++- setup.py | 2 +- tilings/__init__.py | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25dbdd73..8a6bcbf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -## [Unreleased] + +## Unreleased + +## [4.0.0 - 2024-03-04] ### Added - added `TileScopePack.requirement_and_row_and_col_placements` - `AssumptionAndPointJumpingFactory` which adds rules where requirements and/or diff --git a/setup.py b/setup.py index 50e6d8b3..fe3f7709 100755 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ def get_version(rel_path): long_description=read("README.rst"), install_requires=[ "comb-spec-searcher==4.2.0", - "permuta==2.2.0", + "permuta==2.3.0", ], python_requires=">=3.8", include_package_data=True, diff --git a/tilings/__init__.py b/tilings/__init__.py index e0ff2d7b..f31c82f4 100644 --- a/tilings/__init__.py +++ b/tilings/__init__.py @@ -2,6 +2,6 @@ from tilings.griddedperm import GriddedPerm from tilings.tiling import Tiling -__version__ = "3.1.0" +__version__ = "4.0.0" __all__ = ["GriddedPerm", "Tiling", "TrackingAssumption"] From 1e9622a282543e16faab51432aa0930f7cd2ba60 Mon Sep 17 00:00:00 2001 From: jaypantone Date: Thu, 13 Mar 2025 09:50:50 -0500 Subject: [PATCH 089/100] Create dependabot.yml --- .github/dependabot.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..9d866e39 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "pip" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" From ce25c461c1a8660120485b2c6fcfac0d0c042436 Mon Sep 17 00:00:00 2001 From: jaypantone Date: Thu, 13 Mar 2025 10:14:41 -0500 Subject: [PATCH 090/100] Update dependabot.yml --- .github/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 9d866e39..2775ad7e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,4 +8,4 @@ updates: - package-ecosystem: "pip" # See documentation for possible values directory: "/" # Location of package manifests schedule: - interval: "weekly" + interval: "daily" From 1f4c7f07fd4bdb4338cb06e722517980cc18befe Mon Sep 17 00:00:00 2001 From: jaypantone Date: Thu, 13 Mar 2025 10:25:01 -0500 Subject: [PATCH 091/100] Update test.yml fix substitution of css version with develop branch --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dfa225f2..055da994 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -58,17 +58,17 @@ jobs: shell: pwsh if: github.ref != 'refs/heads/master' && runner.os == 'Windows' && !(github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'master') run: | - (Get-Content setup.py) -replace 'comb-spec-searcher==\d+.\d+.\d+', $('comb_spec_searcher@git+https://github.com/PermutaTriangle/comb_spec_searcher"' + $([environment]::newline) + ' "@develop') | Out-File -encoding UTF8 setup.py + (Get-Content setup.py) -replace 'comb-spec-searcher==\d+.\d+.\d+', 'comb_spec_searcher@git+https://github.com/PermutaTriangle/comb_spec_searcher@develop' | Out-File -encoding UTF8 setup.py (Get-Content setup.py) -replace 'permuta==\d+.\d+.\d+', 'permuta@git+https://github.com/PermutaTriangle/Permuta@develop' | Out-File -encoding UTF8 setup.py - name: replace css and permuta dependency (unix) if: github.ref != 'refs/heads/master' && runner.os != 'Windows' && !(github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'master') run: | if [ "$RUNNER_OS" == "macOS" ]; then brew install gnu-sed - gsed -i -E 's/comb-spec-searcher==[0-9]+.[0-9]+.[0-9]+/comb_spec_searcher@git+https:\/\/github.com\/PermutaTriangle\/comb_spec_searcher"\n "@develop/g' setup.py + gsed -i -E 's/comb-spec-searcher==[0-9]+.[0-9]+.[0-9]+/comb_spec_searcher@git+https:\/\/github.com\/PermutaTriangle\/comb_spec_searcher@develop/g' setup.py gsed -i -E 's/permuta==[0-9]+.[0-9]+.[0-9]+/permuta@git+https:\/\/github.com\/PermutaTriangle\/Permuta@develop/g' setup.py else - sed -i -E 's/comb-spec-searcher==[0-9]+.[0-9]+.[0-9]+/comb_spec_searcher@git+https:\/\/github.com\/PermutaTriangle\/comb_spec_searcher"\n "@develop/g' setup.py + sed -i -E 's/comb-spec-searcher==[0-9]+.[0-9]+.[0-9]+/comb_spec_searcher@git+https:\/\/github.com\/PermutaTriangle\/comb_spec_searcher@develop/g' setup.py sed -i -E 's/permuta==[0-9]+.[0-9]+.[0-9]+/permuta@git+https:\/\/github.com\/PermutaTriangle\/Permuta@develop/g' setup.py fi - name: install dependencies From c730d09e9176e64891861ff74f859a774b891233 Mon Sep 17 00:00:00 2001 From: jaypantone Date: Thu, 13 Mar 2025 10:29:44 -0500 Subject: [PATCH 092/100] Update test.yml --- .github/workflows/test.yml | 73 ++++++++++++++++++++++++++++++++++---- 1 file changed, 67 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 055da994..ae428bf6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -58,18 +58,79 @@ jobs: shell: pwsh if: github.ref != 'refs/heads/master' && runner.os == 'Windows' && !(github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'master') run: | - (Get-Content setup.py) -replace 'comb-spec-searcher==\d+.\d+.\d+', 'comb_spec_searcher@git+https://github.com/PermutaTriangle/comb_spec_searcher@develop' | Out-File -encoding UTF8 setup.py - (Get-Content setup.py) -replace 'permuta==\d+.\d+.\d+', 'permuta@git+https://github.com/PermutaTriangle/Permuta@develop' | Out-File -encoding UTF8 setup.py + Write-Host "Starting dependency replacement on Windows" + Write-Host "Original setup.py content:" + Get-Content setup.py | Out-String | Write-Host + + Write-Host "Replacing comb-spec-searcher..." + (Get-Content setup.py) -replace 'comb-spec-searcher==\d+\.\d+\.\d+', 'comb_spec_searcher@git+https://github.com/PermutaTriangle/comb_spec_searcher@develop' | Out-File -encoding UTF8 setup.py + + Write-Host "Replacing permuta..." + (Get-Content setup.py) -replace 'permuta==\d+\.\d+\.\d+', 'permuta@git+https://github.com/PermutaTriangle/Permuta@develop' | Out-File -encoding UTF8 setup.py + + Write-Host "Modified setup.py content:" + Get-Content setup.py | Out-String | Write-Host + + # Check if replacements were successful + $content = Get-Content setup.py | Out-String + if ($content -match 'comb_spec_searcher@git\+https://github\.com/PermutaTriangle/comb_spec_searcher@develop') { + Write-Host "✅ comb_spec_searcher replacement successful" + } else { + Write-Host "❌ comb_spec_searcher replacement FAILED" + Write-Host "Checking exact pattern in setup.py:" + Select-String -Path setup.py -Pattern "comb-spec-searcher" | Write-Host + } + + if ($content -match 'permuta@git\+https://github\.com/PermutaTriangle/Permuta@develop') { + Write-Host "✅ permuta replacement successful" + } else { + Write-Host "❌ permuta replacement FAILED" + Write-Host "Checking exact pattern in setup.py:" + Select-String -Path setup.py -Pattern "permuta" | Write-Host + } - name: replace css and permuta dependency (unix) if: github.ref != 'refs/heads/master' && runner.os != 'Windows' && !(github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'master') run: | + echo "Starting dependency replacement on Unix (${RUNNER_OS})" + echo "Original setup.py content:" + cat setup.py + if [ "$RUNNER_OS" == "macOS" ]; then + echo "Using gsed on macOS..." brew install gnu-sed - gsed -i -E 's/comb-spec-searcher==[0-9]+.[0-9]+.[0-9]+/comb_spec_searcher@git+https:\/\/github.com\/PermutaTriangle\/comb_spec_searcher@develop/g' setup.py - gsed -i -E 's/permuta==[0-9]+.[0-9]+.[0-9]+/permuta@git+https:\/\/github.com\/PermutaTriangle\/Permuta@develop/g' setup.py + + echo "Replacing comb-spec-searcher..." + gsed -i -E 's/comb-spec-searcher==[0-9]+\.[0-9]+\.[0-9]+/comb_spec_searcher@git+https:\/\/github.com\/PermutaTriangle\/comb_spec_searcher@develop/g' setup.py + + echo "Replacing permuta..." + gsed -i -E 's/permuta==[0-9]+\.[0-9]+\.[0-9]+/permuta@git+https:\/\/github.com\/PermutaTriangle\/Permuta@develop/g' setup.py else - sed -i -E 's/comb-spec-searcher==[0-9]+.[0-9]+.[0-9]+/comb_spec_searcher@git+https:\/\/github.com\/PermutaTriangle\/comb_spec_searcher@develop/g' setup.py - sed -i -E 's/permuta==[0-9]+.[0-9]+.[0-9]+/permuta@git+https:\/\/github.com\/PermutaTriangle\/Permuta@develop/g' setup.py + echo "Using sed on Linux..." + echo "Replacing comb-spec-searcher..." + sed -i -E 's/comb-spec-searcher==[0-9]+\.[0-9]+\.[0-9]+/comb_spec_searcher@git+https:\/\/github.com\/PermutaTriangle\/comb_spec_searcher@develop/g' setup.py + + echo "Replacing permuta..." + sed -i -E 's/permuta==[0-9]+\.[0-9]+\.[0-9]+/permuta@git+https:\/\/github.com\/PermutaTriangle\/Permuta@develop/g' setup.py + fi + + echo "Modified setup.py content:" + cat setup.py + + # Check if replacements were successful + if grep -q "comb_spec_searcher@git+https://github.com/PermutaTriangle/comb_spec_searcher@develop" setup.py; then + echo "✅ comb_spec_searcher replacement successful" + else + echo "❌ comb_spec_searcher replacement FAILED" + echo "Checking exact pattern in setup.py:" + grep -n "comb-spec-searcher" setup.py || echo "Pattern not found" + fi + + if grep -q "permuta@git+https://github.com/PermutaTriangle/Permuta@develop" setup.py; then + echo "✅ permuta replacement successful" + else + echo "❌ permuta replacement FAILED" + echo "Checking exact pattern in setup.py:" + grep -n "permuta" setup.py || echo "Pattern not found" fi - name: install dependencies run: python -m pip install --upgrade pip tox From d3b5754439aa90e9864ba4de33ded1883d93fe3c Mon Sep 17 00:00:00 2001 From: jaypantone Date: Thu, 13 Mar 2025 10:33:48 -0500 Subject: [PATCH 093/100] Update test.yml --- .github/workflows/test.yml | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ae428bf6..ef6da8fd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -62,18 +62,19 @@ jobs: Write-Host "Original setup.py content:" Get-Content setup.py | Out-String | Write-Host + # Use the correct pip install-from-git format Write-Host "Replacing comb-spec-searcher..." - (Get-Content setup.py) -replace 'comb-spec-searcher==\d+\.\d+\.\d+', 'comb_spec_searcher@git+https://github.com/PermutaTriangle/comb_spec_searcher@develop' | Out-File -encoding UTF8 setup.py + (Get-Content setup.py) -replace 'comb-spec-searcher==\d+\.\d+\.\d+', 'git+https://github.com/PermutaTriangle/comb_spec_searcher.git@develop#egg=comb_spec_searcher' | Out-File -encoding UTF8 setup.py Write-Host "Replacing permuta..." - (Get-Content setup.py) -replace 'permuta==\d+\.\d+\.\d+', 'permuta@git+https://github.com/PermutaTriangle/Permuta@develop' | Out-File -encoding UTF8 setup.py + (Get-Content setup.py) -replace 'permuta==\d+\.\d+\.\d+', 'git+https://github.com/PermutaTriangle/Permuta.git@develop#egg=permuta' | Out-File -encoding UTF8 setup.py Write-Host "Modified setup.py content:" Get-Content setup.py | Out-String | Write-Host # Check if replacements were successful $content = Get-Content setup.py | Out-String - if ($content -match 'comb_spec_searcher@git\+https://github\.com/PermutaTriangle/comb_spec_searcher@develop') { + if ($content -match 'git\+https://github\.com/PermutaTriangle/comb_spec_searcher\.git@develop#egg=comb_spec_searcher') { Write-Host "✅ comb_spec_searcher replacement successful" } else { Write-Host "❌ comb_spec_searcher replacement FAILED" @@ -81,7 +82,7 @@ jobs: Select-String -Path setup.py -Pattern "comb-spec-searcher" | Write-Host } - if ($content -match 'permuta@git\+https://github\.com/PermutaTriangle/Permuta@develop') { + if ($content -match 'git\+https://github\.com/PermutaTriangle/Permuta\.git@develop#egg=permuta') { Write-Host "✅ permuta replacement successful" } else { Write-Host "❌ permuta replacement FAILED" @@ -100,24 +101,24 @@ jobs: brew install gnu-sed echo "Replacing comb-spec-searcher..." - gsed -i -E 's/comb-spec-searcher==[0-9]+\.[0-9]+\.[0-9]+/comb_spec_searcher@git+https:\/\/github.com\/PermutaTriangle\/comb_spec_searcher@develop/g' setup.py + gsed -i -E 's/comb-spec-searcher==[0-9]+\.[0-9]+\.[0-9]+/git+https:\/\/github.com\/PermutaTriangle\/comb_spec_searcher.git@develop#egg=comb_spec_searcher/g' setup.py echo "Replacing permuta..." - gsed -i -E 's/permuta==[0-9]+\.[0-9]+\.[0-9]+/permuta@git+https:\/\/github.com\/PermutaTriangle\/Permuta@develop/g' setup.py + gsed -i -E 's/permuta==[0-9]+\.[0-9]+\.[0-9]+/git+https:\/\/github.com\/PermutaTriangle\/Permuta.git@develop#egg=permuta/g' setup.py else echo "Using sed on Linux..." echo "Replacing comb-spec-searcher..." - sed -i -E 's/comb-spec-searcher==[0-9]+\.[0-9]+\.[0-9]+/comb_spec_searcher@git+https:\/\/github.com\/PermutaTriangle\/comb_spec_searcher@develop/g' setup.py + sed -i -E 's/comb-spec-searcher==[0-9]+\.[0-9]+\.[0-9]+/git+https:\/\/github.com\/PermutaTriangle\/comb_spec_searcher.git@develop#egg=comb_spec_searcher/g' setup.py echo "Replacing permuta..." - sed -i -E 's/permuta==[0-9]+\.[0-9]+\.[0-9]+/permuta@git+https:\/\/github.com\/PermutaTriangle\/Permuta@develop/g' setup.py + sed -i -E 's/permuta==[0-9]+\.[0-9]+\.[0-9]+/git+https:\/\/github.com\/PermutaTriangle\/Permuta.git@develop#egg=permuta/g' setup.py fi echo "Modified setup.py content:" cat setup.py # Check if replacements were successful - if grep -q "comb_spec_searcher@git+https://github.com/PermutaTriangle/comb_spec_searcher@develop" setup.py; then + if grep -q "git+https://github.com/PermutaTriangle/comb_spec_searcher.git@develop#egg=comb_spec_searcher" setup.py; then echo "✅ comb_spec_searcher replacement successful" else echo "❌ comb_spec_searcher replacement FAILED" @@ -125,7 +126,7 @@ jobs: grep -n "comb-spec-searcher" setup.py || echo "Pattern not found" fi - if grep -q "permuta@git+https://github.com/PermutaTriangle/Permuta@develop" setup.py; then + if grep -q "git+https://github.com/PermutaTriangle/Permuta.git@develop#egg=permuta" setup.py; then echo "✅ permuta replacement successful" else echo "❌ permuta replacement FAILED" From 7f69dde7ecf59e26dbd581c9b2d488adcf5d765c Mon Sep 17 00:00:00 2001 From: jaypantone Date: Thu, 13 Mar 2025 10:36:53 -0500 Subject: [PATCH 094/100] Update setup.py --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index fe3f7709..0916bee7 100755 --- a/setup.py +++ b/setup.py @@ -34,8 +34,8 @@ def get_version(rel_path): packages=find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests"]), long_description=read("README.rst"), install_requires=[ - "comb-spec-searcher==4.2.0", - "permuta==2.3.0", + "comb-spec-searcher>=4.2.0", + "permuta>=2.3.0", ], python_requires=">=3.8", include_package_data=True, From 6a52022375cdf2f980b234a485ad66bdaaa44c05 Mon Sep 17 00:00:00 2001 From: jaypantone Date: Thu, 13 Mar 2025 10:38:02 -0500 Subject: [PATCH 095/100] Update test.yml --- .github/workflows/test.yml | 88 ++++++-------------------------------- 1 file changed, 12 insertions(+), 76 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ef6da8fd..2bb594ee 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -54,85 +54,21 @@ jobs: - uses: actions/setup-python@v2 with: python-version: ${{ matrix.python }} - - name: replace css and permuta dependency (win) - shell: pwsh - if: github.ref != 'refs/heads/master' && runner.os == 'Windows' && !(github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'master') + - name: Install dependencies from develop branch + if: github.ref != 'refs/heads/master' && !(github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'master') run: | - Write-Host "Starting dependency replacement on Windows" - Write-Host "Original setup.py content:" - Get-Content setup.py | Out-String | Write-Host + echo "Installing dependencies from develop branches instead of PyPI" + python -m pip install git+https://github.com/PermutaTriangle/comb_spec_searcher.git@develop + python -m pip install git+https://github.com/PermutaTriangle/Permuta.git@develop - # Use the correct pip install-from-git format - Write-Host "Replacing comb-spec-searcher..." - (Get-Content setup.py) -replace 'comb-spec-searcher==\d+\.\d+\.\d+', 'git+https://github.com/PermutaTriangle/comb_spec_searcher.git@develop#egg=comb_spec_searcher' | Out-File -encoding UTF8 setup.py + # Optional: Install the project in development mode to work with the new dependencies + python -m pip install -e . - Write-Host "Replacing permuta..." - (Get-Content setup.py) -replace 'permuta==\d+\.\d+\.\d+', 'git+https://github.com/PermutaTriangle/Permuta.git@develop#egg=permuta' | Out-File -encoding UTF8 setup.py - - Write-Host "Modified setup.py content:" - Get-Content setup.py | Out-String | Write-Host - - # Check if replacements were successful - $content = Get-Content setup.py | Out-String - if ($content -match 'git\+https://github\.com/PermutaTriangle/comb_spec_searcher\.git@develop#egg=comb_spec_searcher') { - Write-Host "✅ comb_spec_searcher replacement successful" - } else { - Write-Host "❌ comb_spec_searcher replacement FAILED" - Write-Host "Checking exact pattern in setup.py:" - Select-String -Path setup.py -Pattern "comb-spec-searcher" | Write-Host - } - - if ($content -match 'git\+https://github\.com/PermutaTriangle/Permuta\.git@develop#egg=permuta') { - Write-Host "✅ permuta replacement successful" - } else { - Write-Host "❌ permuta replacement FAILED" - Write-Host "Checking exact pattern in setup.py:" - Select-String -Path setup.py -Pattern "permuta" | Write-Host - } - - name: replace css and permuta dependency (unix) - if: github.ref != 'refs/heads/master' && runner.os != 'Windows' && !(github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'master') - run: | - echo "Starting dependency replacement on Unix (${RUNNER_OS})" - echo "Original setup.py content:" - cat setup.py - - if [ "$RUNNER_OS" == "macOS" ]; then - echo "Using gsed on macOS..." - brew install gnu-sed - - echo "Replacing comb-spec-searcher..." - gsed -i -E 's/comb-spec-searcher==[0-9]+\.[0-9]+\.[0-9]+/git+https:\/\/github.com\/PermutaTriangle\/comb_spec_searcher.git@develop#egg=comb_spec_searcher/g' setup.py - - echo "Replacing permuta..." - gsed -i -E 's/permuta==[0-9]+\.[0-9]+\.[0-9]+/git+https:\/\/github.com\/PermutaTriangle\/Permuta.git@develop#egg=permuta/g' setup.py - else - echo "Using sed on Linux..." - echo "Replacing comb-spec-searcher..." - sed -i -E 's/comb-spec-searcher==[0-9]+\.[0-9]+\.[0-9]+/git+https:\/\/github.com\/PermutaTriangle\/comb_spec_searcher.git@develop#egg=comb_spec_searcher/g' setup.py - - echo "Replacing permuta..." - sed -i -E 's/permuta==[0-9]+\.[0-9]+\.[0-9]+/git+https:\/\/github.com\/PermutaTriangle\/Permuta.git@develop#egg=permuta/g' setup.py - fi - - echo "Modified setup.py content:" - cat setup.py - - # Check if replacements were successful - if grep -q "git+https://github.com/PermutaTriangle/comb_spec_searcher.git@develop#egg=comb_spec_searcher" setup.py; then - echo "✅ comb_spec_searcher replacement successful" - else - echo "❌ comb_spec_searcher replacement FAILED" - echo "Checking exact pattern in setup.py:" - grep -n "comb-spec-searcher" setup.py || echo "Pattern not found" - fi - - if grep -q "git+https://github.com/PermutaTriangle/Permuta.git@develop#egg=permuta" setup.py; then - echo "✅ permuta replacement successful" - else - echo "❌ permuta replacement FAILED" - echo "Checking exact pattern in setup.py:" - grep -n "permuta" setup.py || echo "Pattern not found" - fi + # Verify installed versions + echo "Checking installed comb_spec_searcher version:" + python -c "import comb_spec_searcher; print(comb_spec_searcher.__version__)" + echo "Checking installed permuta version:" + python -c "import permuta; print(permuta.__version__)" - name: install dependencies run: python -m pip install --upgrade pip tox - name: run From 9c5aaa3b82b6c70182d3bb835fe6581434c38296 Mon Sep 17 00:00:00 2001 From: jaypantone Date: Thu, 13 Mar 2025 12:29:59 -0500 Subject: [PATCH 096/100] removes db verification everywhere (#524) * removes db verification everywhere * updare sympy req * debugging * debugging --- tests/algorithms/test_enumeration.py | 39 +------------ tests/strategies/test_constructor_equiv.py | 2 + tests/strategies/test_encoding.py | 2 - tests/strategies/test_verification.py | 24 -------- tests/test_strategy_pack.py | 4 +- tests/test_tilescope.py | 12 +--- tilings/algorithms/__init__.py | 3 +- tilings/algorithms/enumeration.py | 64 +--------------------- tilings/cli.py | 8 --- tilings/strategies/__init__.py | 2 - tilings/strategies/relax_assumption.py | 2 +- tilings/strategies/verification.py | 61 +-------------------- tilings/strategy_pack.py | 12 +--- tilings/tiling.py | 2 - tox.ini | 9 ++- 15 files changed, 19 insertions(+), 227 deletions(-) diff --git a/tests/algorithms/test_enumeration.py b/tests/algorithms/test_enumeration.py index 659c462e..c148e120 100644 --- a/tests/algorithms/test_enumeration.py +++ b/tests/algorithms/test_enumeration.py @@ -5,11 +5,7 @@ from comb_spec_searcher.utils import taylor_expand from tilings import GriddedPerm, Tiling -from tilings.algorithms import ( - DatabaseEnumeration, - LocalEnumeration, - MonotoneTreeEnumeration, -) +from tilings.algorithms import LocalEnumeration, MonotoneTreeEnumeration from tilings.exception import InvalidOperationError @@ -439,36 +435,3 @@ def test_corner(self): expected_enum = [0, 1, 5, 17, 50, 138, 370, 979, 2575, 6755, 17700] assert enum.verified() assert taylor_expand(enum.get_genf()) == expected_enum - - -class TestDatabaseEnumeration(CommonTest): - @pytest.fixture - def enum_verified(self): - t = Tiling.from_string("123_132_231") - return DatabaseEnumeration(t) - - @pytest.fixture - def enum_not_verified(self): - t = Tiling.from_string("1324") - return DatabaseEnumeration(t) - - def test_get_genf(self, enum_verified): - assert enum_verified.get_genf() == sympy.sympify( - "(x**2 - x + 1)/(x**2 - 2*x + 1)" - ) - - @pytest.mark.slow - def test_load_verified_tilings(self): - DatabaseEnumeration.load_verified_tiling() - assert DatabaseEnumeration.all_verified_tilings - sample = next(iter(DatabaseEnumeration.all_verified_tilings)) - Tiling.from_bytes(sample) - - def test_verification_with_cache(self): - t = Tiling.from_string("123_132_231") - DatabaseEnumeration.all_verified_tilings = frozenset() - assert DatabaseEnumeration(t).verified() - DatabaseEnumeration.all_verified_tilings = frozenset([1, 2, 3, 4]) - assert not DatabaseEnumeration(t).verified() - DatabaseEnumeration.all_verified_tilings = frozenset([t.to_bytes()]) - assert DatabaseEnumeration(t).verified() diff --git a/tests/strategies/test_constructor_equiv.py b/tests/strategies/test_constructor_equiv.py index fa4ee902..65aa47be 100644 --- a/tests/strategies/test_constructor_equiv.py +++ b/tests/strategies/test_constructor_equiv.py @@ -666,6 +666,8 @@ def test_complement_multiple_params(): ) for strategy in ObstructionInferralFactory()(t): rule = strategy(t) + print(rule) + print(rule.to_reverse_rule(0)) assert not rule.to_reverse_rule(0).is_equivalence() with pytest.raises(AssertionError): EquivalencePathRule([rule.to_reverse_rule(0)]) diff --git a/tests/strategies/test_encoding.py b/tests/strategies/test_encoding.py index 28274614..ad7923ce 100644 --- a/tests/strategies/test_encoding.py +++ b/tests/strategies/test_encoding.py @@ -16,7 +16,6 @@ CellInsertionFactory, CellReductionFactory, ComponentFusionFactory, - DatabaseVerificationStrategy, DeflationFactory, DummyStrategy, ElementaryVerificationStrategy, @@ -404,7 +403,6 @@ def indices_and_row(strategy): + partition_ignoreparent_workable(FactorStrategy) + partition_ignoreparent_workable_tracked(FactorWithInterleavingStrategy) + partition_ignoreparent_workable_tracked(FactorWithMonotoneInterleavingStrategy) - + ignoreparent(DatabaseVerificationStrategy) + ignoreparent(LocallyFactorableVerificationStrategy) + ignoreparent(ElementaryVerificationStrategy) + ignoreparent(LocalVerificationStrategy) diff --git a/tests/strategies/test_verification.py b/tests/strategies/test_verification.py index e605a037..623d4520 100644 --- a/tests/strategies/test_verification.py +++ b/tests/strategies/test_verification.py @@ -12,7 +12,6 @@ from tilings.assumptions import TrackingAssumption from tilings.strategies import ( BasicVerificationStrategy, - DatabaseVerificationStrategy, ElementaryVerificationStrategy, InsertionEncodingVerificationStrategy, LocallyFactorableVerificationStrategy, @@ -921,29 +920,6 @@ def test_get_genf(self, strategy, enum_verified): ) -class TestDatabaseVerificationStrategy(CommonTest): - @pytest.fixture - def strategy(self): - return DatabaseVerificationStrategy() - - @pytest.fixture - def formal_step(self): - return "tiling is in the database" - - @pytest.fixture - def enum_verified(self): - return [Tiling.from_string("123_132_231")] - - @pytest.fixture - def enum_not_verified(self): - return [Tiling.from_string("1324")] - - def test_get_genf(self, strategy, enum_verified): - assert strategy.get_genf(enum_verified[0]) == sympy.sympify( - "(x**2 - x + 1)/(x**2 - 2*x + 1)" - ) - - class TestOneByOneVerificationStrategy(CommonTest): @pytest.fixture def strategy(self): diff --git a/tests/test_strategy_pack.py b/tests/test_strategy_pack.py index a80975a4..b5cb0df7 100644 --- a/tests/test_strategy_pack.py +++ b/tests/test_strategy_pack.py @@ -105,12 +105,10 @@ def length_row_col_partial(pack): ) packs.extend( - [pack.make_database() for pack in packs] - + [pack.make_elementary() for pack in packs] + [pack.make_elementary() for pack in packs] + [pack.make_fusion() for pack in packs] + [pack.add_all_symmetry() for pack in packs] + [pack.make_interleaving() for pack in packs] - + [pack.make_database().add_all_symmetry() for pack in packs] + [pack.make_fusion().add_all_symmetry() for pack in packs] + [pack.make_interleaving() for pack in packs] + [pack.add_initial(SlidingFactory()) for pack in packs] diff --git a/tests/test_tilescope.py b/tests/test_tilescope.py index 3ab0f3cd..8459a090 100644 --- a/tests/test_tilescope.py +++ b/tests/test_tilescope.py @@ -15,7 +15,7 @@ from tilings.tilescope import GuidedSearcher, TileScope point_placements = TileScopePack.point_placements() -all_the_strategies_verify_database = TileScopePack.all_the_strategies().make_database() +all_the_strategies = TileScopePack.all_the_strategies() all_the_strategies_fusion = TileScopePack.all_the_strategies().make_fusion( tracked=False ) @@ -129,14 +129,6 @@ def test_123(): assert isinstance(spec, CombinatorialSpecification) -@pytest.mark.timeout(120) -@pytest.mark.skip(reason="Too inconsistent connection db") -def test_123_with_db(): - searcher = TileScope("123", all_the_strategies_verify_database) - spec = searcher.auto_search(smallest=True) - assert isinstance(spec, CombinatorialSpecification) - - @pytest.mark.timeout(20) def test_1342_1423(): searcher = TileScope("1342_1423", point_placements_component_fusion) @@ -309,6 +301,8 @@ def test_expansion(): """ pack = TileScopePack.only_root_placements(3, 1) css = TileScope("132", pack) + print(pack) + print(css) spec = css.auto_search(smallest=True) spec = spec.expand_verified() assert sum(1 for rule in spec if isinstance(rule, ReverseRule)) == 1 diff --git a/tilings/algorithms/__init__.py b/tilings/algorithms/__init__.py index ef70b8ac..15530dea 100644 --- a/tilings/algorithms/__init__.py +++ b/tilings/algorithms/__init__.py @@ -1,4 +1,4 @@ -from .enumeration import DatabaseEnumeration, LocalEnumeration, MonotoneTreeEnumeration +from .enumeration import LocalEnumeration, MonotoneTreeEnumeration from .factor import Factor, FactorWithInterleaving, FactorWithMonotoneInterleaving from .fusion import ComponentFusion, Fusion from .gridded_perm_generation import GriddedPermsOnTiling @@ -18,7 +18,6 @@ from .subclass_verification import SubclassVerificationAlgorithm __all__ = [ - "DatabaseEnumeration", "LocalEnumeration", "MonotoneTreeEnumeration", "Factor", diff --git a/tilings/algorithms/enumeration.py b/tilings/algorithms/enumeration.py index c1bec536..93a96176 100644 --- a/tilings/algorithms/enumeration.py +++ b/tilings/algorithms/enumeration.py @@ -1,7 +1,7 @@ import abc from collections import deque from itertools import chain -from typing import TYPE_CHECKING, Any, Dict, FrozenSet, Iterable, Optional +from typing import TYPE_CHECKING, Any, Dict, Iterable, Optional import requests from sympy import Expr, Symbol, diff, simplify, sympify, var @@ -137,8 +137,6 @@ def get_genf(self, **kwargs) -> Any: gf = None if MonotoneTreeEnumeration(self.tiling).verified(): gf = MonotoneTreeEnumeration(self.tiling).get_genf() - if DatabaseEnumeration(self.tiling).verified(): - gf = DatabaseEnumeration(self.tiling).get_genf() if gf is not None: funcs[self.tiling] = gf return gf @@ -324,63 +322,3 @@ def _cell_num_point(self, cell): else: raise RuntimeError("Unexpected number of requirements") return minlen, maxlen - - -class DatabaseEnumeration(Enumeration): - """ - Enumeration strategy for a tilings that are in the database. - - There is not always a specification for a tiling in the database but you can always - find the generating function and the minimal polynomial in the database. - """ - - API_ROOT_URL = "https://api.permpal.com" - all_verified_tilings: FrozenSet[bytes] = frozenset() - num_verified_request = 0 - - @classmethod - def load_verified_tiling(cls): - """ - Load all the verified tiling in the attribute `all_verified_tilings` of - the class. - - That speeds up the verification test. - """ - if not DatabaseEnumeration.all_verified_tilings: - uri = f"{cls.API_ROOT_URL}/all_verified_tilings" - response = requests.get(uri, timeout=10) - response.raise_for_status() - compressed_tilings = map(bytes.fromhex, response.json()) - cls.all_verified_tilings = frozenset(compressed_tilings) - - def _get_tiling_entry(self): - """ - Retrieve the tiling entry from the database. Returns None if the tiling - is not in the database. - """ - key = self.tiling.to_bytes().hex() - search_url = f"{DatabaseEnumeration.API_ROOT_URL}/verified_tiling/key/{key}" - r = requests.get(search_url, timeout=10) - if r.status_code == 404: - return None - r.raise_for_status() - return r.json() - - def verified(self): - """ - Check if a tiling is verified. - - After a 100 checks it loads all the saved tiling from the database to - speed up future requests. - """ - DatabaseEnumeration.num_verified_request += 1 - if DatabaseEnumeration.all_verified_tilings: - return self.tiling.to_bytes() in DatabaseEnumeration.all_verified_tilings - if DatabaseEnumeration.num_verified_request > 10: - DatabaseEnumeration.load_verified_tiling() - return self._get_tiling_entry() is not None - - def get_genf(self, **kwargs) -> Any: - if not self.verified(): - raise InvalidOperationError("The tiling is not verified") - return sympify(self._get_tiling_entry()["genf"]) diff --git a/tilings/cli.py b/tilings/cli.py index 0a99a7f0..44a649aa 100644 --- a/tilings/cli.py +++ b/tilings/cli.py @@ -82,8 +82,6 @@ def build_pack(args: argparse.Namespace) -> TileScopePack: pack = pack_builder(**kwargs) if args.fusion: pack = pack.make_fusion() - if args.database: - pack = pack.make_database() if args.symmetries: pack = pack.add_all_symmetry() if args.elementary: @@ -138,12 +136,6 @@ def search_spec(args): parser_tree.add_argument( "-f", "--fusion", action="store_true", help="Adds fusion to the pack." ) -parser_tree.add_argument( - "-d", - "--database", - action="store_true", - help="Adds database verification to the pack.", -) parser_tree.add_argument( "-s", "--symmetries", action="store_true", help="Adds symmetries to the pack" ) diff --git a/tilings/strategies/__init__.py b/tilings/strategies/__init__.py index 00108ea9..15011b20 100644 --- a/tilings/strategies/__init__.py +++ b/tilings/strategies/__init__.py @@ -54,7 +54,6 @@ from .verification import ( BasicVerificationStrategy, ComponentVerificationStrategy, - DatabaseVerificationStrategy, ElementaryVerificationStrategy, InsertionEncodingVerificationStrategy, LocallyFactorableVerificationStrategy, @@ -121,7 +120,6 @@ # Verification "BasicVerificationStrategy", "ComponentVerificationStrategy", - "DatabaseVerificationStrategy", "ElementaryVerificationStrategy", "LocallyFactorableVerificationStrategy", "LocalVerificationStrategy", diff --git a/tilings/strategies/relax_assumption.py b/tilings/strategies/relax_assumption.py index c519c6c0..1d35ca0b 100644 --- a/tilings/strategies/relax_assumption.py +++ b/tilings/strategies/relax_assumption.py @@ -100,7 +100,7 @@ def to_jsonable(self) -> dict: @classmethod def from_dict(cls, d: dict) -> "RelaxAssumptionStrategy": - return cls(d.pop("child"), d.pop("assumption_idx"), **d) + return cls(Tiling.from_dict(d.pop("child")), d.pop("assumption_idx"), **d) class RelaxAssumptionFactory(StrategyFactory[Tiling]): diff --git a/tilings/strategies/verification.py b/tilings/strategies/verification.py index faa4e667..9488d7f3 100644 --- a/tilings/strategies/verification.py +++ b/tilings/strategies/verification.py @@ -25,11 +25,7 @@ ) from tilings import GriddedPerm, Tiling from tilings.algorithms import locally_factorable_shift -from tilings.algorithms.enumeration import ( - DatabaseEnumeration, - LocalEnumeration, - MonotoneTreeEnumeration, -) +from tilings.algorithms.enumeration import LocalEnumeration, MonotoneTreeEnumeration from tilings.assumptions import ComponentAssumption, TrackingAssumption from tilings.strategies import ( DetectComponentsStrategy, @@ -47,7 +43,6 @@ __all__ = [ "BasicVerificationStrategy", "OneByOneVerificationStrategy", - "DatabaseVerificationStrategy", "LocallyFactorableVerificationStrategy", "ElementaryVerificationStrategy", "LocalVerificationStrategy", @@ -503,60 +498,6 @@ def from_dict(cls, d: dict) -> "ComponentVerificationStrategy": return cls(**d) -class DatabaseVerificationStrategy(TileScopeVerificationStrategy): - """ - Enumeration strategy for a tilings that are in the database. - - There is not always a specification for a tiling in the database but you - can always find the generating function by looking up the database. - """ - - def pack(self, comb_class: Tiling) -> StrategyPack: - # TODO: check database for tiling - raise InvalidOperationError( - "Cannot get a specification for a tiling in the database" - ) - - def verified(self, comb_class: Tiling): - return DatabaseEnumeration(comb_class).verified() - - def formal_step(self) -> str: - return "tiling is in the database" - - def get_genf( - self, comb_class: Tiling, funcs: Optional[Dict[Tiling, Function]] = None - ) -> Any: - if not self.verified(comb_class): - raise StrategyDoesNotApply("tiling is not in the database") - return DatabaseEnumeration(comb_class).get_genf() - - def get_terms(self, comb_class: Tiling, n: int) -> Terms: - raise NotImplementedError( - "Not implemented method to count objects for database verified tilings" - ) - - def generate_objects_of_size( - self, comb_class: Tiling, n: int, **parameters: int - ) -> Iterator[GriddedPerm]: - raise NotImplementedError( - "Not implemented method to generate objects for database verified tilings" - ) - - def random_sample_object_of_size( - self, comb_class: Tiling, n: int, **parameters: int - ) -> GriddedPerm: - raise NotImplementedError( - "Not implemented random sample for database verified tilings" - ) - - def __str__(self) -> str: - return "database verification" - - @classmethod - def from_dict(cls, d: dict) -> "DatabaseVerificationStrategy": - return cls(**d) - - class LocallyFactorableVerificationStrategy(BasisAwareVerificationStrategy): """ Verification strategy for a locally factorable tiling. diff --git a/tilings/strategy_pack.py b/tilings/strategy_pack.py index c1bf9112..ea5a7236 100644 --- a/tilings/strategy_pack.py +++ b/tilings/strategy_pack.py @@ -276,12 +276,6 @@ def make_elementary(self) -> "TileScopePack": strat.ElementaryVerificationStrategy(), "elementary" ) - def make_database(self) -> "TileScopePack": - """ - Create a new pack by adding database verification to the current pack. - """ - return self.add_verification(strat.DatabaseVerificationStrategy(), "database") - def add_all_symmetry(self) -> "TileScopePack": """Create a new pack by turning on symmetry on the current pack.""" if self.symmetries: @@ -299,7 +293,6 @@ def kitchen_sinkify( # pylint: disable=R0912 Create a new pack with the following added: Short Obs verification (unless short_obs_len = 0) No Root Cell verification - Database verification Deflation Point and/or Assumption Jumping Generalized Monotone Sliding @@ -313,10 +306,10 @@ def kitchen_sinkify( # pylint: disable=R0912 Relax assumptions Will be made tracked or not, depending on preference. Note that nothing is done with positive / point corroboration, requirement - corroboration, or database verification. + corroboration. Different stratgies will be added at different levels - Level 1: short obs, no root cell, database verification, symmetries, + Level 1: short obs, no root cell, symmetries, obs inferral, interleaving factor without unions Level 2: deflation, point/assumption jumping, sliding, free cell reduction, req corrob, targeted row/col placements, relax assumptions, @@ -352,7 +345,6 @@ def kitchen_sinkify( # pylint: disable=R0912 ver_strats += [ strat.NoRootCellVerificationStrategy(), - strat.DatabaseVerificationStrategy(), ] for strategy in ver_strats: diff --git a/tilings/tiling.py b/tilings/tiling.py index 9269a162..f8c00856 100644 --- a/tilings/tiling.py +++ b/tilings/tiling.py @@ -1842,7 +1842,6 @@ def get_genf(self, *args, **kwargs) -> Any: return sympy.sympify(0) from .strategies import ( BasicVerificationStrategy, - DatabaseVerificationStrategy, InsertionEncodingVerificationStrategy, LocallyFactorableVerificationStrategy, LocalVerificationStrategy, @@ -1856,7 +1855,6 @@ def get_genf(self, *args, **kwargs) -> Any: LocallyFactorableVerificationStrategy(), InsertionEncodingVerificationStrategy(), MonotoneTreeVerificationStrategy(), - DatabaseVerificationStrategy(), LocalVerificationStrategy(), ] for enum_strat in enum_stragies: diff --git a/tox.ini b/tox.ini index add7e7d8..2f0209d9 100644 --- a/tox.ini +++ b/tox.ini @@ -22,9 +22,12 @@ basepython = pypy38: pypy3.8 pypy39: pypy3.9 deps = - pytest==7.2.0 - pytest-timeout==2.1.0 -commands = pytest + pytest>=8.3.5 + pytest-timeout>=2.1.0 +commands = + pip install --force-reinstall git+https://github.com/PermutaTriangle/comb_spec_searcher.git@develop + pip freeze + pytest [pytest] addopts = --doctest-modules --doctest-ignore-import-errors From 3450f277982a6c20d60c3406f0e30f86638aed3c Mon Sep 17 00:00:00 2001 From: jaypantone Date: Thu, 13 Mar 2025 14:36:33 -0500 Subject: [PATCH 097/100] test version updates (#525) * test version updates * fix * ignore our problems so they go away * tox mypy * update * pylint fixes --- .github/workflows/build-and-deploy.yml | 2 +- .github/workflows/test.yml | 41 ++++++++++++---------- pylintrc | 3 +- setup.py | 7 ++-- tilings/algorithms/enumeration.py | 2 +- tilings/misc.py | 30 ++++++---------- tilings/strategies/assumption_splitting.py | 2 ++ tilings/strategies/factor.py | 5 +++ tilings/strategy_pack.py | 2 +- tilings/tilescope.py | 37 ++++++++++--------- tox.ini | 30 ++++++++-------- 11 files changed, 84 insertions(+), 77 deletions(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 67de9fc6..d198ea2f 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -12,7 +12,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 with: - python-version: "3.10" + python-version: "3.13" - name: install dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2bb594ee..3dc4036e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,43 +9,44 @@ jobs: fail-fast: false matrix: include: - - python: "3.10" + - python: "3.13" toxenv: flake8 os: ubuntu-latest - - python: "3.10" + - python: "3.13" toxenv: mypy os: ubuntu-latest - - python: "3.10" + - python: "3.13" toxenv: pylint os: ubuntu-latest - - python: "3.10" + - python: "3.13" toxenv: black os: ubuntu-latest - - python: 3.8 - toxenv: py38 - os: ubuntu-latest - - python: 3.9 - toxenv: py39 - os: ubuntu-latest - python: "3.10" toxenv: py310 os: ubuntu-latest - python: "3.11" toxenv: py311 os: ubuntu-latest - - python: pypy-3.8 - toxenv: pypy38 + - python: "3.12" + toxenv: py312 os: ubuntu-latest - - python: pypy-3.9 - toxenv: pypy39 + - python: "3.13" + toxenv: py313 os: ubuntu-latest - - python: "3.10" - toxenv: py310 + - python: "pypy-3.10" + toxenv: pypy310 + os: ubuntu-latest + - python: "pypy-3.11" + toxenv: pypy311 + os: ubuntu-latest + + - python: "3.13" + toxenv: py313 os: macos-latest - - python: "3.10" - toxenv: py310 + - python: "3.13" + toxenv: py313 os: windows-latest runs-on: ${{ matrix.os }} @@ -70,7 +71,9 @@ jobs: echo "Checking installed permuta version:" python -c "import permuta; print(permuta.__version__)" - name: install dependencies - run: python -m pip install --upgrade pip tox + run: | + python -m pip install --upgrade pip tox + pip install setuptools wheel twine - name: run env: TOXENV: ${{ matrix.toxenv }} diff --git a/pylintrc b/pylintrc index 7ef81862..0564c0cc 100644 --- a/pylintrc +++ b/pylintrc @@ -85,7 +85,8 @@ disable=missing-class-docstring, suppressed-message, useless-suppression, deprecated-pragma, - use-symbolic-message-instead + use-symbolic-message-instead, + too-many-positional-arguments # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option diff --git a/setup.py b/setup.py index 0916bee7..9c945de1 100755 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ def get_version(rel_path): packages=find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests"]), long_description=read("README.rst"), install_requires=[ - "comb-spec-searcher>=4.2.0", + "comb-spec-searcher>=4.2.1", "permuta>=2.3.0", ], python_requires=">=3.8", @@ -45,9 +45,10 @@ def get_version(rel_path): "Intended Audience :: Science/Research", "License :: OSI Approved :: BSD License", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Education", diff --git a/tilings/algorithms/enumeration.py b/tilings/algorithms/enumeration.py index 93a96176..76651731 100644 --- a/tilings/algorithms/enumeration.py +++ b/tilings/algorithms/enumeration.py @@ -248,7 +248,7 @@ def get_genf(self, **kwargs) -> Any: if __debug__: lhs = taylor_expand(F, n=6) rhs = [len(list(self.tiling.objects_of_size(i))) for i in range(7)] - assert lhs == rhs, f"Bad genf\n{lhs}\n{rhs}" + assert lhs == rhs, f"Bad genf\n{lhs}\n{rhs}" return F @staticmethod diff --git a/tilings/misc.py b/tilings/misc.py index a28ed316..50850a85 100644 --- a/tilings/misc.py +++ b/tilings/misc.py @@ -137,8 +137,7 @@ def f(mu, nu, sigma, n, a): if mu == 2: yield visit(n, a) else: - for v in f(mu - 1, nu - 1, (mu + sigma) % 2, n, a): - yield v + yield from f(mu - 1, nu - 1, (mu + sigma) % 2, n, a) if nu == mu + 1: a[mu] = mu - 1 yield visit(n, a) @@ -151,19 +150,15 @@ def f(mu, nu, sigma, n, a): else: a[mu] = mu - 1 if (a[nu] + sigma) % 2 == 1: - for v in b(mu, nu - 1, 0, n, a): - yield v + yield from b(mu, nu - 1, 0, n, a) else: - for v in f(mu, nu - 1, 0, n, a): - yield v + yield from f(mu, nu - 1, 0, n, a) while a[nu] > 0: a[nu] = a[nu] - 1 if (a[nu] + sigma) % 2 == 1: - for v in b(mu, nu - 1, 0, n, a): - yield v + yield from b(mu, nu - 1, 0, n, a) else: - for v in f(mu, nu - 1, 0, n, a): - yield v + yield from f(mu, nu - 1, 0, n, a) def b(mu, nu, sigma, n, a): if nu == mu + 1: @@ -174,19 +169,15 @@ def b(mu, nu, sigma, n, a): a[mu] = 0 elif nu > mu + 1: if (a[nu] + sigma) % 2 == 1: - for v in f(mu, nu - 1, 0, n, a): - yield v + yield from f(mu, nu - 1, 0, n, a) else: - for v in b(mu, nu - 1, 0, n, a): - yield v + yield from b(mu, nu - 1, 0, n, a) while a[nu] < mu - 1: a[nu] = a[nu] + 1 if (a[nu] + sigma) % 2 == 1: - for v in f(mu, nu - 1, 0, n, a): - yield v + yield from f(mu, nu - 1, 0, n, a) else: - for v in b(mu, nu - 1, 0, n, a): - yield v + yield from b(mu, nu - 1, 0, n, a) if (mu + sigma) % 2 == 1: a[nu - 1] = 0 else: @@ -194,8 +185,7 @@ def b(mu, nu, sigma, n, a): if mu == 2: yield visit(n, a) else: - for v in b(mu - 1, nu - 1, (mu + sigma) % 2, n, a): - yield v + yield from b(mu - 1, nu - 1, (mu + sigma) % 2, n, a) n = len(ns) a = [0] * (n + 1) diff --git a/tilings/strategies/assumption_splitting.py b/tilings/strategies/assumption_splitting.py index e82cb1bc..a410f832 100644 --- a/tilings/strategies/assumption_splitting.py +++ b/tilings/strategies/assumption_splitting.py @@ -357,6 +357,8 @@ def __repr__(self) -> str: interleaving = "all" elif self.factor_class is factor.FactorWithMonotoneInterleaving: interleaving = "monotone" + else: + raise ValueError("Unknown factor class") args = ", ".join( [ f"interleaving={interleaving!r}", diff --git a/tilings/strategies/factor.py b/tilings/strategies/factor.py index f256b85f..11c3f4a4 100644 --- a/tilings/strategies/factor.py +++ b/tilings/strategies/factor.py @@ -648,6 +648,8 @@ def __repr__(self) -> str: interleaving = "all" elif self.factor_class is FactorWithMonotoneInterleavingStrategy: interleaving = "monotone" + else: + raise ValueError("Invalid interleaving type") args = ", ".join( [ f"interleaving={interleaving!r}", @@ -667,6 +669,9 @@ def to_jsonable(self) -> dict: interleaving = "all" elif self.factor_class is FactorWithMonotoneInterleavingStrategy: interleaving = "monotone" + else: + raise ValueError("Invalid interleaving type") + d["interleaving"] = interleaving d["unions"] = self.unions d["ignore_parent"] = self.ignore_parent diff --git a/tilings/strategy_pack.py b/tilings/strategy_pack.py index ea5a7236..de7f9e91 100644 --- a/tilings/strategy_pack.py +++ b/tilings/strategy_pack.py @@ -380,7 +380,7 @@ def kitchen_sinkify( # pylint: disable=R0912 except ValueError: pass - ks_pack = ks_pack.make_interleaving(tracked=tracked, unions=(level > 1)) + ks_pack = ks_pack.make_interleaving(tracked=tracked, unions=level > 1) try: ks_pack = ks_pack.add_all_symmetry() diff --git a/tilings/tilescope.py b/tilings/tilescope.py index 238563a7..d6b2e174 100644 --- a/tilings/tilescope.py +++ b/tilings/tilescope.py @@ -60,21 +60,23 @@ def __init__( ) -> None: """Initialise TileScope.""" - if isinstance(start_class, str): - basis = Basis.from_string(start_class) - elif isinstance(start_class, Tiling): - if start_class.dimensions == (1, 1): - basis = Basis(*[o.patt for o in start_class.obstructions]) + if isinstance(start_class, Tiling): start_tiling = start_class + if start_tiling.dimensions == (1, 1): + basis = Basis(*[o.patt for o in start_tiling.obstructions]) else: - try: - basis = Basis(*start_class) - except TypeError as e: - raise ValueError( - "start class must be a string, an iterable of Perm or a tiling" - ) from e - - if not isinstance(start_class, Tiling): + # Handle the non-Tiling cases + if isinstance(start_class, str): + basis = Basis.from_string(start_class) + else: + try: + basis = Basis(*start_class) + except TypeError as e: + raise ValueError( + "start class must be a string, an iterable of Perm or a tiling" + ) from e + + # Now create start_tiling from basis start_tiling = Tiling( obstructions=[GriddedPerm.single_cell(patt, (0, 0)) for patt in basis] ) @@ -413,8 +415,7 @@ def __init__(self) -> None: self.int_to_assumption_type: List[Type[TrackingAssumption]] = [] def __iter__(self) -> Iterator[int]: - for key in self.label_to_info: - yield key + yield from self.label_to_info def __contains__(self, key: Key) -> bool: if isinstance(key, Tiling): @@ -624,8 +625,12 @@ def set_empty(self, key: Key, empty: bool = True) -> None: if isinstance(key, int): if 0 <= key < len(self.label_to_tilings): underlying_label, _ = self._decompress_key(self.label_to_tilings[key]) - if isinstance(key, Tiling): + else: + raise ValueError("Invalid key") + elif isinstance(key, Tiling): underlying_label = self.classdb.get_label(key.remove_assumptions()) + else: + raise ValueError("Invalid key") self.classdb.set_empty(underlying_label, empty) def status(self) -> str: diff --git a/tox.ini b/tox.ini index 2f0209d9..a9127f83 100644 --- a/tox.ini +++ b/tox.ini @@ -6,24 +6,24 @@ [tox] envlist = flake8, mypy, pylint, black - py{38,39,310,311}, - pypy{38,39} + py{310,311,312,313}, + pypy{310,311} [default] -basepython=python3.10 +basepython=python3.13 [testenv] description = run test basepython = - py38: python3.8 - py39: python3.9 py310: python3.10 py311: python3.11 - pypy38: pypy3.8 - pypy39: pypy3.9 + py312: python3.12 + py313: python3.13 + pypy310: pypy3.10 + pypy311: pypy3.11 deps = - pytest>=8.3.5 - pytest-timeout>=2.1.0 + pytest + pytest-timeout commands = pip install --force-reinstall git+https://github.com/PermutaTriangle/comb_spec_searcher.git@develop pip freeze @@ -40,8 +40,8 @@ description = run flake8 (linter) basepython = {[default]basepython} skip_install = True deps = - flake8==5.0.4 - flake8-isort==5.0.0 + flake8 + flake8-isort commands = flake8 --isort-show-traceback tilings tests setup.py @@ -49,16 +49,16 @@ commands = description = run pylint (static code analysis) basepython = {[default]basepython} deps = - pylint==2.15.5 + pylint commands = pylint tilings [testenv:mypy] description = run mypy (static type checker) basepython = {[default]basepython} deps = - mypy==0.990 - types-requests==2.28.11.4 - types-tabulate==0.9.0 + mypy + types-requests + types-tabulate commands = mypy [testenv:black] From 9a8e2a347006bd7649b3d5ff355cf885a54d76a9 Mon Sep 17 00:00:00 2001 From: jaypantone Date: Thu, 13 Mar 2025 14:56:26 -0500 Subject: [PATCH 098/100] Update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a6bcbf9..4d1f0a11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## Unreleased -## [4.0.0 - 2024-03-04] +## [4.0.0 - 2025-03-13] ### Added +- python3.9 are deprecated, python3.10 - python3.13 are supported - added `TileScopePack.requirement_and_row_and_col_placements` - `AssumptionAndPointJumpingFactory` which adds rules where requirements and/or assumptions are swapped around a fusable row or column. From 4c0fe013790672117123cf91955e66270087106a Mon Sep 17 00:00:00 2001 From: jaypantone Date: Wed, 9 Jul 2025 17:15:38 +0100 Subject: [PATCH 099/100] Alt contains (#526) * alternative contains patt methods * one more contender for contains * __contains__ uses contains_patt * tweaks * a few more ideas * remove repeated method * cleaning up * formatting and added comment * make pylint go away * changelog * tweaks * change _len to len --------- Co-authored-by: Christian Bean --- CHANGELOG.md | 4 +++ tilings/griddedperm.py | 55 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d1f0a11..4edbb946 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## Unreleased +### Changed +- new GriddedPerm.contains_patt function checks positions first, then pattern + and as a result can lead to huge 2x - 4x speed improvement in specification + finding ## [4.0.0 - 2025-03-13] ### Added diff --git a/tilings/griddedperm.py b/tilings/griddedperm.py index fb4697fc..cb59cb0d 100644 --- a/tilings/griddedperm.py +++ b/tilings/griddedperm.py @@ -17,8 +17,12 @@ def __init__( ) -> None: self._patt = Perm(pattern) self._pos = tuple(positions) - if len(self._patt) != len(self._pos): - raise ValueError("Pattern and position list have unequal lengths.") + # After testing in June 2025, we found that caching the length gave a small + # but meaningful speedup because it gets called so often. + self.len = len(self._pos) + assert self.len == len( + self._patt + ), "Pattern and positions must have the same length" self._cells: FrozenSet[Cell] = frozenset(self._pos) @classmethod @@ -70,7 +74,42 @@ def avoids(self, *patts: "GriddedPerm") -> bool: def contains(self, *patts: "GriddedPerm") -> bool: """Return true if self contains an occurrence of any of patts.""" - return any(any(True for _ in patt.occurrences_in(self)) for patt in patts) + return any(self.contains_patt(patt) for patt in patts) + + def contains_patt(self, patt: "GriddedPerm") -> bool: + """Returns true if self contains an occurrence of patt.""" + # In June 2025 we wrote 10 different containment methods to find the + # fastest one. This one won, and it led to a 2x - 4x speedup in some + # spec finding and fast-counting examples. First it checks if the + # positions of self could potentially contain the positions of patt, + # and only if so does it check if there is an occurrence of patt in + # self. We also cached GriddedPerm.len at this time. + return self.contains_pos(patt) and any(True for _ in patt.occurrences_in(self)) + + def contains_patt_proper(self, patt: "GriddedPerm") -> bool: + """Returns true if self contains a proper occurrence of patt.""" + # See comment for contains_patt. We also found that when only looking + # for a proper occurrence, this is faster. + return ( + self.len > patt.len + and self.contains_pos(patt) + and any(True for _ in patt.occurrences_in(self)) + ) + + def contains_pos(self, patt: "GriddedPerm") -> bool: + """Returns true if the positions of self could potentially contain the + positions of patt.""" + if not patt: + return True + + pattlen = patt.len + pattidx = 0 + for cell in self._pos: + if patt._pos[pattidx] == cell: # pylint: disable=protected-access + pattidx += 1 + if pattidx == pattlen: + return True + return False def remove_cells(self, cells: Iterable[Cell]) -> "GriddedPerm": """Remove any points in the cell given and return a new gridded @@ -315,8 +354,8 @@ def is_single_row(self) -> bool: return len(set(y for (_, y) in self._cells)) == 1 def is_empty(self) -> bool: - """Check if the gridded permutation is the gridded permutation.""" - return not bool(self._patt) + """Check if the gridded permutation is the empty permutation.""" + return not self.len def is_interleaving(self) -> bool: """Check if the gridded permutation occupies two cells that are in the @@ -529,7 +568,7 @@ def points_in_row(j): ) res += row_boundary - for (idx, val) in enumerate(self.patt): + for idx, val in enumerate(self.patt): x, y = self.pos[idx] # insert into this spot: # (idx + x + 1) is the horizontal index. idx is points to left, and @@ -668,7 +707,7 @@ def show(self, scale: float = 10.0) -> None: HTMLViewer.open_svg(self.to_svg(image_scale=scale)) def __len__(self) -> int: - return len(self._patt) + return self.len def __repr__(self) -> str: return f"{type(self).__name__}({tuple(self._patt)!r}, {self.pos})" @@ -688,7 +727,7 @@ def __lt__(self, other: "GriddedPerm") -> bool: return (self._patt, self._pos) < (other.patt, other.pos) def __contains__(self, other: "GriddedPerm") -> bool: - return next((True for _ in other.occurrences_in(self)), False) + return self.contains_patt(other) def __iter__(self) -> Iterator[Tuple[int, Cell]]: return zip(self.patt, self.pos) From 44f4cdeb2741e6ed31b159d438fe20ee866f99bc Mon Sep 17 00:00:00 2001 From: jaypantone Date: Thu, 15 Jan 2026 12:27:44 -0600 Subject: [PATCH 100/100] Modernize packaging with pyproject.toml and hatchling (#528) * Modernize packaging with pyproject.toml and hatchling - Replace setup.py and MANIFEST.in with comprehensive pyproject.toml using hatchling backend - Update minimum Python version to 3.10 (drop 3.8, 3.9 support) - Update GitHub Actions to v4/v5 and use python -m build - Update tox.ini: remove setup.py from flake8, use black>=22.10.0 - Update Black target version to py310 - Fix .zenodo.json syntax error (missing comma) - Add CITATION.cff for GitHub citation support - Update README.rst badges (GitHub Actions, Black, mypy) and installation instructions - Bump version to 4.1.0 * Fix Christian Bean affiliation to Keele University, fix changelog format * Apply black formatting for Python 3.10 target * Pin black==25.1.0 and apply formatting * Add Python 3.14 testing as main version * update deploy to py314 --- .github/workflows/build-and-deploy.yml | 10 +-- .github/workflows/test.yml | 35 ++++----- .zenodo.json | 6 +- CHANGELOG.md | 13 +++- CITATION.cff | 32 ++++++++ MANIFEST.in | 1 - README.rst | 21 +++--- pyproject.toml | 79 ++++++++++++++++++-- setup.py | 58 -------------- tests/algorithms/test_enumeration.py | 17 +---- tests/strategies/test_encoding.py | 7 +- tests/strategies/test_fusion_strat.py | 1 - tests/strategies/test_sanity_check.py | 4 +- tests/strategies/test_verification.py | 14 +--- tests/test_assumptions.py | 1 - tests/test_griddedperm.py | 5 +- tilings/__init__.py | 2 +- tilings/algorithms/fusion.py | 3 +- tilings/algorithms/minimal_gridded_perms.py | 2 +- tilings/algorithms/requirement_placement.py | 8 +- tilings/algorithms/row_col_separation.py | 1 + tilings/algorithms/sliding.py | 28 ++++--- tilings/algorithms/subclass_verification.py | 1 - tilings/bijections.py | 2 +- tilings/gui_launcher.py | 1 - tilings/misc.py | 1 + tilings/strategies/assumption_insertion.py | 8 +- tilings/strategies/assumption_splitting.py | 2 +- tilings/strategies/cell_reduction.py | 1 + tilings/strategies/deflation.py | 1 + tilings/strategies/detect_components.py | 6 +- tilings/strategies/factor.py | 3 +- tilings/strategies/fusion/constructor.py | 17 +++-- tilings/strategies/fusion/fusion.py | 1 - tilings/strategies/pointing.py | 16 ++-- tilings/strategies/requirement_placement.py | 1 - tilings/strategies/row_and_col_separation.py | 1 + tilings/strategies/verification.py | 6 +- tilings/strategy_pack.py | 4 +- tilings/tilescope.py | 2 +- tox.ini | 11 +-- 41 files changed, 245 insertions(+), 188 deletions(-) create mode 100644 CITATION.cff delete mode 100644 MANIFEST.in delete mode 100755 setup.py diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index d198ea2f..faccd5cc 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -9,16 +9,16 @@ jobs: build-and-deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: - python-version: "3.13" + python-version: "3.14" - name: install dependencies run: | python -m pip install --upgrade pip - pip install setuptools wheel twine + pip install build twine - name: build - run: python setup.py sdist + run: python -m build - name: deploy env: TWINE_USERNAME: __token__ diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3dc4036e..4963c6d0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,16 +9,16 @@ jobs: fail-fast: false matrix: include: - - python: "3.13" + - python: "3.14" toxenv: flake8 os: ubuntu-latest - - python: "3.13" + - python: "3.14" toxenv: mypy os: ubuntu-latest - - python: "3.13" + - python: "3.14" toxenv: pylint os: ubuntu-latest - - python: "3.13" + - python: "3.14" toxenv: black os: ubuntu-latest @@ -34,6 +34,9 @@ jobs: - python: "3.13" toxenv: py313 os: ubuntu-latest + - python: "3.14" + toxenv: py314 + os: ubuntu-latest - python: "pypy-3.10" toxenv: pypy310 @@ -42,17 +45,17 @@ jobs: toxenv: pypy311 os: ubuntu-latest - - python: "3.13" - toxenv: py313 + - python: "3.14" + toxenv: py314 os: macos-latest - - python: "3.13" - toxenv: py313 + - python: "3.14" + toxenv: py314 os: windows-latest runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} - name: Install dependencies from develop branch @@ -61,22 +64,20 @@ jobs: echo "Installing dependencies from develop branches instead of PyPI" python -m pip install git+https://github.com/PermutaTriangle/comb_spec_searcher.git@develop python -m pip install git+https://github.com/PermutaTriangle/Permuta.git@develop - - # Optional: Install the project in development mode to work with the new dependencies + + # Install the project in development mode to work with the new dependencies python -m pip install -e . - + # Verify installed versions echo "Checking installed comb_spec_searcher version:" python -c "import comb_spec_searcher; print(comb_spec_searcher.__version__)" echo "Checking installed permuta version:" python -c "import permuta; print(permuta.__version__)" - name: install dependencies - run: | - python -m pip install --upgrade pip tox - pip install setuptools wheel twine + run: python -m pip install --upgrade pip tox - name: run env: TOXENV: ${{ matrix.toxenv }} run: tox - name: setup - run: python setup.py install + run: pip install -e . diff --git a/.zenodo.json b/.zenodo.json index f10f421e..29a65e4a 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -3,7 +3,7 @@ "title": "tilings", "creators": [ { - "affiliation": "Reykjavik University", + "affiliation": "Keele University", "name": "Christian Bean" }, { @@ -20,10 +20,10 @@ { "affiliation": "Marquette University", "name": "Jay Pantone" - } + }, { "affiliation": "Reykjavik University", "name": "Henning Ulfarsson" - }, + } ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 4edbb946..3c317fe0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,13 +5,22 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -## Unreleased +## [Unreleased] + +## [4.1.0] - 2026-01-15 ### Changed - new GriddedPerm.contains_patt function checks positions first, then pattern and as a result can lead to huge 2x - 4x speed improvement in specification finding +- Migrated to modern pyproject.toml packaging with hatchling backend +- Minimum Python version is now 3.10 (dropped 3.8, 3.9 support) +- Updated GitHub Actions to use latest action versions (checkout@v4, setup-python@v5) +- Updated Black target version to Python 3.10 + +### Removed +- Removed legacy setup.py and MANIFEST.in files -## [4.0.0 - 2025-03-13] +## [4.0.0] - 2025-03-13 ### Added - python3.9 are deprecated, python3.10 - python3.13 are supported - added `TileScopePack.requirement_and_row_and_col_placements` diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 00000000..2e33fe6d --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,32 @@ +cff-version: 1.2.0 +message: "If you use this software, please cite it as below." +type: software +title: "tilings: A Python library for gridded permutations and tilings" +url: "https://github.com/PermutaTriangle/Tilings" +repository-code: "https://github.com/PermutaTriangle/Tilings" +license: BSD-3-Clause +authors: + - given-names: "Christian" + family-names: "Bean" + affiliation: "Keele University" + - given-names: "Jon Steinn" + family-names: "Eliasson" + affiliation: "Reykjavik University" + - given-names: "Tomas Ken" + family-names: "Magnusson" + - given-names: "Émile" + family-names: "Nadeau" + affiliation: "Reykjavik University" + - given-names: "Jay" + family-names: "Pantone" + affiliation: "Marquette University" + - given-names: "Henning" + family-names: "Ulfarsson" + affiliation: "Reykjavik University" +identifiers: + - type: doi + value: "10.5281/zenodo.4948344" + description: "DOI for all versions" + - type: url + value: "https://zenodo.org/badge/latestdoi/121506164" + description: "Latest Zenodo DOI" diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 27c4da87..00000000 --- a/MANIFEST.in +++ /dev/null @@ -1 +0,0 @@ - include tilings/py.typed diff --git a/README.rst b/README.rst index fa152180..874ac7bb 100644 --- a/README.rst +++ b/README.rst @@ -1,12 +1,18 @@ Tilings ======= -.. image:: https://travis-ci.org/PermutaTriangle/Tilings.svg?branch=master - :alt: Travis - :target: https://travis-ci.org/PermutaTriangle/Tilings -.. image:: https://coveralls.io/repos/github/PermutaTriangle/Tilings/badge.svg?branch=master +.. image:: https://github.com/PermutaTriangle/Tilings/actions/workflows/test.yml/badge.svg + :alt: Tests + :target: https://github.com/PermutaTriangle/Tilings/actions/workflows/test.yml +.. image:: https://img.shields.io/badge/code%20style-black-000000.svg + :alt: Code style: black + :target: https://github.com/psf/black +.. image:: https://img.shields.io/badge/mypy-checked-blue + :alt: mypy: checked + :target: https://mypy-lang.org/ +.. image:: https://img.shields.io/coveralls/github/PermutaTriangle/Tilings.svg :alt: Coveralls - :target: https://coveralls.io/github/PermutaTriangle/Tilings?branch=master + :target: https://coveralls.io/github/PermutaTriangle/Tilings .. image:: https://img.shields.io/pypi/v/Tilings.svg :alt: PyPI :target: https://pypi.python.org/pypi/Tilings @@ -14,9 +20,6 @@ Tilings :target: https://pypi.python.org/pypi/Tilings .. image:: https://img.shields.io/pypi/pyversions/Tilings.svg :target: https://pypi.python.org/pypi/Tilings -.. image:: https://requires.io/github/PermutaTriangle/Tilings/requirements.svg?branch=master - :target: https://requires.io/github/PermutaTriangle/Tilings/requirements/?branch=master - :alt: Requirements Status .. image:: https://zenodo.org/badge/121506164.svg :target: https://zenodo.org/badge/latestdoi/121506164 @@ -54,7 +57,7 @@ development mode by cloning the repository, running .. code:: bash - ./setup.py develop + pip install -e . To verify that your installation is correct, you can try to get a specification for `Av(12)` by running in your terminal: diff --git a/pyproject.toml b/pyproject.toml index 01c77571..cc3aa80c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,80 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "tilings" +dynamic = ["version"] +authors = [ + {name = "Permuta Triangle", email = "permutatriangle@gmail.com"}, +] +description = "A Python library for gridded permutations and tilings." +readme = "README.rst" +license = {text = "BSD-3-Clause"} +keywords = [ + "permutation", + "perm", + "gridded", + "pattern", + "tiling", + "avoid", + "contain", + "occurrences", + "grid", + "class", +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Education", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: BSD License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Topic :: Education", + "Topic :: Scientific/Engineering :: Mathematics", +] +requires-python = ">=3.10" +dependencies = [ + "comb-spec-searcher>=4.2.1", + "permuta>=2.3.0", +] + +[project.urls] +Source = "https://github.com/PermutaTriangle/Tilings" +Tracker = "https://github.com/PermutaTriangle/Tilings/issues" + +[project.scripts] +tilescope = "tilings.cli:main" + +[tool.hatch.version] +path = "tilings/__init__.py" + +[tool.hatchling.build.targets.wheel] +packages = ["tilings"] + +[tool.hatchling.build.targets.sdist] +include = [ + "tilings", + "tests", + "README.rst", + "LICENSE", + "CHANGELOG.md", + "CITATION.cff", +] + [tool.black] -target-version = ['py37'] +target-version = ['py310'] include = '\.pyi?$' exclude = ''' - ( /( - \.eggs # exclude a few common directories in the - | \.git # root of the project + \.eggs + | \.git | \.hg | \.mypy_cache | \.tox @@ -16,7 +84,6 @@ exclude = ''' | build | dist )/ - | foo.py # also separately exclude a file named foo.py in - # the root of the project + | foo.py ) ''' diff --git a/setup.py b/setup.py deleted file mode 100755 index 9c945de1..00000000 --- a/setup.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python -import os - -from setuptools import find_packages, setup - - -def read(fname): - return open(os.path.join(os.path.dirname(__file__), fname), encoding="utf-8").read() - - -def get_version(rel_path): - for line in read(rel_path).splitlines(): - if line.startswith("__version__"): - delim = '"' if '"' in line else "'" - return line.split(delim)[1] - raise RuntimeError("Unable to find version string.") - - -setup( - name="tilings", - version=get_version("tilings/__init__.py"), - author="Permuta Triangle", - author_email="permutatriangle@gmail.com", - description="A Python library for gridded permutations and tilings.", - license="BSD-3", - keywords=( - "permutation perm gridded pattern tiling avoid contain" "occurrences grid class" - ), - url="https://github.com/PermutaTriangle/Tilings", - project_urls={ - "Source": "https://github.com/PermutaTriangle/Tilings", - "Tracker": "https://github.com/PermutaTriangle/Tilings/issues", - }, - packages=find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests"]), - long_description=read("README.rst"), - install_requires=[ - "comb-spec-searcher>=4.2.1", - "permuta>=2.3.0", - ], - python_requires=">=3.8", - include_package_data=True, - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Education", - "Intended Audience :: Science/Research", - "License :: OSI Approved :: BSD License", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy", - "Topic :: Education", - "Topic :: Scientific/Engineering :: Mathematics", - ], - entry_points={"console_scripts": ["tilescope=tilings.cli:main"]}, -) diff --git a/tests/algorithms/test_enumeration.py b/tests/algorithms/test_enumeration.py index c148e120..18ae7b18 100644 --- a/tests/algorithms/test_enumeration.py +++ b/tests/algorithms/test_enumeration.py @@ -251,10 +251,7 @@ def test_not_verified(self, enum_with_list_req, onebyone_enum, enum_with_crossin def test_get_genf(self, enum_verified): x = sympy.Symbol("x") expected_gf = -( - sympy.sqrt( - -(4 * x**3 - 14 * x**2 + 8 * x - 1) / (2 * x**2 - 4 * x + 1) - ) - - 1 + sympy.sqrt(-(4 * x**3 - 14 * x**2 + 8 * x - 1) / (2 * x**2 - 4 * x + 1)) - 1 ) / (2 * x * (x**2 - 3 * x + 1)) assert sympy.simplify(enum_verified.get_genf() - expected_gf) == 0 t = Tiling( @@ -324,8 +321,7 @@ def test_interleave_fixed_length(self, enum_verified): == 20 * x**11 * dummy_var**3 * cell_var**3 ) assert ( - enum_verified._interleave_fixed_length(F, (1, 0), 0) - == x**8 * dummy_var**3 + enum_verified._interleave_fixed_length(F, (1, 0), 0) == x**8 * dummy_var**3 ) def test_interleave_fixed_lengths(self, enum_verified): @@ -392,14 +388,7 @@ def test_genf_with_big_finite_cell(self): genf = enum.get_genf().expand() x = sympy.var("x") assert ( - genf - == 1 - + 2 * x - + 4 * x**2 - + 8 * x**3 - + 14 * x**4 - + 20 * x**5 - + 20 * x**6 + genf == 1 + 2 * x + 4 * x**2 + 8 * x**3 + 14 * x**4 + 20 * x**5 + 20 * x**6 ) def test_with_two_reqs(self): diff --git a/tests/strategies/test_encoding.py b/tests/strategies/test_encoding.py index ad7923ce..5999a0ef 100644 --- a/tests/strategies/test_encoding.py +++ b/tests/strategies/test_encoding.py @@ -170,7 +170,12 @@ def maxreqlen_extrabasis_ignoreparent_one_cell_only(strategy): ignore_parent=ignore_parent, one_cell_only=one_cell_only, ) - for (maxreqlen, extra_basis, ignore_parent, one_cell_only,) in product( + for ( + maxreqlen, + extra_basis, + ignore_parent, + one_cell_only, + ) in product( (1, 2, 3), (None, [Perm((0, 1))], [Perm((0, 2, 1)), Perm((0, 1, 2))]), (True, False), diff --git a/tests/strategies/test_fusion_strat.py b/tests/strategies/test_fusion_strat.py index bbd9f4a3..3427b045 100644 --- a/tests/strategies/test_fusion_strat.py +++ b/tests/strategies/test_fusion_strat.py @@ -340,7 +340,6 @@ def easy_fusable( def test_fusion_gfs(): - x = var("x") k_0 = var("k_0") k_1 = var("k_1") diff --git a/tests/strategies/test_sanity_check.py b/tests/strategies/test_sanity_check.py index 4b331de1..648c2340 100644 --- a/tests/strategies/test_sanity_check.py +++ b/tests/strategies/test_sanity_check.py @@ -525,7 +525,9 @@ assumptions=(TrackingAssumption((GriddedPerm((0,), ((0, 0),)),)),), ) ), - RequirementInsertionStrategy(gps=(GriddedPerm((0,), ((1, 0),)),),)( + RequirementInsertionStrategy( + gps=(GriddedPerm((0,), ((1, 0),)),), + )( Tiling( obstructions=( GriddedPerm((0, 1), ((0, 0), (0, 0))), diff --git a/tests/strategies/test_verification.py b/tests/strategies/test_verification.py index 623d4520..4e207da7 100644 --- a/tests/strategies/test_verification.py +++ b/tests/strategies/test_verification.py @@ -677,10 +677,7 @@ def test_get_specification(self, strategy, enum_verified): def test_get_genf(self, strategy, enum_verified): x = sympy.Symbol("x") expected_gf = -( - sympy.sqrt( - -(4 * x**3 - 14 * x**2 + 8 * x - 1) / (2 * x**2 - 4 * x + 1) - ) - - 1 + sympy.sqrt(-(4 * x**3 - 14 * x**2 + 8 * x - 1) / (2 * x**2 - 4 * x + 1)) - 1 ) / (2 * x * (x**2 - 3 * x + 1)) assert sympy.simplify(strategy.get_genf(enum_verified[0]) - expected_gf) == 0 @@ -758,14 +755,7 @@ def test_genf_with_big_finite_cell(self, strategy): genf = strategy.get_genf(t).expand() x = sympy.var("x") assert ( - genf - == 1 - + 2 * x - + 4 * x**2 - + 8 * x**3 - + 14 * x**4 - + 20 * x**5 - + 20 * x**6 + genf == 1 + 2 * x + 4 * x**2 + 8 * x**3 + 14 * x**4 + 20 * x**5 + 20 * x**6 ) def test_with_two_reqs(self, strategy): diff --git a/tests/test_assumptions.py b/tests/test_assumptions.py index 0b00ca38..7dd37eea 100644 --- a/tests/test_assumptions.py +++ b/tests/test_assumptions.py @@ -77,7 +77,6 @@ def all_tilings( def test_bytes(tplaced, tplaced_tracked, all_tilings): - assert len(tplaced.assumptions) == 0 remade = Tiling.from_bytes(tplaced.to_bytes()) assert tplaced != tplaced_tracked diff --git a/tests/test_griddedperm.py b/tests/test_griddedperm.py index 0d8666db..658ed508 100644 --- a/tests/test_griddedperm.py +++ b/tests/test_griddedperm.py @@ -491,7 +491,10 @@ def test_permute_rows(): Perm((1, 3, 2, 0, 4)), ((0, 1), (0, 2), (0, 1), (1, 0), (2, 2)) ) - assert GriddedPerm((0,), ((0, 0),),).permute_rows((1, 2, 0)) == GriddedPerm( + assert GriddedPerm( + (0,), + ((0, 0),), + ).permute_rows((1, 2, 0)) == GriddedPerm( (0,), ((0, 2),), ) diff --git a/tilings/__init__.py b/tilings/__init__.py index f31c82f4..cdd506a0 100644 --- a/tilings/__init__.py +++ b/tilings/__init__.py @@ -2,6 +2,6 @@ from tilings.griddedperm import GriddedPerm from tilings.tiling import Tiling -__version__ = "4.0.0" +__version__ = "4.1.0" __all__ = ["GriddedPerm", "Tiling", "TrackingAssumption"] diff --git a/tilings/algorithms/fusion.py b/tilings/algorithms/fusion.py index f5092357..038f3304 100644 --- a/tilings/algorithms/fusion.py +++ b/tilings/algorithms/fusion.py @@ -1,6 +1,7 @@ """ The implementation of the fusion algorithm """ + import collections from itertools import chain from typing import ( @@ -169,7 +170,7 @@ def positive_left_right_requirements( one point, and the right contains at least one point. """ left, right = [], [] - for (x, y) in self._tiling.active_cells: + for x, y in self._tiling.active_cells: if self._fuse_row and y == self._row_idx: left.append(GriddedPerm.single_cell((0,), (x, y))) right.append(GriddedPerm.single_cell((0,), (x, y + 1))) diff --git a/tilings/algorithms/minimal_gridded_perms.py b/tilings/algorithms/minimal_gridded_perms.py index 0d3e87ac..d6df29af 100644 --- a/tilings/algorithms/minimal_gridded_perms.py +++ b/tilings/algorithms/minimal_gridded_perms.py @@ -454,7 +454,7 @@ def _process_work_packet( qpacket: QueuePacket, queue: List[QueuePacket] ) -> Iterator[GriddedPerm]: # now we try inserting a new point into the cell - for (cell, localised) in self._get_cells_to_try(qpacket): + for cell, localised in self._get_cells_to_try(qpacket): # The `localised` bool tells us if we inserted into # a cell as it didn't contain the patterns in the # cell. If not, then we update the last cell, to diff --git a/tilings/algorithms/requirement_placement.py b/tilings/algorithms/requirement_placement.py index a3997518..e5fb85d0 100644 --- a/tilings/algorithms/requirement_placement.py +++ b/tilings/algorithms/requirement_placement.py @@ -153,9 +153,11 @@ def _gridded_perm_translation_with_point( # TODO: to prepare for intervals consider all ways of drawing a # rectangle around point in cell. new_pos = [ - self._point_translation(gp, i, (point_index, gp.patt[point_index])) - if i != point_index - else self._placed_cell(gp.pos[point_index]) + ( + self._point_translation(gp, i, (point_index, gp.patt[point_index])) + if i != point_index + else self._placed_cell(gp.pos[point_index]) + ) for i in range(len(gp)) ] return gp.__class__(gp.patt, new_pos) diff --git a/tilings/algorithms/row_col_separation.py b/tilings/algorithms/row_col_separation.py index bcf2ec43..4c4dc7fe 100644 --- a/tilings/algorithms/row_col_separation.py +++ b/tilings/algorithms/row_col_separation.py @@ -7,6 +7,7 @@ The role of the class `RowColSeparation` is to make sure that row columns separation is idempotent by applying the core algorithm until it stabilises. """ + import heapq from itertools import combinations, product from typing import TYPE_CHECKING, Dict, List, Tuple diff --git a/tilings/algorithms/sliding.py b/tilings/algorithms/sliding.py index 2780e652..d5864f20 100644 --- a/tilings/algorithms/sliding.py +++ b/tilings/algorithms/sliding.py @@ -82,9 +82,11 @@ def slide_assumption( gp.patt, ( ( - c2 - if gp.pos[0][0] == c1 - else (c1 if gp.pos[0][0] == c2 else gp.pos[0][0]), + ( + c2 + if gp.pos[0][0] == c1 + else (c1 if gp.pos[0][0] == c2 else gp.pos[0][0]) + ), 0, ), ), @@ -99,9 +101,11 @@ def slide_assumption( gp.patt, ( ( - c2 - if gp.pos[0][0] == c1 - else (c1 if gp.pos[0][0] == c2 else gp.pos[0][0]), + ( + c2 + if gp.pos[0][0] == c1 + else (c1 if gp.pos[0][0] == c2 else gp.pos[0][0]) + ), 0, ), ), @@ -405,9 +409,11 @@ def _slide_obstructions( # The 12...n obstrudtion that connects av12 and av123 yield GriddedPerm( range(n), - ((av_12, 0),) * (n - 1) + ((av_123, 0),) - if av_12 < av_123 - else ((av_123, 0),) + ((av_12, 0),) * (n - 1), + ( + ((av_12, 0),) * (n - 1) + ((av_123, 0),) + if av_12 < av_123 + else ((av_123, 0),) + ((av_12, 0),) * (n - 1) + ), ) elif gp in self.col_info[av_123][2]: # The one with two points in av_123 are altered so that the two @@ -435,9 +441,7 @@ def _slide_obstructions( yield gp @staticmethod - def _gp_slide_split( - gp: GriddedPerm, c1: int, c2: int - ) -> Tuple[ + def _gp_slide_split(gp: GriddedPerm, c1: int, c2: int) -> Tuple[ Tuple[Deque[int], Deque[int], Deque[int], Deque[int], Deque[int], Deque[int]], Tuple[List[int], List[int], List[int]], ]: diff --git a/tilings/algorithms/subclass_verification.py b/tilings/algorithms/subclass_verification.py index c687d178..4d3503e8 100644 --- a/tilings/algorithms/subclass_verification.py +++ b/tilings/algorithms/subclass_verification.py @@ -69,7 +69,6 @@ def subclasses(self) -> Tuple[Perm, ...]: return cast(Tuple[Perm, ...], self._subclasses) def compute_subclasses(self) -> None: - self._subclasses = tuple() perms_to_check = self.quick_pare() diff --git a/tilings/bijections.py b/tilings/bijections.py index 81f08574..21ba7c69 100644 --- a/tilings/bijections.py +++ b/tilings/bijections.py @@ -91,7 +91,7 @@ def _init_assumptions(self) -> Tuple[AssumptionLabels, AssumptionLabels]: @staticmethod def _get_next( - path: Deque[Tuple[AbstractRule, int]] + path: Deque[Tuple[AbstractRule, int]], ) -> Tuple[Rule[Tiling, GriddedPerm], int]: """Get next rule and the index of its child we traverse through.""" r, idx = path.popleft() diff --git a/tilings/gui_launcher.py b/tilings/gui_launcher.py index 0eb30a7f..8d1df1de 100644 --- a/tilings/gui_launcher.py +++ b/tilings/gui_launcher.py @@ -14,7 +14,6 @@ except ImportError: def _fake_version(_str: str) -> str: - print("=== If you are using older version of python than 3.8 ===") print(f"To use for {sys.executable} you need importlib-metadata.") print(f"{sys.executable} -m pip install importlib-metadata", flush=True) diff --git a/tilings/misc.py b/tilings/misc.py index 50850a85..5b747a99 100644 --- a/tilings/misc.py +++ b/tilings/misc.py @@ -2,6 +2,7 @@ Collection of function that are not directly related to the code but still useful. """ + from functools import reduce from typing import ( Collection, diff --git a/tilings/strategies/assumption_insertion.py b/tilings/strategies/assumption_insertion.py index 524f69f0..0a4ecd86 100644 --- a/tilings/strategies/assumption_insertion.py +++ b/tilings/strategies/assumption_insertion.py @@ -77,9 +77,11 @@ def _build_child_param_map(self, parent: Tiling, child: Tiling) -> ParametersMap } child_param_to_parent_param = {v: k for k, v in self.extra_parameters.items()} child_pos_to_parent_pos: Tuple[Tuple[int, ...], ...] = tuple( - tuple() - if param in self.new_parameters - else (parent_param_to_pos[child_param_to_parent_param[param]],) + ( + tuple() + if param in self.new_parameters + else (parent_param_to_pos[child_param_to_parent_param[param]],) + ) for param in child.extra_parameters ) return self.build_param_map( diff --git a/tilings/strategies/assumption_splitting.py b/tilings/strategies/assumption_splitting.py index a410f832..52ec4009 100644 --- a/tilings/strategies/assumption_splitting.py +++ b/tilings/strategies/assumption_splitting.py @@ -77,7 +77,7 @@ def compositions_dict(value: int, parameters: Tuple[str, ...]): yield dict(zip(parameters, comp)) def union_params( - sub_params: Tuple[Dict[str, int], ...] + sub_params: Tuple[Dict[str, int], ...], ) -> Optional[Dict[str, int]]: new_params: Dict[str, int] = {} for params in sub_params: diff --git a/tilings/strategies/cell_reduction.py b/tilings/strategies/cell_reduction.py index 9ff68ea9..07fce85e 100644 --- a/tilings/strategies/cell_reduction.py +++ b/tilings/strategies/cell_reduction.py @@ -1,4 +1,5 @@ """The cell reduction strategy.""" + from typing import Callable, Dict, Iterator, List, Optional, Set, Tuple, cast import sympy diff --git a/tilings/strategies/deflation.py b/tilings/strategies/deflation.py index 4a8a2f6a..74473799 100644 --- a/tilings/strategies/deflation.py +++ b/tilings/strategies/deflation.py @@ -1,4 +1,5 @@ """The deflation strategy.""" + from typing import Callable, Dict, Iterator, List, Optional, Tuple, cast import sympy diff --git a/tilings/strategies/detect_components.py b/tilings/strategies/detect_components.py index 4b06d77d..af20a98c 100644 --- a/tilings/strategies/detect_components.py +++ b/tilings/strategies/detect_components.py @@ -163,9 +163,9 @@ def extra_parameters( for assumption in comb_class.assumptions: mapped_assumption = assumption.remove_components(comb_class) if mapped_assumption.gps: - extra_parameters[ - comb_class.get_assumption_parameter(assumption) - ] = child.get_assumption_parameter(mapped_assumption) + extra_parameters[comb_class.get_assumption_parameter(assumption)] = ( + child.get_assumption_parameter(mapped_assumption) + ) return (extra_parameters,) def formal_step(self) -> str: diff --git a/tilings/strategies/factor.py b/tilings/strategies/factor.py index 11c3f4a4..3564905a 100644 --- a/tilings/strategies/factor.py +++ b/tilings/strategies/factor.py @@ -297,7 +297,7 @@ def assumptions_to_add(self, comb_class: Tiling) -> Tuple[TrackingAssumption, .. @staticmethod def interleaving_rows_and_cols( - partition: Tuple[Tuple[Cell, ...], ...] + partition: Tuple[Tuple[Cell, ...], ...], ) -> Tuple[Set[int], Set[int]]: """ Return the set of cols and the set of rows that are being interleaved when @@ -552,7 +552,6 @@ class FactorWithMonotoneInterleavingStrategy(FactorWithInterleavingStrategy): class FactorFactory(StrategyFactory[Tiling]): - FACTOR_ALGO_AND_CLASS = { None: (Factor, FactorStrategy), "monotone": ( diff --git a/tilings/strategies/fusion/constructor.py b/tilings/strategies/fusion/constructor.py index 5cf811de..0f279237 100644 --- a/tilings/strategies/fusion/constructor.py +++ b/tilings/strategies/fusion/constructor.py @@ -13,6 +13,7 @@ We will assume we are always fusing two adjacent columns, and discuss the left and right hand sides accordingly. """ + import enum from collections import Counter, defaultdict from functools import reduce @@ -100,9 +101,11 @@ def __init__( ( "left" if parent_fusion_parameter in self.left_sided_parameters - else "right" - if parent_fusion_parameter in self.right_sided_parameters - else "both" + else ( + "right" + if parent_fusion_parameter in self.right_sided_parameters + else "both" + ) ) for parent_fusion_parameter in self.parent_fusion_parameters ] @@ -506,9 +509,11 @@ def update_subparams( updated_value = ( value + number_of_left_points if parameter in self.right_sided_parameters - else value + number_of_right_points - if parameter in self.left_sided_parameters - else value + else ( + value + number_of_right_points + if parameter in self.left_sided_parameters + else value + ) ) child_parameter = self.extra_parameters[parameter] if child_parameter not in res: diff --git a/tilings/strategies/fusion/fusion.py b/tilings/strategies/fusion/fusion.py index 18644cfd..29ceafe7 100644 --- a/tilings/strategies/fusion/fusion.py +++ b/tilings/strategies/fusion/fusion.py @@ -35,7 +35,6 @@ def is_equivalence( return False def _ensure_level_objects(self, n: int) -> None: - if self.subobjects is None: raise RuntimeError("set_subrecs must be set first") while n >= len(self.objects_cache): diff --git a/tilings/strategies/pointing.py b/tilings/strategies/pointing.py index c9f60a29..641d8e68 100644 --- a/tilings/strategies/pointing.py +++ b/tilings/strategies/pointing.py @@ -2,6 +2,7 @@ The directionless point placement strategy that is counted by the 'pointing' constructor. """ + from collections import Counter from itertools import product from typing import ( @@ -158,9 +159,9 @@ def extra_parameters( ] for ass, mapped_ass in zip(comb_class.assumptions, mapped_assumptions): if mapped_ass.gps: - params[ - comb_class.get_assumption_parameter(ass) - ] = child.get_assumption_parameter(mapped_ass) + params[comb_class.get_assumption_parameter(ass)] = ( + child.get_assumption_parameter(mapped_ass) + ) res.append(params) return tuple(res) @@ -206,9 +207,11 @@ def get_terms( terms = DisjointUnion.get_terms(self, parent_terms, subterms, n) return Counter( { - key: value // (key[self.division_index] + self.shift) - if (key[self.division_index] + self.shift) != 0 - else value + key: ( + value // (key[self.division_index] + self.shift) + if (key[self.division_index] + self.shift) != 0 + else value + ) for key, value in terms.items() } ) @@ -387,7 +390,6 @@ def to_jsonable(self) -> dict: @classmethod def from_dict(cls, d: dict) -> "AssumptionPointingFactory": - return cls(**d) diff --git a/tilings/strategies/requirement_placement.py b/tilings/strategies/requirement_placement.py index 27a2aff7..5791c136 100644 --- a/tilings/strategies/requirement_placement.py +++ b/tilings/strategies/requirement_placement.py @@ -680,7 +680,6 @@ def __str__(self) -> str: class AllPlacementsFactory(AbstractRequirementPlacementFactory): - PLACEMENT_STRATS: Tuple[AbstractRequirementPlacementFactory, ...] = ( PatternPlacementFactory(point_only=False), # subreqs=True covers everything but it blows up massively! diff --git a/tilings/strategies/row_and_col_separation.py b/tilings/strategies/row_and_col_separation.py index 7ac3ce9c..e842f5c4 100644 --- a/tilings/strategies/row_and_col_separation.py +++ b/tilings/strategies/row_and_col_separation.py @@ -2,6 +2,7 @@ The row and column separation strategy. The details of the algorithm can be found in the algorithms folder. """ + from typing import Dict, Iterator, Optional, Tuple from comb_spec_searcher import DisjointUnionStrategy diff --git a/tilings/strategies/verification.py b/tilings/strategies/verification.py index 9488d7f3..87d433b4 100644 --- a/tilings/strategies/verification.py +++ b/tilings/strategies/verification.py @@ -118,9 +118,9 @@ def get_genf( gp = next(comb_class.minimal_gridded_perms()) expected = {"x": len(gp)} for assumption in comb_class.assumptions: - expected[ - comb_class.get_assumption_parameter(assumption) - ] = assumption.get_value(gp) + expected[comb_class.get_assumption_parameter(assumption)] = ( + assumption.get_value(gp) + ) return reduce(mul, [var(k) ** n for k, n in expected.items()], 1) def __repr__(self) -> str: diff --git a/tilings/strategy_pack.py b/tilings/strategy_pack.py index de7f9e91..7208a0f9 100644 --- a/tilings/strategy_pack.py +++ b/tilings/strategy_pack.py @@ -33,7 +33,7 @@ def add_basis(self, basis: Iterable[Perm]) -> "TileScopePack": symmetry = bool(self.symmetries) def replace_list( - strats: Tuple[Union[AbstractStrategy, StrategyFactory], ...] + strats: Tuple[Union[AbstractStrategy, StrategyFactory], ...], ) -> List[Union[AbstractStrategy, StrategyFactory]]: """Return a new list with the replaced 1x1 strat.""" res: List[Union[AbstractStrategy, StrategyFactory]] = [] @@ -74,7 +74,7 @@ def setup_subclass_verification(self, start_tiling: "Tiling") -> "TileScopePack" """ def replace_list( - strats: Tuple[Union[AbstractStrategy, StrategyFactory], ...] + strats: Tuple[Union[AbstractStrategy, StrategyFactory], ...], ) -> List[Union[AbstractStrategy, StrategyFactory]]: """ Find subclass verification and alter its perms_to_check variable. diff --git a/tilings/tilescope.py b/tilings/tilescope.py index d6b2e174..29b604b8 100644 --- a/tilings/tilescope.py +++ b/tilings/tilescope.py @@ -58,7 +58,6 @@ def __init__( expand_verified: bool = False, debug: bool = False, ) -> None: - """Initialise TileScope.""" if isinstance(start_class, Tiling): start_tiling = start_class @@ -127,6 +126,7 @@ def _rules_from_strategy( # type: ignore Yield all the rules given by a strategy/strategy factory whose children all satisfy the max_assumptions constraint. """ + # pylint: disable=arguments-differ def num_child_assumptions(child: Tiling) -> int: return sum( diff --git a/tox.ini b/tox.ini index a9127f83..0aba2056 100644 --- a/tox.ini +++ b/tox.ini @@ -6,11 +6,11 @@ [tox] envlist = flake8, mypy, pylint, black - py{310,311,312,313}, + py{310,311,312,313,314}, pypy{310,311} [default] -basepython=python3.13 +basepython=python3.14 [testenv] description = run test @@ -19,12 +19,13 @@ basepython = py311: python3.11 py312: python3.12 py313: python3.13 + py314: python3.14 pypy310: pypy3.10 pypy311: pypy3.11 deps = pytest pytest-timeout -commands = +commands = pip install --force-reinstall git+https://github.com/PermutaTriangle/comb_spec_searcher.git@develop pip freeze pytest @@ -43,7 +44,7 @@ deps = flake8 flake8-isort commands = - flake8 --isort-show-traceback tilings tests setup.py + flake8 --isort-show-traceback tilings tests [testenv:pylint] description = run pylint (static code analysis) @@ -65,5 +66,5 @@ commands = mypy description = check that comply with autoformating basepython = {[default]basepython} deps = - black==22.10.0 + black==25.12.0 commands = black --check --diff .