Source code for alhambra.fastreduceD

# type: ignore

from __future__ import annotations

from copy import copy
from dataclasses import dataclass

# from turtle import st
from typing import (
    Any,
    Iterable,
    Literal,
    Protocol,
    Sequence,
    Type,
    TypeVar,
    cast,
    overload,
    TYPE_CHECKING,
)

if TYPE_CHECKING:
    from .tilesets import TileSet

import numpy as np

if False:
    from .tilesets import TileSet

import logging
from random import shuffle

import numpy.typing as npt

# from ..alhambra_old.alhambra_old import fastlatticedefect as fld
# from ..alhambra_old.alhambra_old import util
from .glues import Glue, GlueList, Use
from .tiles import HDupleTile, SingleTile, Tile, TileList, VDupleTile

[docs]TMap = dict[tuple[int, int], set[tuple[int, int, int]]]
@dataclass
[docs]class FTile:
[docs] name: str
[docs] used: bool
[docs] glues: npt.NDArray[np.int64]
[docs] use: npt.NDArray[np.int64]
[docs] color: Any
[docs] structure: Type[Tile]
[docs] dfake: int
[docs] sfake: int
@dataclass
[docs]class FTilesArray:
[docs] color: Any
[docs] use: np.ndarray[Any, np.dtype[np.int64]]
[docs] glues: np.ndarray[Any, np.dtype[np.int64]]
[docs] name: np.ndarray[Any, np.dtype[np.str0]]
[docs] used: np.ndarray[Any, np.dtype[np.bool8]]
[docs] structure: np.ndarray[Any, np.dtype[np.str0]]
[docs] dfake: np.ndarray[Any, np.dtype[np.int64]]
[docs] sfake: np.ndarray[Any, np.dtype[np.int64]]
[docs] def __len__(self) -> int: return len(self.name)
@classmethod
[docs] def from_ftiles(cls, ftiles: Iterable[FTile]) -> FTilesArray: color = np.array([x.color for x in ftiles]) name = np.array([x.name for x in ftiles]) glues = np.array([x.glues for x in ftiles]) use = np.array([x.use for x in ftiles]) used = np.array([x.used for x in ftiles], dtype=bool) structure = np.array([x.structure for x in ftiles]) dfake = np.array([x.dfake for x in ftiles]) sfake = np.array([x.sfake for x in ftiles]) return FTilesArray( color=color, name=name, glues=glues, use=use, used=used, structure=structure, dfake=dfake, sfake=sfake, )
[docs]TAU = 2
[docs]other = [2, 3, 0, 1]
[docs]class TileWithUse(Protocol): @property
[docs] def use(self) -> list[int]: ...
# Use is now going to have 4 possible values: Null, Input, Output, Both, Permanent
[docs]uU = 0
[docs]uN = 1
[docs]uI = 2
[docs]uO = 3
[docs]uB = 4
[docs]uP = 5
# null, input, output, both, permanent
[docs]invertuse = [0, 1, 3, 2, 4, 5]
[docs]usedict = { "U": uU, "N": uN, "I": uI, "O": uO, "B": uB, "u": uU, "n": uN, "i": uI, "o": uO, "b": uB, }
[docs]Equiv = npt.NDArray[np.int64]
[docs]class FGlueList: def __init__(self, glues: GlueList[Glue]): name = [] strength = [] type = [] complement = [] use = [] self.tonum = {} # Ensure every glue has a complement newglues = GlueList() for g in glues: if g.complement.ident() not in glues.data.keys(): newglues.add(g.complement) glues.update(newglues) for i, g in enumerate(glues): g = cast(Glue, g) name.append(g.ident()) type.append(g.type) self.tonum.update({g.ident(): i}) strength.append(g.abstractstrength if g.abstractstrength is not None else 1) if g.use is None: use.append(uB) else: use.append(g.use.value) for gn in name: complement.append(name.index(gn + "*" if gn[-1] != "*" else gn[:-1])) self.name = np.array(name) self.strength = np.array(strength) self.type = np.array(type) self.complement = np.array(complement) self.use = np.array(use)
[docs] def blankequiv(self) -> Equiv: return np.arange(0, len(self.name))
[docs] def iseq(self, equiv: Equiv, a: int, b: int) -> bool: return equiv[a] == equiv[b]
[docs] def domerge(self, equiv: Equiv, a: int, b: int, preserveuse: bool = False) -> Equiv: if self.type[a] != self.type[b]: raise ValueError("structure") elif self.strength[a] != self.strength[b]: raise ValueError("strength") elif equiv[a] == equiv[self.complement[b]]: raise ValueError("self-comp") elif preserveuse and (self.use[a] != self.use[b]): raise ValueError("use") else: equiv = copy(equiv) newg, oldg = sorted((equiv[a], equiv[b])) newc, oldc = sorted((equiv[self.complement[a]], equiv[self.complement[b]])) equiv[equiv == oldg] = newg equiv[equiv == oldc] = newc return equiv
# U_UNUSED = -1 # U_INPUT = 1 # U_OUTPUT = 0
[docs]class FTileList:
[docs] stiles: FTilesArray
[docs] htiles: FTilesArray
[docs] vtiles: FTilesArray
def __init__(self, tiles: TileList[Tile], gluelist: FGlueList): self.tiles: list[FTile] = [] self.totile: dict[str, FTile] = {} for t in tiles: glues = np.array([gluelist.tonum[x.ident()] for x in t.edges]) if t.is_fake: continue if t.use is None: # if "input" in t.keys(): # use = np.array([[uO, uI][int(x)] for x in t["input"]]) # used = True # else: used = False use = np.array([uU for _ in t.edges]) else: used = True use = np.array([x.value if x is not None else uB for x in t.use]) # color = "label" in t.keys() self.tiles.append( FTile( name=t.ident(), color=False, # FIXME use=use, glues=glues, used=used, structure=t.__class__, dfake=0, sfake=0, ) ) assert t.name not in self.totile.keys() self.totile[t.name] = self.tiles[-1] stiles = [] htiles = [] vtiles = [] for t in self.tiles: if issubclass(t.structure, SingleTile): stiles.append(t) elif issubclass(t.structure, HDupleTile): stiles += _ffakesingle(t, gluelist) htiles.append(t) elif issubclass(t.structure, VDupleTile): stiles += _ffakesingle(t, gluelist) vtiles.append(t) else: raise NotImplementedError self.stiles = FTilesArray.from_ftiles(stiles) for i in range(0, len(self.stiles)): x, y = _ffakedouble_n(i, self.stiles, gluelist) htiles += x vtiles += y self.htiles = FTilesArray.from_ftiles(htiles) self.vtiles = FTilesArray.from_ftiles(vtiles)
[docs]RSEL = (2, 3, 0, 1)
[docs]FTI = (1, 0, 1, 0)
[docs]FTS = (VDupleTile, HDupleTile, VDupleTile, HDupleTile)
[docs]def _fdg(dir, gs1, gs2): if dir == 0: return [gs2[0], gs2[1], gs1[1], gs1[2], gs1[3], gs2[3]] elif dir == 1: return [gs1[0], gs2[0], gs2[1], gs2[2], gs1[2], gs1[3]] elif dir == 2: return [gs1[0], gs1[1], gs2[1], gs2[2], gs2[3], gs1[3]] elif dir == 3: return [gs2[0], gs1[0], gs1[1], gs1[2], gs2[2], gs2[3]] else: raise ValueError(dir)
@overload
[docs]def _ffakedouble_n( tn: int, sta, gluelist: FGlueList, outputonly: bool = True, *, dir4: Literal[True], equiv=None, ) -> tuple[list[FTile], list[FTile], list[FTile], list[FTile]]: ...
@overload def _ffakedouble_n( tn: int, sta, gluelist: FGlueList, outputonly: bool = True, *, dir4: Literal[False] = False, equiv=None, ) -> tuple[list[FTile], list[FTile]]: ... def _ffakedouble_n( tn: int, sta, gluelist: FGlueList, outputonly: bool = True, *, dir4: bool = False, equiv=None, ) -> tuple[list[FTile], list[FTile]] | tuple[ list[FTile], list[FTile], list[FTile], list[FTile] ]: if equiv is None: equiv = gluelist.blankequiv() if not dir4: faketiles: tuple[list[FTile], list[FTile]] | tuple[ list[FTile], list[FTile], list[FTile], list[FTile] ] = ([], []) fti = FTI else: faketiles = ( [], [], [], [], ) fti = (0, 1, 2, 3) if sta.sfake[tn]: ddir = np.flatnonzero(gluelist.tonum["fakedouble"] == sta.glues[tn])[0] for dir in range(0, 4): if (sta.use[tn, dir] == uI) and outputonly: continue if outputonly: oti = np.nonzero( ( equiv[gluelist.complement[sta.glues[tn, dir]]] == equiv[sta.glues[:, RSEL[dir]]] ) & (sta.use[:, RSEL[dir]] == uI) & (sta.used) ) else: oti = np.nonzero( ( equiv[gluelist.complement[sta.glues[tn, dir]]] == equiv[sta.glues[:, RSEL[dir]]] ) ) for i in oti[0]: faketiles[fti[dir]].append( FTile( color=False, used=True, name=sta.name[tn] + "_{}_".format(dir) + sta.name[i], structure=FTS[dir], glues=np.array(_fdg(dir, sta.glues[tn, :], sta.glues[i])), use=np.array(_fdg(dir, sta.use[tn, :], sta.use[i])), dfake=dir + 1, sfake=(sta.sfake[tn] or sta.sfake[i]), ) ) if sta.sfake[tn] and (dir == ddir): # type: ignore faketiles[fti[dir]].append( FTile( color=False, used=True, name=sta.name[tn] + "_{}_".format(dir) + sta.name[tn + sta.sfake[tn]], structure=FTS[dir], glues=np.array( _fdg( dir, sta.glues[tn, :], sta.glues[tn + sta.sfake[tn]], ) ), use=np.array( _fdg( dir, sta.use[tn, :], sta.use[tn + sta.sfake[tn]], ) ), dfake=dir + 1, sfake=True, ) ) return faketiles # THESE SELECTORS ARE 1-INDEXED!!! 0 corresponds to fake double tile bond.
[docs]HSEL = ((1, 0, 5, 6), (2, 3, 4, 0))
[docs]VSEL = ((1, 2, 0, 6), (0, 3, 4, 5))
[docs]def _ffakesingle(ftile: FTile, gluelist: FGlueList) -> Sequence[FTile]: # FIXME: should be more generalized. Currently only tau=2 if issubclass(ftile.structure, VDupleTile): sel = HSEL elif issubclass(ftile.structure, HDupleTile): sel = VSEL else: raise NotImplementedError # Start by making the tiles, then change around the inputs fdb = gluelist.tonum["fakedouble"] glues: list[list[int]] = [[([fdb] + list(ftile.glues))[x] for x in y] for y in sel] fuse = [[([uP] + list(ftile.use))[x] for x in y] for y in sel] use = [] used = [] names = [ftile.name + "_fakedouble_a", ftile.name + "_fakedouble_b"] for gu in zip(glues, fuse): if sum(gluelist.strength[g] for g, u in zip(*gu) if u == uI) >= TAU: use.append(gu[1]) used.append(True) else: use.append(gu[1]) used.append(False) return [ FTile( color=ftile.color, use=u, glues=np.array(g), name=n, used=ud, structure=SingleTile, dfake=0, sfake=sfo, ) for u, g, n, ud, sfo in zip(use, glues, names, used, [1, -1]) ]
[docs]class _FastTileSet:
[docs] gluelist: FGlueList
[docs] tilelist: FTileList
def __init__(self, tilesystem: "TileSet"): self.gluelist = FGlueList( tilesystem.allglues + [ # End({"name": "hp", "type": "hairpin", "strength": 0}), Glue("fakedouble", "fakedouble", abstractstrength=0, use=Use.PERMANENT) ] ) self.tilelist = FTileList( tilesystem.tiles # + sum([x.named_rotations() for x in tilesystem.tiles], TileList()) # FIXME , self.gluelist, )
[docs] def applyequiv(self, ts: TileSet, equiv) -> TileSet: ts = ts.copy() alreadythere: list[tuple[list[Glue], bool]] = [] for tile in ts.tiles: tile.edges = [ self.gluelist.name[equiv[self.gluelist.tonum[e.ident()]]] for e in tile.edges ] if (tile.edges, False) in alreadythere: # FIXME: False was label tile.fake = True continue rs = [tile] # + tile.rotations FIXME alreadythere.extend((list(t.edges), False) for t in rs) # FIXME # if "seed" in ts.keys(): # for t in ts.seed["adapters"]: # if "ends" in t.keys(): # t["ends"] = [ # self.gluelist.name[equiv[self.gluelist.tonum[e]]] # for e in t["ends"] # ] # ts["info"] = ts.get("info", dict()) # ts["info"]["fgluemerge"] = ts["info"].get("fgluemerge", list()) # ts["info"]["fgluemerge"].append([int(x) for x in equiv]) return ts
[docs] def togluemergespec(self, ts, equiv): gms = util.GlueMergeSpec() for i in range(0, len(equiv)): if i != equiv[i]: gms.add(self.gluelist.name[equiv[i]], self.gluelist.name[i]) return gms
[docs]def ptins( fts: _FastTileSet, equiv: Equiv | None = None, tau=2 ) -> list[list[np.ndarray[Any, np.dtype[np.int64]]]]: """Calculate potential tile attachments to input neighborhoods""" ptins = [] if equiv is None: equiv = fts.gluelist.blankequiv() for ta in [fts.tilelist.stiles, fts.tilelist.htiles, fts.tilelist.vtiles]: ptin = [] ti: int for ti in np.arange(0, len(ta.used))[ta.used]: # Only iterate through used tiles isel = ta.use[ti] == uI # used edges gsel = equiv[ta.glues[ti, isel]] == equiv[ta.glues[:, isel]] # matching glues strs = np.sum( fts.gluelist.strength[ta.glues[ti, isel]] * gsel, axis=1 ) # strengths of matching matches = np.nonzero((strs >= tau) & (ta.dfake == 0)) # indices of matching # (excludes fake doubles, which can't actually attach # (because they are # actually two singles) to their own local neighborhoods) ptin.append(matches) ptins.append(ptin) return ptins
[docs]def gmatch(fts, g1, g2, equiv=None): if equiv is None: equiv = fts.gluelist.blankequiv() return equiv[g1] == equiv[g2]
[docs]def gcomp(fts, g1, g2, equiv=None): if equiv is None: equiv = fts.gluelist.blankequiv() return equiv[fts.gluelist.complement[g1]] == equiv[g2]
[docs]def findmovetiles(fts, tilei, direction, allin, allout): use = fts.tilelist.stiles.use[tilei, direction] if use == uN: return {} if use == uP: otherdoublei = tilei + fts.tilelist.stiles.sfake[tilei] return {(otherdoublei, allin, allout, other[direction])} if use == uI: tilematches = np.nonzero( gcomp( fts, fts.tilelist.stiles.glues[:, other[direction]], fts.tilelist.stiles.glues[tilei, direction], ) & ( (fts.tilelist.stiles.use[:, other[direction]] == uO) | (fts.tilelist.stiles.use[:, other[direction]] == uB) ) )[0] return {(tmatch, allin, False, other[direction]) for tmatch in tilematches} if use == uO: tilematches = np.nonzero( gcomp( fts, fts.tilelist.stiles.glues[:, other[direction]], fts.tilelist.stiles.glues[tilei, direction], ) & ( (fts.tilelist.stiles.use[:, other[direction]] == uI) | (fts.tilelist.stiles.use[:, other[direction]] == uB) ) )[0] return {(tmatch, False, allout, other[direction]) for tmatch in tilematches} if use == uB: tilematches = np.nonzero( gcomp( fts, fts.tilelist.stiles.glues[:, other[direction]], fts.tilelist.stiles.glues[tilei, direction], ) & ( (fts.tilelist.stiles.use[:, other[direction]] == uI) | (fts.tilelist.stiles.use[:, other[direction]] == uO) | (fts.tilelist.stiles.use[:, other[direction]] == uB) ) )[0] return {(tmatch, allin, allout, other[direction]) for tmatch in tilematches}
[docs]def _2go_moveandfill( fts: _FastTileSet, t: int, i: int, allin=True, allout=True, pdir=None, x=0, y=0, old_d=None, ) -> TMap: if old_d is None: d: dict[tuple[int, int], set[Any]] = dict() else: d = old_d s: set[tuple[int, int, int]] = d.get((x, y), set()) d[(x, y)] = s for edge in (0, 1, 2, 3): if allin: continue elif edge == pdir: if (fts.tilelist.stiles.use[t, edge] == uI) & (not allout): s.add((edge, fts.tilelist.stiles.glues[t, edge], t)) elif fts.tilelist.stiles.use[t, edge] == uI: s.add((edge, fts.tilelist.stiles.glues[t, edge], t)) if i == 0: return d for edge in (0, 1, 2, 3): if edge == pdir: continue dx, dy = directions[edge] moves = findmovetiles(fts, t, edge, allin, allout) # assert moves is not None # FIXME if moves is None: continue # FIXME for newt, newallin, newallout, newpdir in moves: d = _2go_moveandfill( fts, newt, i - 1, newallin, newallout, newpdir, x + dx, y + dy, d ) return d
[docs]def _2go_findtrialmoves(fts: _FastTileSet, tilei, direction, equiv=None): use = fts.tilelist.stiles.use[tilei, direction] if use == uN: return {} if use == uP: otherdoublei = tilei + fts.tilelist.stiles.sfake[tilei] return { (otherdoublei, 0, other[direction]) } # other tile, (is not a new tile -> 0), prev direction else: tilematches = np.nonzero( gcomp( fts, fts.tilelist.stiles.glues[:, other[direction]], fts.tilelist.stiles.glues[tilei, direction], equiv, ) )[0] return {(tmatch, 1, other[direction]) for tmatch in tilematches}
[docs]directions = [(0, -1), (1, 0), (0, 1), (-1, 0)]
[docs]def _2go_checkandmove(fts, ct, x, y, i, tmap: TMap, exclude=tuple(), equiv=None): for direction in (0, 1, 2, 3): if direction in exclude: continue if (fts.tilelist.stiles.use[ct, direction] == uP) | ( fts.tilelist.stiles.use[ct, direction] == uN ): continue for d, mg, ot in tmap.get((x, y), set()): if d != direction: continue # FIXME optimize out if gmatch(fts, mg, fts.tilelist.stiles.glues[ct, direction], equiv): return True, ot, ct, x, y # Move to next, if we still have moves if i == 0: return False, None, None, None, None # reached the end for direction in (0, 1, 2, 3): if direction in exclude: continue tm = _2go_findtrialmoves(fts, ct, direction, equiv) dx, dy = directions[direction] for newct, inccount, newexclude in tm: # print("checkandmove({}, {}, {}, {},tmap, ({}))".format(newct, x+dx, y+dy, i-inccount, newexclude)) r = _2go_checkandmove( fts, newct, x + dx, y + dy, i - inccount, tmap, (newexclude,), equiv ) if r[0] is not False: return r return False, None, None, None, None
@overload
[docs]def is_2go_nn( fts: _FastTileSet, tn: int, un: int, equiv: np.ndarray[Any, np.dtype[np.int64]], tau: int = 2, tmaps: dict[int, TMap] | None = None, *, retall: Literal[True], also22go: bool = False, ) -> tuple[bool, tuple[Any, Any] | None]: ...
@overload def is_2go_nn( fts: _FastTileSet, tn: int, un: int, equiv: np.ndarray[Any, np.dtype[np.int64]], tau: int = 2, tmaps: dict[int, TMap] | None = None, *, retall: Literal[False] = False, also22go: bool = False, ) -> bool: ... def is_2go_nn( fts: _FastTileSet, tn: int, un: int, equiv: np.ndarray[Any, np.dtype[np.int64]], tau: int = 2, tmaps: dict[int, TMap] | None = None, *, retall: bool = False, also22go: bool = False, ) -> bool | tuple[bool, tuple[Any, Any] | None]: if also22go: raise NotImplementedError() # Before starting, check to see if t and u are actually the same: if np.all( equiv[fts.tilelist.stiles.glues[tn]] == equiv[fts.tilelist.stiles.glues[un]] ): # if not also22go: if not retall: return False else: return False, None # else: # if not retall: # return False, False # else: # return False, False, None, None if tmaps is not None: tmap = tmaps[tn] else: tmap = _2go_moveandfill(fts, tn, 2) res = _2go_checkandmove(fts, un, 0, 0, 1, tmap=tmap, exclude=tuple(), equiv=equiv) if res[0] is False: if not retall: return False else: return False, None else: if not retall: return True else: return True, (res[1], res[2])
[docs]def gen_2go_maps(fts: _FastTileSet) -> dict[int, TMap]: sel: np.ndarray[Any, np.dtype[np.int64]] = np.flatnonzero(fts.tilelist.stiles.used) maps: dict[int, TMap] = dict() for t in sel: maps[t] = _2go_moveandfill(fts, t, 2) return maps
[docs]def gen_2go_profile(fts: _FastTileSet, equiv=None, tmaps=None, also22go: bool = False): if tmaps is None: tmaps = gen_2go_maps(fts) if equiv is None: equiv = fts.gluelist.blankequiv() sens1s = ptins(fts, tau=1, equiv=equiv)[0] sens2s = [] sens22s = [] sel = np.flatnonzero(fts.tilelist.stiles.used) for tn, uns in enumerate(sens1s): t = sel[tn] x2 = [] x22 = [] for un in uns[0]: if also22go: s2, s22 = is_2go_single_nn( # FIXME: this was still 2go_single; why? fts, t, un, equiv, tau=2, also22go=True ) else: s2 = is_2go_nn(fts, t, un, equiv, tmaps=tmaps, also22go=False) if s2: x2.append(un) if also22go and s22: x22.append(un) sens2s.append(x2) if also22go: sens22s.append(x22) if also22go: return sens2s, sens22s else: return sens2s
[docs]def is_2go_equiv( fts: _FastTileSet, equiv: Equiv | None = None, tmaps=None, origsens=None ): if tmaps is None: tmaps = gen_2go_maps(fts) if equiv is None: equiv = fts.gluelist.blankequiv() if origsens is None: origsens = gen_2go_profile(fts, tmaps=tmaps) sens1s = ptins(fts, tau=1, equiv=equiv)[0] sel = np.flatnonzero(fts.tilelist.stiles.used) for tn, (uns, os) in enumerate(zip(sens1s, origsens)): t = sel[tn] for un in uns[0]: r, p = is_2go_nn(fts, t, un, equiv, tmaps=tmaps, retall=True) if r and (un not in os): # Next if checks to see if sensitive tile is actually identical # to an already sensitive tile: # FIXME: this would not be necessary if we stored whether a # tile was a duplicate. However, that would break the # glue-equiv-only method... if not np.any( np.all( equiv[fts.tilelist.stiles.glues[os]] == equiv[fts.tilelist.stiles.glues[un]], axis=1, ) ): return ( False, ( fts.tilelist.stiles.name[fts.tilelist.stiles.used][tn], fts.tilelist.stiles.name[un], ), [fts.tilelist.stiles.name[x] for x in p], ) return True, None, None
[docs]def is_22go_equiv(fts, equiv=None, ins2go=None, orig22go=None): if ins2go is None: ins2go = gen_2go_single_ins(fts, tau=2) if equiv is None: equiv = fts.gluelist.blankequiv() if orig22go is None: _, orig22go = gen_2go_profile(fts, also22go=True) sens1s = ptins(fts, tau=1, equiv=equiv)[0] for tn, (uns, in2go, os22) in enumerate(zip(sens1s, ins2go, orig22go)): for un in uns[0]: r2, r22, p2, p22 = is_2go_single_nn( fts, tn, un, equiv, tau=2, in2go=in2go, also22go=True, retall=True ) if r22 and (un not in os22): # Next if checks to see if sensitive tile is actually identical # to an already sensitive tile: # FIXME: this would not be necessary if we stored whether a # tile was a duplicate. However, that would break the # glue-equiv-only method... if not np.any( np.all( equiv[fts.tilelist.stiles.glues[os22]] == equiv[fts.tilelist.stiles.glues[un]], axis=1, ) ): return ( False, ( fts.tilelist.stiles.name[fts.tilelist.stiles.used][tn], fts.tilelist.stiles.name[un], ), p22, ) return True, None, None
[docs]def fta_to_ft(stiles, un, sel=None): if sel is None: sel = np.ones_like(stiles.used, dtype=bool) return FTile( color=stiles.color[sel][un], use=stiles.use[sel, :][un, :], glues=stiles.glues[sel, :][un, :], name=stiles.name[sel][un], used=stiles.used[sel][un], structure=stiles.structure[sel][un], dfake=stiles.dfake[sel][un], sfake=stiles.sfake[sel][un], )
[docs]def isatamequiv(fts, equiv, initptins=None): if initptins is None: initptins = ptins(fts, fts.gluelist.blankequiv()) npt = ptins(fts, equiv) for x, y, tl in zip( initptins, npt, [fts.tilelist.stiles, fts.tilelist.htiles, fts.tilelist.vtiles] ): for xx, yy in zip(x, y): if len(xx[0]) == len(yy[0]) and (np.all(xx[0] == yy[0])): # No change continue elif len(xx[0]) == 1: # Deterministic start mm = ~np.all((equiv[tl.glues[xx]] == equiv[tl.glues[yy]]), axis=1) | ~( tl.color[xx] == tl.color[yy] ) if np.any(mm): return False, (tl.name[xx[0][0]], tl.name[yy[0][mm][0]]) elif len(xx[0]) == 0: return False, None else: raise NotImplementedError(xx) return True, None
[docs]def tilemerge(fts, equiv, t1, t2, preserveuse=False): if t1.structure.name != t2.structure.name: raise ValueError if t1.color != t2.color: raise ValueError for g1, g2 in zip(t1.glues, t2.glues): equiv = fts.gluelist.domerge(equiv, g1, g2, preserveuse=preserveuse) return equiv
[docs]def _findpotentialtilemerges(fts, equiv): ppairs = [] for ti in range(0, len(fts.tilelist.tiles)): t1 = fts.tilelist.tiles[ti] if not t1.used: continue ppairs.extend( (t1, t) for t in fts.tilelist.tiles[ti:] if (t1.color == t.color) and (t1.structure.name == t.structure.name) and (t1.name != t.name) ) shuffle(ppairs) return ppairs
[docs]def _findpotentialgluemerges(fts, equiv): ppairs = [] for g1 in np.arange(0, len(fts.gluelist.strength)): g2s = ( g1 + 1 + np.nonzero( (fts.gluelist.strength[g1 + 1 :] == fts.gluelist.strength[g1]) & (fts.gluelist.structure[g1 + 1 :] == fts.gluelist.structure[g1]) )[0] ) ppairs.extend((g1, g2) for g2 in g2s) shuffle(ppairs) return ppairs
[docs]def _recfix( fts, equiv, tp, initptins, check2go=False, tmaps=None, orig2go=None, orig22go=None, check22go=False, checkld=False, chain=None, preserveuse=False, ): log = logging.getLogger(__name__) if chain is None: chain = [] equiv = tilemerge( fts, equiv, fts.tilelist.totile[tp[0]], fts.tilelist.totile[tp[1]], preserveuse=preserveuse, ) ae, badpair = isatamequiv(fts, equiv, initptins=initptins) if check2go and ae: ae, badpair, _ = is_2go_equiv(fts, equiv, tmaps=tmaps, origsens=orig2go) if ae and check22go: ae, badpair, _ = is_22go_equiv(fts, equiv, tmaps=tmaps, orig22go=orig22go) if ae and checkld: ld_e = fld.latticedefects(fts, "e", equiv=equiv) ld_w = fld.latticedefects(fts, "w", equiv=equiv) if (len(ld_e) > 0) or (len(ld_w) > 0): raise ValueError if ae: log.debug("Recfix succeeds with {}, {}, {}".format(tp[0], tp[1], chain)) return equiv elif badpair is None: raise ValueError else: chain.append(tp) return _recfix( fts, equiv, badpair, initptins, check2go, tmaps, orig2go, orig22go=orig22go, check22go=check22go, checkld=checkld, chain=chain, preserveuse=preserveuse, )
[docs]def _tilereduce( fts, equiv=None, check2go=False, initptins=None, check22go=False, checkld=False, preserveuse=False, ): log = logging.getLogger(__name__) if equiv is None: equiv = fts.gluelist.blankequiv() todo = _findpotentialtilemerges(fts, equiv) if initptins is None: initptins = ptins(fts) if check2go: tmaps = gen_2go_maps(fts) origsens = gen_2go_profile(fts, tmaps=tmaps) else: tmaps = None origsens = None for todoi, (t1, t2) in enumerate(todo): try: nequiv = tilemerge(fts, equiv, t1, t2, preserveuse=preserveuse) except ValueError: continue ae, badpair = isatamequiv(fts, nequiv, initptins=initptins) if ae and check2go: ae, badpair, _ = is_2go_equiv(fts, nequiv, tmaps, origsens=origsens) if ae and check22go: ae, badpair, _ = is_22go_equiv( fts, nequiv, ins2go=ins2go, orig22go=orig22go ) if ae and checkld: ld_e = fld.latticedefects(fts, "e", equiv=nequiv) ld_w = fld.latticedefects(fts, "w", equiv=nequiv) if (len(ld_e) > 0) or (len(ld_w) > 0): continue if ae: equiv = nequiv log.debug( "Reduced {}, {} ({} of {} pairs done)".format( t1.name, t2.name, todoi, len(todo) ) ) elif badpair is None: continue else: try: equiv = _recfix( fts, equiv, badpair, initptins, check2go, tmaps, origsens, check22go=check22go, checkld=checkld, chain=[(t1.name, t2.name)], preserveuse=preserveuse, ) except ValueError: continue except KeyError: continue return equiv
[docs]def _gluereduce( fts: _FastTileSet, equiv: Equiv | None = None, check2go: bool = False, check22go: bool = False, checkld: bool = False, initptins=None, preserveuse: bool = False, ): log = logging.getLogger(__name__) if equiv is None: equiv = fts.gluelist.blankequiv() todo = _findpotentialgluemerges(fts, equiv) if initptins is None: initptins = ptins(fts) if check2go: tmaps = gen_2go_maps(fts) origsens = gen_2go_profile(fts, tmaps=tmaps) else: ins2go = None origsens = None orig22go = None tmaps = None for todoi, (g1, g2) in enumerate(todo): try: nequiv = fts.gluelist.domerge(equiv, g1, g2, preserveuse=preserveuse) except ValueError: continue ae, badpair = isatamequiv(fts, nequiv, initptins=initptins) if ae and check2go: ae, badpair, _ = is_2go_equiv(fts, nequiv, tmaps=tmaps, origsens=origsens) if ae and check22go: ae, badpair, _ = is_22go_equiv( fts, nequiv, ins2go=ins2go, orig22go=orig22go ) if ae and checkld: ld_e = fld.latticedefects(fts, "e", equiv=nequiv) ld_w = fld.latticedefects(fts, "w", equiv=nequiv) if (len(ld_e) > 0) or (len(ld_w) > 0): continue if ae: equiv = nequiv log.debug( "Glue reduction: {}, {} ({}/{} done)".format( fts.gluelist.name[g1], fts.gluelist.name[g2], todoi, len(todo) ) ) elif badpair is None: continue else: try: equiv = _recfix( fts, equiv, badpair, initptins, check2go, tmaps, origsens, check22go=check22go, checkld=checkld, chain=[(fts.gluelist.name[g1], fts.gluelist.name[g2])], preserveuse=preserveuse, ) except ValueError: continue except KeyError: continue return equiv
[docs]def _single_reduce_tiles(p): feq, ts, fts, params = p # starttime = time.time() initptins = ptins(fts) e = _tilereduce(fts, equiv=feq, initptins=initptins, **params) # endtime = time.time() # te = fts.applyequiv(ts, e) # nt = len([y for y in te.tiles if 'fake' not in y.keys()]) # ng = len(te.allends) # a = isatamequiv(fts, e) # g2 = is_2go_equiv(fts, e)[0] # g22 = is_22go_equiv(fts, e)[0] # lde = len(fld.latticedefects(fts, equiv=e, direction='e')) == 0 # ldw = len(fld.latticedefects(fts, equiv=e, direction='w')) == 0 # print('TR: Found {}t, {}g: aTAM {} 2GO {} 22GO {} LD {}/{} in {}s'.format( # nt, ng, a, g2, g22, lde, ldw, endtime - starttime)) # te.to_file(nb + 'step1-{}t{}g.yaml'.format(nt, ng)) return e
[docs]def _single_reduce_ends(p): equiv, ts, fts, params = p # starttime = time.time() initptins = ptins(fts) e = _gluereduce(fts, equiv=equiv, initptins=initptins, **params) # FIXME: all this is code for checking things and printing logs. # endtime = time.time() # te = fts.applyequiv(ts, e) # nt = len([y for y in te.tiles if 'fake' not in y.keys()]) # ng = len(te.allends) # a = isatamequiv(fts, e) # g2 = is_2go_equiv(fts, e)[0] # g22 = is_22go_equiv(fts, e)[0] # lde = len(fld.latticedefects(fts, equiv=e, direction='e')) == 0 # ldw = len(fld.latticedefects(fts, equiv=e, direction='w')) == 0 # print('ER: Found {}t, {}g: aTAM {} 2GO {} 22GO {} LD {}/{} in {}s'.format( # nt, ng, a, g2, g22, lde, ldw, endtime - starttime)) # te.to_file(nb + '-step2-{}t{}g.yaml'.format(nt, ng)) return e
[docs]def reduce_tiles( tileset, preserve=("s22", "ld"), tries=10, threads=1, returntype="equiv", best=1, key=None, initequiv=None, ): """ Apply tile reduction algorithm, preserving some set of properties, and using a multiprocessing pool. Parameters ---------- tileset: TileSet The system to reduce. preserve: a tuple or list of strings, optional The properties to preserve. Currently supported are 's1' for first order sensitivity, 's2' for second order sensitivity, 's22' for two-by-two sensitivity, 'ld' for small lattice defects, and 'gs' for glue sense (to avoid spurious hierarchical attachment). Default is currently ('s22', 'ld'). tries: int, optional The number of times to run the algorithm. threads: int, optional The number of threads to use (using multiprocessing). returntype: 'TileSet' or 'equiv' (default 'equiv') The type of object to return. If 'equiv', returns an array of glue equivalences (or list, if best != 1) that can be applied to the tileset with apply_equiv, or used for further reduction. If 'TileSet', return a TileSet with the equiv already applied (or a list, if best != 1). best: int or None, optional The number of systems to return. If 1, the result will be returned directly; if k > 1, a list will be returned of the best k results (per cmp); if k = None, a list of *all* results will be returned, sorted by cmp. (default 1) key: function (ts, equiv1, equiv2) -> some number/comparable A comparison function for equivs, to sort the results. FIXME: documentation needed. Default (if None) here is to sort by number of glues in the system, regardless of number of tiles. initequiv: equiv If provided, the equivalence array to start from. If None, start from the tileset without any merged glues. Returns ------- reduced: single TileSet The reduced system/systems """ fts = _FastTileSet(tileset) if key is None: key = lambda x: len( np.unique(x) ) # number of unique numbers in equiv, equivalent # to number of glues in reduced system. FIXME? # FIXME: could do a better job here params = { "check2go": "s2" in preserve, "check22go": "s22" in preserve, "checkld": "ld" in preserve, "preserveuse": "gs" in preserve, } if initequiv is None: initequiv = fts.gluelist.blankequiv() if threads > 1: from multiprocessing import Pool with Pool(threads) as pool: equivs = pool.map( _single_reduce_tiles, [[initequiv, tileset, fts, params]] * tries ) else: equivs = [ _single_reduce_tiles(x) for x in ([[initequiv, tileset, fts, params]] * tries) ] equivs.sort(key=key) if returntype == "TileSet": equivs = [tileset.apply_equiv(equiv) for equiv in equivs] if best == 1: return equivs[0] else: return equivs[0:best]
[docs]def reduce_ends( tileset, preserve=("s22", "ld"), tries=10, threads=1, returntype="equiv", best=1, key=None, initequiv=None, ): """ Apply end reduction algorithm, preserving some set of properties, and using a multiprocessing pool. Parameters ---------- tileset: TileSet The system to reduce. preserve: a tuple or list of strings, optional The properties to preserve. Currently supported are 's1' for first order sensitivity, 's2' for second order sensitivity, 's22' for two-by-two sensitivity, 'ld' for small lattice defects, and 'gs' for glue sense (to avoid spurious hierarchical attachment). Default is currently ('s22', 'ld'). tries: int, optional The number of times to run the algorithm. threads: int, optional The number of threads to use (using multiprocessing). returntype: 'TileSet' or 'equiv' (default 'equiv') The type of object to return. If 'equiv', returns an array of glue equivalences (or list, if best != 1) that can be applied to the tileset with apply_equiv, or used for further reduction. If 'TileSet', return a TileSet with the equiv already applied (or a list, if best != 1). best: int or None, optional The number of systems to return. If 1, the result will be returned directly; if k > 1, a list will be returned of the best k results (per cmp); if k = None, a list of *all* results will be returned, sorted by cmp. (default 1) key: function (ts, equiv1, equiv2) -> some number/comparable A comparison function for equivs, to sort the results. FIXME: documentation needed. Default (if None) here is to sort by number of glues in the system, regardless of number of tiles. initequiv: equiv If provided, the equivalence array to start from. If None, start from the tileset without any merged glues. Returns ------- reduced: single TileSet The reduced system/systems """ fts = _FastTileSet(tileset) if key is None: key = lambda x: len( np.unique(x) ) # number of unique numbers in equiv, equivalent # to number of glues in reduced system. FIXME? # FIXME: could do a better job here params = { "check2go": "s2" in preserve, "check22go": "s22" in preserve, "checkld": "ld" in preserve, "preserveuse": "gs" in preserve, } if initequiv is None: initequiv = fts.gluelist.blankequiv() if threads > 1: from multiprocessing import Pool with Pool(threads) as pool: equivs = pool.map( _single_reduce_ends, [[initequiv, tileset, fts, params]] * tries ) else: equivs = [ _single_reduce_ends(x) for x in ([[initequiv, tileset, fts, params]] * tries) ] equivs.sort(key=key) if returntype == "TileSet": equivs = [tileset.apply_equiv(equiv) for equiv in equivs] if best == 1: return equivs[0] else: return equivs[0:best]