Skip to content

Enumeration scheme #476

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 22 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 195 additions & 0 deletions tilings/algorithms/fusion.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@
Iterator,
List,
Optional,
Set,
Tuple,
)

from permuta import Perm
from tilings.algorithms.map import RowColMap
from tilings.assumptions import (
ComponentAssumption,
SkewComponentAssumption,
Expand Down Expand Up @@ -680,3 +683,195 @@ def __str__(self) -> str:
s = "ComponentFusion Algorithm for:\n"
s += str(self._tiling)
return s


class FiniteFusion(Fusion):
def __init__(
self,
tiling: "Tiling",
*,
row_idx: Optional[int] = None,
col_idx: Optional[int] = None,
tracked: bool = False,
isolation_level: Optional[str] = None,
):
self._unfused_obs_and_reqs: Optional[
Tuple[Tuple[GriddedPerm, ...], Tuple[Tuple[GriddedPerm, ...], ...]]
] = None
super().__init__(
tiling,
row_idx=row_idx,
col_idx=col_idx,
tracked=tracked,
isolation_level=isolation_level,
)

def is_in_fuse_region(self, gp: GriddedPerm) -> bool:
if self._col_idx is not None:
return all(x in (self._col_idx, self._col_idx + 1) for x, _ in gp.pos)
return all(y in (self._row_idx, self._row_idx + 1) for _, y in gp.pos)

def fuses_to_all_perm_cell(self, fused_obs: Set[GriddedPerm]) -> bool:
if self._fuse_row:
return not any(
ob.is_single_cell() and any(ob.get_points_row(self._row_idx))
for ob in fused_obs
)
return not any(
ob.is_single_cell() and any(ob.get_points_col(self._col_idx))
for ob in fused_obs
)

def fused_tiling(self) -> "Tiling":
if self._fused_tiling is None:
extra_obs = self.extra_obs()
fused_obs = set(
self.fuse_gridded_perm(ob)
for ob in self._tiling.obstructions
if ob not in extra_obs
)
if not self.fuses_to_all_perm_cell(fused_obs):
fused_reqs = set(
frozenset(self.fuse_gridded_perm(gp) for gp in req)
for req in self._tiling.requirements
)
fused_ass = set(
ass.__class__([self.fuse_gridded_perm(gp) for gp in ass.gps])
for ass in self._tiling.assumptions
)
if self._tracked:
fused_ass.add(self.new_assumption())
self._fused_tiling = self._tiling.__class__(
fused_obs, fused_reqs, fused_ass
)
else:
# in this case we will be creating an Av() cell, so it implies
# ordinary fusion is happening to a finite region.
self._fused_tiling = super().fused_tiling()
return self._fused_tiling

def unfused_fused_obs_reqs(
self,
) -> Tuple[Tuple[GriddedPerm, ...], Tuple[Tuple[GriddedPerm, ...], ...]]:
if self._unfused_obs_and_reqs is None:
fused_tiling = self.fused_tiling()
obs = tuple(
chain.from_iterable(
self.unfuse_gridded_perm(gp) for gp in fused_tiling.obstructions
)
)
reqs = tuple(
tuple(chain.from_iterable(self.unfuse_gridded_perm(gp) for gp in req))
for req in fused_tiling.requirements
)
self._unfused_obs_and_reqs = obs, reqs
return self._unfused_obs_and_reqs

def fusable(self) -> bool:
obs, reqs = self.unfused_fused_obs_reqs()
reduced_obs = tuple(
o1 for o1 in obs if not any(o2 in o1 for o2 in self._tiling.obstructions)
)
return self._tiling == self._tiling.__class__(
self._tiling.obstructions + reduced_obs,
self._tiling.requirements + reqs,
self._tiling.assumptions,
already_minimized_obs=True,
remove_empty_rows_and_cols=False,
)

def extra_obs(
self,
) -> Set[GriddedPerm]:
valid_gap_vectors = set(self.get_valid_gap_vectors())
return set(
ob
for ob in self._tiling.obstructions
if any(self.gp_satifies_gap_vector(ob, vec) for vec in valid_gap_vectors)
)

def gp_satifies_gap_vector(self, gp: GriddedPerm, vector: Tuple[int, int]) -> bool:
return self.is_in_fuse_region(gp) and self.get_vector_from_gp(gp) == vector

def get_vector_from_gp(self, gp: GriddedPerm) -> Tuple[int, int]:
if self._col_idx is not None:
return (
sum(1 for x, _ in gp.pos if x == self._col_idx),
sum(1 for x, _ in gp.pos if x == self._col_idx + 1),
)
return (
sum(1 for _, y in gp.pos if y == self._row_idx),
sum(1 for _, y in gp.pos if y == self._row_idx + 1),
)

def get_sk_from_gap_vector(self, vector: Tuple[int, int]) -> Iterator[GriddedPerm]:
if not self._fuse_row:
row_map = RowColMap(
row_map={x: 0 for x in range(self._tiling.dimensions[1])},
col_map={0: 0},
is_identity=False,
)
col_pos = (self._col_idx,) * vector[0] + (self._col_idx + 1,) * vector[1]
Sk = (
GriddedPerm.single_cell(p, (0, 0)) for p in Perm.of_length(sum(vector))
)
for gp in row_map.preimage_gps(Sk):
new_pos = tuple((x, y) for (_, y), x in zip(gp.pos, col_pos))
yield GriddedPerm(gp.patt, new_pos)
else:
col_map = RowColMap(
row_map={0: 0},
col_map={x: 0 for x in range(self._tiling.dimensions[0])},
is_identity=False,
)
Sk = (
GriddedPerm.single_cell(p, (0, 0)) for p in Perm.of_length(sum(vector))
)
row_heights = (self._row_idx,) * vector[0] + (self._row_idx + 1,) * vector[
1
]
for gp in col_map.preimage_gps(Sk):
row_pos = gp.patt.apply(row_heights)
new_pos = tuple((x, y) for (x, _), y in zip(gp.pos, row_pos))
yield GriddedPerm(gp.patt, new_pos)

def is_valid_gap_vector(self, vector: Tuple[int, int]) -> bool:
sk = self.get_sk_from_gap_vector(vector)
return all(gp.contains(*self._tiling.obstructions) for gp in sk)

def get_valid_gap_vectors(self) -> Iterator[Tuple[int, int]]:
gap_vectors = {
self.get_vector_from_gp(ob)
for ob in self._tiling.obstructions
if self.is_in_fuse_region(ob)
}
return filter(self.is_valid_gap_vector, gap_vectors)

def assumption_for_gap_vector(
self, gv: Tuple[int, int]
) -> Iterator[TrackingAssumption]:
"""
Returns the assumption that tracks the each row / column affected by the gap
vector.
"""
if self._fuse_row:
if gv[0] != 0:
yield TrackingAssumption.from_cells(
self._tiling.cells_in_row(self._row_idx)
)
if gv[1] != 0:
yield TrackingAssumption.from_cells(
self._tiling.cells_in_row(self._row_idx + 1)
)
else:
if gv[0] != 0:
yield TrackingAssumption.from_cells(
self._tiling.cells_in_col(self._col_idx)
)
if gv[1] != 0:
yield TrackingAssumption.from_cells(
self._tiling.cells_in_col(self._col_idx + 1)
)

def assumptions_for_valid_gap_vectors(self) -> Iterator[TrackingAssumption]:
pass
97 changes: 95 additions & 2 deletions tilings/algorithms/map.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from typing import TYPE_CHECKING, Dict, Tuple
import itertools
from typing import TYPE_CHECKING, Callable, Dict, Iterable, Iterator, List, Tuple

from tilings.exception import InvalidOperationError

if TYPE_CHECKING:
from tilings.assumptions import TrackingAssumption
from tilings.assumptions import Tiling, TrackingAssumption
from tilings.griddedperm import GriddedPerm

Cell = Tuple[int, int]
Expand Down Expand Up @@ -117,6 +118,98 @@ def map_col(self, col: int) -> int:
"""
return self._col_map[col]

# Pre-image method
def preimage_row(self, row: int) -> Iterator[int]:
"""Returns all the preimages of the given row."""
return (k for k, v in self._row_map.items() if v == row)

def preimage_col(self, col: int) -> Iterator[int]:
"""Returns all the preimages of the given column."""
return (k for k, v in self._col_map.items() if v == col)

def preimage_cell(self, cell: Cell) -> Iterator[Cell]:
"""Returns all the preimages of the given cell."""
col, row = cell
return itertools.product(self.preimage_col(col), self.preimage_row(row))

@staticmethod
def _preimage_gp_col(
gp_cols: Tuple[int, ...], preimage_func: Callable[[int], Iterator[int]]
) -> Iterator[Tuple[int, ...]]:
"""
Return all the possible sequence of column for a preimage of the gridded
permutation using the given preimage_func.
"""
possible_col = [sorted(preimage_func(col)) for col in gp_cols]
partial_pos: List[int] = []
partial_pos_indices: List[int] = []
while True:
# Padding the current solution with the leftmost options
while len(partial_pos) < len(gp_cols):
last_col = partial_pos[-1] if partial_pos else 0
for new_col_idx, col in enumerate(possible_col[len(partial_pos)]):
if last_col <= col:
partial_pos.append(col)
partial_pos_indices.append(new_col_idx)
break
else:
break
else:
yield tuple(partial_pos)
# increasing the rightmost pos that can be increased.
while partial_pos:
partial_pos.pop()
partial_pos_last_index = partial_pos_indices.pop()
if partial_pos_last_index + 1 < len(possible_col[len(partial_pos)]):
break
else:
break
partial_pos.append(
possible_col[len(partial_pos)][partial_pos_last_index + 1]
)
partial_pos_indices.append(partial_pos_last_index + 1)

def preimage_gp(self, gp: "GriddedPerm") -> Iterator["GriddedPerm"]:
"""
Returns all the preimages of the given gridded permutation.
Gridded permutations that are contradictory are filtered out.
"""
gp_cols = tuple(col for col, _ in gp.pos)
preimage_col_pos = self._preimage_gp_col(gp_cols, self.preimage_col)
gp_rows = gp.patt.inverse().apply(row for _, row in gp.pos)
preimage_row_pos: Iterator[Tuple[int, ...]] = map(
gp.patt.apply, self._preimage_gp_col(gp_rows, self.preimage_row)
)
for pos in itertools.product(preimage_col_pos, preimage_row_pos):
new_gp = gp.__class__(gp.patt, zip(*pos))
yield new_gp

def preimage_gps(self, gps: Iterable["GriddedPerm"]) -> Iterator["GriddedPerm"]:
"""
Returns all the preimages of the given gridded permutations.
Gridded permutations that are contradictory are filtered out.
"""
for gp in gps:
yield from self.preimage_gp(gp)

def preimage_obstruction_and_requirements(
self, tiling: "Tiling"
) -> Tuple[List["GriddedPerm"], List[List["GriddedPerm"]]]:
if tiling.assumptions:
raise NotImplementedError("Not implemented for tilings with assumptions")
obs = itertools.chain.from_iterable(
self.preimage_gp(ob) for ob in tiling.obstructions
)
reqs = (
itertools.chain.from_iterable(self.preimage_gp(req) for req in req_list)
for req_list in tiling.requirements
)
return list(obs), list(list(r) for r in reqs)

def preimage_tiling(self, tiling: "Tiling") -> "Tiling":
return tiling.__class__(*self.preimage_obstruction_and_requirements(tiling))

# Other method
def max_row(self) -> int:
"""Return the biggest row index in the image."""
return max(self._row_map.values())
Expand Down
4 changes: 4 additions & 0 deletions tilings/strategies/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
)
from .factor import FactorFactory
from .fusion import ComponentFusionFactory, FusionFactory
from .gap_vectors import RelaxGapVectorFactory, RemoveGapVectorFactory
from .monotone_sliding import MonotoneSlidingFactory
from .obstruction_inferral import (
EmptyCellInferralFactory,
Expand Down Expand Up @@ -104,6 +105,9 @@
# Fusion
"ComponentFusionFactory",
"FusionFactory",
# Gap vector
"RelaxGapVectorFactory",
"RemoveGapVectorFactory",
# Inferral
"EmptyCellInferralFactory",
"ObstructionInferralFactory",
Expand Down
Loading