From f5036d7b9c57b6e73582b3a51eebe35ccfa9ba33 Mon Sep 17 00:00:00 2001
From: ReedActon <110858904+ReedActon@users.noreply.github.com>
Date: Mon, 19 Jan 2026 21:21:56 +0000
Subject: [PATCH 01/24] Unplacement Changes
ParamCleaner unplacement now does partial unplacement
---
mapplings/cleaners/parameter_cleaner.py | 55 ++++++++++++-------------
1 file changed, 26 insertions(+), 29 deletions(-)
diff --git a/mapplings/cleaners/parameter_cleaner.py b/mapplings/cleaners/parameter_cleaner.py
index 18132a2..7f877bd 100644
--- a/mapplings/cleaners/parameter_cleaner.py
+++ b/mapplings/cleaners/parameter_cleaner.py
@@ -2,7 +2,7 @@
from typing import Iterable
from gridded_cayley_permutations.row_col_map import RowColMap
-from gridded_cayley_permutations.unplacement import PointUnplacement
+from gridded_cayley_permutations.unplacement import PartialUnplacement
from gridded_cayley_permutations import Tiling
from mapplings import Parameter
@@ -102,34 +102,31 @@ def check_for_blank(columns: Iterable[int], image: int, check_rows: bool):
@reg(2, run_on_enumerators=False)
def unplace_points(param: Parameter) -> Parameter:
"""Unplaces all possible points in the parameter"""
- found = True
- new_param = Parameter(param.ghost, param.map)
- while found:
- found = False
- for cell in new_param.point_cells():
- algo = PointUnplacement(new_param.ghost, cell)
- if not algo.cell_in_valid_region():
- continue
- if (
- not new_param.col_map[cell[0] - 1]
- == new_param.col_map[cell[0]]
- == new_param.col_map[cell[0] + 1]
- ):
- continue
- if (
- not new_param.row_map[cell[1] - 1]
- == new_param.row_map[cell[1]]
- == new_param.row_map[cell[1] + 1]
- ):
- continue
- check_reqs = algo.intersecting_req_list()
- if PointUnplacement(new_param.ghost, cell).point_can_be_unplaced(
- check_reqs
- ):
- new_param = new_param.unplace_point(cell)
- found = True
- break
- return new_param
+ algo = PartialUnplacement(param.ghost)
+ points = param.point_cells()
+ cells, cols, rows = set[tuple[int, int]](), set[int](), set[int]()
+ for cell in points:
+ valid = algo.cell_in_valid_region(cell)
+ if valid[0] and param.col_map[cell[0] - 1] == param.col_map[cell[0] + 1]:
+ cells.add(cell)
+ cols.add(cell[0])
+ if valid[1] and param.row_map[cell[1] - 1] == param.row_map[cell[1] + 1]:
+ cells.add(cell)
+ rows.add(cell[1])
+ unplace_cols, unplace_rows = algo.fusable_check(cells, cols, rows)
+ new_ghost = algo.unplace(unplace_cols, unplace_rows)
+ col_preimages, row_preimages = algo.adjustment_map(
+ unplace_cols, unplace_rows
+ ).preimage_map()
+ new_col_map = {
+ i: param.col_map[col_preimages[i][0]]
+ for i in range(new_ghost.dimensions[0])
+ }
+ new_row_map = {
+ i: param.row_map[row_preimages[i][0]]
+ for i in range(new_ghost.dimensions[1])
+ }
+ return Parameter(new_ghost, RowColMap(new_col_map, new_row_map))
# Internal Methods
From 0eedd42a10238f92a12f83217f47d00da1274e3c Mon Sep 17 00:00:00 2001
From: ReedActon <110858904+ReedActon@users.noreply.github.com>
Date: Thu, 22 Jan 2026 11:57:37 +0000
Subject: [PATCH 02/24] Fixes
Changed Param Counting. Added conditions to blank row/col removal. Made the ParamCleaner log work.
---
mapplings/cleaners/mappling_cleaner.py | 15 ++++-------
mapplings/cleaners/parameter_cleaner.py | 34 +++++++++++++++++++++++++
mapplings/parameter.py | 2 +-
3 files changed, 40 insertions(+), 11 deletions(-)
diff --git a/mapplings/cleaners/mappling_cleaner.py b/mapplings/cleaners/mappling_cleaner.py
index 6a5556a..88a01fd 100644
--- a/mapplings/cleaners/mappling_cleaner.py
+++ b/mapplings/cleaners/mappling_cleaner.py
@@ -50,13 +50,15 @@ def _clean_parameters(mappling: MappedTiling) -> MappedTiling:
if getattr(func, "run_on_avoiders"):
new_avoiders = ParameterList(
param.update_active_cells(mappling.tiling)
- for param in new_avoiders.apply_to_all(func)
+ for param in new_avoiders.apply_to_all(
+ param_cleaner.logger(func)
+ )
)
if getattr(func, "run_on_containers"):
new_containers = [
ParameterList(
param.update_active_cells(mappling.tiling)
- for param in c_list.apply_to_all(func)
+ for param in c_list.apply_to_all(param_cleaner.logger(func))
)
for c_list in new_containers
]
@@ -64,7 +66,7 @@ def _clean_parameters(mappling: MappedTiling) -> MappedTiling:
new_enumerators = [
ParameterList(
param.update_active_cells(mappling.tiling)
- for param in e_list.apply_to_all(func)
+ for param in e_list.apply_to_all(param_cleaner.logger(func))
)
for e_list in new_enumerators
]
@@ -277,16 +279,9 @@ def forward_map_parameter_gcps_from_avoiders(
avoiders, containers, enumerators = mappling.ace_parameters()
new_avoiders = []
for avoider in avoiders:
- empty_cells = (
- set(product(range(avoider.dimensions[0]), range(avoider.dimensions[1])))
- - avoider.active_cells
- )
injective_cells = avoider.active_cells & (
avoider.injective_cells() - avoider.point_cells()
)
- if injective_cells == avoider.active_cells and empty_cells:
- new_avoiders.append(avoider)
- continue
new_reqs = []
for req_list in avoider.requirements:
req_list_positions = set(
diff --git a/mapplings/cleaners/parameter_cleaner.py b/mapplings/cleaners/parameter_cleaner.py
index 7f877bd..d69324d 100644
--- a/mapplings/cleaners/parameter_cleaner.py
+++ b/mapplings/cleaners/parameter_cleaner.py
@@ -23,6 +23,7 @@ class ParamCleaner(GenericCleaner[Parameter]):
global_tracker = CleanerLog[Parameter](
reg.registered_functions, name="Global Tracker"
)
+
all_loggers = {global_tracker}
# Final Methods
@@ -57,6 +58,7 @@ def reduce_empty_rows_and_cols(param: Parameter) -> Parameter:
@reg(1, run_on_enumerators=False)
def remove_blank_rows_and_cols(param: Parameter) -> Parameter:
"""Deletes all rows and cols which have no obs or reqs"""
+
columns, rows = param.find_blank_columns_and_rows()
cols_to_remove, rows_to_remove = set(), set()
if param.positive_cells():
@@ -96,6 +98,36 @@ def check_for_blank(columns: Iterable[int], image: int, check_rows: bool):
or len(rows_to_remove) == param.dimensions[1]
):
return Parameter(Tiling([], [], (1, 1)), RowColMap({0: 0}, {0: 0}))
+
+ if param.point_cells():
+ cols_with_point, rows_with_point = map(set, zip(*param.point_cells()))
+ temp_cols, temp_rows = set(), set()
+ for col in cols_to_remove:
+ if col - 1 in cols_with_point:
+ if col + 1 in cols_to_remove:
+ temp_cols.add(col + 1)
+ else:
+ temp_cols.add(col)
+ cols_to_remove = set()
+ for col in temp_cols:
+ if col + 1 in cols_with_point:
+ if col - 1 in temp_cols:
+ cols_to_remove.add(col - 1)
+ else:
+ cols_to_remove.add(col)
+ for row in rows_to_remove:
+ if row - 1 in rows_with_point:
+ if row + 1 in rows_to_remove:
+ temp_rows.add(row + 1)
+ else:
+ temp_rows.add(row)
+ rows_to_remove = set()
+ for row in temp_rows:
+ if row + 1 in rows_with_point:
+ if row - 1 in temp_rows:
+ rows_to_remove.add(row - 1)
+ else:
+ rows_to_remove.add(row)
return param.delete_rows_and_columns(cols_to_remove, rows_to_remove)
@staticmethod
@@ -114,6 +146,8 @@ def unplace_points(param: Parameter) -> Parameter:
cells.add(cell)
rows.add(cell[1])
unplace_cols, unplace_rows = algo.fusable_check(cells, cols, rows)
+ if not (unplace_cols or unplace_rows):
+ return param
new_ghost = algo.unplace(unplace_cols, unplace_rows)
col_preimages, row_preimages = algo.adjustment_map(
unplace_cols, unplace_rows
diff --git a/mapplings/parameter.py b/mapplings/parameter.py
index eca77ab..008513b 100644
--- a/mapplings/parameter.py
+++ b/mapplings/parameter.py
@@ -59,7 +59,7 @@ def gcp_has_preimage(self, gcp: GriddedCayleyPerm) -> bool:
"""Determines if the sub-gridding of the gcp that lives in the image region
has a preimage on the ghost"""
sub_gridding = gcp.sub_gridded_cayley_perm(self.image_cells())
- if not sub_gridding.positions and not self.positive_cells():
+ if not sub_gridding.positions and not self.requirements:
return True
for preimage in self.map.preimage_of_gridded_cperm(sub_gridding):
if self.gcp_in_tiling(preimage):
From 6282067f0bd675026a22438089b413995acb6f8b Mon Sep 17 00:00:00 2001
From: ReedActon <110858904+ReedActon@users.noreply.github.com>
Date: Thu, 22 Jan 2026 15:27:50 +0000
Subject: [PATCH 03/24] Ugly Fixes
I don't like this code, but its for hunting down what's actually broken.
---
mapplings/cleaners/mappling_cleaner.py | 36 +++++++--
mapplings/cleaners/parameter_cleaner.py | 102 ++++++++++++++++--------
mapplings/parameter.py | 55 +++++++++++++
mapplings/parameter_list.py | 7 +-
4 files changed, 157 insertions(+), 43 deletions(-)
diff --git a/mapplings/cleaners/mappling_cleaner.py b/mapplings/cleaners/mappling_cleaner.py
index 88a01fd..db3448b 100644
--- a/mapplings/cleaners/mappling_cleaner.py
+++ b/mapplings/cleaners/mappling_cleaner.py
@@ -226,7 +226,7 @@ def remove_empty_rows_and_cols(mappling: MappedTiling) -> MappedTiling:
@reg(12)
def simple_reduce_redundant_parameters(mappling: MappedTiling) -> MappedTiling:
"""Removes any parameter implied by another with a basic check"""
- new_avoiders = mappling.avoiding_parameters.simple_remove_redundant()
+ new_avoiders = mappling.avoiding_parameters.simple_remove_redundant(True)
new_containers = [
c_list.simple_remove_redundant()
for c_list in mappling.containing_parameters
@@ -279,10 +279,20 @@ def forward_map_parameter_gcps_from_avoiders(
avoiders, containers, enumerators = mappling.ace_parameters()
new_avoiders = []
for avoider in avoiders:
+ if not (avoider.obstructions or avoider.requirements):
+ new_avoiders.append(avoider)
+ continue
+ empty_cells = (
+ set(product(range(avoider.dimensions[0]), range(avoider.dimensions[1])))
+ - avoider.active_cells
+ )
injective_cells = avoider.active_cells & (
avoider.injective_cells() - avoider.point_cells()
)
- new_reqs = []
+ if injective_cells == avoider.active_cells and empty_cells:
+ new_avoiders.append(avoider)
+ continue
+ new_reqs, add_reqs = [], set[GriddedCayleyPerm]()
for req_list in avoider.requirements:
req_list_positions = set(
chain.from_iterable((req.positions for req in req_list))
@@ -290,10 +300,15 @@ def forward_map_parameter_gcps_from_avoiders(
if not req_list_positions.issubset(injective_cells):
new_reqs.append(req_list)
continue
- new_base = new_base.add_obstructions(
- avoider.map.map_gridded_cperms(req_list)
- )
- new_obs = avoider.obstructions
+ add_reqs.update(set(req_list))
+ new_base = new_base.add_obstructions(
+ avoider.map.map_gridded_cperms(add_reqs)
+ )
+ new_obs = {
+ ob
+ for ob in avoider.obstructions
+ if not any((ob.contains_gridded_cperm(req) for req in add_reqs))
+ }
if new_obs or new_reqs:
new_avoiders.append(
Parameter(
@@ -418,15 +433,22 @@ def _reduce_parameter_gcps(mappling: MappedTiling, param: Parameter) -> Paramete
simplify.remove_redundant_obstructions()
new_obs = []
for ob in simplify.obstructions:
+ if ob.pattern in (CayleyPermutation([0, 1]), CayleyPermutation([1, 0])):
+ if all(
+ position in param.single_value_cells() for position in ob.positions
+ ):
+ new_obs.append(ob)
+ continue
if len(set(ob.positions)) == 1:
cell = ob.positions[0]
if (
- cell in point_cells
+ cell in param.single_value_cells()
and (param.col_map[cell[0]], param.row_map[cell[1]])
not in mappling_point_cells
):
new_obs.append(ob)
continue
+
if any(
param.map.map_gridded_cperm(ob).contains_gridded_cperm(mt_ob)
for mt_ob in mappling.obstructions
diff --git a/mapplings/cleaners/parameter_cleaner.py b/mapplings/cleaners/parameter_cleaner.py
index d69324d..db15376 100644
--- a/mapplings/cleaners/parameter_cleaner.py
+++ b/mapplings/cleaners/parameter_cleaner.py
@@ -1,6 +1,7 @@
"""Module with the parameter cleaner"""
from typing import Iterable
+from itertools import chain
from gridded_cayley_permutations.row_col_map import RowColMap
from gridded_cayley_permutations.unplacement import PartialUnplacement
from gridded_cayley_permutations import Tiling
@@ -61,24 +62,26 @@ def remove_blank_rows_and_cols(param: Parameter) -> Parameter:
columns, rows = param.find_blank_columns_and_rows()
cols_to_remove, rows_to_remove = set(), set()
- if param.positive_cells():
- positive_cols, positive_rows = map(set, zip(*param.positive_cells()))
+ if param.requirements:
+ req_cols, req_rows = zip(
+ *chain(*(set(req.positions) for req in chain(*param.requirements)))
+ )
else:
- positive_cols, positive_rows = set(), set()
+ req_cols, req_rows = tuple(), tuple()
def check_for_blank(columns: Iterable[int], image: int, check_rows: bool):
for col in columns:
if check_rows:
if col in rows_to_remove:
break
- if param.row_map[col] == image and col not in positive_rows:
+ if param.row_map[col] == image and col not in req_rows:
rows_to_remove.add(col)
else:
break
else:
if col in cols_to_remove:
break
- if param.col_map[col] == image and col not in positive_cols:
+ if param.col_map[col] == image and col not in req_cols:
cols_to_remove.add(column)
else:
break
@@ -99,35 +102,66 @@ def check_for_blank(columns: Iterable[int], image: int, check_rows: bool):
):
return Parameter(Tiling([], [], (1, 1)), RowColMap({0: 0}, {0: 0}))
- if param.point_cells():
- cols_with_point, rows_with_point = map(set, zip(*param.point_cells()))
- temp_cols, temp_rows = set(), set()
- for col in cols_to_remove:
- if col - 1 in cols_with_point:
- if col + 1 in cols_to_remove:
- temp_cols.add(col + 1)
- else:
- temp_cols.add(col)
- cols_to_remove = set()
- for col in temp_cols:
- if col + 1 in cols_with_point:
- if col - 1 in temp_cols:
- cols_to_remove.add(col - 1)
- else:
- cols_to_remove.add(col)
- for row in rows_to_remove:
- if row - 1 in rows_with_point:
- if row + 1 in rows_to_remove:
- temp_rows.add(row + 1)
- else:
- temp_rows.add(row)
- rows_to_remove = set()
- for row in temp_rows:
- if row + 1 in rows_with_point:
- if row - 1 in temp_rows:
- rows_to_remove.add(row - 1)
- else:
- rows_to_remove.add(row)
+ single_pos, single_val = (
+ param.single_position_cells(),
+ param.single_value_cells(),
+ )
+ cols_with_point, rows_with_point = set[int](), set[int]()
+ if single_pos:
+ cols_with_point, _ = zip(*single_pos)
+ if single_val:
+ _, rows_with_point = zip(*single_val)
+ temp_cols, temp_rows = set(), set()
+ for col in cols_to_remove:
+ if (
+ col - 1 in cols_with_point
+ and param.col_map[col] == param.col_map[col - 1]
+ ):
+ if (
+ col + 1 in cols_to_remove
+ and param.col_map[col] == param.col_map[col + 1]
+ ):
+ temp_cols.add(col + 1)
+ else:
+ temp_cols.add(col)
+ cols_to_remove = set()
+ for col in temp_cols:
+ if (
+ col + 1 in cols_with_point
+ and param.col_map[col] == param.col_map[col + 1]
+ ):
+ if (
+ col - 1 in temp_cols
+ and param.col_map[col] == param.col_map[col - 1]
+ ):
+ cols_to_remove.add(col - 1)
+ else:
+ cols_to_remove.add(col)
+ for row in rows_to_remove:
+ if (
+ row - 1 in rows_with_point
+ and param.row_map[row] == param.row_map[row - 1]
+ ):
+ if (
+ row + 1 in rows_to_remove
+ and param.row_map[row] == param.row_map[row + 1]
+ ):
+ temp_rows.add(row + 1)
+ else:
+ temp_rows.add(row)
+ rows_to_remove = set()
+ for row in temp_rows:
+ if (
+ row + 1 in rows_with_point
+ and param.row_map[row] == param.row_map[row + 1]
+ ):
+ if (
+ row - 1 in temp_rows
+ and param.row_map[row] == param.row_map[row - 1]
+ ):
+ rows_to_remove.add(row - 1)
+ else:
+ rows_to_remove.add(row)
return param.delete_rows_and_columns(cols_to_remove, rows_to_remove)
@staticmethod
diff --git a/mapplings/parameter.py b/mapplings/parameter.py
index 008513b..3d7c467 100644
--- a/mapplings/parameter.py
+++ b/mapplings/parameter.py
@@ -12,6 +12,7 @@
from gridded_cayley_permutations.factors import Factors
Cell = tuple[int, int]
+Objects = defaultdict[tuple[int, ...], list[GriddedCayleyPerm]]
class Parameter(Tiling):
@@ -49,6 +50,29 @@ def injective_cells(self) -> set[Cell]:
)
return set(product(inj_cols, inj_rows))
+ def single_value_cells(self) -> set[Cell]:
+ """Returns the set of cells with at most one value"""
+ cells = set[Cell]()
+ for cell in self.active_cells:
+ if (
+ GriddedCayleyPerm((0, 1), (cell, cell)) in self.obstructions
+ and GriddedCayleyPerm((1, 0), (cell, cell)) in self.obstructions
+ ):
+ cells.add(cell)
+ return cells
+
+ def single_position_cells(self) -> set[Cell]:
+ """Returns the set of cells with at most one position"""
+ cells = set[Cell]()
+ for cell in self.active_cells:
+ if (
+ GriddedCayleyPerm((0, 1), (cell, cell)) in self.obstructions
+ and GriddedCayleyPerm((1, 0), (cell, cell)) in self.obstructions
+ and GriddedCayleyPerm((0, 0), (cell, cell)) in self.obstructions
+ ):
+ cells.add(cell)
+ return cells
+
def preimage_of_gcp(self, gcperm: GriddedCayleyPerm) -> Iterator[GriddedCayleyPerm]:
"""Returns the preimage of a gridded cayley permutation"""
for gcp in self.map.preimage_of_gridded_cperm(gcperm):
@@ -365,6 +389,37 @@ def to_html_representation(self) -> str:
return "".join(result)
+ def objects_of_size(self, n, **parameters) -> Iterator[GriddedCayleyPerm]:
+ """Return gridded Cayley permutations of size n in the tiling."""
+ for val in self.get_objects(n).values():
+ yield from val
+
+ def get_objects(self, n: int) -> Objects:
+ """Return the objects of size n in the tiling."""
+ objects = defaultdict(list)
+ col_map = {
+ val: key for key, val in enumerate(sorted(set(self.col_map.values())))
+ }
+ row_map = {
+ val: key for key, val in enumerate(sorted(set(self.row_map.values())))
+ }
+ map_reduction = RowColMap(
+ {key: col_map[val] for key, val in self.col_map.items()},
+ {key: row_map[val] for key, val in self.row_map.items()},
+ )
+ temp = Parameter(self.ghost, map_reduction)
+ base = Tiling([], [], (len(col_map), len(row_map)))
+ for gcp in base.objects_of_size(n):
+ if temp.gcp_has_preimage(gcp):
+ param = self.get_parameters(gcp)
+ objects[param].append(gcp)
+ return objects
+
+ def get_parameters(self, obj: GriddedCayleyPerm) -> tuple[int, ...]:
+ """Parameters are not what you think!!! This is specific to
+ combinatorical class parameters"""
+ return (1,)
+
# dunder methods
def to_jsonable(self) -> dict:
diff --git a/mapplings/parameter_list.py b/mapplings/parameter_list.py
index 4037c92..b3f1095 100644
--- a/mapplings/parameter_list.py
+++ b/mapplings/parameter_list.py
@@ -69,7 +69,7 @@ def remove_empty(self) -> "ParameterList":
"""Removes parameters with empty ghost"""
return ParameterList(param for param in self if not param.is_empty())
- def simple_remove_redundant(self) -> "ParameterList":
+ def simple_remove_redundant(self, reverse: bool = False) -> "ParameterList":
"""Removes any parameter implied by another through a basic check"""
exclude = set[Parameter]()
for param0, param1 in combinations(self, 2):
@@ -82,7 +82,10 @@ def simple_remove_redundant(self) -> "ParameterList":
if param0.map != temp_param.map:
continue
if param0.ghost.is_subset(temp_param.ghost):
- exclude.add(param1)
+ if reverse:
+ exclude.add(param0)
+ else:
+ exclude.add(param1)
return ParameterList(param for param in self if param not in exclude)
def to_html(self) -> str:
From 2e70e07623e8cc828a38daee8e66d56a697f5a26 Mon Sep 17 00:00:00 2001
From: ReedActon <110858904+ReedActon@users.noreply.github.com>
Date: Mon, 26 Jan 2026 11:53:04 +0000
Subject: [PATCH 04/24] Changes From Meeting
I left the motzkin playground changes because I thought they would be helpful.
---
mapplings/cleaners/mappling_cleaner.py | 18 ++++++-
mapplings/mapped_tiling.py | 12 +++--
mapplings/parameter.py | 4 +-
mapplings/parameter_list.py | 4 +-
playground/cleaner_motzkin.py | 72 +++++++++++++++++++++++++-
5 files changed, 99 insertions(+), 11 deletions(-)
diff --git a/mapplings/cleaners/mappling_cleaner.py b/mapplings/cleaners/mappling_cleaner.py
index db3448b..1888a77 100644
--- a/mapplings/cleaners/mappling_cleaner.py
+++ b/mapplings/cleaners/mappling_cleaner.py
@@ -4,7 +4,7 @@
from itertools import chain, product
from functools import partial
-from gridded_cayley_permutations import GriddedCayleyPerm, Tiling
+from gridded_cayley_permutations import GriddedCayleyPerm, Tiling, RowColMap
from gridded_cayley_permutations.simplify_obstructions_and_requirements import (
SimplifyObstructionsAndRequirements,
)
@@ -462,7 +462,21 @@ def _reduce_parameter_gcps(mappling: MappedTiling, param: Parameter) -> Paramete
req_list, simplify.requirements
)
]
- new_ghost = Tiling(new_obs, new_reqs, param.dimensions, simplify=False)
+ final_reqs = []
+ for req_list in new_reqs:
+ new_req_list = [
+ req
+ for req in req_list
+ if all(
+ not param.map.map_gridded_cperm(req).contains_gridded_cperm(ob)
+ for ob in mappling.obstructions
+ )
+ ]
+ if new_req_list:
+ final_reqs.append(new_req_list)
+ else:
+ return Parameter(Tiling.empty_tiling(), RowColMap({}, {}))
+ new_ghost = Tiling(new_obs, final_reqs, param.dimensions, simplify=False)
return Parameter(new_ghost, param.map)
@staticmethod
diff --git a/mapplings/mapped_tiling.py b/mapplings/mapped_tiling.py
index b65bb39..2a6fc93 100644
--- a/mapplings/mapped_tiling.py
+++ b/mapplings/mapped_tiling.py
@@ -267,13 +267,19 @@ def __str__(self) -> str:
"Base tiling: \n"
+ str(self.tiling)
+ "\nAvoiding parameters:\n"
- + "\n".join([str(p) for p in self.avoiding_parameters])
+ + "\n".join([str(p) for p in sorted(self.avoiding_parameters)])
+ "\nContaining parameters:\n"
+ "\nNew containing parameters list \n".join(
- ["\n".join([str(p) for p in ps]) for ps in self.containing_parameters]
+ [
+ "\n".join([str(p) for p in sorted(ps)])
+ for ps in self.containing_parameters
+ ]
)
+ "\nEnumerating parameters:\n"
+ "\nNew enumerating parameters list\n".join(
- ["\n".join([str(p) for p in ps]) for ps in self.enumerating_parameters]
+ [
+ "\n".join([str(p) for p in sorted(ps)])
+ for ps in self.enumerating_parameters
+ ]
)
)
diff --git a/mapplings/parameter.py b/mapplings/parameter.py
index 3d7c467..02ab439 100644
--- a/mapplings/parameter.py
+++ b/mapplings/parameter.py
@@ -458,12 +458,12 @@ def __hash__(self) -> int:
def __leq__(self, other: object) -> bool:
if not isinstance(other, Parameter):
return NotImplemented
- return (self.ghost, self.map) <= (other.ghost, other.map)
+ return (self.map, self.ghost) <= (other.map, other.ghost)
def __lt__(self, other: object) -> bool:
if not isinstance(other, Parameter):
return NotImplemented
- return (self.ghost, self.map) < (other.ghost, other.map)
+ return (self.map, self.ghost) < (other.map, other.ghost)
def _string_table(self) -> list[str]:
"""Creates a list of strings for each row of the __str__ grid"""
diff --git a/mapplings/parameter_list.py b/mapplings/parameter_list.py
index b3f1095..61770be 100644
--- a/mapplings/parameter_list.py
+++ b/mapplings/parameter_list.py
@@ -19,7 +19,7 @@
FuncTypeT = TypeVar("FuncTypeT")
ArgsType = TypeVarTuple("ArgsType")
-OPEN_DISPLAY = "" # Change to "open" for fully expanded html trees
+OPEN_DISPLAY = "open" # Change to "open" for fully expanded html trees
class ParameterList(frozenset[Parameter]):
@@ -90,7 +90,7 @@ def simple_remove_redundant(self, reverse: bool = False) -> "ParameterList":
def to_html(self) -> str:
"""Returns a html of all parameters in self seperated by a line"""
- return "
".join((param.to_html_representation() for param in self))
+ return "
".join((param.to_html_representation() for param in sorted(self)))
def html_dropdown(self, label: str, border_color: str = "grey") -> str:
"""Makes a cute html dropdown for the parameter list"""
diff --git a/playground/cleaner_motzkin.py b/playground/cleaner_motzkin.py
index 64409b1..a72bb85 100644
--- a/playground/cleaner_motzkin.py
+++ b/playground/cleaner_motzkin.py
@@ -1,11 +1,21 @@
from cayley_permutations import CayleyPermutation
from gridded_cayley_permutations import Tiling, GriddedCayleyPerm
-from mapplings import MappedTiling, Parameter
-from mapplings.cleaners import MTCleaner
+from mapplings import MappedTiling, Parameter, ParameterList
+from mapplings.cleaners import MTCleaner, ParamCleaner
from gridded_cayley_permutations.row_col_map import RowColMap
from mapplings.strategies import MappedTileScopePack
from comb_spec_searcher import CombinatorialSpecificationSearcher
+from mapplings.strategies.tilescope_strategies import (
+ MapplingCellInsertionFactory,
+ MapplingPointPlacementFactory,
+ CleaningStrategy,
+ MapplingFactorStrategy,
+ MapplingLessThanOrEqualRowColSeparationStrategy,
+ MapplingLessThanRowColSeparationStrategy,
+)
+
+cleaner = MTCleaner.make_full_cleaner()
til = MappedTiling.from_vincular_with_obs(CayleyPermutation([0, 1, 2]), [])
ghost = til.delete_rows([4])
@@ -30,4 +40,62 @@
searcher = CombinatorialSpecificationSearcher(mappling, pack, debug=False)
spec = searcher.auto_search(status_update=10)
+print(spec.count_objects_of_size(5))
spec.show()
+
+M0 = cleaner(mappling)
+print(M0)
+
+print("------------------ Cell Insertion ------------------")
+rule0 = list(MapplingCellInsertionFactory()(M0))[0](M0)
+print(rule0.sanity_check(5))
+M1 = rule0.children[1]
+print(M1)
+
+print("------------------ Placed Rightmost ------------------")
+rule1 = list(MapplingPointPlacementFactory()(M1))[0](M1)
+print(rule1.sanity_check(5))
+M2 = rule1.children[1]
+M3 = cleaner(M2)
+print(M3)
+
+
+print("------------------ Factored ------------------")
+
+rule2 = MapplingFactorStrategy()(M3)
+print(rule2.sanity_check(5))
+M4 = cleaner(rule2.children[0])
+
+print(M4)
+print("------------------ RC Seperation ------------------")
+rule3 = MapplingLessThanRowColSeparationStrategy()(M4)
+print(rule3.sanity_check(5))
+M5 = rule3.children[0]
+
+M6 = cleaner(M5)
+print(M6)
+print("------------------ Factored ------------------")
+rule4 = MapplingFactorStrategy()(M6)
+print(rule4.sanity_check(5))
+M7 = rule4.children[1]
+print(M7)
+M7 = cleaner(M7)
+print("------------------ Cell Insertion ------------------")
+rule5 = list(MapplingCellInsertionFactory()(M7))[0](M7)
+print(rule5.sanity_check(5))
+M8 = rule5.children[1]
+print(M8)
+
+print("------------------ Placed Top-Rightmost ------------------")
+rule6 = list(MapplingPointPlacementFactory()(M8))[1](M8)
+print(rule6.sanity_check(5))
+M9 = rule6.children[1]
+M10 = cleaner(M9)
+print(cleaner(M10))
+print("------------------ Factored ------------------")
+rule7 = MapplingFactorStrategy()(M10)
+print(rule7.sanity_check(5))
+M11 = rule7.children[1]
+M12 = cleaner(M11)
+print(M12)
+print(M12 == M0)
From 25b643b7e49c0edd9adc5a768aeef8c6e43b5ae3 Mon Sep 17 00:00:00 2001
From: ReedActon <110858904+ReedActon@users.noreply.github.com>
Date: Mon, 26 Jan 2026 13:58:35 +0000
Subject: [PATCH 05/24] More Fixes
Small ob and reduce param gcps
---
mapplings/cleaners/mappling_cleaner.py | 55 +++++++++++++++++++++++---
1 file changed, 50 insertions(+), 5 deletions(-)
diff --git a/mapplings/cleaners/mappling_cleaner.py b/mapplings/cleaners/mappling_cleaner.py
index 1888a77..186711e 100644
--- a/mapplings/cleaners/mappling_cleaner.py
+++ b/mapplings/cleaners/mappling_cleaner.py
@@ -243,7 +243,11 @@ def simple_reduce_redundant_parameters(mappling: MappedTiling) -> MappedTiling:
def reduce_all_parameter_gcps(mappling: MappedTiling) -> MappedTiling:
"""Removes all obs and reqs that are implied by the base tiling from all Parameters"""
param_reducer = partial(MTCleaner._reduce_parameter_gcps, mappling)
- return mappling.apply_to_all_parameters(param_reducer)
+ avoiders, containers, enumerators = mappling.apply_to_all_parameters(
+ param_reducer
+ ).ace_parameters()
+ new_avoiders = ParameterList(av for av in avoiders if av.dimensions != (0, 0))
+ return MappedTiling(mappling.tiling, new_avoiders, containers, enumerators)
@staticmethod
@reg(11)
@@ -260,12 +264,50 @@ def small_ob_inferral(mappling: MappedTiling) -> MappedTiling:
for ob in small_obs:
if ob.pattern == CayleyPermutation((0, 0)):
- new_mappling = new_mappling.apply_to_all_parameters(
- MTCleaner._cayley_ob_adjust_param, (ob,)
+ new_avoiders = tuple(
+ avoider
+ for avoider in new_mappling.avoiding_parameters.apply_to_all(
+ MTCleaner._cayley_ob_adjust_param, (ob,)
+ )
+ if avoider.dimensions != (0, 0)
+ )
+ new_containers = tuple(
+ ParameterList(
+ c_list.apply_to_all(MTCleaner._cayley_ob_adjust_param, (ob,))
+ )
+ for c_list in mappling.containing_parameters
+ )
+ new_enumerators = tuple(
+ ParameterList(
+ e_list.apply_to_all(MTCleaner._cayley_ob_adjust_param, (ob,))
+ )
+ for e_list in mappling.enumerating_parameters
+ )
+ new_mappling = MappedTiling(
+ mappling.tiling, new_avoiders, new_containers, new_enumerators
)
else:
- new_mappling = new_mappling.apply_to_all_parameters(
- MTCleaner._ob_adjust_param, (ob,)
+ new_avoiders = tuple(
+ avoider
+ for avoider in new_mappling.avoiding_parameters.apply_to_all(
+ MTCleaner._ob_adjust_param, (ob,)
+ )
+ if avoider.dimensions != (0, 0)
+ )
+ new_containers = tuple(
+ ParameterList(
+ c_list.apply_to_all(MTCleaner._ob_adjust_param, (ob,))
+ )
+ for c_list in mappling.containing_parameters
+ )
+ new_enumerators = tuple(
+ ParameterList(
+ e_list.apply_to_all(MTCleaner._ob_adjust_param, (ob,))
+ )
+ for e_list in mappling.enumerating_parameters
+ )
+ new_mappling = MappedTiling(
+ mappling.tiling, new_avoiders, new_containers, new_enumerators
)
return new_mappling
@@ -527,6 +569,9 @@ def _ob_adjust_param(param: Parameter, input_ob: GriddedCayleyPerm) -> Parameter
continue
if (cell[1] < point[1]) == increasing:
add_obs.append(GriddedCayleyPerm((0,), [cell]))
+ for req_list in param.requirements:
+ if all(req.contains(add_obs) for req in req_list):
+ return Parameter(Tiling.empty_tiling(), RowColMap({}, {}))
return Parameter(new_ghost.add_obstructions(add_obs), param.map)
@staticmethod
From 700ada12528c57effb4a14e1844494d93bda285f Mon Sep 17 00:00:00 2001
From: Abigail Ollson
Date: Tue, 27 Jan 2026 08:31:49 +0000
Subject: [PATCH 06/24] hare file
---
playground/hare_2_stack_sortable.py | 55 +++++++++++++++++++++++++++++
1 file changed, 55 insertions(+)
create mode 100644 playground/hare_2_stack_sortable.py
diff --git a/playground/hare_2_stack_sortable.py b/playground/hare_2_stack_sortable.py
new file mode 100644
index 0000000..fdbef08
--- /dev/null
+++ b/playground/hare_2_stack_sortable.py
@@ -0,0 +1,55 @@
+from cayley_permutations import CayleyPermutation
+from gridded_cayley_permutations import Tiling, GriddedCayleyPerm
+from mapplings import MappedTiling, Parameter
+from gridded_cayley_permutations.row_col_map import RowColMap
+from mapplings.strategies import MappedTileScopePack
+from comb_spec_searcher import CombinatorialSpecificationSearcher
+import json
+from mapplings.cleaners import MTCleaner, ParamCleaner
+
+# MTCleaner.global_debug_toggle(2)
+# ParamCleaner.global_debug_toggle(2)
+
+til = MappedTiling.from_vincular_with_obs(CayleyPermutation([2, 1, 3, 0]), [])
+ghost = til.add_obstructions([GriddedCayleyPerm(CayleyPermutation([0]), [(2, 8)])])
+avoiding_parameters = [
+ Parameter(ghost, RowColMap({i: 0 for i in range(9)}, {i: 0 for i in range(9)}))
+]
+mappling = MappedTiling(
+ Tiling(
+ [
+ GriddedCayleyPerm(
+ CayleyPermutation([1, 2, 3, 0]), ((0, 0), (0, 0), (0, 0), (0, 0))
+ ),
+ ],
+ [],
+ (1, 1),
+ ),
+ avoiding_parameters,
+ [],
+ [],
+)
+pack = MappedTileScopePack.point_placement(mappling)
+searcher = CombinatorialSpecificationSearcher(mappling, pack, debug=False)
+
+spec = searcher.auto_search(status_update=30)
+spec.show()
+
+
+json_dict = spec.to_jsonable()
+json_str = json.dumps(json_dict)
+with open("hare_2_stack_sortable.json", "w") as f:
+ f.write(json_str)
+
+new_spec = spec.expand_verified()
+new_spec.show()
+
+json_dict = new_spec.to_jsonable()
+json_str = json.dumps(json_dict)
+with open("hare_2_stack_sortable_expanded.json", "w") as f:
+ f.write(json_str)
+
+new_spec.get_genf()
+print([new_spec.count_objects_of_size(i) for i in range(10)])
+
+spec.sanity_check()
From 9f9a5b178a13b0f97c80375f0893cd1db03da6e9 Mon Sep 17 00:00:00 2001
From: ReedActon <110858904+ReedActon@users.noreply.github.com>
Date: Thu, 29 Jan 2026 14:21:29 +0000
Subject: [PATCH 07/24] Fix Remove Blank
One down, so many to go.
---
mapplings/cleaners/parameter_cleaner.py | 137 +++++++-----------------
mapplings/parameter.py | 19 ++++
2 files changed, 58 insertions(+), 98 deletions(-)
diff --git a/mapplings/cleaners/parameter_cleaner.py b/mapplings/cleaners/parameter_cleaner.py
index db15376..1e0cecc 100644
--- a/mapplings/cleaners/parameter_cleaner.py
+++ b/mapplings/cleaners/parameter_cleaner.py
@@ -1,6 +1,6 @@
"""Module with the parameter cleaner"""
-from typing import Iterable
+from typing import Iterator
from itertools import chain
from gridded_cayley_permutations.row_col_map import RowColMap
from gridded_cayley_permutations.unplacement import PartialUnplacement
@@ -60,108 +60,49 @@ def reduce_empty_rows_and_cols(param: Parameter) -> Parameter:
def remove_blank_rows_and_cols(param: Parameter) -> Parameter:
"""Deletes all rows and cols which have no obs or reqs"""
- columns, rows = param.find_blank_columns_and_rows()
- cols_to_remove, rows_to_remove = set(), set()
- if param.requirements:
- req_cols, req_rows = zip(
- *chain(*(set(req.positions) for req in chain(*param.requirements)))
- )
- else:
- req_cols, req_rows = tuple(), tuple()
-
- def check_for_blank(columns: Iterable[int], image: int, check_rows: bool):
- for col in columns:
- if check_rows:
- if col in rows_to_remove:
- break
- if param.row_map[col] == image and col not in req_rows:
- rows_to_remove.add(col)
- else:
- break
- else:
- if col in cols_to_remove:
- break
- if param.col_map[col] == image and col not in req_cols:
- cols_to_remove.add(column)
- else:
- break
-
- for column in columns:
- image_col = param.map.col_map[column]
- cols_to_remove.add(column)
- check_for_blank(range(column - 1, -1, -1), image_col, False)
- check_for_blank(range(column + 1, param.dimensions[0]), image_col, False)
- for blank_row in rows:
- image_row = param.row_map[blank_row]
- rows_to_remove.add(blank_row)
- check_for_blank(range(blank_row - 1, -1, -1), image_row, True)
- check_for_blank(range(blank_row + 1, param.dimensions[1]), image_row, True)
+ blank = tuple(map(set, param.blank_and_near_blank()))
+ req_cols, req_rows = map(
+ set[int],
+ zip(*chain(*(set(req.positions) for req in chain(*param.requirements)))),
+ )
+ col_preimages, row_preimages = param.map.preimage_map()
+ try:
+ cols_with_point, _ = map(set[int], zip(*param.single_position_cells()))
+ except ValueError:
+ cols_with_point = set[int]()
+ try:
+ _, rows_with_point = map(set[int], zip(*param.single_value_cells()))
+ except ValueError:
+ rows_with_point = set[int]()
+
+ splits = cols_with_point | req_cols, rows_with_point | req_rows
+
+ def to_remove(
+ preimages: dict[int, tuple[int, ...]], find_rows: bool
+ ) -> Iterator[set[int]]:
+ for preimage in sorted(preimages.values()):
+ if not set(preimage) & blank[find_rows]:
+ continue
+ slice_start = 0
+ for i, check in enumerate(preimage):
+ if check in splits[find_rows]:
+ section = set(preimage[slice_start:i])
+ if len(section) > 1:
+ try:
+ yield section - {tuple(section & blank[find_rows])[0]}
+ except IndexError:
+ pass
+ slice_start = i + 1
+ if slice_start == 0:
+ yield set(preimage)
+
+ cols_to_remove = set(chain(*to_remove(col_preimages, False)))
+ rows_to_remove = set(chain(*to_remove(row_preimages, True)))
if (
len(cols_to_remove) == param.dimensions[0]
or len(rows_to_remove) == param.dimensions[1]
):
return Parameter(Tiling([], [], (1, 1)), RowColMap({0: 0}, {0: 0}))
-
- single_pos, single_val = (
- param.single_position_cells(),
- param.single_value_cells(),
- )
- cols_with_point, rows_with_point = set[int](), set[int]()
- if single_pos:
- cols_with_point, _ = zip(*single_pos)
- if single_val:
- _, rows_with_point = zip(*single_val)
- temp_cols, temp_rows = set(), set()
- for col in cols_to_remove:
- if (
- col - 1 in cols_with_point
- and param.col_map[col] == param.col_map[col - 1]
- ):
- if (
- col + 1 in cols_to_remove
- and param.col_map[col] == param.col_map[col + 1]
- ):
- temp_cols.add(col + 1)
- else:
- temp_cols.add(col)
- cols_to_remove = set()
- for col in temp_cols:
- if (
- col + 1 in cols_with_point
- and param.col_map[col] == param.col_map[col + 1]
- ):
- if (
- col - 1 in temp_cols
- and param.col_map[col] == param.col_map[col - 1]
- ):
- cols_to_remove.add(col - 1)
- else:
- cols_to_remove.add(col)
- for row in rows_to_remove:
- if (
- row - 1 in rows_with_point
- and param.row_map[row] == param.row_map[row - 1]
- ):
- if (
- row + 1 in rows_to_remove
- and param.row_map[row] == param.row_map[row + 1]
- ):
- temp_rows.add(row + 1)
- else:
- temp_rows.add(row)
- rows_to_remove = set()
- for row in temp_rows:
- if (
- row + 1 in rows_with_point
- and param.row_map[row] == param.row_map[row + 1]
- ):
- if (
- row - 1 in temp_rows
- and param.row_map[row] == param.row_map[row - 1]
- ):
- rows_to_remove.add(row - 1)
- else:
- rows_to_remove.add(row)
return param.delete_rows_and_columns(cols_to_remove, rows_to_remove)
@staticmethod
diff --git a/mapplings/parameter.py b/mapplings/parameter.py
index 02ab439..7fd9ec8 100644
--- a/mapplings/parameter.py
+++ b/mapplings/parameter.py
@@ -142,6 +142,25 @@ def update_active_cells(self, tiling: Tiling) -> "Parameter":
temp.active_cells = self.active_cells
return temp
+ def blank_and_near_blank(self) -> tuple[tuple[int, ...], tuple[int, ...]]:
+ if self.dimensions == (0, 0):
+ return tuple(), tuple()
+ if not self.obstructions and not self.requirements:
+ return tuple(range(self.dimensions[0])), tuple(range(self.dimensions[1]))
+ req_cells = tuple(
+ chain(*(set(req.positions) for req in chain(*self.requirements)))
+ )
+ check_cells = (
+ cell
+ for cell in self.not_blank_cells()
+ if cell in req_cells
+ or (cell[0] not in self.point_cols and cell[1] not in self.point_rows)
+ )
+ not_blank_cols, not_blank_rows = zip(*check_cells)
+ blank_cols = tuple(set(range(self.dimensions[0])) - set(not_blank_cols))
+ blank_rows = tuple(set(range(self.dimensions[1])) - set(not_blank_rows))
+ return blank_cols, blank_rows
+
def find_blank_columns_and_rows_in_param(
self, tiling: Tiling
) -> tuple[list[int], list[int]]:
From 3b8df1b367d4bfb4dacaaa6ecba3b5efca154590 Mon Sep 17 00:00:00 2001
From: ReedActon <110858904+ReedActon@users.noreply.github.com>
Date: Thu, 29 Jan 2026 16:32:31 +0000
Subject: [PATCH 08/24] Blank Row/Col Fixes
Fixes mentioned in discord
---
mapplings/cleaners/parameter_cleaner.py | 32 +++++++++++++++----------
1 file changed, 20 insertions(+), 12 deletions(-)
diff --git a/mapplings/cleaners/parameter_cleaner.py b/mapplings/cleaners/parameter_cleaner.py
index 1e0cecc..bd11288 100644
--- a/mapplings/cleaners/parameter_cleaner.py
+++ b/mapplings/cleaners/parameter_cleaner.py
@@ -60,20 +60,29 @@ def reduce_empty_rows_and_cols(param: Parameter) -> Parameter:
def remove_blank_rows_and_cols(param: Parameter) -> Parameter:
"""Deletes all rows and cols which have no obs or reqs"""
- blank = tuple(map(set, param.blank_and_near_blank()))
- req_cols, req_rows = map(
- set[int],
- zip(*chain(*(set(req.positions) for req in chain(*param.requirements)))),
- )
+ blank = tuple(map(set[int], param.blank_and_near_blank()))
+ if not any(blank):
+ return param
+
col_preimages, row_preimages = param.map.preimage_map()
+
+ req_cols, req_rows = set[int](), set[int]()
+ if param.requirements:
+ req_cols, req_rows = map(
+ set[int],
+ zip(
+ *chain(*(set(req.positions) for req in chain(*param.requirements)))
+ ),
+ )
+ cols_with_point, rows_with_point = set[int](), set[int]()
try:
cols_with_point, _ = map(set[int], zip(*param.single_position_cells()))
except ValueError:
- cols_with_point = set[int]()
+ pass
try:
_, rows_with_point = map(set[int], zip(*param.single_value_cells()))
except ValueError:
- rows_with_point = set[int]()
+ pass
splits = cols_with_point | req_cols, rows_with_point | req_rows
@@ -87,12 +96,11 @@ def to_remove(
for i, check in enumerate(preimage):
if check in splits[find_rows]:
section = set(preimage[slice_start:i])
- if len(section) > 1:
- try:
- yield section - {tuple(section & blank[find_rows])[0]}
- except IndexError:
- pass
slice_start = i + 1
+ try:
+ yield section - {tuple(section & blank[find_rows])[0]}
+ except ValueError:
+ pass
if slice_start == 0:
yield set(preimage)
From ba14bd40922697fee438db8359ca1f009aac0142 Mon Sep 17 00:00:00 2001
From: ReedActon <110858904+ReedActon@users.noreply.github.com>
Date: Fri, 30 Jan 2026 15:45:17 +0000
Subject: [PATCH 09/24] Param Comparison
---
mapplings/parameter.py | 38 +++++++++++++++++++++++++++++++++++++-
1 file changed, 37 insertions(+), 1 deletion(-)
diff --git a/mapplings/parameter.py b/mapplings/parameter.py
index 7fd9ec8..07fd7e2 100644
--- a/mapplings/parameter.py
+++ b/mapplings/parameter.py
@@ -2,7 +2,7 @@
from collections import defaultdict
from copy import copy
-from typing import Iterator, Iterable
+from typing import Iterator, Iterable, Optional
from itertools import product, chain
from cayley_permutations import CayleyPermutation
@@ -408,6 +408,42 @@ def to_html_representation(self) -> str:
return "".join(result)
+ def compare_to(
+ self, other: "Parameter", depth: int = 4
+ ) -> tuple[bool, Optional[GriddedCayleyPerm]]:
+ """Compares the gcps that live on self to the gcps on other up to size depth"""
+
+ col_map = {
+ val: key
+ for key, val in enumerate(
+ sorted(set(self.col_map.values()) | set(other.col_map.values()))
+ )
+ }
+ row_map = {
+ val: key
+ for key, val in enumerate(
+ sorted(set(self.row_map.values()) | set(other.row_map.values()))
+ )
+ }
+ self_reduction = RowColMap(
+ {key: col_map[val] for key, val in self.col_map.items()},
+ {key: row_map[val] for key, val in self.row_map.items()},
+ )
+ other_reduction = RowColMap(
+ {key: col_map[val] for key, val in other.col_map.items()},
+ {key: row_map[val] for key, val in other.row_map.items()},
+ )
+ temp_self = Parameter(self.ghost, self_reduction)
+ temp_other = Parameter(other.ghost, other_reduction)
+ base = Tiling([], [], (len(col_map), len(row_map)))
+ i = 0
+ while i < depth:
+ for gcp in base.objects_of_size(i):
+ if temp_self.gcp_has_preimage(gcp) != temp_other.gcp_has_preimage(gcp):
+ return False, gcp
+ i += 1
+ return True, None
+
def objects_of_size(self, n, **parameters) -> Iterator[GriddedCayleyPerm]:
"""Return gridded Cayley permutations of size n in the tiling."""
for val in self.get_objects(n).values():
From e0790254686b2d8a14b3b1594a209308100e8c4f Mon Sep 17 00:00:00 2001
From: ReedActon <110858904+ReedActon@users.noreply.github.com>
Date: Fri, 30 Jan 2026 15:49:08 +0000
Subject: [PATCH 10/24] Different Error
---
mapplings/cleaners/parameter_cleaner.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/mapplings/cleaners/parameter_cleaner.py b/mapplings/cleaners/parameter_cleaner.py
index bd11288..2b0483b 100644
--- a/mapplings/cleaners/parameter_cleaner.py
+++ b/mapplings/cleaners/parameter_cleaner.py
@@ -99,7 +99,7 @@ def to_remove(
slice_start = i + 1
try:
yield section - {tuple(section & blank[find_rows])[0]}
- except ValueError:
+ except IndexError:
pass
if slice_start == 0:
yield set(preimage)
From a370b9f25eb4c16d3ed378536bbfd94ac2816b2a Mon Sep 17 00:00:00 2001
From: ReedActon <110858904+ReedActon@users.noreply.github.com>
Date: Fri, 30 Jan 2026 16:27:53 +0000
Subject: [PATCH 11/24] No ParamCleaner Debug
---
tests/cleaner/mappling_cleaner/test_insert_containers.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/cleaner/mappling_cleaner/test_insert_containers.py b/tests/cleaner/mappling_cleaner/test_insert_containers.py
index 76c3e6a..8a9c19b 100644
--- a/tests/cleaner/mappling_cleaner/test_insert_containers.py
+++ b/tests/cleaner/mappling_cleaner/test_insert_containers.py
@@ -11,7 +11,7 @@
from mapplings.cleaners import MTCleaner, ParamCleaner
MTCleaner.DEBUG = 2
-ParamCleaner.DEBUG = 2
+ParamCleaner.DEBUG = 0
@pytest.mark.skip(reason="strategy not working yet")
From e9d779762981092b9d4f664dc813fe91ee947e78 Mon Sep 17 00:00:00 2001
From: ReedActon <110858904+ReedActon@users.noreply.github.com>
Date: Fri, 30 Jan 2026 16:32:19 +0000
Subject: [PATCH 12/24] tox
---
mapplings/cleaners/mappling_cleaner.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/mapplings/cleaners/mappling_cleaner.py b/mapplings/cleaners/mappling_cleaner.py
index 186711e..0effd4c 100644
--- a/mapplings/cleaners/mappling_cleaner.py
+++ b/mapplings/cleaners/mappling_cleaner.py
@@ -469,7 +469,6 @@ def _reduce_parameter_gcps(mappling: MappedTiling, param: Parameter) -> Paramete
param.map.preimage_of_requirements(mappling.requirements),
mappling.dimensions,
)
- point_cells = param.point_cells()
mappling_point_cells = mappling.point_cells()
simplify.remove_factors_from_obstructions()
simplify.remove_redundant_obstructions()
From e47e57e2881b420e3d7b939314824b041b175e53 Mon Sep 17 00:00:00 2001
From: ReedActon <110858904+ReedActon@users.noreply.github.com>
Date: Fri, 30 Jan 2026 16:38:13 +0000
Subject: [PATCH 13/24] Docstring
---
mapplings/parameter.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/mapplings/parameter.py b/mapplings/parameter.py
index 07fd7e2..74f13fe 100644
--- a/mapplings/parameter.py
+++ b/mapplings/parameter.py
@@ -143,6 +143,7 @@ def update_active_cells(self, tiling: Tiling) -> "Parameter":
return temp
def blank_and_near_blank(self) -> tuple[tuple[int, ...], tuple[int, ...]]:
+ """Finds blank rows and cols allowing point row/col intersection"""
if self.dimensions == (0, 0):
return tuple(), tuple()
if not self.obstructions and not self.requirements:
From 1b62521c3ba9ba290769c1f5e413a3bda3af2b9d Mon Sep 17 00:00:00 2001
From: Abigail Ollson
Date: Tue, 3 Feb 2026 10:32:17 +0000
Subject: [PATCH 14/24] table 1
---
playground/table 1.py | 303 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 303 insertions(+)
create mode 100644 playground/table 1.py
diff --git a/playground/table 1.py b/playground/table 1.py
new file mode 100644
index 0000000..2c9ffe4
--- /dev/null
+++ b/playground/table 1.py
@@ -0,0 +1,303 @@
+from cayley_permutations import CayleyPermutation
+from gridded_cayley_permutations import Tiling, GriddedCayleyPerm
+from mapplings import MappedTiling, Parameter, ParameterList
+from gridded_cayley_permutations.row_col_map import RowColMap
+from mapplings.strategies.mapped_tilescope import MappedTileScopePack
+from comb_spec_searcher import CombinatorialSpecificationSearcher
+import json
+from mapplings.cleaners import MTCleaner, ParamCleaner
+
+MTCleaner.global_debug_toggle(2)
+
+run_time = 3600 * 5
+debug = True
+
+from_table = []
+"""Row 1"""
+## Basis 1 ##
+til = MappedTiling.from_vincular_with_obs(CayleyPermutation([0, 1, 2]), [])
+ghost = til.delete_columns([4])
+
+til2 = MappedTiling.from_vincular_with_obs(CayleyPermutation([2, 1, 0]), [])
+ghost2 = til2.delete_columns([2])
+
+avoiding_parameters = [
+ Parameter(ghost, RowColMap({i: 0 for i in range(6)}, {i: 0 for i in range(7)})),
+ Parameter(ghost2, RowColMap({i: 0 for i in range(6)}, {i: 0 for i in range(7)})),
+]
+mappling = MappedTiling(
+ Tiling(
+ [GriddedCayleyPerm(CayleyPermutation([0, 0]), [(0, 0), (0, 0)])],
+ [],
+ (1, 1),
+ ),
+ avoiding_parameters,
+ [],
+ [],
+)
+
+try:
+ pack = MappedTileScopePack.point_placement(mappling)
+ searcher = CombinatorialSpecificationSearcher(mappling, pack, debug=debug)
+ spec = searcher.auto_search(status_update=30, max_expansion_time=run_time)
+ spec.show()
+ json_dict = spec.to_jsonable()
+ json_str = json.dumps(json_dict)
+ with open("table_1_mappling_1.json", "w") as f:
+ f.write(json_str)
+ spec.get_genf()
+ from_table.append(mappling)
+except Exception as e:
+ print(f"Failed row 1 basis 1: {e}")
+
+## Basis 2 ##
+
+til3 = MappedTiling.from_vincular_with_obs(CayleyPermutation([2, 1, 0]), [])
+ghost3 = til3.delete_columns([4])
+
+til4 = MappedTiling.from_vincular_with_obs(CayleyPermutation([0, 1, 2]), [])
+ghost4 = til4.delete_columns([2])
+
+avoiding_parameters = [
+ Parameter(ghost3, RowColMap({i: 0 for i in range(6)}, {i: 0 for i in range(7)})),
+ Parameter(ghost4, RowColMap({i: 0 for i in range(6)}, {i: 0 for i in range(7)})),
+]
+mappling = MappedTiling(
+ Tiling(
+ [GriddedCayleyPerm(CayleyPermutation([0, 0]), [(0, 0), (0, 0)])],
+ [],
+ (1, 1),
+ ),
+ avoiding_parameters,
+ [],
+ [],
+)
+
+try:
+ pack = MappedTileScopePack.point_placement(mappling)
+ searcher = CombinatorialSpecificationSearcher(mappling, pack, debug=debug)
+ spec = searcher.auto_search(status_update=30, max_expansion_time=run_time)
+ spec.show()
+ json_dict = spec.to_jsonable()
+ json_str = json.dumps(json_dict)
+ with open("table_1_mappling_2.json", "w") as f:
+ f.write(json_str)
+ spec.get_genf()
+ from_table.append(mappling)
+except Exception as e:
+ print(f"Failed row 1 basis 2: {e}")
+
+"""Row 2"""
+## Basis 1 ##
+til = MappedTiling.from_vincular_with_obs(CayleyPermutation([0, 1, 2]), [])
+ghost = til.delete_columns([4])
+
+til2 = MappedTiling.from_vincular_with_obs(CayleyPermutation([2, 1, 0]), [])
+ghost2 = til2.delete_columns([4])
+
+avoiding_parameters = [
+ Parameter(ghost, RowColMap({i: 0 for i in range(6)}, {i: 0 for i in range(7)})),
+ Parameter(ghost2, RowColMap({i: 0 for i in range(6)}, {i: 0 for i in range(7)})),
+]
+mappling = MappedTiling(
+ Tiling(
+ [GriddedCayleyPerm(CayleyPermutation([0, 0]), [(0, 0), (0, 0)])],
+ [],
+ (1, 1),
+ ),
+ avoiding_parameters,
+ [],
+ [],
+)
+try:
+ pack = MappedTileScopePack.point_placement(mappling)
+ searcher = CombinatorialSpecificationSearcher(mappling, pack, debug=debug)
+ spec = searcher.auto_search(status_update=30, max_expansion_time=run_time)
+ spec.show()
+ json_dict = spec.to_jsonable()
+ json_str = json.dumps(json_dict)
+ with open("table_1_mappling_3.json", "w") as f:
+ f.write(json_str)
+ spec.get_genf()
+ from_table.append(mappling)
+except Exception as e:
+ print(f"Failed row 2 basis 1: {e}")
+
+## Basis 2 ##
+
+til3 = MappedTiling.from_vincular_with_obs(CayleyPermutation([2, 1, 0]), [])
+ghost3 = til3.delete_columns([2])
+
+til4 = MappedTiling.from_vincular_with_obs(CayleyPermutation([0, 1, 2]), [])
+ghost4 = til4.delete_columns([2])
+
+avoiding_parameters = [
+ Parameter(ghost3, RowColMap({i: 0 for i in range(6)}, {i: 0 for i in range(7)})),
+ Parameter(ghost4, RowColMap({i: 0 for i in range(6)}, {i: 0 for i in range(7)})),
+]
+mappling = MappedTiling(
+ Tiling(
+ [GriddedCayleyPerm(CayleyPermutation([0, 0]), [(0, 0), (0, 0)])],
+ [],
+ (1, 1),
+ ),
+ avoiding_parameters,
+ [],
+ [],
+)
+try:
+ pack = MappedTileScopePack.point_placement(mappling)
+ searcher = CombinatorialSpecificationSearcher(mappling, pack, debug=debug)
+ spec = searcher.auto_search(status_update=30, max_expansion_time=run_time)
+ spec.show()
+ json_dict = spec.to_jsonable()
+ json_str = json.dumps(json_dict)
+ with open("table_1_mappling_4.json", "w") as f:
+ f.write(json_str)
+ spec.get_genf()
+except Exception as e:
+ print(f"Failed row 2 basis 2: {e}")
+from_table.append(mappling)
+
+"""Row 3"""
+## Basis 1 ##
+til = MappedTiling.from_vincular_with_obs(CayleyPermutation([0, 1, 2]), [])
+ghost = til.delete_columns([4])
+
+til2 = MappedTiling.from_vincular_with_obs(CayleyPermutation([1, 2, 0]), [])
+ghost2 = til2.delete_columns([4])
+
+avoiding_parameters = [
+ Parameter(ghost, RowColMap({i: 0 for i in range(6)}, {i: 0 for i in range(7)})),
+ Parameter(ghost2, RowColMap({i: 0 for i in range(6)}, {i: 0 for i in range(7)})),
+]
+mappling = MappedTiling(
+ Tiling(
+ [GriddedCayleyPerm(CayleyPermutation([0, 0]), [(0, 0), (0, 0)])],
+ [],
+ (1, 1),
+ ),
+ avoiding_parameters,
+ [],
+ [],
+)
+try:
+ pack = MappedTileScopePack.point_placement(mappling)
+ searcher = CombinatorialSpecificationSearcher(mappling, pack, debug=debug)
+ spec = searcher.auto_search(status_update=30, max_expansion_time=run_time)
+ spec.show()
+ json_dict = spec.to_jsonable()
+ json_str = json.dumps(json_dict)
+ with open("table_1_mappling_5.json", "w") as f:
+ f.write(json_str)
+ spec.get_genf()
+ from_table.append(mappling)
+except Exception as e:
+ print(f"Failed row 3 basis 1: {e}")
+
+## Basis 2 ##
+
+til3 = MappedTiling.from_vincular_with_obs(CayleyPermutation([2, 1, 0]), [])
+ghost3 = til2.delete_columns([4])
+
+til4 = MappedTiling.from_vincular_with_obs(CayleyPermutation([1, 0, 2]), [])
+ghost4 = til2.delete_columns([4])
+
+avoiding_parameters = [
+ Parameter(ghost3, RowColMap({i: 0 for i in range(6)}, {i: 0 for i in range(7)})),
+ Parameter(ghost4, RowColMap({i: 0 for i in range(6)}, {i: 0 for i in range(7)})),
+]
+mappling = MappedTiling(
+ Tiling(
+ [GriddedCayleyPerm(CayleyPermutation([0, 0]), [(0, 0), (0, 0)])],
+ [],
+ (1, 1),
+ ),
+ avoiding_parameters,
+ [],
+ [],
+)
+try:
+ pack = MappedTileScopePack.point_placement(mappling)
+ searcher = CombinatorialSpecificationSearcher(mappling, pack, debug=debug)
+ spec = searcher.auto_search(status_update=30, max_expansion_time=run_time)
+ spec.show()
+ json_dict = spec.to_jsonable()
+ json_str = json.dumps(json_dict)
+ with open("table_1_mappling_6.json", "w") as f:
+ f.write(json_str)
+ spec.get_genf()
+ from_table.append(mappling)
+except Exception as e:
+ print(f"Failed row 3 basis 2: {e}")
+
+## Basis 3 ##
+til = MappedTiling.from_vincular_with_obs(CayleyPermutation([0, 1, 2]), [])
+ghost = til.delete_columns([2])
+
+til2 = MappedTiling.from_vincular_with_obs(CayleyPermutation([2, 0, 1]), [])
+ghost2 = til2.delete_columns([2])
+
+avoiding_parameters = [
+ Parameter(ghost, RowColMap({i: 0 for i in range(6)}, {i: 0 for i in range(7)})),
+ Parameter(ghost2, RowColMap({i: 0 for i in range(6)}, {i: 0 for i in range(7)})),
+]
+mappling = MappedTiling(
+ Tiling(
+ [GriddedCayleyPerm(CayleyPermutation([0, 0]), [(0, 0), (0, 0)])],
+ [],
+ (1, 1),
+ ),
+ avoiding_parameters,
+ [],
+ [],
+)
+try:
+ pack = MappedTileScopePack.point_placement(mappling)
+ searcher = CombinatorialSpecificationSearcher(mappling, pack, debug=debug)
+ spec = searcher.auto_search(status_update=30, max_expansion_time=run_time)
+ spec.show()
+ json_dict = spec.to_jsonable()
+ json_str = json.dumps(json_dict)
+ with open("table_1_mappling_7.json", "w") as f:
+ f.write(json_str)
+ spec.get_genf()
+ from_table.append(mappling)
+except Exception as e:
+ print(f"Failed row 3 basis 3: {e}")
+
+## Basis 4 ##
+
+til3 = MappedTiling.from_vincular_with_obs(CayleyPermutation([2, 1, 0]), [])
+ghost3 = til3.delete_columns([2])
+
+til4 = MappedTiling.from_vincular_with_obs(CayleyPermutation([0, 2, 1]), [])
+ghost4 = til4.delete_columns([2])
+
+avoiding_parameters = [
+ Parameter(ghost3, RowColMap({i: 0 for i in range(6)}, {i: 0 for i in range(7)})),
+ Parameter(ghost4, RowColMap({i: 0 for i in range(6)}, {i: 0 for i in range(7)})),
+]
+mappling = MappedTiling(
+ Tiling(
+ [GriddedCayleyPerm(CayleyPermutation([0, 0]), [(0, 0), (0, 0)])],
+ [],
+ (1, 1),
+ ),
+ avoiding_parameters,
+ [],
+ [],
+)
+try:
+ pack = MappedTileScopePack.point_placement(mappling)
+ searcher = CombinatorialSpecificationSearcher(mappling, pack, debug=debug)
+ spec = searcher.auto_search(status_update=30, max_expansion_time=run_time)
+ spec.show()
+ json_dict = spec.to_jsonable()
+ json_str = json.dumps(json_dict)
+ with open("table_1_mappling_8.json", "w") as f:
+ f.write(json_str)
+ spec.get_genf()
+ from_table.append(mappling)
+except Exception as e:
+ print(f"Failed row 3 basis 4: {e}")
From 6387e477f9b68d82d9c0a982aa5b3353ed2dca4a Mon Sep 17 00:00:00 2001
From: ReedActon <110858904+ReedActon@users.noreply.github.com>
Date: Tue, 3 Feb 2026 13:08:52 +0000
Subject: [PATCH 15/24] Small Clean Up
added requirement_cells function and adjusted remove_blank.
---
mapplings/cleaners/parameter_cleaner.py | 25 ++++++-------------------
mapplings/parameter.py | 6 ++++++
2 files changed, 12 insertions(+), 19 deletions(-)
diff --git a/mapplings/cleaners/parameter_cleaner.py b/mapplings/cleaners/parameter_cleaner.py
index 2b0483b..72f1dcb 100644
--- a/mapplings/cleaners/parameter_cleaner.py
+++ b/mapplings/cleaners/parameter_cleaner.py
@@ -66,25 +66,10 @@ def remove_blank_rows_and_cols(param: Parameter) -> Parameter:
col_preimages, row_preimages = param.map.preimage_map()
- req_cols, req_rows = set[int](), set[int]()
- if param.requirements:
- req_cols, req_rows = map(
- set[int],
- zip(
- *chain(*(set(req.positions) for req in chain(*param.requirements)))
- ),
- )
- cols_with_point, rows_with_point = set[int](), set[int]()
try:
- cols_with_point, _ = map(set[int], zip(*param.single_position_cells()))
+ splits = tuple(map(set[int], zip(*param.requirement_cells())))
except ValueError:
- pass
- try:
- _, rows_with_point = map(set[int], zip(*param.single_value_cells()))
- except ValueError:
- pass
-
- splits = cols_with_point | req_cols, rows_with_point | req_rows
+ splits = set[int](), set[int]()
def to_remove(
preimages: dict[int, tuple[int, ...]], find_rows: bool
@@ -92,6 +77,9 @@ def to_remove(
for preimage in sorted(preimages.values()):
if not set(preimage) & blank[find_rows]:
continue
+ if not splits[find_rows]:
+ yield set(preimage)
+ continue
slice_start = 0
for i, check in enumerate(preimage):
if check in splits[find_rows]:
@@ -110,7 +98,7 @@ def to_remove(
len(cols_to_remove) == param.dimensions[0]
or len(rows_to_remove) == param.dimensions[1]
):
- return Parameter(Tiling([], [], (1, 1)), RowColMap({0: 0}, {0: 0}))
+ return Parameter(Tiling([], [], (0, 0)), RowColMap({}, {}))
return param.delete_rows_and_columns(cols_to_remove, rows_to_remove)
@staticmethod
@@ -163,7 +151,6 @@ def _fuse_valid_rows_or_cols(param: Parameter, fuse_rows: bool) -> Parameter:
new_ghost = new_ghost.delete_columns([new_idx])
del new_maps[fuse_rows][old_idx + extend]
extend += 1
- continue
old_idx += extend
new_idx += 1
extend = 1
diff --git a/mapplings/parameter.py b/mapplings/parameter.py
index 74f13fe..bc9ed85 100644
--- a/mapplings/parameter.py
+++ b/mapplings/parameter.py
@@ -142,6 +142,12 @@ def update_active_cells(self, tiling: Tiling) -> "Parameter":
temp.active_cells = self.active_cells
return temp
+ def requirement_cells(self) -> set[Cell]:
+ """Returns every cell that contains a requirement"""
+ if not self.requirements:
+ return set[Cell]()
+ return set(chain(*(set(req.positions) for req in chain(*self.requirements))))
+
def blank_and_near_blank(self) -> tuple[tuple[int, ...], tuple[int, ...]]:
"""Finds blank rows and cols allowing point row/col intersection"""
if self.dimensions == (0, 0):
From 64420916b0acc519ae4b65a0e7e43577d2607ada Mon Sep 17 00:00:00 2001
From: ReedActon <110858904+ReedActon@users.noreply.github.com>
Date: Tue, 3 Feb 2026 13:25:33 +0000
Subject: [PATCH 16/24] Oops
typing error
---
mapplings/cleaners/parameter_cleaner.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/mapplings/cleaners/parameter_cleaner.py b/mapplings/cleaners/parameter_cleaner.py
index 72f1dcb..91f7b4e 100644
--- a/mapplings/cleaners/parameter_cleaner.py
+++ b/mapplings/cleaners/parameter_cleaner.py
@@ -67,7 +67,8 @@ def remove_blank_rows_and_cols(param: Parameter) -> Parameter:
col_preimages, row_preimages = param.map.preimage_map()
try:
- splits = tuple(map(set[int], zip(*param.requirement_cells())))
+ req_cols, req_rows = map(set[int], zip(*param.requirement_cells()))
+ splits = req_cols, req_rows
except ValueError:
splits = set[int](), set[int]()
From 81014bffdc8a6a94d789287089238a33bd856a1b Mon Sep 17 00:00:00 2001
From: ReedActon <110858904+ReedActon@users.noreply.github.com>
Date: Thu, 5 Feb 2026 12:05:42 +0000
Subject: [PATCH 17/24] Test Fix
---
mapplings/cleaners/parameter_cleaner.py | 1 +
tests/cleaner/param_cleaner/test_remove_blank_rowcols.py | 4 +---
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/mapplings/cleaners/parameter_cleaner.py b/mapplings/cleaners/parameter_cleaner.py
index 91f7b4e..44a7dd2 100644
--- a/mapplings/cleaners/parameter_cleaner.py
+++ b/mapplings/cleaners/parameter_cleaner.py
@@ -152,6 +152,7 @@ def _fuse_valid_rows_or_cols(param: Parameter, fuse_rows: bool) -> Parameter:
new_ghost = new_ghost.delete_columns([new_idx])
del new_maps[fuse_rows][old_idx + extend]
extend += 1
+ continue
old_idx += extend
new_idx += 1
extend = 1
diff --git a/tests/cleaner/param_cleaner/test_remove_blank_rowcols.py b/tests/cleaner/param_cleaner/test_remove_blank_rowcols.py
index 7ae9ef2..58d6409 100644
--- a/tests/cleaner/param_cleaner/test_remove_blank_rowcols.py
+++ b/tests/cleaner/param_cleaner/test_remove_blank_rowcols.py
@@ -32,9 +32,7 @@ def test_remove_blank_rowcols():
ParameterList(frozenset()),
(
ParameterList(
- frozenset(
- {Parameter(Tiling((), (), (1, 1)), RowColMap({0: 0}, {0: 0}))}
- )
+ frozenset({Parameter(Tiling([], [], (0, 0)), RowColMap({}, {}))})
),
),
(),
From efb2f5e65f2f2ed8f98d8dd2c5fa25f949df8ba8 Mon Sep 17 00:00:00 2001
From: ReedActon <110858904+ReedActon@users.noreply.github.com>
Date: Sun, 8 Feb 2026 12:15:41 +0000
Subject: [PATCH 18/24] Ghost Fusion
---
mapplings/cleaners/parameter_cleaner.py | 79 ++++++++++++++++---------
1 file changed, 51 insertions(+), 28 deletions(-)
diff --git a/mapplings/cleaners/parameter_cleaner.py b/mapplings/cleaners/parameter_cleaner.py
index 44a7dd2..996d544 100644
--- a/mapplings/cleaners/parameter_cleaner.py
+++ b/mapplings/cleaners/parameter_cleaner.py
@@ -1,6 +1,6 @@
"""Module with the parameter cleaner"""
-from typing import Iterator
+from typing import Iterator, Iterable
from itertools import chain
from gridded_cayley_permutations.row_col_map import RowColMap
from gridded_cayley_permutations.unplacement import PartialUnplacement
@@ -26,15 +26,29 @@ class ParamCleaner(GenericCleaner[Parameter]):
)
all_loggers = {global_tracker}
+
# Final Methods
@staticmethod
@reg(3, run_on_enumerators=False)
def reduce_by_fusion(param: Parameter) -> Parameter:
"""Fuses valid rows and columns"""
- return ParamCleaner._fuse_valid_rows_or_cols(
- ParamCleaner._fuse_valid_rows_or_cols(param, True), False
+ deleted_cols, deleted_rows = ParamCleaner._find_indixes_to_fuse(
+ param, False
+ ), ParamCleaner._find_indixes_to_fuse(param, True)
+ temp = Parameter(
+ Tiling(param.obstructions, [], param.dimensions, False), param.map
+ ).delete_rows_and_columns(deleted_cols, deleted_rows)
+ new_ghost = Tiling(
+ temp.obstructions,
+ [
+ ParamCleaner._make_adjustment_map(
+ param, deleted_cols, deleted_rows
+ ).map_gridded_cperms(param.minimal_gridded_cperms())
+ ],
+ temp.dimensions,
)
+ return Parameter(new_ghost, temp.map)
@staticmethod
@reg(0)
@@ -137,28 +151,37 @@ def unplace_points(param: Parameter) -> Parameter:
# Internal Methods
@staticmethod
- def _fuse_valid_rows_or_cols(param: Parameter, fuse_rows: bool) -> Parameter:
- """fully fuses rows or cols of the parameter if they are fusable and map to the same index.
- direction = 0 for cols, directions = 1 for rows"""
- new_ghost = param.ghost
- new_maps = [param.col_map, param.row_map]
- old_idx, new_idx, extend = 0, 0, 1
- while old_idx + extend < param.dimensions[fuse_rows]:
- if new_maps[fuse_rows][old_idx] == new_maps[fuse_rows][old_idx + extend]:
- if new_ghost.is_fusable(fuse_rows, new_idx):
- if fuse_rows:
- new_ghost = new_ghost.delete_rows([new_idx])
- else:
- new_ghost = new_ghost.delete_columns([new_idx])
- del new_maps[fuse_rows][old_idx + extend]
- extend += 1
- continue
- old_idx += extend
- new_idx += 1
- extend = 1
- new_direction_map = {
- idx: new_maps[fuse_rows][value]
- for idx, value in enumerate(new_maps[fuse_rows].keys())
- }
- new_maps[fuse_rows] = new_direction_map
- return Parameter(new_ghost, RowColMap(*new_maps))
+ def _find_indixes_to_fuse(param: Parameter, fuse_rows: bool) -> Iterator[int]:
+ """Yields all indices that can be fused"""
+ maps = param.col_map, param.row_map
+ temp = Parameter(
+ Tiling(param.obstructions, [], param.dimensions, False), param.map
+ )
+ for i in range(param.dimensions[fuse_rows] - 1):
+ if maps[fuse_rows][i] == maps[fuse_rows][i + 1]:
+ if temp.is_fusable(fuse_rows, i):
+ yield i
+
+ @staticmethod
+ def _make_adjustment_map(
+ original_param: Parameter,
+ deleted_cols: Iterable[int],
+ deleted_rows: Iterable[int],
+ ) -> RowColMap:
+ """Makes a map from original param to that param after cols and rows are deleted"""
+ col_correction, row_correction = dict[int, int](), dict[int, int]()
+ adjust = 0
+ for i in range(original_param.dimensions[0]):
+ if i in deleted_cols:
+ col_correction[i] = col_correction[i - 1]
+ adjust += 1
+ else:
+ col_correction[i] = i - adjust
+ adjust = 0
+ for i in range(original_param.dimensions[1]):
+ if i in deleted_rows:
+ row_correction[i] = row_correction[i - 1]
+ adjust += 1
+ else:
+ row_correction[i] = i - adjust
+ return RowColMap(col_correction, row_correction)
From b2789d9be6ca330a5e36666966a1305fab1af3ca Mon Sep 17 00:00:00 2001
From: ReedActon <110858904+ReedActon@users.noreply.github.com>
Date: Sun, 8 Feb 2026 12:23:03 +0000
Subject: [PATCH 19/24] Test Fix
---
mapplings/cleaners/mappling_cleaner.py | 12 ++++++------
mapplings/cleaners/parameter_cleaner.py | 2 ++
2 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/mapplings/cleaners/mappling_cleaner.py b/mapplings/cleaners/mappling_cleaner.py
index 0effd4c..a255303 100644
--- a/mapplings/cleaners/mappling_cleaner.py
+++ b/mapplings/cleaners/mappling_cleaner.py
@@ -275,16 +275,16 @@ def small_ob_inferral(mappling: MappedTiling) -> MappedTiling:
ParameterList(
c_list.apply_to_all(MTCleaner._cayley_ob_adjust_param, (ob,))
)
- for c_list in mappling.containing_parameters
+ for c_list in new_mappling.containing_parameters
)
new_enumerators = tuple(
ParameterList(
e_list.apply_to_all(MTCleaner._cayley_ob_adjust_param, (ob,))
)
- for e_list in mappling.enumerating_parameters
+ for e_list in new_mappling.enumerating_parameters
)
new_mappling = MappedTiling(
- mappling.tiling, new_avoiders, new_containers, new_enumerators
+ new_mappling.tiling, new_avoiders, new_containers, new_enumerators
)
else:
new_avoiders = tuple(
@@ -298,16 +298,16 @@ def small_ob_inferral(mappling: MappedTiling) -> MappedTiling:
ParameterList(
c_list.apply_to_all(MTCleaner._ob_adjust_param, (ob,))
)
- for c_list in mappling.containing_parameters
+ for c_list in new_mappling.containing_parameters
)
new_enumerators = tuple(
ParameterList(
e_list.apply_to_all(MTCleaner._ob_adjust_param, (ob,))
)
- for e_list in mappling.enumerating_parameters
+ for e_list in new_mappling.enumerating_parameters
)
new_mappling = MappedTiling(
- mappling.tiling, new_avoiders, new_containers, new_enumerators
+ new_mappling.tiling, new_avoiders, new_containers, new_enumerators
)
return new_mappling
diff --git a/mapplings/cleaners/parameter_cleaner.py b/mapplings/cleaners/parameter_cleaner.py
index 996d544..fcf912a 100644
--- a/mapplings/cleaners/parameter_cleaner.py
+++ b/mapplings/cleaners/parameter_cleaner.py
@@ -39,6 +39,8 @@ def reduce_by_fusion(param: Parameter) -> Parameter:
temp = Parameter(
Tiling(param.obstructions, [], param.dimensions, False), param.map
).delete_rows_and_columns(deleted_cols, deleted_rows)
+ if not param.requirements:
+ return temp
new_ghost = Tiling(
temp.obstructions,
[
From 80f620aed2b7f1748f45004a8d817e75cf097fcd Mon Sep 17 00:00:00 2001
From: ReedActon <110858904+ReedActon@users.noreply.github.com>
Date: Mon, 9 Feb 2026 09:38:54 +0000
Subject: [PATCH 20/24] Redundant Parameters
A rewrite to simple reduce redundant that makes it less simple
---
mapplings/parameter.py | 11 +++++-
mapplings/parameter_list.py | 70 +++++++++++++++++++++++++++++++------
2 files changed, 69 insertions(+), 12 deletions(-)
diff --git a/mapplings/parameter.py b/mapplings/parameter.py
index bc9ed85..e1c7c50 100644
--- a/mapplings/parameter.py
+++ b/mapplings/parameter.py
@@ -295,7 +295,16 @@ def sub_parameter(self, cells: Iterable[Cell]) -> "Parameter":
cols, rows = zip(*cells)
cols_to_delete = {i for i in range(self.dimensions[0]) if i not in cols}
rows_to_delete = {i for i in range(self.dimensions[1]) if i not in rows}
- return self.delete_rows_and_columns(cols_to_delete, rows_to_delete)
+ temp = Tiling(self.obstructions, [], self.dimensions)
+ for req_list in self.requirements:
+ new_req_list = set[GriddedCayleyPerm]()
+ for req in req_list:
+ if any(pos in cells for pos in req.positions):
+ new_req_list.add(req.sub_gridded_cayley_perm(cells))
+ if len(new_req_list) == len(req_list):
+ temp = temp.add_requirement_list(new_req_list)
+ new_param = Parameter(temp, self.map)
+ return new_param.delete_rows_and_columns(cols_to_delete, rows_to_delete)
def factor(self) -> Iterator["Parameter"]:
"""Factors the ghost and combines factors with overlapping images."""
diff --git a/mapplings/parameter_list.py b/mapplings/parameter_list.py
index 61770be..1de0098 100644
--- a/mapplings/parameter_list.py
+++ b/mapplings/parameter_list.py
@@ -72,20 +72,68 @@ def remove_empty(self) -> "ParameterList":
def simple_remove_redundant(self, reverse: bool = False) -> "ParameterList":
"""Removes any parameter implied by another through a basic check"""
exclude = set[Parameter]()
+
+ def match_reverse(smaller: Parameter, bigger: Parameter) -> None:
+ if reverse:
+ exclude.add(smaller)
+ else:
+ exclude.add(bigger)
+
+ def compare(smaller: Parameter, bigger: Parameter) -> bool:
+ temp_bigger = bigger.sub_parameter(
+ bigger.map.preimage_of_cells(smaller.image_cells())
+ )
+ if len(bigger.requirements) != len(temp_bigger.requirements):
+ return False
+
+ if smaller.map != temp_bigger.map:
+ return False
+ if not reverse:
+ if any(smaller.gcp_in_tiling(ob) for ob in temp_bigger.obstructions):
+ return False
+ for req_list in temp_bigger.requirements:
+ # any req list that doesnt have any req implied by all reqs of a small req list
+ if all(
+ all(
+ any(small_req.avoids([req]) for small_req in small_list)
+ for small_list in smaller.requirements
+ )
+ for req in req_list
+ ):
+ return False
+ return True
+ if any(temp_bigger.gcp_in_tiling(ob) for ob in smaller.obstructions):
+ return False
+ for req_list in smaller.requirements:
+ # any req list that doesnt have any req implied by all reqs of a small req list
+ if all(
+ all(
+ any(big_req.avoids([req]) for big_req in big_list)
+ for big_list in temp_bigger.requirements
+ )
+ for req in req_list
+ ):
+ return False
+ return True
+
for param0, param1 in combinations(self, 2):
if {param0, param1} & exclude:
continue
- image_cells = param0.image_cells()
- if not image_cells.issubset(param1.image_cells()):
- continue
- temp_param = param1.sub_parameter(param1.map.preimage_of_cells(image_cells))
- if param0.map != temp_param.map:
- continue
- if param0.ghost.is_subset(temp_param.ghost):
- if reverse:
- exclude.add(param0)
- else:
- exclude.add(param1)
+ image_cells = param0.image_cells(), param1.image_cells()
+ if image_cells[0] == image_cells[1]:
+ if compare(param0, param1):
+ match_reverse(param0, param1)
+ elif compare(param1, param0):
+ match_reverse(param1, param0)
+
+ elif image_cells[0].issubset(image_cells[1]):
+ if compare(param0, param1):
+ match_reverse(param0, param1)
+
+ elif image_cells[1].issubset(image_cells[0]):
+ if compare(param1, param0):
+ match_reverse(param1, param0)
+
return ParameterList(param for param in self if param not in exclude)
def to_html(self) -> str:
From 702a5047c550775d2bb965651d05429beffa0307 Mon Sep 17 00:00:00 2001
From: ReedActon <110858904+ReedActon@users.noreply.github.com>
Date: Tue, 17 Feb 2026 15:17:28 +0000
Subject: [PATCH 21/24] Remove Redundant
No longer simple
---
mapplings/algorithms/param_redundancy.py | 143 ++++++++++++++++++++
mapplings/cleaners/mappling_cleaner.py | 6 +-
mapplings/parameter_list.py | 158 +++++++++++++++++++++--
3 files changed, 294 insertions(+), 13 deletions(-)
create mode 100644 mapplings/algorithms/param_redundancy.py
diff --git a/mapplings/algorithms/param_redundancy.py b/mapplings/algorithms/param_redundancy.py
new file mode 100644
index 0000000..3699a5d
--- /dev/null
+++ b/mapplings/algorithms/param_redundancy.py
@@ -0,0 +1,143 @@
+from typing import Iterable
+from itertools import chain, combinations, product
+from gridded_cayley_permutations import Tiling, RowColMap
+from gridded_cayley_permutations.simplify_obstructions_and_requirements import (
+ SimplifyObstructionsAndRequirements as Simplify,
+)
+
+from mapplings import ParameterList, Parameter
+
+
+class CompareParameters:
+ """Used to check for redundant parameters"""
+
+ def __init__(self, params: Iterable[Parameter]):
+ self.params = ParameterList(params)
+ self.redundant = set[Parameter]()
+ self.find_redundant()
+
+ def __call__(self) -> ParameterList:
+ return ParameterList(
+ param for param in self.params if param not in self.redundant
+ )
+
+ @staticmethod
+ def all_maps(
+ param1: Parameter, param2: Parameter
+ ) -> tuple[tuple[RowColMap, ...], tuple[RowColMap, ...]]:
+ """Every way to map from one param to the other"""
+ preimages1, preimages2 = param1.map.preimage_map(), param2.map.preimage_map()
+ if param1.positive_cells():
+ positive1 = tuple(map(set[int], zip(*param1.positive_cells())))
+ else:
+ positive1 = (set[int](), set[int]())
+ if param2.positive_cells():
+ positive2 = tuple(map(set[int], zip(*param2.positive_cells())))
+ else:
+ positive2 = (set[int](), set[int]())
+
+ def make_maps(
+ row_maps: bool,
+ ) -> tuple[tuple[dict[int, int], ...], tuple[dict[int, int], ...]]:
+ all_sections1, all_sections2 = (
+ list[set[tuple[tuple[int, int], ...]]](),
+ list[set[tuple[tuple[int, int], ...]]](),
+ )
+ for image in set(preimages1[row_maps].keys()) & set(
+ preimages2[row_maps].keys()
+ ):
+ check_positive = (
+ set(preimages1[row_maps][image]) & positive1[row_maps],
+ set(preimages2[row_maps][image]) & positive2[row_maps],
+ )
+ section_maps1, section_maps2 = (
+ set[tuple[tuple[int, int], ...]](),
+ set[tuple[tuple[int, int], ...]](),
+ )
+ if len(preimages1[row_maps][image]) > len(preimages2[row_maps][image]):
+ for indices in combinations(
+ preimages1[row_maps][image], len(preimages2[row_maps][image])
+ ):
+
+ if not check_positive[0].issubset(indices):
+ continue
+ section_maps1.add(
+ tuple(zip(indices, preimages2[row_maps][image]))
+ )
+ section_maps2.add(
+ tuple(zip(preimages2[row_maps][image], indices))
+ )
+ else:
+ for indices in combinations(
+ preimages2[row_maps][image], len(preimages1[row_maps][image])
+ ):
+ if not check_positive[1].issubset(indices):
+ continue
+ section_maps2.add(
+ tuple(zip(indices, preimages1[row_maps][image]))
+ )
+ section_maps1.add(
+ tuple(zip(preimages1[row_maps][image], indices))
+ )
+ all_sections1.append(section_maps1)
+ all_sections2.append(section_maps2)
+ return tuple(
+ dict(chain(*tuples)) for tuples in product(*all_sections1)
+ ), tuple(dict(chain(*tuples)) for tuples in product(*all_sections2))
+
+ col_maps = make_maps(False)
+ row_maps = make_maps(True)
+ return tuple(
+ RowColMap(*maps) for maps in product(col_maps[0], row_maps[0])
+ ), tuple(RowColMap(*maps) for maps in product(col_maps[1], row_maps[1]))
+
+ @staticmethod
+ def redundancy_check(param1: Parameter, param2: Parameter):
+ """Returns true if param1 implies param2"""
+ positive_images1 = {
+ (param1.col_map[x], param1.row_map[y]) for x, y in param1.positive_cells()
+ }
+ positive_images2 = {
+ (param2.col_map[x], param2.row_map[y]) for x, y in param2.positive_cells()
+ }
+ intersection = param1.image_cells() & param2.image_cells()
+ if not (
+ positive_images1.issubset(intersection)
+ and positive_images2.issubset(intersection)
+ ):
+ return False
+ maps = CompareParameters.all_maps(param1, param2)[0]
+ req_free2 = Tiling(param2.obstructions, [], param2.dimensions)
+
+ def _check(temp_map: RowColMap) -> bool:
+ temp_param = Parameter(param1.ghost, temp_map)
+ if any(
+ req_free2.gcp_in_tiling(temp_param.map.map_gridded_cperm(ob))
+ for ob in temp_param.obstructions
+ ):
+ print("NO", temp_param.map)
+ return False
+ for req_list in temp_param.requirements:
+ if not Simplify.requirement_implied_by_some_requirement(
+ temp_param.map.map_gridded_cperms(req_list), param2.requirements
+ ):
+ print("NO", temp_param.map)
+ return False
+ print("Yes?", temp_param.map)
+ return True
+
+ for temp_map in maps:
+ if _check(temp_map):
+ return True
+ return False
+
+ def find_redundant(self) -> None:
+ """Finds all redundant parameters in the list"""
+ for p1, p2 in combinations(self.params, 2):
+ if {p1, p2} & self.redundant:
+ continue
+ if self.redundancy_check(p1, p2):
+ self.redundant.add(p1)
+ continue
+ if self.redundancy_check(p2, p1):
+ self.redundant.add(p2)
diff --git a/mapplings/cleaners/mappling_cleaner.py b/mapplings/cleaners/mappling_cleaner.py
index a255303..6db4bf0 100644
--- a/mapplings/cleaners/mappling_cleaner.py
+++ b/mapplings/cleaners/mappling_cleaner.py
@@ -15,6 +15,7 @@
from .cleaner import GenericCleaner, Register, CleanerLog
from .parameter_cleaner import ParamCleaner
+
default_param_cleaner = ParamCleaner.make_full_cleaner("Param Default Cleaner")
@@ -226,10 +227,9 @@ def remove_empty_rows_and_cols(mappling: MappedTiling) -> MappedTiling:
@reg(12)
def simple_reduce_redundant_parameters(mappling: MappedTiling) -> MappedTiling:
"""Removes any parameter implied by another with a basic check"""
- new_avoiders = mappling.avoiding_parameters.simple_remove_redundant(True)
+ new_avoiders = mappling.avoiding_parameters.remove_redundant()
new_containers = [
- c_list.simple_remove_redundant()
- for c_list in mappling.containing_parameters
+ c_list.remove_redundant() for c_list in mappling.containing_parameters
]
return MappedTiling(
mappling.tiling,
diff --git a/mapplings/parameter_list.py b/mapplings/parameter_list.py
index 1de0098..f32f812 100644
--- a/mapplings/parameter_list.py
+++ b/mapplings/parameter_list.py
@@ -6,10 +6,14 @@
TypeVar,
TypeVarTuple,
Union,
+ Iterable,
)
-from itertools import chain, combinations
+from itertools import chain, combinations, product
-from gridded_cayley_permutations import Tiling
+from gridded_cayley_permutations import Tiling, RowColMap
+from gridded_cayley_permutations.simplify_obstructions_and_requirements import (
+ SimplifyObstructionsAndRequirements as Simplify,
+)
from .parameter import Parameter
@@ -91,14 +95,9 @@ def compare(smaller: Parameter, bigger: Parameter) -> bool:
if not reverse:
if any(smaller.gcp_in_tiling(ob) for ob in temp_bigger.obstructions):
return False
- for req_list in temp_bigger.requirements:
- # any req list that doesnt have any req implied by all reqs of a small req list
- if all(
- all(
- any(small_req.avoids([req]) for small_req in small_list)
- for small_list in smaller.requirements
- )
- for req in req_list
+ for req_list in smaller.requirements:
+ if not Simplify.requirement_implied_by_some_requirement(
+ req_list, temp_bigger.requirements
):
return False
return True
@@ -136,6 +135,10 @@ def compare(smaller: Parameter, bigger: Parameter) -> bool:
return ParameterList(param for param in self if param not in exclude)
+ def remove_redundant(self) -> "ParameterList":
+ """Returns self with redundant parameters removed"""
+ return CompareParameters(self)()
+
def to_html(self) -> str:
"""Returns a html of all parameters in self seperated by a line"""
return "
".join((param.to_html_representation() for param in sorted(self)))
@@ -175,3 +178,138 @@ def __lt__(self, other: object):
if isinstance(other, ParameterList):
return tuple(sorted(self)) < tuple(sorted(other))
return NotImplemented
+
+
+class CompareParameters:
+ """Used to check for redundant parameters"""
+
+ def __init__(self, params: Iterable[Parameter]):
+ self.params = ParameterList(params)
+ self.redundant = set[Parameter]()
+ self.find_redundant()
+
+ def __call__(self) -> ParameterList:
+ return ParameterList(
+ param for param in self.params if param not in self.redundant
+ )
+
+ @staticmethod
+ def all_maps(
+ param1: Parameter, param2: Parameter
+ ) -> tuple[tuple[RowColMap, ...], tuple[RowColMap, ...]]:
+ """Every way to map from one param to the other"""
+ preimages1, preimages2 = param1.map.preimage_map(), param2.map.preimage_map()
+ if param1.positive_cells():
+ positive1 = tuple(map(set[int], zip(*param1.positive_cells())))
+ else:
+ positive1 = (set[int](), set[int]())
+ if param2.positive_cells():
+ positive2 = tuple(map(set[int], zip(*param2.positive_cells())))
+ else:
+ positive2 = (set[int](), set[int]())
+
+ def make_maps(
+ row_maps: bool,
+ ) -> tuple[tuple[dict[int, int], ...], tuple[dict[int, int], ...]]:
+ all_sections1, all_sections2 = (
+ list[set[tuple[tuple[int, int], ...]]](),
+ list[set[tuple[tuple[int, int], ...]]](),
+ )
+ for image in set(preimages1[row_maps].keys()) & set(
+ preimages2[row_maps].keys()
+ ):
+ check_positive = (
+ set(preimages1[row_maps][image]) & positive1[row_maps],
+ set(preimages2[row_maps][image]) & positive2[row_maps],
+ )
+ section_maps1, section_maps2 = (
+ set[tuple[tuple[int, int], ...]](),
+ set[tuple[tuple[int, int], ...]](),
+ )
+ if len(preimages1[row_maps][image]) > len(preimages2[row_maps][image]):
+ for indices in combinations(
+ preimages1[row_maps][image], len(preimages2[row_maps][image])
+ ):
+
+ if not check_positive[0].issubset(indices):
+ continue
+ section_maps1.add(
+ tuple(zip(indices, preimages2[row_maps][image]))
+ )
+ section_maps2.add(
+ tuple(zip(preimages2[row_maps][image], indices))
+ )
+ else:
+ for indices in combinations(
+ preimages2[row_maps][image], len(preimages1[row_maps][image])
+ ):
+ if not check_positive[1].issubset(indices):
+ continue
+ section_maps2.add(
+ tuple(zip(indices, preimages1[row_maps][image]))
+ )
+ section_maps1.add(
+ tuple(zip(preimages1[row_maps][image], indices))
+ )
+ all_sections1.append(section_maps1)
+ all_sections2.append(section_maps2)
+ return tuple(
+ dict(chain(*tuples)) for tuples in product(*all_sections1)
+ ), tuple(dict(chain(*tuples)) for tuples in product(*all_sections2))
+
+ col_maps = make_maps(False)
+ row_maps = make_maps(True)
+ return tuple(
+ RowColMap(*maps) for maps in product(col_maps[0], row_maps[0])
+ ), tuple(RowColMap(*maps) for maps in product(col_maps[1], row_maps[1]))
+
+ @staticmethod
+ def redundancy_check(param1: Parameter, param2: Parameter):
+ """Returns true if param1 implies param2"""
+ positive_images1 = {
+ (param1.col_map[x], param1.row_map[y]) for x, y in param1.positive_cells()
+ }
+ positive_images2 = {
+ (param2.col_map[x], param2.row_map[y]) for x, y in param2.positive_cells()
+ }
+ intersection = param1.image_cells() & param2.image_cells()
+ if not (
+ positive_images1.issubset(intersection)
+ and positive_images2.issubset(intersection)
+ ):
+ return False
+ maps = CompareParameters.all_maps(param1, param2)[0]
+ req_free2 = Tiling(param2.obstructions, [], param2.dimensions)
+
+ def _check(temp_map: RowColMap) -> bool:
+ temp_param = Parameter(param1.ghost, temp_map)
+ if any(
+ req_free2.gcp_in_tiling(temp_param.map.map_gridded_cperm(ob))
+ for ob in temp_param.obstructions
+ ):
+ print("NO", temp_param.map)
+ return False
+ for req_list in temp_param.requirements:
+ if not Simplify.requirement_implied_by_some_requirement(
+ temp_param.map.map_gridded_cperms(req_list), param2.requirements
+ ):
+ print("NO", temp_param.map)
+ return False
+ print("Yes?", temp_param.map)
+ return True
+
+ for temp_map in maps:
+ if _check(temp_map):
+ return True
+ return False
+
+ def find_redundant(self) -> None:
+ """Finds all redundant parameters in the list"""
+ for p1, p2 in combinations(self.params, 2):
+ if {p1, p2} & self.redundant:
+ continue
+ if self.redundancy_check(p1, p2):
+ self.redundant.add(p1)
+ continue
+ if self.redundancy_check(p2, p1):
+ self.redundant.add(p2)
From 2b04e87619d1deedbd4d4a4d7e175c67accf7db9 Mon Sep 17 00:00:00 2001
From: ReedActon <110858904+ReedActon@users.noreply.github.com>
Date: Tue, 17 Feb 2026 17:15:45 +0000
Subject: [PATCH 22/24] Fixed and New Function
Fixed redundancy check. Added a rough version of insert blank
---
mapplings/cleaners/parameter_cleaner.py | 70 +++++++++++++++++++++++--
mapplings/parameter_list.py | 46 ++++++++++++++--
2 files changed, 108 insertions(+), 8 deletions(-)
diff --git a/mapplings/cleaners/parameter_cleaner.py b/mapplings/cleaners/parameter_cleaner.py
index d92359f..1ab4b35 100644
--- a/mapplings/cleaners/parameter_cleaner.py
+++ b/mapplings/cleaners/parameter_cleaner.py
@@ -2,6 +2,7 @@
from typing import Iterator, Iterable
from itertools import chain
+from cayley_permutations import CayleyPermutation
from gridded_cayley_permutations.row_col_map import RowColMap
from gridded_cayley_permutations.unplacement import PartialUnplacement
from gridded_cayley_permutations import Tiling
@@ -30,7 +31,7 @@ class ParamCleaner(GenericCleaner[Parameter]):
# Final Methods
@staticmethod
- @reg(3, run_on_enumerators=False)
+ @reg(4, run_on_enumerators=False)
def reduce_by_fusion(param: Parameter) -> Parameter:
"""Fuses valid rows and columns"""
deleted_cols, deleted_rows = set(
@@ -76,7 +77,7 @@ def reduce_empty_rows_and_cols(param: Parameter) -> Parameter:
def remove_blank_rows_and_cols(param: Parameter) -> Parameter:
"""Deletes all rows and cols which have no obs or reqs"""
- blank = tuple(map(set[int], param.blank_and_near_blank()))
+ blank = tuple(map(set[int], param.find_blank_columns_and_rows()))
if not any(blank):
return param
col_preimages, row_preimages = param.map.preimage_map()
@@ -116,7 +117,7 @@ def to_remove(
return param.delete_rows_and_columns(cols_to_remove, rows_to_remove)
@staticmethod
- @reg(2, run_on_enumerators=False)
+ @reg(3, run_on_enumerators=False)
def unplace_points(param: Parameter) -> Parameter:
"""Unplaces all possible points in the parameter"""
algo = PartialUnplacement(param.ghost)
@@ -147,6 +148,69 @@ def unplace_points(param: Parameter) -> Parameter:
}
return Parameter(new_ghost, RowColMap(new_col_map, new_row_map))
+ @staticmethod
+ @reg(2, run_on_enumerators=False)
+ def insert_blank(param: Parameter) -> Parameter:
+ look_at = [
+ req_list
+ for req_list in param.requirements
+ if len(req_list) == 1
+ and req_list[0].pattern
+ in (CayleyPermutation((0, 1)), CayleyPermutation((1, 0)))
+ ]
+ if not look_at:
+ return param
+ look_at = [
+ req_list
+ for req_list in look_at
+ if (
+ {req_list[0].positions[0][0], req_list[0].positions[1][0]}.issubset(
+ param.point_cols
+ )
+ )
+ and (req_list[0].positions[0][0] + 1 == req_list[0].positions[1][0])
+ ]
+ if not look_at:
+ return param
+ look_at = [
+ req_list
+ for req_list in look_at
+ if req_list[0].positions[0][1] == req_list[0].positions[1][1]
+ ]
+ if not look_at:
+ return param
+ blank_cols = param.find_blank_columns_and_rows()[0]
+ to_insert = set[int]()
+ for req_list in look_at:
+ req = req_list[0]
+ if param.col_map[req.positions[0][0]] != param.col_map[req.positions[1][0]]:
+ continue
+ if req.positions[0][0] - 1 or req.positions[1][0] + 1 in blank_cols:
+ to_insert.add(req.positions[0][0])
+ col_adjust = {
+ i: i + sum((j < i for j in to_insert)) for i in range(param.dimensions[0])
+ }
+ adjust = RowColMap(col_adjust, {i: i for i in range(param.dimensions[1])})
+ new_obs = adjust.map_gridded_cperms(param.obstructions)
+ new_reqs = adjust.map_requirements(param.requirements)
+ new_col_map = dict[int, int]()
+ tweak = 0
+ for i in range(param.dimensions[0] + len(to_insert)):
+ if i - tweak - 1 in to_insert:
+ new_col_map[i] = new_col_map[i - 1]
+ tweak += 1
+ else:
+ new_col_map[i] = param.col_map[i - tweak]
+
+ return Parameter(
+ Tiling(
+ new_obs,
+ new_reqs,
+ (param.dimensions[0] + len(to_insert), param.dimensions[1]),
+ ),
+ RowColMap(new_col_map, param.row_map),
+ )
+
# Internal Methods
@staticmethod
diff --git a/mapplings/parameter_list.py b/mapplings/parameter_list.py
index f32f812..0ae84c6 100644
--- a/mapplings/parameter_list.py
+++ b/mapplings/parameter_list.py
@@ -189,6 +189,8 @@ def __init__(self, params: Iterable[Parameter]):
self.find_redundant()
def __call__(self) -> ParameterList:
+ if len(self.params) < 2:
+ return self.params
return ParameterList(
param for param in self.params if param not in self.redundant
)
@@ -208,6 +210,9 @@ def all_maps(
else:
positive2 = (set[int](), set[int]())
+ point_indices1 = param1.point_cols, param1.point_rows
+ point_indices2 = param2.point_cols, param2.point_rows
+
def make_maps(
row_maps: bool,
) -> tuple[tuple[dict[int, int], ...], tuple[dict[int, int], ...]]:
@@ -218,6 +223,16 @@ def make_maps(
for image in set(preimages1[row_maps].keys()) & set(
preimages2[row_maps].keys()
):
+ maxes = max(preimages1[row_maps][image]), max(
+ preimages2[row_maps][image]
+ )
+ mins = min(preimages1[row_maps][image]), min(
+ preimages2[row_maps][image]
+ )
+ force_extremal = (
+ [i in point_indices1[row_maps] for i in (mins[0], maxes[0])],
+ [i in point_indices2[row_maps] for i in (maxes[1], mins[1])],
+ )
check_positive = (
set(preimages1[row_maps][image]) & positive1[row_maps],
set(preimages2[row_maps][image]) & positive2[row_maps],
@@ -230,6 +245,10 @@ def make_maps(
for indices in combinations(
preimages1[row_maps][image], len(preimages2[row_maps][image])
):
+ if force_extremal[1][0] and mins[0] not in indices:
+ continue
+ if force_extremal[1][1] and maxes[0] not in indices:
+ continue
if not check_positive[0].issubset(indices):
continue
@@ -243,6 +262,10 @@ def make_maps(
for indices in combinations(
preimages2[row_maps][image], len(preimages1[row_maps][image])
):
+ if force_extremal[0][0] and mins[1] not in indices:
+ continue
+ if force_extremal[0][1] and maxes[1] not in indices:
+ continue
if not check_positive[1].issubset(indices):
continue
section_maps2.add(
@@ -283,19 +306,32 @@ def redundancy_check(param1: Parameter, param2: Parameter):
def _check(temp_map: RowColMap) -> bool:
temp_param = Parameter(param1.ghost, temp_map)
+ backwards_map = RowColMap(
+ {val: key for key, val in temp_map.col_map.items()},
+ {val: key for key, val in temp_map.row_map.items()},
+ )
if any(
- req_free2.gcp_in_tiling(temp_param.map.map_gridded_cperm(ob))
+ req_free2.gcp_in_tiling(
+ temp_param.map.map_gridded_cperm(
+ ob.sub_gridded_cayley_perm(backwards_map.image_cells)
+ )
+ )
for ob in temp_param.obstructions
):
- print("NO", temp_param.map)
return False
for req_list in temp_param.requirements:
+ temp_req_list = [
+ req
+ for req in req_list
+ if set(req.positions).issubset(backwards_map.image_cells)
+ ]
+ if not temp_req_list:
+ return False
if not Simplify.requirement_implied_by_some_requirement(
- temp_param.map.map_gridded_cperms(req_list), param2.requirements
+ temp_param.map.map_gridded_cperms(temp_req_list),
+ param2.requirements,
):
- print("NO", temp_param.map)
return False
- print("Yes?", temp_param.map)
return True
for temp_map in maps:
From d69631e6c28ee07507a700783c579c4394f1be2d Mon Sep 17 00:00:00 2001
From: ReedActon <110858904+ReedActon@users.noreply.github.com>
Date: Wed, 18 Feb 2026 12:46:36 +0000
Subject: [PATCH 23/24] Insert blank rows
rough version of insert blank rows
---
mapplings/cleaners/parameter_cleaner.py | 69 +++++++++++++++++++++++--
1 file changed, 66 insertions(+), 3 deletions(-)
diff --git a/mapplings/cleaners/parameter_cleaner.py b/mapplings/cleaners/parameter_cleaner.py
index 1ab4b35..98e870d 100644
--- a/mapplings/cleaners/parameter_cleaner.py
+++ b/mapplings/cleaners/parameter_cleaner.py
@@ -31,7 +31,7 @@ class ParamCleaner(GenericCleaner[Parameter]):
# Final Methods
@staticmethod
- @reg(4, run_on_enumerators=False)
+ @reg(5, run_on_enumerators=False)
def reduce_by_fusion(param: Parameter) -> Parameter:
"""Fuses valid rows and columns"""
deleted_cols, deleted_rows = set(
@@ -117,7 +117,7 @@ def to_remove(
return param.delete_rows_and_columns(cols_to_remove, rows_to_remove)
@staticmethod
- @reg(3, run_on_enumerators=False)
+ @reg(4, run_on_enumerators=False)
def unplace_points(param: Parameter) -> Parameter:
"""Unplaces all possible points in the parameter"""
algo = PartialUnplacement(param.ghost)
@@ -150,7 +150,7 @@ def unplace_points(param: Parameter) -> Parameter:
@staticmethod
@reg(2, run_on_enumerators=False)
- def insert_blank(param: Parameter) -> Parameter:
+ def insert_blank_cols(param: Parameter) -> Parameter:
look_at = [
req_list
for req_list in param.requirements
@@ -210,6 +210,69 @@ def insert_blank(param: Parameter) -> Parameter:
),
RowColMap(new_col_map, param.row_map),
)
+
+ @staticmethod
+ @reg(3, run_on_enumerators=False)
+ def insert_blank_rows(param: Parameter) -> Parameter:
+ look_at = [
+ req_list
+ for req_list in param.requirements
+ if len(req_list) == 1
+ and req_list[0].pattern
+ in (CayleyPermutation((0, 1)), CayleyPermutation((1, 0)))
+ ]
+ if not look_at:
+ return param
+ look_at = [
+ req_list
+ for req_list in look_at
+ if (
+ {req_list[0].positions[0][1], req_list[0].positions[1][1]}.issubset(
+ param.point_rows
+ )
+ )
+ and (req_list[0].positions[0][1] + 1 == req_list[0].positions[1][1])
+ ]
+ if not look_at:
+ return param
+ look_at = [
+ req_list
+ for req_list in look_at
+ if req_list[0].positions[0][0] == req_list[0].positions[1][0]
+ ]
+ if not look_at:
+ return param
+ blank_rows = param.find_blank_columns_and_rows()[1]
+ to_insert = set[int]()
+ for req_list in look_at:
+ req = req_list[0]
+ if param.row_map[req.positions[0][1]] != param.row_map[req.positions[1][1]]:
+ continue
+ if req.positions[0][1] - 1 or req.positions[1][1] + 1 in blank_rows:
+ to_insert.add(req.positions[0][1])
+ row_adjust = {
+ i: i + sum((j < i for j in to_insert)) for i in range(param.dimensions[1])
+ }
+ adjust = RowColMap({i: i for i in range(param.dimensions[1])}, row_adjust)
+ new_obs = adjust.map_gridded_cperms(param.obstructions)
+ new_reqs = adjust.map_requirements(param.requirements)
+ new_row_map = dict[int, int]()
+ tweak = 0
+ for i in range(param.dimensions[1] + len(to_insert)):
+ if i - tweak - 1 in to_insert:
+ new_row_map[i] = new_row_map[i - 1]
+ tweak += 1
+ else:
+ new_row_map[i] = param.col_map[i - tweak]
+
+ return Parameter(
+ Tiling(
+ new_obs,
+ new_reqs,
+ (param.dimensions[0], param.dimensions[1] + len(to_insert)),
+ ),
+ RowColMap(param.col_map, new_row_map),
+ )
# Internal Methods
From fdbab516d6391bed418256d2e39fad954bf117f2 Mon Sep 17 00:00:00 2001
From: ReedActon <110858904+ReedActon@users.noreply.github.com>
Date: Wed, 18 Feb 2026 14:35:34 +0000
Subject: [PATCH 24/24] Updated Functions and Tweaks
Made the insertion function actually good. Changed how some strategies work.
---
mapplings/cleaners/cleaner.py | 4 +-
mapplings/cleaners/parameter_cleaner.py | 160 +++++--------------
mapplings/parameter.py | 45 ++++++
mapplings/strategies/tilescope_strategies.py | 8 +-
4 files changed, 91 insertions(+), 126 deletions(-)
diff --git a/mapplings/cleaners/cleaner.py b/mapplings/cleaners/cleaner.py
index 9f4126d..3854564 100644
--- a/mapplings/cleaners/cleaner.py
+++ b/mapplings/cleaners/cleaner.py
@@ -493,9 +493,7 @@ def status_update(cls) -> str:
"right",
"right",
)
- logs = list(
- log for log in cls.all_loggers if log.log_level > 0 and log.runs > 0
- )
+ logs = list(log for log in cls.all_loggers if log.log_level > 0)
if not logs:
return f"Logging dissabled for all {cls.__name__} cleaners.\n"
logs.sort(key=lambda log: log.total_times, reverse=True)
diff --git a/mapplings/cleaners/parameter_cleaner.py b/mapplings/cleaners/parameter_cleaner.py
index 98e870d..6e2fedc 100644
--- a/mapplings/cleaners/parameter_cleaner.py
+++ b/mapplings/cleaners/parameter_cleaner.py
@@ -23,7 +23,7 @@ class ParamCleaner(GenericCleaner[Parameter]):
run_on_enumerators=True,
)
global_tracker = CleanerLog[Parameter](
- reg.registered_functions, name="Global Tracker"
+ reg.registered_functions, log_level=2,name="Global Tracker"
)
all_loggers = {global_tracker}
@@ -150,129 +150,49 @@ def unplace_points(param: Parameter) -> Parameter:
@staticmethod
@reg(2, run_on_enumerators=False)
- def insert_blank_cols(param: Parameter) -> Parameter:
- look_at = [
- req_list
- for req_list in param.requirements
- if len(req_list) == 1
- and req_list[0].pattern
- in (CayleyPermutation((0, 1)), CayleyPermutation((1, 0)))
- ]
- if not look_at:
- return param
- look_at = [
- req_list
- for req_list in look_at
- if (
- {req_list[0].positions[0][0], req_list[0].positions[1][0]}.issubset(
- param.point_cols
- )
- )
- and (req_list[0].positions[0][0] + 1 == req_list[0].positions[1][0])
- ]
- if not look_at:
- return param
- look_at = [
- req_list
- for req_list in look_at
- if req_list[0].positions[0][1] == req_list[0].positions[1][1]
- ]
- if not look_at:
+ def insert_blank(param: Parameter) -> Parameter:
+ """Inserts a blank col/row in between descents/ascents wherever possible"""
+ if not param.requirements:
return param
- blank_cols = param.find_blank_columns_and_rows()[0]
- to_insert = set[int]()
- for req_list in look_at:
- req = req_list[0]
- if param.col_map[req.positions[0][0]] != param.col_map[req.positions[1][0]]:
+ to_insert = [set[int](), set[int]()]
+ maps = param.col_map, param.row_map
+ blank = tuple(map(set[int], param.find_blank_columns_and_rows()))
+ point_indices = param.point_cols, param.point_rows
+
+ def validate(index1: int, index2: int, check_rows: bool) -> bool:
+ indeices = {index1, index2}
+ if maps[check_rows][index1] != maps[check_rows][index2]:
+ return False
+ if not indeices.issubset(point_indices[check_rows]):
+ return False
+ if {index1 - 1, index2 + 1} & blank[check_rows]:
+ return True
+ return False
+
+ for req_list in param.requirements:
+ if not len(req_list) == 1:
continue
- if req.positions[0][0] - 1 or req.positions[1][0] + 1 in blank_cols:
- to_insert.add(req.positions[0][0])
- col_adjust = {
- i: i + sum((j < i for j in to_insert)) for i in range(param.dimensions[0])
- }
- adjust = RowColMap(col_adjust, {i: i for i in range(param.dimensions[1])})
- new_obs = adjust.map_gridded_cperms(param.obstructions)
- new_reqs = adjust.map_requirements(param.requirements)
- new_col_map = dict[int, int]()
- tweak = 0
- for i in range(param.dimensions[0] + len(to_insert)):
- if i - tweak - 1 in to_insert:
- new_col_map[i] = new_col_map[i - 1]
- tweak += 1
- else:
- new_col_map[i] = param.col_map[i - tweak]
-
- return Parameter(
- Tiling(
- new_obs,
- new_reqs,
- (param.dimensions[0] + len(to_insert), param.dimensions[1]),
- ),
- RowColMap(new_col_map, param.row_map),
- )
-
- @staticmethod
- @reg(3, run_on_enumerators=False)
- def insert_blank_rows(param: Parameter) -> Parameter:
- look_at = [
- req_list
- for req_list in param.requirements
- if len(req_list) == 1
- and req_list[0].pattern
- in (CayleyPermutation((0, 1)), CayleyPermutation((1, 0)))
- ]
- if not look_at:
- return param
- look_at = [
- req_list
- for req_list in look_at
- if (
- {req_list[0].positions[0][1], req_list[0].positions[1][1]}.issubset(
- param.point_rows
- )
- )
- and (req_list[0].positions[0][1] + 1 == req_list[0].positions[1][1])
- ]
- if not look_at:
- return param
- look_at = [
- req_list
- for req_list in look_at
- if req_list[0].positions[0][0] == req_list[0].positions[1][0]
- ]
- if not look_at:
- return param
- blank_rows = param.find_blank_columns_and_rows()[1]
- to_insert = set[int]()
- for req_list in look_at:
req = req_list[0]
- if param.row_map[req.positions[0][1]] != param.row_map[req.positions[1][1]]:
+ if req.pattern not in (
+ CayleyPermutation((0, 1)),
+ CayleyPermutation((1, 0)),
+ ):
continue
- if req.positions[0][1] - 1 or req.positions[1][1] + 1 in blank_rows:
- to_insert.add(req.positions[0][1])
- row_adjust = {
- i: i + sum((j < i for j in to_insert)) for i in range(param.dimensions[1])
- }
- adjust = RowColMap({i: i for i in range(param.dimensions[1])}, row_adjust)
- new_obs = adjust.map_gridded_cperms(param.obstructions)
- new_reqs = adjust.map_requirements(param.requirements)
- new_row_map = dict[int, int]()
- tweak = 0
- for i in range(param.dimensions[1] + len(to_insert)):
- if i - tweak - 1 in to_insert:
- new_row_map[i] = new_row_map[i - 1]
- tweak += 1
- else:
- new_row_map[i] = param.col_map[i - tweak]
-
- return Parameter(
- Tiling(
- new_obs,
- new_reqs,
- (param.dimensions[0], param.dimensions[1] + len(to_insert)),
- ),
- RowColMap(param.col_map, new_row_map),
- )
+ cell1, cell2 = req.positions
+ if (cell1[0] != cell2[0]) and (cell1[1] != cell2[1]):
+ continue
+ if cell1[0] + 1 == cell2[0]:
+ if validate(cell1[0], cell2[0], False):
+ to_insert[0].add(cell1[0])
+ continue
+ idx1, idx2 = sorted([cell1[1], cell2[1]])
+ if idx1 + 1 == idx2:
+ if validate(idx1, idx2, True):
+ to_insert[1].add(idx1)
+ if not any(to_insert):
+ return param
+
+ return param.insert_cols_and_rows(*to_insert)
# Internal Methods
diff --git a/mapplings/parameter.py b/mapplings/parameter.py
index e1c7c50..578cd25 100644
--- a/mapplings/parameter.py
+++ b/mapplings/parameter.py
@@ -290,6 +290,51 @@ def delete_preimage_of_rows_and_columns(
new_row_map[key] = temp_param.row_map[key] = value - adjust
return Parameter(temp_param.ghost, RowColMap(new_col_map, new_row_map))
+ def insert_cols_and_rows(
+ self, cols: Iterable[int], rows: Iterable[int]
+ ) -> "Parameter":
+ """Inserts a blank col or col at each index.
+ New col/row gets map data from the col/row at the given index."""
+ col_adjust = {
+ i: i + sum((j < i for j in cols)) for i in range(self.dimensions[0])
+ }
+ row_adjust = {
+ i: i + sum((j < i for j in rows)) for i in range(self.dimensions[1])
+ }
+ adjust = RowColMap(col_adjust, row_adjust)
+ new_obs = adjust.map_gridded_cperms(self.obstructions)
+ new_reqs = adjust.map_requirements(self.requirements)
+ new_col_map = dict[int, int]()
+ new_row_map = dict[int, int]()
+ new_dimensions = (
+ self.dimensions[0] + len(tuple(cols)),
+ self.dimensions[1] + len(tuple(rows)),
+ )
+ tweak = 0
+
+ for i in range(new_dimensions[0]):
+ if i - tweak - 1 in cols:
+ new_col_map[i] = new_col_map[i - 1]
+ tweak += 1
+ else:
+ new_col_map[i] = self.col_map[i - tweak]
+ tweak = 0
+ for i in range(new_dimensions[1]):
+ if i - tweak - 1 in rows:
+ new_row_map[i] = new_row_map[i - 1]
+ tweak += 1
+ else:
+ new_row_map[i] = self.row_map[i - tweak]
+ return Parameter(
+ Tiling(
+ new_obs,
+ new_reqs,
+ new_dimensions,
+ False,
+ ),
+ RowColMap(new_col_map, new_row_map),
+ )
+
def sub_parameter(self, cells: Iterable[Cell]) -> "Parameter":
"""Returns the parameter containing only the specified cells"""
cols, rows = zip(*cells)
diff --git a/mapplings/strategies/tilescope_strategies.py b/mapplings/strategies/tilescope_strategies.py
index 1cfcaf6..1f1018e 100644
--- a/mapplings/strategies/tilescope_strategies.py
+++ b/mapplings/strategies/tilescope_strategies.py
@@ -41,8 +41,6 @@
)
from mapplings.cleaners import MTCleaner, ParamCleaner
-
-MTCleaner.global_log_toggle(1)
temp = CombinatorialSpecificationSearcher.status
@@ -294,7 +292,7 @@ class MapplingFactorStrategy(FactorStrategy):
A strategy for finding factors in a mapped tiling.
"""
- cleaner = MTCleaner.make_full_cleaner("Factoring Cleaner")
+ cleaner = MTCleaner([], "Factoring Cleaner")
def decomposition_function(self, comb_class) -> tuple[MappedTiling, ...]:
factors = Factor(comb_class).find_factors()
@@ -348,6 +346,8 @@ class MapplingLessThanRowColSeparationStrategy(LessThanRowColSeparationStrategy)
def decomposition_function(self, comb_class):
algo = LTRowColSeparationMT(comb_class)
+ if algo.separation.row_col_map.is_identity():
+ raise StrategyDoesNotApply
return tuple(map(self.__class__.cleaner, algo.separate()))
@@ -360,4 +360,6 @@ class MapplingLessThanOrEqualRowColSeparationStrategy(
def decomposition_function(self, comb_class):
algo = LTORERowColSeparationMT(comb_class)
+ if algo.separation.row_col_map.is_identity():
+ raise StrategyDoesNotApply
return tuple(map(self.__class__.cleaner, algo.separate()))