def components(tiling): """Return the component of a tiling. Two cells are in the same component if they are in the same row or column.""" n, m = tiling.dimensions def cell_to_int(cell): return cell[0] * m + cell[1] def int_to_cell(i): return (i // m, i % m) cells = list(tiling.active_cells) uf = UnionFind(n * m) for i in range(len(cells)): for j in range(i + 1, len(cells)): c1, c2 = cells[i], cells[j] if c1[0] == c2[0] or c1[1] == c2[1]: uf.unite(cell_to_int(c1), cell_to_int(c2)) # Collect the connected components of the cells all_components = {} for cell in cells: i = uf.find(cell_to_int(cell)) if i in all_components: all_components[i].append(cell) else: all_components[i] = [cell] component_cells = list(set(cells) for cells in all_components.values()) return component_cells
def factors(self) -> List["GriddedPerm"]: """Return a list containing the factors of a gridded permutation. A factor is a sub gridded permutation that is isolated on its own rows and columns.""" uf = UnionFind(len(self.pos)) for j, (_, (x_r, y_r)) in enumerate(self): for i, (_, (x_l, y_l)) in enumerate(islice(self, j)): if x_l == x_r or y_l == y_r: uf.unite(i, j) # Collect the connected factors of the cells all_factors: Dict[int, List[Cell]] = {} for i, cell in enumerate(self.pos): x = uf.find(i) if x in all_factors: all_factors[x].append(cell) else: all_factors[x] = [cell] factor_cells = list(set(cells) for cells in all_factors.values()) return [self.get_gridded_perm_in_cells(comp) for comp in factor_cells]
def factors(self) -> List["GriddedPerm"]: """Return a list containing the factors of a gridded permutation. A factor is a sub gridded permutation that is isolated on its own rows and columns.""" uf = UnionFind(len(self.pos)) for i in range(len(self.pos)): for j in range(i + 1, len(self.pos)): c1, c2 = self.pos[i], self.pos[j] if c1[0] == c2[0] or c1[1] == c2[1]: uf.unite(i, j) # Collect the connected factors of the cells all_factors: Dict[Cell, List[Cell]] = {} for i, cell in enumerate(self.pos): x = uf.find(i) if x in all_factors: all_factors[x].append(cell) else: all_factors[x] = [cell] factor_cells = list(set(cells) for cells in all_factors.values()) return [self.get_gridded_perm_in_cells(comp) for comp in factor_cells]
def is_boundary_anchored(patt): inv = patt.pattern.inverse() right, top, left, bottom = patt.has_anchored_point() in_bound = set() if not right and not left and not top and not bottom: return False if right: in_bound.add(len(patt) - 1) if left: in_bound.add(0) if top: in_bound.add(inv[len(patt) - 1]) if bottom: in_bound.add(inv[0]) uf = UnionFind(len(patt)) for i in range(1, len(patt)): if all((i, j) in patt.shading for j in range(len(patt) + 1)): uf.unite(i - 1, i) if all((j, i) in patt.shading for j in range(len(patt) + 1)): uf.unite(inv[i - 1], inv[i]) # print(uf.leaders) return uf.leaders == set(uf.find(b) for b in in_bound)
lst = map(int,val.split()) surprising.append(lst) else: assert False f.close() coinc = [] pat = '\\pattern{scale=0.75}{%d}{%s}' % (len(perm), ','.join([ '%d/%s' % (i+1,v) for i,v in enumerate(perm) ])) for lst in surprising: co = [] for p in sorted(lst): # XXX: make this faster, if needed here = [ i for i in range(2**((n+1)**2)) if uf.find(i) == uf.find(p) ] best = -1 for x in here: if best == -1 or hamming(best) < hamming(x): best = x co.append(best) s = '\\begin{center}\n' s += 'Coincidence %d\n' % (len(coinc)+1) print co for x in co: s += pat + '{' + ','.join( '%d/%d' % (i//(n+1),i%(n+1)) for i in range((n+1)**2) if (x & (1<<i)) != 0 ) + '}\n' s += '\\end{center}\n'
class Factor: """ Algorithm to compute the factorisation of a tiling. Two active cells are in the same factor if they are in the same row or column, or they share an obstruction or a requirement. If using tracking assumptions, then two cells will also be in the same factor if they are covered by the same assumption. """ def __init__(self, tiling: "Tiling") -> None: self._tiling = tiling self._active_cells = tiling.active_cells nrow = tiling.dimensions[1] ncol = tiling.dimensions[0] self._cell_unionfind = UnionFind(nrow * ncol) self._components: Optional[Tuple[Set[Cell], ...]] = None self._factors_obs_and_reqs: Optional[ List[ Tuple[ Tuple[GriddedPerm, ...], Tuple[ReqList, ...], Tuple[TrackingAssumption, ...], ] ] ] = None def _cell_to_int(self, cell: Cell) -> int: nrow = self._tiling.dimensions[1] return cell[0] * nrow + cell[1] def _int_to_cell(self, i: int) -> Cell: nrow = self._tiling.dimensions[1] return (i // nrow, i % nrow) def _get_cell_representative(self, cell: Cell) -> Cell: """ Return the representative of a cell in the union find. """ i = self._cell_to_int(cell) return self._cell_unionfind.find(i) # type: ignore def _unite_cells(self, cells: Iterable[Cell]) -> None: """ Put all the cells of `cells` in the same component of the UnionFind. """ cell_iterator = iter(cells) c1 = next(cell_iterator) c1_int = self._cell_to_int(c1) for c2 in cell_iterator: c2_int = self._cell_to_int(c2) self._cell_unionfind.unite(c1_int, c2_int) def _unite_assumptions(self) -> None: """ For each TrackingAssumption unite all the positions of the gridded perms. """ for assumption in self._tiling.assumptions: if isinstance(assumption, ComponentAssumption): self._unite_cells(chain.from_iterable(gp.pos for gp in assumption.gps)) else: for gp in assumption.gps: self._unite_cells(gp.pos) def _unite_obstructions(self) -> None: """ For each obstruction unite all the position of the obstruction. """ for ob in self._tiling.obstructions: self._unite_cells(ob.pos) def _unite_requirements(self) -> None: """ For each requirement unite all the cell in all the requirements of the list. """ for req_list in self._tiling.requirements: req_cells = chain.from_iterable(req.pos for req in req_list) self._unite_cells(req_cells) @staticmethod def _same_row_or_col(cell1: Cell, cell2: Cell) -> bool: """ Test if `cell1` and `cell2` or in the same row or columns """ return cell1[0] == cell2[0] or cell1[1] == cell2[1] def _unite_rows_and_cols(self) -> None: """ Unite all the active cell that are on the same row or column. """ cell_pair_to_unite = ( c for c in combinations(self._active_cells, r=2) if self._same_row_or_col(c[0], c[1]) ) for c1, c2 in cell_pair_to_unite: self._unite_cells((c1, c2)) def _unite_all(self) -> None: """ Unite all the cells that share an obstruction, a requirement list, a row or a column. """ self._unite_obstructions() self._unite_requirements() self._unite_assumptions() self._unite_rows_and_cols() def get_components(self) -> Tuple[Set[Cell], ...]: """ Returns the tuple of all the components. Each component is set of cells. """ if self._components is not None: return self._components self._unite_all() all_components: Dict[Cell, Set[Cell]] = defaultdict(set) for cell in self._active_cells: rep = self._get_cell_representative(cell) all_components[rep].add(cell) self._components = tuple(all_components.values()) return self._components def _get_factors_obs_and_reqs( self, ) -> List[ Tuple[ Tuple[GriddedPerm, ...], Tuple[ReqList, ...], Tuple[TrackingAssumption, ...] ], ]: """ Returns a list of all the irreducible factors of the tiling. Each factor is a tuple (obstructions, requirements) """ if self._factors_obs_and_reqs is not None: return self._factors_obs_and_reqs factors = [] for component in self.get_components(): obstructions = tuple( ob for ob in self._tiling.obstructions if ob.pos[0] in component ) requirements = tuple( req for req in self._tiling.requirements if req[0].pos[0] in component ) # TODO: consider skew/sum assumptions assumptions = tuple( ass.__class__(gp for gp in ass.gps if gp.pos[0] in component) for ass in self._tiling.assumptions ) factors.append( ( obstructions, requirements, tuple(set(ass for ass in assumptions if ass.gps)), ) ) self._factors_obs_and_reqs = factors return self._factors_obs_and_reqs def factorable(self) -> bool: """ Returns `True` if the tiling has more than one factor. """ return len(self.get_components()) > 1 def factors(self) -> Tuple["Tiling", ...]: """ Returns all the irreducible factors of the tiling. """ return tuple( self._tiling.__class__( obstructions=f[0], requirements=f[1], assumptions=f[2], simplify=False ) for f in self._get_factors_obs_and_reqs() ) def reducible_factorisations(self) -> Iterator[Tuple["Tiling", ...]]: """ Iterator over all reducible factorisation that can be obtained by grouping of irreducible factors. Each factorisation is a list of tiling. For example if T = T1 x T2 x T3 then (T1 x T3) x T2 is a possible reducible factorisation. """ min_comp = self._get_factors_obs_and_reqs() for partition in partitions_iterator(min_comp): factors = [] for part in partition: obstructions, requirements, assumptions = zip(*part) factors.append( self._tiling.__class__( obstructions=chain(*obstructions), requirements=chain(*requirements), assumptions=chain(*assumptions), simplify=False, ) ) yield tuple(factors)
def factor(tiling, **kwargs): """ The factor strategy that decomposes a tiling into its connected factors. The factors are the connected components of the graph of the tiling, where vertices are the cells. Two vertices are connected if there exists a obstruction or requirement occupying both cells. Two cells are also connected if they share the same row or column unless the interleaving or point_interleaving keyword arguments are set to True. When point interleavings are allowed, two cells in the same row or column are not connected. When general interleavings are allowed, two cells in the same row or column are not connected. """ interleaving = kwargs.get("interleaving", False) point_interleaving = kwargs.get("point_interleaving", False) n, m = tiling.dimensions def cell_to_int(cell): return cell[0] * m + cell[1] def int_to_cell(i): return (i // m, i % m) cells = list(tiling.active_cells) uf = UnionFind(n * m) # Unite by obstructions for ob in tiling.obstructions: for i in range(len(ob.pos)): for j in range(i+1, len(ob.pos)): uf.unite(cell_to_int(ob.pos[i]), cell_to_int(ob.pos[j])) # Unite by requirements for req_list in tiling.requirements: req_cells = list(union_reduce(req.pos for req in req_list)) for i in range(len(req_cells)): for j in range(i + 1, len(req_cells)): uf.unite(cell_to_int(req_cells[i]), cell_to_int(req_cells[j])) # If interleaving not allowed, unite by row/col if not interleaving: for i in range(len(cells)): for j in range(i+1, len(cells)): c1, c2 = cells[i], cells[j] if (point_interleaving and (c1 in tiling.point_cells or c2 in tiling.point_cells)): continue if c1[0] == c2[0] or c1[1] == c2[1]: uf.unite(cell_to_int(c1), cell_to_int(c2)) # Collect the connected components of the cells all_components = {} for cell in cells: i = uf.find(cell_to_int(cell)) if i in all_components: all_components[i].append(cell) else: all_components[i] = [cell] component_cells = list(set(cells) for cells in all_components.values()) # If the tiling is a single connected component if len(component_cells) <= 1: return # Collect the factors of the tiling factors = [] strategy = [] # the vanilla factors for cell_component in component_cells: obstructions = [ob for ob in tiling.obstructions if ob.pos[0] in cell_component] requirements = [req for req in tiling.requirements if req[0].pos[0] in cell_component] if obstructions or requirements: factors.append((obstructions, requirements)) strategy.append(Tiling(obstructions=obstructions, requirements=requirements, minimize=False)) if kwargs.get("workable", True): work = [True for _ in strategy] else: work = [False for _ in strategy] yield Rule("The factors of the tiling.", strategy, inferable=[False for _ in strategy], workable=work, possibly_empty=[False for _ in strategy], ignore_parent=kwargs.get("workable", True), constructor='cartesian') if kwargs.get("unions", False): for partition in partition_list(factors): strategy = [] for part in partition: obstructions, requirements = zip(*part) strategy.append(Tiling(obstructions=chain(*obstructions), requirements=chain(*requirements), minimize=False)) yield Rule("The union of factors of the tiling", strategy, possibly_empty=[False for _ in strategy], inferable=[False for _ in strategy], workable=[False for _ in strategy], constructor='cartesian')