import logging as log
import numpy as np
[docs]secondclasses = {
(False, True): "O",
(False, False): "U",
(True, True): "U",
(True, False): "U",
}
[docs]firstsortorder = ["F", "M", "C", "D"]
[docs]secondsortorder = ["f", "b", "r", "d"]
[docs]def senspairs(tileset, **params):
# Begin by changing the tileset into a form useful for our calculations.
tiles = []
for tile in tileset["tiles"]:
# Ignore non-single tiles.
if len(tile["ends"]) != 4:
log.warning("Ignoring non-single tile {0}".format(tile["name"]))
continue
# Make the actual tile list.
ends = np.array(tile["ends"])
inputs = np.array(tile["input"])
tiles.append((tile["name"], ends, inputs))
senslist = []
# Now consider each tile:
for An, Ae, Ai in tiles:
senslist += _find_pairs_from_tile(An, Ae, Ai, tiles, **params)
# Now do something with the senslist.
return senslist
[docs]def _find_pairs_from_tile(An, Ae, Ai, tiles, **params):
"""
Given a tile of name `An`, ends `Ae`, and IO `Ai`, and a list of tiles `tiles`,
find all sensitive end type pairs.
"""
senslist = []
# We need to iterate over tiles (again!):
for Bn, Be, Bi in tiles:
# Skip if the tiles are identical:
if (Be == Ae).all():
if An == Bn:
continue
else:
log.warning("{0} and {1} have *identical* ends.".format(An, Bn))
# Now we need to iterate over each non-inert common side:
for side in (np.nonzero(np.ravel((Ae == Be) & (Ai != -1) & (Bi != -1))))[0]:
senslist += _pairs_commonside(An, Ae, Ai, Bn, Be, Bi, side, tiles, **params)
return senslist
[docs]def _pairs_commonside(An, Ae, Ai, Bn, Be, Bi, common, tiles, order=2, **params):
"""
Given two tiles and a common side, find all sensitive pairs.
"""
senslist = []
# We need to loop over every side except the common one.
for mismatch in range(0, 4):
# Skip the common mismatch
if mismatch == common:
continue
# Skip any inert mismatch
if Ai[mismatch] == -1 or Bi[mismatch] == -1:
continue
# Skip, but warn, about any second common mismatch
if Ae[mismatch] == Be[mismatch]:
log.debug("Tiles {} and {} have two common sides.".format(An, Bn))
continue
# Now we know we have a sensitive pair, and we just need to determine type.
# First order type at this point is entirely IO dependent.
# Second order will require more loops...
# This is the first-order sensitivity type.
if (Ai[common]) >= 1 and (Ai[mismatch]) >= 1:
senstype = "O1"
else:
senstype = "U1"
# Now we need to find second-order
if order == 2:
st1, st2 = (
senstype,
_second_order_type(
An, Ae, Ai, Bn, Be, Bi, common, mismatch, tiles, senstype
),
)
if st1 == "O1" and st2 == "O2":
sens = ("2GO", "2NGO")
elif st1 == "O1" and st2 == "U2":
sens = ("1GO", "2NGO")
elif st1 == "O1" and st2 == "":
sens = ("1GO", "1NGO")
elif st1 == "U1" and st2 == "":
sens = ("0GO", "1NGO")
elif st1 == "U1" and st2 == "U2":
sens = ("0GO", "2NGO")
else:
raise "Impossible situation {},{}".format(st1, st2)
senslist.append((Ae[mismatch], Be[mismatch], sens))
return senslist
[docs]def _second_order_type(An, Ae, Ai, Bn, Be, Bi, common, mismatch, tiles, firsttype):
types = set()
# We need to consider every non-mismatch, non-common side of A:
for internal in range(0, 4):
if internal == common:
continue
if internal == mismatch:
continue
# Now we need to consider every tile that fits there:
for A2n, A2e, A2i in tiles:
if not (A2e[flipdir[internal]] == comp(Ae[internal])):
continue
# And then consider every tile that corresponds...
for B2n, B2e, B2i in tiles:
if B2e[flipdir[internal]] != comp(Be[internal]):
continue
# And now we consider every common side of that except the bond it has with the
# first tile...
for secondedge in (
np.nonzero(np.ravel((A2e == B2e) & (A2i != -1) & (B2i != -1)))
)[0]:
if secondedge == flipdir[internal]:
continue
types.add(secondclasses[(Ai[internal] >= 1, A2i[secondedge] >= 1)])
if len(types) == 0:
return ""
elif "O" in types and firsttype == "O1":
return "O2"
else:
return "U2"
[docs]def consolidate_pairs(senslist, onlytop=False, **params):
# We start out with a [ (e1, e2, type) ] list of sensitivity types.
# We need to consolidate it.
# To begin, we'll make a dict...
sensdict = {"1GO": set(), "2GO": set(), "1NGO": set(), "2NGO": set()}
for end1, end2, senstype in senslist:
# First we need to sort the ends:
if end1 > end2:
end2, end1 = end1, end2
if params["comcomp"] >= 1:
if end1[-1] == "/" and end2[-1] == "/":
end1 = end1[:-1]
end2 = end2[:-1]
if params["comcomp"] == 2:
if end1[-1] == "/" and end2[-1] != "/":
end1 = end1[:-1]
end2 = end2 + "/"
for s in senstype:
if s == "0GO":
continue
sensdict[s].add((end1, end2))
if s == "2GO":
sensdict["1GO"].add((end1, end2))
if s == "2NGO":
sensdict["1NGO"].add((end1, end2))
if onlytop:
sensdict["1NGO"].difference_update(sensdict["1GO"])
sensdict["1NGO"].difference_update(sensdict["2NGO"])
if not sensdict["1GO"].issubset(sensdict["2NGO"]):
log.warning(
"1GO is not a subset of 2NGO. Contains {}.".format(
sensdict["1GO"].difference(sensdict["2NGO"])
)
)
sensdict["2NGO"].difference_update(sensdict["1GO"])
sensdict["1GO"].difference_update(sensdict["2GO"])
return sensdict
[docs]def comp(edge):
if not (type(edge) is np.ndarray):
if edge[-1] == "/":
return edge[:-1]
else:
return edge + "/"
if type(edge) is np.ndarray:
return np.array([comp(x) for x in edge])