diff --git a/mapplings/algorithms/__init__.py b/mapplings/algorithms/__init__.py index b50dedc..47aef10 100644 --- a/mapplings/algorithms/__init__.py +++ b/mapplings/algorithms/__init__.py @@ -3,7 +3,7 @@ from .factor import Factor, ILFactorInverted, ILFactorNormal from .row_col_sep_mt import LTRowColSeparationMT, LTORERowColSeparationMT from .point_placement import MTRequirementPlacement - +from .parmeter_placement import ParameterPlacement __all__ = [ "Factor", @@ -12,5 +12,6 @@ "LTRowColSeparationMT", "LTORERowColSeparationMT", "MTRequirementPlacement", + "ParameterPlacement", ] __version__ = "0.1.0" diff --git a/mapplings/algorithms/factor.py b/mapplings/algorithms/factor.py index 4ff09b3..bec81f3 100644 --- a/mapplings/algorithms/factor.py +++ b/mapplings/algorithms/factor.py @@ -84,7 +84,7 @@ class ILFactorNormal(Factor): """Does IL factoring with 00 obs as normal""" @cached_property - def find_factors_as_cells(self): + def find_factors_as_cells(self) -> tuple[tuple[Cell, ...], ...]: self.combine_cells_in_obs_and_reqs() self.combine_cells_from_parameters() factors = [] @@ -99,16 +99,16 @@ def find_factors_as_cells(self): def make_enumerators(self): """Creates the enumerators needed for interleaving""" - factor_rows_and_cols = ( - map(tuple, map(set, zip(*factor))) for factor in self.find_factors_as_cells - ) - factor_rows_and_cols = tuple( - map(lambda x: tuple(chain.from_iterable(x)), zip(*factor_rows_and_cols)) - ) + cols_in_factors = list[int]() + rows_in_factors = list[int]() + for factor in self.find_factors_as_cells: + f_cols, f_rows = map(set, zip(*factor)) + cols_in_factors += list(f_cols) + rows_in_factors += list(f_rows) new_enumerators = set() dimensions = self.tiling.dimensions for row in range(dimensions[1]): - if factor_rows_and_cols[1].count(row) > 1: + if rows_in_factors.count(row) > 1: new_enumerators.add( ParameterList( ( @@ -123,7 +123,7 @@ def make_enumerators(self): ) ) for col in range(dimensions[0]): - if factor_rows_and_cols[0].count(col) > 1: + if cols_in_factors.count(col) > 1: new_enumerators.add( ParameterList( ( @@ -157,11 +157,18 @@ def combine_cells_in_obs_and_reqs(self): new_obs = set() for row in range(self.tiling.dimensions[1]): for col1, col2 in combinations(range(self.tiling.dimensions[0]), 2): - new_obs.add(GriddedCayleyPerm((0, 0), ((col1, row), (col2, row)))) + cell1, cell2 = (col1, row), (col2, row) + if cell1 in self.cells_dict and cell2 in self.cells_dict: + new_obs.add(GriddedCayleyPerm((0, 0), (cell1, cell2))) new_obs.symmetric_difference_update(set(self.tiling.obstructions)) for gcp in new_obs: if not self.point_row_ob(gcp): for cell, cell2 in combinations((gcp.find_active_cells()), 2): + + if cell2 not in self.cells_dict: + print(cell, cell2) + print(self.tiling) + print(gcp) self.combine_cells(cell, cell2) for cell, cell2 in chain.from_iterable( combinations( diff --git a/mapplings/algorithms/parmeter_placement.py b/mapplings/algorithms/parmeter_placement.py new file mode 100644 index 0000000..e1445ee --- /dev/null +++ b/mapplings/algorithms/parmeter_placement.py @@ -0,0 +1,175 @@ +"""Algorithm for parameter placement""" + +from cayley_permutations import CayleyPermutation + +from gridded_cayley_permutations import GriddedCayleyPerm, Tiling +from gridded_cayley_permutations.point_placements import PointPlacement +from gridded_cayley_permutations.row_col_map import RowColMap + +from mapplings import MappedTiling, Parameter, ParameterList +from .point_placement import MTRequirementPlacement + + +Cell = tuple[int, int] + + +class ParameterPlacement: + """For a given mappling and containing parameter, places the parameter + in a cell of the tiling of the mappling. Places the point of the parameter + at the given index (0 based) in the given direction.""" + + def __init__(self, mappling: MappedTiling, param_list: ParameterList) -> None: + """cell is the cell in the tiling which the parameter will be placed into""" + assert len(param_list) == 1, "Too many Parameters in ParameterList." + assert ( + param_list in mappling.containing_parameters + ), "ParameterList does not exist." + self.mappling = mappling + new_containers = list(mappling.containing_parameters) + new_containers.remove(param_list) + self.adjusted_mappling = MappedTiling( + mappling.tiling, + mappling.avoiding_parameters, + new_containers, + mappling.enumerating_parameters, + ) + self.param = tuple(param_list)[0] + + def param_placement(self, direction: int, index_of_pattern: int) -> MappedTiling: + """Place a parameter in the tiling. + index_of_pattern is the index of the pattern that is placed in the tiling and is 0 based. + """ + param_cell = tuple(self.param.point_cells())[index_of_pattern] + cell = (self.param.col_map[param_cell[0]], self.param.col_map[param_cell[1]]) + new_mappling = MTRequirementPlacement( + self.adjusted_mappling + ).directionless_point_placement(cell) + new_avoiding_parameters = list( + new_mappling.avoiding_parameters + ) + self.find_new_avoiding_parameters(direction, cell, param_cell) + + new_containing_parameters = self.update_containing_parameters( + param_cell, cell, new_mappling.containing_parameters + ) + new_base = new_mappling.tiling + algo = PointPlacement(self.mappling.tiling) + point_obs, point_reqs = algo.point_obstructions_and_requirements(cell) + new_obs = new_base.obstructions + point_obs + new_reqs = new_base.requirements + point_reqs + new_base = Tiling(new_obs, new_reqs, new_base.dimensions) + return MappedTiling( + new_base, + new_avoiding_parameters, + new_containing_parameters, + new_mappling.enumerating_parameters, + ) + + def find_new_avoiding_parameters( + self, direction: int, base_cell: Cell, param_cell: Cell + ): + """Return a list of new avoiding parameters for the new mappling.""" + cells_to_insert_in = self.cells_to_insert_point_in( + direction, base_cell, param_cell + ) + new_avoiding_parameters = [] + for cell in cells_to_insert_in: + new_ghost = PointPlacement(self.param.ghost).directionless_point_placement( + cell + ) + new_avoiding_parameters.append( + MTRequirementPlacement( + self.mappling + ).new_parameter_from_point_placed_tiling(self.param, new_ghost, cell) + ) + return new_avoiding_parameters + + def cells_in_parameter(self, base_cell: Cell) -> tuple[Cell, ...]: + """Gets the preimage of the base cell according to the param map.""" + return self.param.map.preimage_of_cell(base_cell) + + def cells_to_insert_point_in( + self, direction: int, base_cell: Cell, param_cell: Cell + ): + """Returns a list of cells in the parameter which a point can be + placed into for the resulting tiling to be an avoiding parameter (cells + which are not further in the given direction so that the pattern in the + parameter will be, therefore it must be avoided.)""" + all_cells = [ + cell + for cell in self.cells_in_parameter(base_cell) + if GriddedCayleyPerm(CayleyPermutation([0]), [cell]) + not in self.param.ghost.obstructions + ] + cell_of_point_being_placed = param_cell + cells_to_insert_in = [ + cell + for cell in all_cells + if PointPlacement(self.param.ghost).farther( + cell_of_point_being_placed, cell, direction + ) + ] + return cells_to_insert_in + + def update_containing_parameters( + self, + param_cell: Cell, + base_cell: Cell, + containing_parameters: tuple[ParameterList, ...], + ) -> tuple[ParameterList, ...]: + """Remove [self.param] from containing parameters and add new + containing parameter list (one that is the identity).""" + param_to_update = Parameter(self.param.ghost, self.param.map) + point_placed_ghost = PointPlacement( + param_to_update.ghost + ).directionless_point_placement(param_cell) + new_map = self.new_containing_param_map( + param_cell, base_cell, point_placed_ghost + ) + new_containing_param = Parameter(point_placed_ghost, new_map) + return (ParameterList((new_containing_param,)),) + containing_parameters + + def new_containing_param_map( + self, param_cell: Cell, base_cell: Cell, ghost: Tiling + ): + """Return a new RowColMap for the containing parameter that was placed.""" + new_row_map = self.adjust_dict_in_param( + param_cell, base_cell, True, ghost.dimensions[1] + ) + new_col_map = self.adjust_dict_in_param( + param_cell, base_cell, False, ghost.dimensions[0] + ) + return RowColMap(new_col_map, new_row_map) + + def adjust_dict_in_param( + self, + middle_cell: Cell, + base_cell: Cell, + adjust_rows: bool, + dimension: int, + ): + """Update a row or col map given the cell in the new parameter where the point is placed + and the old row or col map. + If adjust_rows = 0 then it returns the new col map, if 1 then it returns the new row map. + """ + if adjust_rows: + new_map = self.param.row_map.copy() + else: + new_map = self.param.col_map.copy() + vals_in_param = set( + cell[adjust_rows] for cell in self.cells_in_parameter(base_cell) + ) + middle_val = middle_cell[adjust_rows] + 1 + for val in range(dimension): + if val not in vals_in_param: + if val > middle_val: + if val not in new_map: + new_map[val] = base_cell[adjust_rows] + 2 + else: + new_map[val] += 2 + elif val < middle_val: + new_map[val] = base_cell[adjust_rows] + elif val > middle_val: + new_map[val] = base_cell[adjust_rows] + 2 + else: + new_map[val] = base_cell[adjust_rows] + 1 + return new_map diff --git a/mapplings/cleaners/mappling_cleaner.py b/mapplings/cleaners/mappling_cleaner.py index 3f6eb8b..da25874 100644 --- a/mapplings/cleaners/mappling_cleaner.py +++ b/mapplings/cleaners/mappling_cleaner.py @@ -69,7 +69,10 @@ def _clean_parameters(mappling: MappedTiling) -> MappedTiling: for e_list in new_enumerators ] return MappedTiling( - mappling.tiling, new_avoiders, new_containers, new_enumerators + mappling.tiling, + new_avoiders, + new_containers, + new_enumerators, ) return _clean_parameters @@ -92,6 +95,13 @@ def try_to_kill(mappling: MappedTiling) -> MappedTiling: for avoider in mappling.avoiding_parameters ): return MappedTiling.empty_mappling() + if any( + ( + all(container.positive_cells() for container in c_list) + for c_list in mappling.containing_parameters + ) + ): + return MappedTiling.empty_mappling() return MappedTiling( Tiling( [GriddedCayleyPerm(CayleyPermutation((0,)), ((0, 0),))], [], (1, 1) @@ -155,7 +165,10 @@ def reap_all_contradictions(mappling: MappedTiling) -> MappedTiling: if new_e_list: new_enumerators.append(e_list) return MappedTiling( - mappling.tiling, new_avoiders, new_containers, new_enumerators + mappling.tiling, + new_avoiders, + new_containers, + new_enumerators, ) @staticmethod diff --git a/mapplings/mapped_tiling.py b/mapplings/mapped_tiling.py index bd39794..4e7b461 100644 --- a/mapplings/mapped_tiling.py +++ b/mapplings/mapped_tiling.py @@ -129,7 +129,9 @@ def get_objects(self, n: int) -> Objects: objects[param].append(gcp) return objects - def get_parameters(self, obj: GriddedCayleyPerm) -> tuple[int, ...]: + def get_parameters( + self, obj: GriddedCayleyPerm + ) -> tuple[int, ...]: # This function is broken """Parameters are not what you think!!! This is specific to combinatorical class parameters""" return tuple( diff --git a/mapplings/strategies/mapped_tilescope.py b/mapplings/strategies/mapped_tilescope.py index 24a51fd..cb689ad 100644 --- a/mapplings/strategies/mapped_tilescope.py +++ b/mapplings/strategies/mapped_tilescope.py @@ -20,6 +20,11 @@ MapplingLessThanRowColSeparationStrategy, MapplingLessThanOrEqualRowColSeparationStrategy, MapplingCellInsertionFactory, + # ParamPlacementFactory, + # AvoiderExorcismFactory, + # ParameterInsertionFactory, + MapplingILFactorStrategy, + MapplingInvertedILFactorStrategy, ) @@ -312,3 +317,60 @@ def insertion_row_and_col_placement(cls, rootmt: MappedTiling): symmetries=[], iterative=False, ) + + @classmethod + def parameter_tomfoolery(cls, rootmt: MappedTiling): + """ + Create a row and column placement strategy pack which initially + makes all cells positive. + """ + return MappedTileScopePack( + initial_strats=[MapplingFactorStrategy()], + inferral_strats=[], + expansion_strats=[ + [ + MapplingLessThanRowColSeparationStrategy(), + ] + ], + ver_strats=[ + AtomStrategy(), + NoParameterVerificationStrategy(rootmt), + # MapplingVerticalInsertionEncodableVerificationStrategy(), + # MapplingHorizontalInsertionEncodableVerificationStrategy(), + ], + name="Param Nonsense", + symmetries=[], + iterative=False, + ) + + @classmethod + def pack_for_1_32( + cls, + ): + """Pack to get the 1-32 tree""" + return MappedTileScopePack( + initial_strats=[ + # MapplingFactorStrategy(), + MapplingInvertedILFactorStrategy(), + MapplingILFactorStrategy(), + ], + inferral_strats=[ + MapplingCellInsertionFactory(), + # ParamPlacementFactory(), + ], + expansion_strats=[ + [ + # ParameterInsertionFactory(ParameterList({special_param})), + MapplingPointPlacementFactory(), + ] + ], + ver_strats=[ + AtomStrategy(), + # NoParameterVerificationStrategy(rootmt), + MapplingVerticalInsertionEncodableVerificationStrategy(), + MapplingHorizontalInsertionEncodableVerificationStrategy(), + ], + name="1-32", + symmetries=[], + iterative=False, + ) diff --git a/mapplings/strategies/tilescope_strategies.py b/mapplings/strategies/tilescope_strategies.py index 6674f28..5b27fcb 100644 --- a/mapplings/strategies/tilescope_strategies.py +++ b/mapplings/strategies/tilescope_strategies.py @@ -1,6 +1,6 @@ """Strategies for mapplings tilescope.""" -from typing import Iterator +from typing import Iterator, Optional, Iterable from gridded_cayley_permutations import Tiling, GriddedCayleyPerm from gridded_cayley_permutations.point_placements import Directions from tilescope.strategies import ( @@ -27,12 +27,14 @@ from comb_spec_searcher import ( DisjointUnionStrategy, CombinatorialSpecificationSearcher, + StrategyFactory, ) from comb_spec_searcher.exception import StrategyDoesNotApply from cayley_permutations import CayleyPermutation -from mapplings import MappedTiling +from mapplings import MappedTiling, ParameterList, Parameter from mapplings.algorithms import ( MTRequirementPlacement, + ParameterPlacement, Factor, ILFactorNormal, ILFactorInverted, @@ -42,6 +44,7 @@ from mapplings.cleaners import MTCleaner, ParamCleaner +MTCleaner.global_debug_toggle(0) MTCleaner.global_log_toggle(1) temp = CombinatorialSpecificationSearcher.status @@ -96,6 +99,259 @@ def __call__( yield strategy +class ParameterPlacementStrategy(DisjointUnionStrategy): + """Strategy for placing parameters""" + + cleaner = MTCleaner.make_full_cleaner("Param Placement Cleaner") + + def __init__( + self, + c_list_index: int, + point_index: int, + direction: int, + ignore_parent: bool = False, + ): + self.c_list_index = c_list_index + self.index = point_index + self.direction = direction + super().__init__(ignore_parent) + + def algorithm(self, mappling: MappedTiling): + """Algorithm used by the decomposition function""" + c_list = mappling.containing_parameters[self.c_list_index] + return ParameterPlacement(mappling, c_list) + + def decomposition_function(self, comb_class): + new_mappling = self.algorithm(comb_class).param_placement( + self.direction, self.index + ) + return (self.__class__.cleaner(new_mappling),) + + def formal_step(self): + return ( + f"Placed point {self.index} of containing parameter {self.c_list_index} " + + f"in direction {self.direction}" + ) + + def backward_map( + self, + comb_class: MappedTiling, + objs: tuple[Optional[GriddedCayleyPerm], ...], + children: Optional[tuple[MappedTiling, ...]] = None, + ) -> Iterator[GriddedCayleyPerm]: + raise NotImplementedError + + def forward_map( + self, + comb_class: MappedTiling, + obj: GriddedCayleyPerm, + children: Optional[tuple[MappedTiling, ...]] = None, + ) -> tuple[Optional[GriddedCayleyPerm], ...]: + raise NotImplementedError + + def to_jsonable(self) -> dict: + """Return a dictionary form of the strategy.""" + d: dict = super().to_jsonable() + d.pop("workable") + d.pop("inferrable") + d.pop("possibly_empty") + d["c_list_index"] = self.c_list_index + d["point_index"] = self.index + d["direction"] = self.direction + return d + + @classmethod + def from_dict(cls, d: dict) -> "ParameterPlacementStrategy": + """Return a strategy from a dictionary.""" + return cls(**d) + + def __repr__(self) -> str: + return ( + f"{self.__class__.__name__}(" + + f"{self.c_list_index}, {self.index}, {self.direction}" + + f"ignore_parent={self.ignore_parent})" + ) + + def __str__(self): + return "Placed a parameter" + + +class ParamPlacementFactory(StrategyFactory[MappedTiling]): + """Tries to place the points of any size 1 containing parameter list""" + + def __call__( + self, comb_class: MappedTiling + ) -> Iterator[ParameterPlacementStrategy]: + for c_index, c_list in enumerate(comb_class.containing_parameters): + if len(c_list) != 1: + continue + param = tuple(c_list)[0] + points = tuple(param.point_cells()) + if not points: + continue + for i in range(len(points)): + for direction in Directions: + yield ParameterPlacementStrategy(c_index, i, direction) + + @classmethod + def from_dict(cls, d: dict) -> "ParamPlacementFactory": + return cls(**d) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}()" + + def __str__(self) -> str: + return "Parameter placement" + + +class ParameterInsertionStrategy(DisjointUnionStrategy): + """Straregy for inserting parameters""" + + cleaner = MTCleaner.make_full_cleaner("Param Insertion Cleaner") + + def __init__( + self, + params: ParameterList, + ignore_parent=False, + ): + self.params = params + super().__init__(ignore_parent) + + def decomposition_function(self, comb_class: MappedTiling): + avoiders, containers, enumerators = comb_class.ace_parameters() + base = comb_class.tiling + new_avoiders = ParameterList(avoiders | self.params) + new_containers = list(containers) + [self.params] + clean = self.__class__.cleaner + m1 = MappedTiling(base, new_avoiders, containers, enumerators) + m2 = MappedTiling(base, avoiders, new_containers, enumerators) + return (clean(m1), clean(m2)) + + def formal_step(self): + return "Contain or avoid parameters" + + def backward_map( + self, + comb_class: MappedTiling, + objs: tuple[Optional[GriddedCayleyPerm], ...], + children: Optional[tuple[MappedTiling, ...]] = None, + ) -> Iterator[GriddedCayleyPerm]: + raise NotImplementedError + + def forward_map( + self, + comb_class: MappedTiling, + obj: GriddedCayleyPerm, + children: Optional[tuple[MappedTiling, ...]] = None, + ) -> tuple[Optional[GriddedCayleyPerm], ...]: + raise NotImplementedError + + def to_jsonable(self) -> dict: + """Return a dictionary form of the strategy.""" + d: dict = super().to_jsonable() + d.pop("workable") + d.pop("inferrable") + d.pop("possibly_empty") + d["params"] = self.params + return d + + @classmethod + def from_dict(cls, d: dict) -> "ParameterInsertionStrategy": + """Return a strategy from a dictionary.""" + return cls(**d) + + +class ParameterInsertionFactory(StrategyFactory[MappedTiling]): + """Inserts special parameters to mapplings with 1x1 base tiling""" + + def __init__(self, special_params: Iterable[Parameter]): + assert all( + (len(param.image_cells()) == 1 for param in special_params) + ), "Param cells must all map to (0,0)" + self.special_params = special_params + super().__init__() + + def __call__(self, comb_class): + if comb_class.dimensions == (1, 1) and comb_class.active_cells: + for param in self.special_params: + yield ParameterInsertionStrategy(ParameterList({param}))(comb_class) + + @classmethod + def from_dict(cls, d: dict) -> "ParameterInsertionFactory": + return cls(**d) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}()" + + def __str__(self) -> str: + return "Special Param Insertion" + + +class AvoiderExorcismFactory(StrategyFactory[MappedTiling]): + """Transforms avoiders which map to a single cell into containers""" + + def __call__(self, comb_class): + avoiders, containers, enumerators = comb_class.ace_parameters() + for avoider in avoiders: + if ( + len(set(avoider.col_map.values())) + == len(set(avoider.row_map.values())) + == 1 + ): + new_avoiders = ParameterList( + {param for param in avoiders if param != avoider} + ) + new_mappling = MappedTiling( + comb_class.tiling, new_avoiders, containers, enumerators + ) + yield ParameterInsertionStrategy(ParameterList({avoider}))(new_mappling) + + @classmethod + def from_dict(cls, d: dict) -> "AvoiderExorcismFactory": + return cls(**d) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}()" + + def __str__(self) -> str: + return "Avoider exorcism" + + +class ContainerExorcismFactory(StrategyFactory[MappedTiling]): + """Transforms solo containers which map to a single cell into avoiders""" + + def __call__(self, comb_class): + avoiders, containers, enumerators = comb_class.ace_parameters() + for c_list in containers: + if len(c_list) > 1: + continue + for container in c_list: + if ( + len(container.col_map.values()) + == len(container.row_map.values()) + == 1 + ): + new_containers = ( + param_list for param_list in containers if param_list != c_list + ) + new_mappling = MappedTiling( + comb_class.tiling, avoiders, new_containers, enumerators + ) + yield ParameterInsertionStrategy(ParameterList({container}))( + new_mappling + ) + + @classmethod + def from_dict(cls, d: dict) -> "ContainerExorcismFactory": + return cls(**d) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}()" + + def __str__(self) -> str: + return "Container exorcism" + + class MapplingPointPlacementFactory(PointPlacementFactory): """ A factory for creating point placement strategies for mapped tilings. diff --git a/playground/param_placement_play.py b/playground/param_placement_play.py new file mode 100644 index 0000000..5c9eed5 --- /dev/null +++ b/playground/param_placement_play.py @@ -0,0 +1,46 @@ +from cayley_permutations import CayleyPermutation +from gridded_cayley_permutations import Tiling, GriddedCayleyPerm +from mapplings import MappedTiling, Parameter, ParameterList +from mapplings.algorithms import ParameterPlacement +from mapplings.cleaners import MTCleaner +from gridded_cayley_permutations.row_col_map import RowColMap +from mapplings.strategies.mapped_tilescope import MappedTileScopePack +from comb_spec_searcher import CombinatorialSpecificationSearcher +from comb_spec_searcher.rule_db import RuleDBForest + +MTCleaner.global_debug_toggle(0) +ghost = Tiling.from_vincular_with_obs(CayleyPermutation((0, 1)), []) +ghost = ghost.add_obstructions( + ( + GriddedCayleyPerm((0,), [(0, 0)]), + GriddedCayleyPerm((0,), [(0, 2)]), + GriddedCayleyPerm((0,), [(2, 0)]), + GriddedCayleyPerm((0,), [(2, 2)]), + GriddedCayleyPerm((0,), [(4, 4)]), + ) +) +P = Parameter( + ghost, RowColMap({0: 0, 1: 0, 2: 0, 3: 0, 4: 0}, {0: 0, 1: 0, 2: 0, 3: 0, 4: 0}) +) +B = Tiling( + [ + GriddedCayleyPerm((0, 0), [(0, 0), (0, 0)]), + GriddedCayleyPerm((0, 1, 2), [(0, 0), (0, 0), (0, 0)]), + ], + [], + (1, 1), +) +print(P) +ruledb = RuleDBForest() +c_list = ParameterList([P]) +mappling = MappedTiling(B, [], [], []) +mappling = MTCleaner.full_cleanup(mappling) +pack = MappedTileScopePack.pack_for_123(P) +searcher = CombinatorialSpecificationSearcher( + mappling, pack, debug=False, ruledb=ruledb +) + +spec = searcher.auto_search( + status_update=30, +) +spec.show()