As it is, this alone should allow for arbitrary monochromatic monoparity slow salvos, meaning the only other thing we
a way to set up the first period-multiplied signal (as well as suitable xWSS recipes). However, in practice it will be very convenient to have a larger toolkit for manipulating period-multiplied signals. Of the top of my head, it will be nice to have: A way to pass the 16x signal along with our track resets, a 16x block layer (for targets), rake variants for the other colors & parities, and probably a rephaser. This did also involve some manual assembly since my tools can't really combine 1x and 16x parts conveniently yet, so it'll be important to fix that too.
Alright, here's a script to generate 16x rakes for all colors and parities.
Code: Select all
import golly as g
from glife import *
import math
import cmath
import os
from scipy import signal
def mod(x,m):
return ((x%m)+m)%m
#Linear algebra stuff
#Wanted to do this myself rather than using a library
#Whether or not this was smart is debatable
#I ended up using scipy instead because it's much faster
#A rational number
class Rational:
#Constructor
def __init__(self, num: int, den: int = 1):
a = math.gcd(num, den)
self.num = (1, -1)[den < 0] * num // a
self.den = abs(den // a)
#Hashing
def __eq__(self, other)->bool:
if isinstance(other, int):
return self.num == other and self.den == 1
elif isinstance(other, Rational):
return self.num == other.num and self.den == other.den
def __hash__(self) -> int:
return hash((self.num,self.den))
#Comparison
def __ne__(self, other)->bool:
if isinstance(other, int):
return self.num != other or self.den != 1
elif isinstance(other, Rational):
return self.num != other.num or self.den != other.den
def __lt__(self, other) -> bool:
if isinstance(other, int | float):
return self.num < other*self.den
elif isinstance(other, Rational):
return self.num*other.den < other.num*self.den
def __le__(self, other) -> bool:
if isinstance(other, int | float):
return self.num <= other*self.den
elif isinstance(other, Rational):
return self.num*other.den <= other.num*self.den
def __gt__(self, other) -> bool:
if isinstance(other, int | float):
return self.num > other*self.den
elif isinstance(other, Rational):
return self.num*other.den > other.num*self.den
def __ge__(self, other) -> bool:
if isinstance(other, int | float):
return self.num >= other*self.den
elif isinstance(other, Rational):
return self.num*other.den >= other.num*self.den
#Arithmetic
def __add__(self, other):
if isinstance(other, int):
return Rational(self.num+other*self.den, self.den)
elif isinstance(other, Rational):
return Rational(self.num*other.den+other.num*self.den, self.den*other.den)
def __sub__(self, other):
if isinstance(other, int):
return Rational(self.num-other*self.den, self.den)
elif isinstance(other, Rational):
return Rational(self.num*other.den-other.num*self.den, self.den*other.den)
def __mul__(self, other):
if isinstance(other, int):
return Rational(self.num*other, self.den)
elif isinstance(other, Rational):
return Rational(self.num*other.num, self.den*other.den)
def __truediv__(self, other):
if isinstance(other, int):
return Rational(self.num, self.den*other)
elif isinstance(other, Rational):
return Rational(self.num*other.den, self.den*other.num)
def __floordiv__(self, other):
return Rational(int(self / other), 1)
def __mod__(self, other):
return (self / other) - (self // other)
def __neg__(self):
return Rational(-self.num, self.den)
def __abs__(self):
return Rational(abs(self.num), self.den)
#Casting
def __int__(self) -> int:
return self.num // self.den
def __float__(self) -> float:
return self.num / self.den
def __str__(self) -> str:
return str(self.num) + "/" + str(self.den)
#Other math
def gcd(*args):
output = abs(args[0])
for i in range(1,len(args)):
output = Rational(math.gcd(output.num, args[i].num), math.lcm(output.den, args[i].den))
return output
#A matrix of rational numbers
class Matrix:
#Constructor
def __init__(self, terms):
#Automatically convert ints to Rationals
self.terms = [[Rational(1) * term for term in row] for row in terms]
self.height = len(terms)
self.width = 0
if self.height > 0:
self.width = len(terms[0])
#Get terms
def sliceRange(s):
if isinstance(s, int):
return range(s,s+1)
elif isinstance(s, slice):
return range(s.start, s.stop)
def __getitem__(self, key: list[int] | list[slice]):
if isinstance(key[0], int) and isinstance(key[1], int):
return self.terms[key[0]][key[1]]
else:
return Matrix([[self.terms[row][col] for col in Matrix.sliceRange(key[1])] for row in Matrix.sliceRange(key[0])])
#Hashing
def __eq__(self, other)->bool:
return self.terms == other.terms
def __hash__(self) -> int:
return hash(tuple(tuple(row) for row in self.terms))
#Arithmetic
def __add__(self, other):
return Matrix([[term1 + term2 for term1, term2 in zip(row1,row2)] for row1, row2 in zip(self.terms,other.terms)])
def __sub__(self, other):
return Matrix([[term1 - term2 for term1, term2 in zip(row1,row2)] for row1, row2 in zip(self.terms,other.terms)])
#Scalar or matrix multiplication
def __mul__(self, other):
if isinstance(other, int | Rational):
return Matrix([[term * other for term in row] for row in self.terms])
elif isinstance(other, Matrix):
return Matrix([[sum([self[rowIndex,sharedIndex]*other[sharedIndex,colIndex] for sharedIndex in range(self.width)], Rational(0)) for colIndex in range(other.width)] for rowIndex in range(self.height)])
elif isinstance(other, CosetMatrix):
return CosetMatrix(self*other.rep, other.lattice)
def __div__(self, other):
return Matrix([[term / other for term in row] for row in self.terms])
def __neg__(self):
return Matrix([[-term for term in row] for row in self.terms])
def transpose(self):
return Matrix([[self[rowIndex,colIndex] for rowIndex in range(self.height)] for colIndex in range(self.width)])
def copy(self):
return Matrix([[term for term in row] for row in self.terms])
def row(self, row: int):
return self[row, 0:self.width]
def col(self, col: int):
return self[0:self.height, col]
def rows(self):
return [self.row(n) for n in range(self.height)]
def cols(self):
return [self.col(n) for n in range(self.width)]
def __str__(self):
output = ""
for row in range(self.height):
output += "["
for col in range(self.width):
output += str(self[row,col])
if col < self.width-1:
output += ","
output += "]"
if row < self.height-1:
output += "\n"
return output
def isZero(self):
return all(all(term == 0 for term in row) for row in self.terms)
#Tools for making block matrices
def blockHorizontal(self, other):
return Matrix([row1+row2 for row1,row2 in zip(self.terms, other.terms)])
def blockVertical(self, other):
return Matrix(self.terms+other.terms)
#Row reduction
def rowReduce(self):
output = self.copy()
rowIndex = 0
colIndex = 0
while rowIndex < output.height and colIndex < output.width:
#Check if this column is zero below rowIndex, if so goto next column
nonzeroRowIndex = next((rowCheckIndex for rowCheckIndex in range(rowIndex, output.height) if output[rowCheckIndex,colIndex] != 0), -1)
if nonzeroRowIndex == -1:
colIndex += 1
else:
#Otherwise, do a row-reduction step
#Swap rows rowIndex and nonzeroRowIndex if needed
if nonzeroRowIndex != rowIndex:
output.swapRowsInPlace(rowIndex, nonzeroRowIndex)
#Normalize to 1
divideByVal = output[rowIndex, colIndex]
output.terms[rowIndex] = [term / divideByVal for term in output.terms[rowIndex]]
for otherRowIndex in range(output.height):
if otherRowIndex != rowIndex:
output.terms[otherRowIndex] = [otherRowTerm - rowTerm * output[otherRowIndex, colIndex] for rowTerm, otherRowTerm in zip(output.terms[rowIndex], output.terms[otherRowIndex])]
rowIndex += 1
return output
def swapRowsInPlace(self, row1: int, row2: int):
self.terms[row1], self.terms[row2] = self.terms[row2], self.terms[row1]
def removeZeroRows(self):
return Matrix([row for row in self.terms if not all(term == 0 for term in row)])
def det(self):
rowReducedForm = self.rowReduce()
return math.prod([rowReducedForm[i,i] for i in range(rowReducedForm.width)])
def rank(self):
return len([row for row in self.terms if not all(term == 0 for term in row)])
def id(n: int):
return Matrix([[Rational(int(row == col)) for col in range(n)] for row in range(n)])
def zero(height: int, width: int):
return Matrix([[Rational(0) for col in range(width)] for row in range(height)])
#Gives the inverse of an invertible square matrix
def inverse(self):
return self.blockHorizontal(Matrix.id(self.width)).rowReduce()[0:self.height,self.width:self.width*2]
#Gives a right inverse
def rightInverse(self):
transpose = self.transpose()
return transpose * (self * transpose).inverse()
#Row reduction but we can only use integer multiples
def integerRowReduce(self):
output = self.copy()
rowIndex = 0
colIndex = 0
while rowIndex < output.height and colIndex < output.width:
#Check if this column is zero below rowIndex, if so goto next column
existsANonzeroEntry = False
rowToTest = rowIndex
while rowToTest < output.height:
if output[rowToTest, colIndex] > 0:
#Found a nonzero entry
existsANonzeroEntry = True
if rowToTest == rowIndex:
#On the starting row, so we don't do much
rowToTest += 1
else:
if output[rowIndex, colIndex] < output[rowToTest, colIndex]:
#swap rows
output.swapRowsInPlace(rowIndex, rowToTest)
else:
#subtract as many terms[rowToTest] from terms[rowIndex] as possible
mult = output[rowIndex, colIndex] // output[rowToTest, colIndex]
output.terms[rowIndex] = [term - testTerm * mult for term, testTerm in zip(output.terms[rowIndex],output.terms[rowToTest])]
elif output[rowToTest, colIndex] < 0:
#Switch sign of this row
output.terms[rowToTest] = [-term for term in output.terms[rowToTest]]
else:
#Entry cleared, move to next row
rowToTest += 1
if existsANonzeroEntry and rowIndex < output.height and colIndex < output.width and output[rowIndex, colIndex] != 0:
#Subtract as much from the rows < rowIndex as possible while leaving the term positive
for rowToModify in range(rowIndex):
mult = output[rowToModify, colIndex] // output[rowIndex, colIndex]
output.terms[rowToModify] = [termToModify - term * mult for term, termToModify in zip(output.terms[rowIndex],output.terms[rowToModify])]
rowIndex += 1
else:
colIndex += 1
return output
#A lattice of rational points
class Lattice:
#Constructor
def __init__(self, basis: Matrix, rowReduceBasis: bool = True):
#Do integer row reduction for our basis by default
if rowReduceBasis:
self.basis = basis.integerRowReduce().removeZeroRows()
else:
self.basis = basis.copy()
self.dimension = self.basis.width
self.rank = self.basis.height
#Find pivot columns
#The index of the first nonzero column in each row
self.pivots = [[x != 0 for x in row].index(True) for row in self.basis.terms]
#Find inverse of the generator matrix
self.changeOfBasis = self.basis.rightInverse()
#Gets canonical coset representative for a matrix
def getCosetRepresentative(self, matrix: Matrix) -> Matrix:
output = matrix.copy()
for matrixRowIndex in range(matrix.height):
for rowIndex in range(len(self.pivots)):
colIndex = self.pivots[rowIndex]
generatorValue = self.basis[rowIndex,colIndex]
matrixValue = output[matrixRowIndex,colIndex]
output.terms[matrixRowIndex] = [output[matrixRowIndex,termIndex] - self.basis[rowIndex,termIndex] * (matrixValue // generatorValue) for termIndex in range(matrix.width)]
return output
#Does this lattice group contain a given row vector
def __contains__(self, vector: Matrix) -> bool:
return self.getCosetRepresentative(vector).isZero()
#Sum of two lattices
def __add__(self, other):
return Lattice(self.basis.blockVertical(other.basis))
def addGenerators(self, basisVectors: Matrix):
return Lattice(self.basis.blockVertical(basisVectors))
#Gets coordinates of the row vectors in a matrix where all row vectors are in this lattice
def getCoordinates(self, matrix: Matrix) -> Matrix:
return matrix * self.changeOfBasis
#Whether or not this lattice contains a multiple of the row vector for each row of a given matrix
def containsMultiple(self, matrix: Matrix) -> bool:
return self.getCoordinates(matrix) * self.basis == matrix
#The intersection of self with the plane other lies in
def intersectWithPlane(self,other):
#add in extra coordinates that don't lie in this plane
otherExtraBasis = other.basis
n = 0
while otherExtraBasis.height < otherExtraBasis.width:
testVector = Matrix([[Rational(int(a==n)) for a in range(otherExtraBasis.width)]])
n += 1
if not Lattice(otherExtraBasis).containsMultiple(testVector):
otherExtraBasis = testVector.blockVertical(otherExtraBasis)
otherExtraBasisInverse = otherExtraBasis.inverse()
basisInOtherCoordsReduced = (self.basis * otherExtraBasisInverse).integerRowReduce()
#Isolate only the rows that don't rely on the first few terms
basisInPlaneInOtherCoords = Matrix([row for row in basisInOtherCoordsReduced.terms if all(row[i]==0 for i in range(other.basis.width - other.basis.height))])
#Convert from coordinates
basisInPlane = basisInPlaneInOtherCoords * otherExtraBasis
return Lattice(basisInPlane)
#The dual of a lattice
def dual(self):
return Lattice(self.changeOfBasis.transpose(), False)
#The intersection of two lattices
def __and__(self, other):
return (self.dual() + other.dual()).dual().intersectWithPlane(self).intersectWithPlane(other)
#The lattice of integer divisors of a given lattice
def divisorLattice(self):
return LatticeGroup(Matrix.id(self.dimension)).intersectWithPlane(self)
#A basis for the torsion-free part of G/H
#Output as a matrix with rows v s.t. v+H form our basis
def quotientTorsionFreeBasis(self, sublattice):
divisorLattice = self.intersectWithPlane(sublattice)
divisorLatticeBasisExtended = divisorLattice.basis
nonPivots = [x for x in range(divisorLattice.dimension) if x not in divisorLattice.pivots]
for nonPivot in nonPivots:
divisorLatticeBasisExtended = self.basis.row(nonPivot).blockVertical(divisorLatticeBasisExtended)
divisorLatticeBasisExtendedInverse = divisorLatticeBasisExtended.inverse()
basisVectorsInDivisorLatticeExtendedCoordsReduced = (self.basis * divisorLatticeBasisExtendedInverse).integerRowReduce()
nonPivotBasisVectorsToCoords = basisVectorsInDivisorLatticeExtendedCoordsReduced[0:(divisorLattice.dimension-divisorLattice.rank),0:divisorLattice.dimension]
return CosetMatrix((nonPivotBasisVectorsToCoords * divisorLatticeBasisExtended).integerRowReduce(), sublattice)
#The torsion elements of G/H
def quotientTorsionElements(self, sublattice):
#We know these are all in divisorLattice
divisorLattice = self.intersectWithPlane(sublattice)
intermediateLattice = sublattice
#Find torsion of all basis elements
generators = Matrix([])
torsions = []
for basisElt in divisorLattice.basis.rows():
#Find the torsion of this basis element in the intermediate lattice
basisEltCoords = basisElt * intermediateLattice.changeOfBasis
#Torsion is the gcd of the elements of basisEltCoords
torsion = Rational.gcd(*basisEltCoords.terms[0]).den
torsions.append(torsion)
generators = generators.blockVertical(basisElt)
intermediateLattice = intermediateLattice.addGenerators(basisElt)
#Output as an iterator
def outputGenerator(basisElts, torsions):
multiplicities = [0 for torsion in torsions]
while True:
yield CosetMatrix(Matrix([multiplicities]) * basisElts, sublattice)
#Increment
index = 0
multiplicities[0]+=1
while index < len(multiplicities) and multiplicities[index] == torsions[index]:
multiplicities[index] = 0
index += 1
if index < len(multiplicities):
multiplicities[index]+=1
if index == len(multiplicities):
break
return outputGenerator(basisElts, torsions)
#Generators for G/H, with torsions (0 if torsion-free)
def quotientGeneratorsWithTorsions(self, sublattice):
#We know these are all in divisorLattice
divisorLattice = self.intersectWithPlane(sublattice)
intermediateLattice = sublattice
#Find torsion of all basis elements
generators = Matrix([])
torsions = []
for basisElt in divisorLattice.basis.rows():
#Find the torsion of this basis element in the intermediate lattice
basisEltCoords = basisElt * intermediateLattice.changeOfBasis
#Torsion is the gcd of the elements of basisEltCoords
torsion = Rational.gcd(*basisEltCoords.terms[0]).den
#if torsion != 1:
torsions.append(torsion)
generators = generators.blockVertical(basisElt)
intermediateLattice = intermediateLattice.addGenerators(basisElt)
#Add in the torsion-free basis
#return list(zip([CosetMatrix(row, sublattice) for row in generators.rows()], torsions)) + [(row,0) for row in self.quotientTorsionFreeBasis(sublattice).rows()]
return list(zip(generators.rows(), torsions)) + [(row.rep,0) for row in self.quotientTorsionFreeBasis(sublattice).rows()]
#Divides an envelope into a dictionary of cosets by a lattice
def divideIntoCosets(self, envelope):
output = dict()
for v in envelope:
key = CosetMatrix(v, self)
if key in output:
output[key].append(v)
else:
output[key] = [v]
#g.warn("".join(str(k.rep)+": "+str([str(x) for x in v])+"\n\n" for k,v in output.items()))
return output
#A matrix of lattice cosets of the form v+H
class CosetMatrix:
#Constructor
def __init__(self, rep: Matrix, lattice: Lattice):
self.lattice = lattice
self.rep = lattice.getCosetRepresentative(rep)
self.width = self.rep.width
self.height = self.rep.height
#Hashing
def __hash__(self) -> int:
return hash((self.rep))
def __eq__(self, other) -> bool:
return self.rep == other.rep
def __ne__(self, other) -> bool:
return self.rep != other.rep
#Arithmetic
def __add__(self, other) -> bool:
if isinstance(other, CosetMatrix):
return CosetMatrix(self.rep+other.rep, self.lattice)
elif isinstance(other, Matrix):
return CosetMatrix(self.rep+other, self.lattice)
def __sub__(self, other) -> bool:
if isinstance(other, CosetMatrix):
return CosetMatrix(self.rep-other.rep, self.lattice)
elif isinstance(other, Matrix):
return CosetMatrix(self.rep-other, self.lattice)
def __neg__(self) -> bool:
return CosetMatrix(-self.rep, self.lattice)
#Matrix multiplication
#Note: We also multiply the lattice basis
def __mul__(self, other):
return CosetMatrix(self.rep * other, Lattice(self.lattice.basis * other))
def rows(self):
return [CosetMatrix(row, self.lattice) for row in self.rep.rows()]
#A function from Z^n -> int
class LatticeFunction:
def __init__(self, data, dimension):
self.data = data
self.dimension = dimension
self.minCoords = [min(int(key[0,i]) for key,value in self.data.items()) for i in range(self.dimension)]
self.maxCoords = [max(int(key[0,i]) for key,value in self.data.items()) for i in range(self.dimension)]
self.coordDiffs = [x-y for x,y in zip(self.maxCoords, self.minCoords)]
#Convolve using Fourier transform
def convolve(self, other):
#Find amounts to wrap around
#These must be wrapArounds[i] a power of 2 satisfying wrapArounds[i] > self.coordDiffs[i] + other.coordDiffs[i]
#wrapArounds = [1<<(x+y).bit_length() for x,y in zip(self.coordDiffs, other.coordDiffs)]
wrapArounds = [x+y+1 for x,y in zip(self.coordDiffs, other.coordDiffs)]
#return LatticeFunction.unwrapData(LatticeFunction.fft([x*y for x,y in zip(LatticeFunction.fft(self.wrapData(wrapArounds), 1, 1), LatticeFunction.fft(other.wrapData(wrapArounds), 1, 1))], -1, 0.5), wrapArounds, Matrix([self.minCoords])+Matrix([other.minCoords]))
selfData = self.wrapData(wrapArounds)
otherData = other.wrapData(wrapArounds)
g.show("Convolving with length " + str(math.prod(wrapArounds)) + " " + str(tuple(wrapArounds)) + "...")
return LatticeFunction.unwrapData(signal.fftconvolve(selfData, otherData), wrapArounds, Matrix([self.minCoords])+Matrix([other.minCoords]))
#Wraps data to a list, where wrapArounds[i] are our sufficiently large powers of 2
def wrapData(self, wrapArounds):
g.show("Wrapping...")
wrapAroundsCumulative = [math.prod(wrapArounds[0:i]) for i in range(len(wrapArounds)+1)]
output = [0 for x in range(math.prod(wrapArounds))]
#Edit output.data
for key, value in self.data.items():
#Offset by self.minCoords so that indices are all positive
keyIndex = sum(mod(int(key[0,i] - self.minCoords[i]), wrapArounds[i])*wrapAroundsCumulative[i] for i in range(self.dimension))
output[keyIndex] = value
return output
#1-dimensional fast Fourier transform of an array of length 2^n
#Using the Cooley-Tukey algorithm
#I don't actually use this because signal.fftconvolve is faster but it was fun to implement
numFFTs = 0
def fft(data, sign: int = 1, scalePerStep = 1):
outputData = data.copy()
dataSize = len(outputData).bit_length() - 1
#Precompute twiddle factors
g.show("FFT " + str(LatticeFunction.numFFTs+1) + "/12: Computing twiddle factors...")
twiddleFactors = [cmath.exp(-sign * 2j * math.pi * k / (1 << dataSize)) for k in range(1 << dataSize)]
for step in range(0,dataSize):
g.show("FFT " + str(LatticeFunction.numFFTs+1) + "/12: " + str(step) + "/" + str(dataSize))
nextData = []
splitPos = dataSize - step - 1
splitPosMaskBit = 1 << splitPos
belowSplitPosMask = splitPosMaskBit - 1
aboveSplitPosMask = ((1 << (dataSize-1)) - 1) & ~belowSplitPosMask
for i in range(len(outputData)):
#Input indices
#How this works: Take i, split into before and after parts at data-step-1, bitshift after part up 1, insert a 0 or 1 bit
lowerInputIndex = (i & belowSplitPosMask) | ((i & aboveSplitPosMask) << 1)
upperInputIndex = lowerInputIndex | splitPosMaskBit
#Parity
paritySign = 1 - ((i >> (dataSize - 1)) << 1)
#Twiddle factor index
k = i & aboveSplitPosMask
#print(str(step) + ", " + str(i) + ": " + str(lowerInputIndex) + ", " + str(upperInputIndex) + str(" ") + str(k) + ", " + str(twiddleFactor))
nextData.append((outputData[lowerInputIndex] + paritySign * twiddleFactors[k] * outputData[upperInputIndex]) * scalePerStep)
#Progress bar for my sanity
outputData = nextData
g.show("FFT " + str(LatticeFunction.numFFTs) + "/6: " + str(dataSize) + "/" + str(dataSize))
LatticeFunction.numFFTs += 1
return outputData
#Unwrap a list to a LatticeFunction (rounding to ints)
def unwrapData(data, wrapArounds, offset):
g.show("Unwrapping...")
wrapAroundsCumulative = [math.prod(wrapArounds[0:i]) for i in range(len(wrapArounds)+1)]
outputData = {}
for keyIndex in range(len(data)):
if not cmath.isclose(data[keyIndex], 0, rel_tol=1e-09, abs_tol=1e-09):
#Nonzero entry
key = Matrix([[mod(keyIndex // wrapAroundsCumulative[i], wrapArounds[i]) for i in range(len(wrapArounds))]]) + offset
outputData[key] = round(data[keyIndex].real)
return LatticeFunction(outputData, len(wrapArounds))
#Wraps data to a function on a quotient group
def wrapToQuotientFunction(self, group, sign: int = 1):
outputData = {}
for key,value in self.data.items():
quotientKey = CosetMatrix(key * group.optimalLatticeBasis * sign, group.sublattice)
if quotientKey in outputData:
outputData[quotientKey] += value
else:
outputData[quotientKey] = value
return QuotientGroupFunction(group, outputData)
#A quotient of lattices
class QuotientGroup:
def __init__(self, lattice: Lattice, sublattice: Lattice):
#On initialization, organize
self.lattice = lattice
self.sublattice = sublattice
self.generatorsWithTorsions = lattice.quotientGeneratorsWithTorsions(sublattice)
#Find optimal basis for compact unwrapping
# I'm pretty sure this is actually pretty optimal for our purposes
self.optimalLatticeBasis = self.lattice.basis
self.optimalChangeOfBasis = self.optimalLatticeBasis.rightInverse()
#Other thing I considered, seems worse though
#self.optimalLatticeBasis = Matrix([generator.terms[0] for generator, torsion in self.generatorsWithTorsions])
#g.warn(str(self.lattice.basis) + "\n\n" + str(self.sublattice.basis) + "\n\n" + str(self.optimalLatticeBasis))
#A function from G/H -> int
class QuotientGroupFunction:
def __init__(self, group: QuotientGroup, data):
self.group = group
#A dictionary from cosets v+group.sublattice to ints
self.data = data
#Unwraps to a LatticeFunction
def unwrap(self):
return LatticeFunction({key.rep * self.group.optimalChangeOfBasis: self.data[key] for key in self.data}, self.group.optimalChangeOfBasis.width)
#Wraps to a further quotient
def wrapToQuotientFunction(self, group, sign: int = 1):
outputData = {}
for key,value in self.data.items():
quotientKey = CosetMatrix(key.rep * group.optimalLatticeBasis * sign, group.sublattice)
if quotientKey in outputData:
outputData[quotientKey] += value
else:
outputData[quotientKey] = value
return QuotientGroupFunction(group, outputData)
#Function convolution
def convolve(self, other):
return self.unwrap().convolve(other.unwrap()).wrapToQuotientFunction(self.group)
#Golly misc help stuff
#Helpful conversions
def toCellSet(cellList):
return {(cellList[i],cellList[i+1]) for i in range(0,len(cellList),2)}
def toCellList(cellSet):
return [num for x,y in cellSet for num in [x,y]]
#nonempty getrect
def getrect():
if g.empty():
return [0,0,1,1]
return g.getrect()
#This is just convenient
def gethash():
return g.hash(getrect())
def getcells():
return g.getcells(getrect())
#Does the pattern contain a given cell list
def patternContains(cellList, x=0, y=0):
return toCellSet(g.transform(cellList,x,y)).issubset(toCellSet(getcells()))
#Tools for pattern decomposition into components
#For a decomposition of Child(p) = DisjointUnion(q_i)
# Returns a decomposition of this pattern as p = DisjointUnion(p_j) such that Child(p_j) = DisjointUnion(q_{i_{j,k}})
# All inputs and outputs are given as cell sets
nbhd = {(x,y) for x in range(-1,2) for y in range(-1,2)}
def findSubpatterns(patternState, childDecomposition):
#Start by decomposing into connected components
#output = findConnectedComponents(patternState, {(x,y) for x in range(-1,2) for y in range(-1,2)})
output = set()
for cell in patternState:
nbhdOfCell = {(cell[0]+x,cell[1]+y) for x,y in nbhd}
#Check against all children in childDecomposition
requiredCells = {cell}
for childCellSet in childDecomposition:
if not nbhdOfCell.isdisjoint(childCellSet):
#This child's component must contain patternState intersect nbhd(childCellSet)
requiredCells |= patternState & set().union(*[{(a[0]+b[0],a[1]+b[1]) for a in nbhd} for b in childCellSet])
#We require all these cells in our component
componentsToUnionWith = {component for component in output if not requiredCells.isdisjoint(component)}
output -= componentsToUnionWith
output.add(frozenset({cell}.union(*componentsToUnionWith)))
#Additionally, unionize any components around cells that don't match
evolvedState = set().union(*childDecomposition)
evolvedComponentStatesUnion = set().union(*[toCellSet(g.evolve(toCellList(component),1)) for component in output])
error = evolvedState ^ evolvedComponentStatesUnion
for errorCell in error:
nbhdOfCell = {(errorCell[0]+x,errorCell[1]+y) for x,y in nbhd}
#Unionize all components intersecting nbhdOfCell
componentsToUnionWith = {component for component in output if not nbhdOfCell.isdisjoint(component)}
output -= componentsToUnionWith
output.add(frozenset().union(*componentsToUnionWith))
return output
#Removes unneccessary cells from the evolution of this pattern (requiring patFinalState in the final result)
def removeAshCells(patEvolution, patFinalState):
#Find the indepdenent subpatterns of this at each stage
patFinalStateCellSet = frozenset(toCellSet(patFinalState))
patFinalStateCellSetWithExtra = frozenset(toCellSet(g.evolve(patEvolution[len(patEvolution)-1], 1)))
patFinalDecomposition = {patFinalStateCellSet} | {frozenset({cell}) for cell in patFinalStateCellSetWithExtra - patFinalStateCellSet}
patDecompositions = [patFinalDecomposition]
for i in reversed(range(len(patEvolution))):
#Find the prior decomposition
prevDecomposition = findSubpatterns(toCellSet(patEvolution[i]), patDecompositions[0])
patDecompositions.insert(0, prevDecomposition)
#Construct list of only the minimal decompositions leading to patFinalState
minComponents = [patFinalStateCellSet]
for i in reversed(range(len(patEvolution))):
#Need component intersects nbhd(minComponents[0])
try:
prevMinComponent = next(component for component in patDecompositions[i] if not component.isdisjoint(set().union(*[{(a[0]+b[0],a[1]+b[1]) for a in nbhd} for b in minComponents[0]])))
minComponents.insert(0, prevMinComponent)
except StopIteration:
#This only happens when our cell has no predecessors
#This isn't common, but can occur when we add in components mid-evolution
continue
#Convert back to cell lists, remove the last component
return [toCellList(component) for component in minComponents[0:len(minComponents)-1]]
cellLattice = Lattice(Matrix.id(3))
#A periodic pattern with inputs and outputs
class PeriodicPattern:
def __init__(self, cellList, dT, dX, dY, inputs = [], outputs = [], computeExtras = False):
self.dX = dX
self.dY = dY
self.dT = dT
self.inputs = inputs
self.outputs = outputs
self.inputNamesToIndexes = {inputs[i][1]:i for i in range(len(inputs))}
self.outputNamesToIndexes = {outputs[i][1]:i for i in range(len(outputs))}
self.periodVector = Matrix([[self.dT, -self.dX, -self.dY]])
self.periodLattice = Lattice(self.periodVector)
self.positionGroup = QuotientGroup(cellLattice, self.periodLattice)
#Find states
currentState = cellList.copy()
self.state = []
g.setrule("B3/S23")
for t in range(self.dT):
#Remove all outputs on this generation from currentState
#TODO: Could be nice to add a way for outputs to be removed 'late'/after a full cycle
# Or generally for the pattern to 'fill in' over multiple cycles, so that sparks are covered too
for outputPattern, outputName, outputTimeToRemove, outputPosition in outputs:
if outputTimeToRemove == t:
currentState = outputPattern.joinInto(currentState, outputPosition, "andnot")
self.state.append(currentState)
#Join all inputs on this generation to currentState
for inputPattern, inputName, inputTimeToAdd, inputPosition in inputs:
if inputTimeToAdd == t:
currentState = inputPattern.joinInto(currentState, inputPosition, "or")
currentState = g.evolve(currentState, 1)
if computeExtras:
#Precompute some envelopes for collision purposes
g.setrule("B12345678/S012345678")
self.envelopeA = LatticeFunction(dict.fromkeys({Matrix([[t,-x,-y]]) for t in range(self.dT) for x,y in toCellSet(g.evolve(self.state[t],1))}, 1), 3)
g.setrule("B3/S012345678")
self.envelopeB = LatticeFunction(dict.fromkeys({Matrix([[t,-x,-y]]) for t in range(self.dT) for x,y in toCellSet(g.evolve(self.state[t],1))}, 1), 3)
g.setrule("B1/S")
self.envelopeC = LatticeFunction(dict.fromkeys({Matrix([[t,-x,-y]]) for t in range(self.dT) for x,y in toCellSet(g.evolve(self.state[t],1))}, 1), 3)
g.setrule("B2/S")
self.envelopeD = LatticeFunction(dict.fromkeys({Matrix([[t,-x,-y]]) for t in range(self.dT) for x,y in toCellSet(g.evolve(self.state[t],1))}, 1), 3)
g.setrule("B3/S23")
#Find states required for pattern to be restored
#TODO: Also find states required for outputs
# Approach we take: Remove any unnecessary cells (parts that permanently have no influence on the rest of the crawler)
#Fill in crawlerStates
#We iterate twice to prevent pruning ash near the end of the cycle that would collide with the crawler later
#TODO: Most of this is just copy-pasted, I could definitely do this better
extendedState = self.state.copy()
for t in range(self.dT):
#Remove all outputs on this generation from currentState
for outputPattern, outputName, outputTimeToRemove, outputPosition in outputs:
if outputTimeToRemove == t:
currentState = outputPattern.joinInto(currentState, outputPosition + Matrix([[0,self.dX,self.dY]]), "andnot")
extendedState.append(currentState)
#Join all inputs on this generation to currentState
for inputPattern, inputName, inputTimeToAdd, inputPosition in inputs:
if inputTimeToAdd == t:
currentState = inputPattern.joinInto(currentState, inputPosition + Matrix([[0,self.dX,self.dY]]), "or")
currentState = g.evolve(currentState, 1)
#TODO: This is *probably* too strict, since some of the pi-crawler pairs I expected don't show up
self.requiredState = removeAshCells(extendedState, g.transform(self.state[0], self.dX*2, self.dY*2))[0:self.dT]
def getStatePosition(self, position, onlyRequired = False):
if onlyRequired:
return g.transform(self.requiredState[int(position.rep[0,0])], int(position.rep[0,1]), int(position.rep[0,2]))
else:
return g.transform(self.state[int(position.rep[0,0])], int(position.rep[0,1]), int(position.rep[0,2]))
def joinInto(self, cellList, position, mode):
if mode == "or":
return g.join(cellList, self.getStatePosition(position))
elif mode == "andnot":
return toCellList(toCellSet(cellList) - toCellSet(self.getStatePosition(position)))
def place(self, position, mode = "or", onlyRequired = False):
g.putcells(self.getStatePosition(position, onlyRequired),0,0,1,0,0,1, mode)
def placeInput(self, position, index, mode = "or"):
stateT = int(position.rep[0,0])
for inputPattern, inputName, inputTimeToAdd, inputPosition in self.inputs:
indexToUse = index + (1,0)[inputTimeToAdd >= stateT]
inputPattern.place(inputPosition + (position.rep + Matrix([[-inputTimeToAdd, 0, 0]]) - Matrix([[self.dT, -self.dX, -self.dY]]) * indexToUse), mode)
def placeOutput(self, position, index, mode = "or"):
stateT = int(position.rep[0,0])
for outputPattern, outputName, outputTimeToRemove, outputPosition in self.outputs:
indexToUse = index + (1,0)[outputTimeToRemove <= stateT]
outputPattern.place(outputPosition + (position.rep + Matrix([[-outputTimeToRemove, 0, 0]]) + Matrix([[self.dT, -self.dX, -self.dY]]) * indexToUse), mode)
#Place with inputs (and outputs)
def placeWithInputs(self, position, numInputs = 0, numOutputs = 0, mode = "or", onlyRequired = False):
self.place(position, mode, onlyRequired)
for i in range(0,numInputs):
self.placeInput(position, i, mode)
for i in range(0,numOutputs):
self.placeOutput(position, i, mode)
#Test this pattern with inputs (and outputs)
# We only want to test against the minimum envelope required to sustain the component
#TODO: Allow more customizability in input/output testing
def test(self, position, onlyRequired = True):
return patternContains(self.getStatePosition(position, onlyRequired))
def testInput(self, position, index):
stateT = int(position.rep[0,0])
for inputPattern, inputName, inputTimeToAdd, inputPosition in self.inputs:
indexToUse = index + (1,0)[inputTimeToAdd >= stateT]
if not inputPattern.test(inputPosition + (position.rep + Matrix([[-inputTimeToAdd, 0, 0]]) - Matrix([[self.dT, -self.dX, -self.dY]]) * indexToUse)):
return False
return True
def testOutput(self, position, index):
stateT = int(position.rep[0,0])
for outputPattern, outputName, outputTimeToRemove, outputPosition in self.outputs:
indexToUse = index + (1,0)[outputTimeToRemove <= stateT]
if not outputPattern.test(outputPosition + (position.rep + Matrix([[-outputTimeToRemove, 0, 0]]) + Matrix([[self.dT, -self.dX, -self.dY]]) * indexToUse)):
return False
return True
def testWithInputs(self, position, numInputs = 0, numOutputs = 0):
return self.test(position) and all(self.testInput(position,i) for i in range(0, numInputs)) and all(self.testOutput(position,i) for i in range(0, numOutputs))
#Enumerate all possible collisions/interaction separations between two objects
#NOTE: This is optimized for the case where self is small
#TODO: Would probably like to make a version optimized for where self is not small
# I'm not entirely sure how best to do that
def enumerateCollisionSeparations(self, other):
separationLattice = self.periodLattice + other.periodLattice
separationGroup = QuotientGroup(cellLattice, separationLattice)
#Wrapping our envelopes first is probably more efficient
prewrapSelfA = self.envelopeA.wrapToQuotientFunction(separationGroup, -1)
prewrapSelfB = self.envelopeB.wrapToQuotientFunction(separationGroup, -1)
prewrapSelfC = self.envelopeC.wrapToQuotientFunction(separationGroup, -1)
prewrapSelfD = self.envelopeD.wrapToQuotientFunction(separationGroup, -1)
prewrapOtherA = other.envelopeA.wrapToQuotientFunction(separationGroup, 1)
prewrapOtherB = other.envelopeB.wrapToQuotientFunction(separationGroup, 1)
prewrapOtherC = other.envelopeC.wrapToQuotientFunction(separationGroup, 1)
prewrapOtherD = other.envelopeD.wrapToQuotientFunction(separationGroup, 1)
collisionSeparationCosets = set(prewrapSelfA.convolve(prewrapOtherB).data) | set(prewrapSelfB.convolve(prewrapOtherA).data) | set(prewrapSelfC.convolve(prewrapOtherD).data) | set(prewrapSelfD.convolve(prewrapOtherC).data)
#Want to find the first possible absolute separations
#This means, for each separationCoset in collisionSeparationCosets, we want to find the first time resulting in an interaction
#Divide our envelopes into cosets
selfCosetsA = separationLattice.divideIntoCosets(self.envelopeA.data)
selfCosetsB = separationLattice.divideIntoCosets(self.envelopeB.data)
selfCosetsC = separationLattice.divideIntoCosets(self.envelopeC.data)
selfCosetsD = separationLattice.divideIntoCosets(self.envelopeD.data)
otherCosetsA = separationLattice.divideIntoCosets(other.envelopeA.data)
otherCosetsB = separationLattice.divideIntoCosets(other.envelopeB.data)
otherCosetsC = separationLattice.divideIntoCosets(other.envelopeC.data)
otherCosetsD = separationLattice.divideIntoCosets(other.envelopeD.data)
#Find earliest interatction cells for each coset
#Idea:
# Want to understand the space of vectors v such that CosetMatrix(v, self.periodLattice) in selfEnvelope, and CosetMatrix(v + separation, other.periodLattice) in otherEnvelope
# That is, exist m,n such that v + m*self.periodVector in selfEnvelope.reps, v + separation + n*other.periodVector in otherEnvelope.reps
# Have x in selfEnvelope.reps, y in otherEnvelope.reps such that v + m*self.periodVector = x, v + separation + n*other.periodVector = y
# Then x+separation-y = m*self.periodVector - n*other.periodVector
# So [m,-n] = (x+separation-y) * changeOfCoords
# In particular, m = (x+separation-y) * changeOfCoords.col(0)
# Then v = x - self.periodVector * ((x+separation-y) * changeOfCoords.col(0))[0,0]
# Specifically, v[0,0] = x[0,0] - self.periodVector[0,0] * ((x+separation-y) * changeOfCoords.col(0))[0,0]
# = x[0,0] - self.periodVector[0,0] * (x * changeOfCoords.col(0))[0,0] + self.periodVector[0,0] * ((y-separation) * changeOfCoords.col(0))[0,0]
# = x * ([[1],[0],[0]] - changeOfCoords.col(0) * self.periodVector[0,0]) + (y-separation) * changeOfCoords.col(0) * self.periodVector[0,0]
cMulOther = self.periodVector.blockVertical(other.periodVector).rightInverse().col(0) * self.periodVector[0,0]
cMulSelf = Matrix([[1],[0],[0]]) - cMulOther
def findContribs(cosetReps, multiplier):
return {coset: min((rep * multiplier)[0,0] for rep in cosetReps[coset]) for coset in cosetReps}
selfContribsA = findContribs(selfCosetsA, cMulSelf)
selfContribsB = findContribs(selfCosetsB, cMulSelf)
selfContribsC = findContribs(selfCosetsC, cMulSelf)
selfContribsD = findContribs(selfCosetsD, cMulSelf)
otherContribsA = findContribs(otherCosetsA, cMulOther)
otherContribsB = findContribs(otherCosetsB, cMulOther)
otherContribsC = findContribs(otherCosetsC, cMulOther)
otherContribsD = findContribs(otherCosetsD, cMulOther)
#Remark: This is rather slow when self is large
#TODO: Would like a better approach to this
def minContrib(separation, selfContribs, otherContribs):
return min((selfContribs[coset] + otherContribs[coset + separation] for coset in selfContribs if coset + separation in otherContribs), default = math.inf)
for separationCoset in collisionSeparationCosets:
minT = min(minContrib(separationCoset.rep, selfContribsA, otherContribsB),
minContrib(separationCoset.rep, selfContribsB, otherContribsA),
minContrib(separationCoset.rep, selfContribsC, otherContribsD),
minContrib(separationCoset.rep, selfContribsD, otherContribsC)) - (separationCoset.rep * cMulOther)[0,0]
yield [CosetMatrix(Matrix([[minT,0,0]]), self.periodLattice), CosetMatrix(Matrix([[minT,0,0]]) + separationCoset.rep, other.periodLattice)]
#Enumerate interactions between two objects with the same velocity
#NOTE: This is less size-dependent
def enumerateInteractionSeparations(self, other):
separationLattice = self.periodLattice + other.periodLattice
separationGroup = QuotientGroup(cellLattice, separationLattice)
#Wrapping our envelopes first is probably more efficient
prewrapSelfA = self.envelopeA.wrapToQuotientFunction(separationGroup, -1)
prewrapSelfB = self.envelopeB.wrapToQuotientFunction(separationGroup, -1)
prewrapSelfC = self.envelopeC.wrapToQuotientFunction(separationGroup, -1)
prewrapSelfD = self.envelopeD.wrapToQuotientFunction(separationGroup, -1)
prewrapOtherA = other.envelopeA.wrapToQuotientFunction(separationGroup, 1)
prewrapOtherB = other.envelopeB.wrapToQuotientFunction(separationGroup, 1)
prewrapOtherC = other.envelopeC.wrapToQuotientFunction(separationGroup, 1)
prewrapOtherD = other.envelopeD.wrapToQuotientFunction(separationGroup, 1)
collisionSeparationCosets = set(prewrapSelfA.convolve(prewrapOtherB).data) | set(prewrapSelfB.convolve(prewrapOtherA).data) | set(prewrapSelfC.convolve(prewrapOtherD).data) | set(prewrapSelfD.convolve(prewrapOtherC).data)
return [[CosetMatrix(Matrix([[0,0,0]]), self.periodLattice), CosetMatrix(coset.rep, other.periodLattice)] for coset in collisionSeparationCosets]
#TODO: Could make a faster method for self-interactions, since half of the convolutions aren't really required
#Multiple patterns with the same period, with compatible inputs and outputs linked
class CompoundPattern:
def __init__(self, componentsWithPositions, name = ""):
#Members are of the form (component, position)
self.componentsWithPositions = componentsWithPositions
self.periodVector = componentsWithPositions[0][0].periodVector
self.periodLattice = componentsWithPositions[0][0].periodLattice
self.dT = int(self.periodVector[0,0])
self.dX = -int(self.periodVector[0,1])
self.dY = -int(self.periodVector[0,2])
#Figure out all inputs and outputs
self.inputDict = {}
for inputComponent, inputComponentName, inputComponentPosition in self.componentsWithPositions:
for inputPattern, inputName, inputTime, inputPosition in inputComponent.inputs:
if not inputPattern in self.inputDict:
self.inputDict[inputPattern] = {}
combinedLattice = inputPattern.periodLattice + self.periodLattice
#What lane is our input on
inputPositionInCompound = inputPosition + inputComponentPosition.rep - Matrix([[inputTime,0,0]])
inputLane = CosetMatrix(inputPositionInCompound.rep, combinedLattice)
if not inputLane in self.inputDict[inputPattern]:
self.inputDict[inputPattern][inputLane] = []
#Our input time and position in the larger compound pattern
inputTimeInCompoundPattern = mod(inputTime - int(inputComponentPosition.rep[0,0]), self.dT)
inputSpacing = ((inputTime - int(inputComponentPosition.rep[0,0])) - inputTimeInCompoundPattern) // self.dT
inputPositionInCompoundPattern = inputPosition.rep + inputComponentPosition.rep + Matrix([[inputTimeInCompoundPattern - inputTime,0,0]]) + self.periodVector * inputSpacing
self.inputDict[inputPattern][inputLane].append((inputTimeInCompoundPattern, inputPositionInCompoundPattern, inputComponentName+"."+inputName))
self.outputDict = {}
for outputComponent, outputComponentName, outputComponentPosition in self.componentsWithPositions:
for outputPattern, outputName, outputTime, outputPosition in outputComponent.outputs:
if not outputPattern in self.outputDict:
self.outputDict[outputPattern] = {}
combinedLattice = outputPattern.periodLattice + self.periodLattice
#What lane is our output on
outputPositionInCompound = outputPosition + outputComponentPosition.rep - Matrix([[outputTime,0,0]])
outputLane = CosetMatrix(outputPositionInCompound.rep, combinedLattice)
if not outputLane in self.outputDict[outputPattern]:
self.outputDict[outputPattern][outputLane] = []
#Our output time and position in the larger compound pattern
outputTimeInCompoundPattern = mod(outputTime - int(outputComponentPosition.rep[0,0]), self.dT)
outputSpacing = ((outputTime - int(outputComponentPosition.rep[0,0])) - outputTimeInCompoundPattern) // self.dT
outputPositionInCompoundPattern = outputPosition.rep + outputComponentPosition.rep + Matrix([[outputTimeInCompoundPattern - outputTime,0,0]]) + self.periodVector * outputSpacing
self.outputDict[outputPattern][outputLane].append((outputTimeInCompoundPattern, outputPositionInCompoundPattern, outputComponentName+"."+outputName))
#Whole pattern's inputs and outputs
#In same format as PeriodicPattern, to allow for nesting
self.inputs = []
self.outputs = []
self.linkages = []
#Input/output index registration with names
self.name = name
self.inputNamesToIndexes = {}
self.outputNamesToIndexes = {}
def registerInput(inputTimeInCompoundPattern, pattern, inputPositionInCompoundPattern, inputName):
self.inputNamesToIndexes[inputName] = len(self.inputs)
self.inputs.append((pattern, inputName, inputTimeInCompoundPattern, CosetMatrix(inputPositionInCompoundPattern, pattern.periodLattice)))
def registerOutput(outputTimeInCompoundPattern, pattern, outputPositionInCompoundPattern, outputName):
self.outputNamesToIndexes[outputName] = len(self.outputs)
self.outputs.append((pattern, outputName, outputTimeInCompoundPattern, CosetMatrix(outputPositionInCompoundPattern, pattern.periodLattice)))
#Figure out compatible input-output pairs and combine 'em
for pattern in self.inputDict:
if pattern in self.outputDict:
combinedLatticeChangeOfBasis = self.periodVector.blockVertical(pattern.periodVector).rightInverse()
for lane in self.inputDict[pattern]:
if lane in self.outputDict[pattern]:
#Positivity/spacing checks on pairs in the same lane
inputsMinPositiveDisplacements = [(math.inf, -1) for i in range(len(self.inputDict[pattern][lane]))]
outputsMinPositiveDisplacements = [(math.inf, -1) for j in range(len(self.outputDict[pattern][lane]))]
for i in range(len(self.inputDict[pattern][lane])):
inputTimeInCompoundPattern, inputPositionInCompoundPattern, inputName = self.inputDict[pattern][lane][i]
for j in range(len(self.outputDict[pattern][lane])):
outputTimeInCompoundPattern, outputPositionInCompoundPattern, outputName = self.outputDict[pattern][lane][j]
inputPos = inputPositionInCompoundPattern + Matrix([[inputTimeInCompoundPattern,0,0]])
outputPos = outputPositionInCompoundPattern + Matrix([[outputTimeInCompoundPattern,0,0]])
#This is incredibly scuffed and probably incorrect
#TODO: Yeah this is definitely incorrect
displacement = int((inputPos - outputPos)[0,0]) + int(((inputPos - outputPos) * combinedLatticeChangeOfBasis)[0,0]) * self.dT
if displacement >= 0 or True:
#Link up if these are an improvement
if displacement < inputsMinPositiveDisplacements[i][0]:
inputsMinPositiveDisplacements[i] = (displacement, j)
if displacement < outputsMinPositiveDisplacements[j][0]:
outputsMinPositiveDisplacements[j] = (displacement, i)
unboundInputs = set(range(len(self.inputDict[pattern][lane])))
unboundOutputs = set(range(len(self.outputDict[pattern][lane])))
#Register linkages for closest compatible pairs
for i in range(len(self.inputDict[pattern][lane])):
j = inputsMinPositiveDisplacements[i][1]
if j != -1 and outputsMinPositiveDisplacements[j][1] == i:
#i,j is a closest compatible pair
unboundInputs.remove(i)
unboundOutputs.remove(j)
inputTimeInCompoundPattern, inputPositionInCompoundPattern, inputName = self.inputDict[pattern][lane][i]
outputTimeInCompoundPattern, outputPositionInCompoundPattern, outputName = self.outputDict[pattern][lane][j]
self.linkages.append((pattern, inputTimeInCompoundPattern, inputPositionInCompoundPattern, outputTimeInCompoundPattern, outputPositionInCompoundPattern))
#Register unbound inputs and outputs
for i in unboundInputs:
inputTimeInCompoundPattern, inputPositionInCompoundPattern, inputName = self.inputDict[pattern][lane][i]
registerInput(inputTimeInCompoundPattern, pattern, inputPositionInCompoundPattern, inputName)
for j in unboundOutputs:
outputTimeInCompoundPattern, outputPositionInCompoundPattern, outputName = self.outputDict[pattern][lane][j]
registerOutput(outputTimeInCompoundPattern, pattern, outputPositionInCompoundPattern, outputName)
else:
for inputTimeInCompoundPattern, inputPositionInCompoundPattern, inputName in self.inputDict[pattern][lane]:
registerInput(inputTimeInCompoundPattern, pattern, inputPositionInCompoundPattern, inputName)
for lane in self.outputDict[pattern]:
if not lane in self.inputDict[pattern]:
for outputTimeInCompoundPattern, outputPositionInCompoundPattern, outputName in self.outputDict[pattern][lane]:
registerOutput(outputTimeInCompoundPattern, pattern, outputPositionInCompoundPattern, outputName)
else:
for lane in self.inputDict[pattern]:
for inputTimeInCompoundPattern, inputPositionInCompoundPattern, inputName in self.inputDict[pattern][lane]:
registerInput(inputTimeInCompoundPattern, pattern, inputPositionInCompoundPattern, inputName)
for pattern in self.outputDict:
if not pattern in self.inputDict:
for lane in self.outputDict[pattern]:
for outputTimeInCompoundPattern, outputPositionInCompoundPattern, outputName in self.outputDict[pattern][lane]:
registerOutput(outputTimeInCompoundPattern, pattern, outputPositionInCompoundPattern, outputName)
def place(self, position, mode = "or"):
#Place in all components
for component, componentName, componentPosition in self.componentsWithPositions:
component.place(position + componentPosition, mode)
#Place in all linkages
stateT = int(position.rep[0,0])
for pattern, inputTimeInCompoundPattern, inputPositionInCompoundPattern, outputTimeInCompoundPattern, outputPositionInCompoundPattern in self.linkages:
#TODO: These are wrong
inputIndexOffset = (1,0)[inputTimeInCompoundPattern >= stateT]
outputIndexOffset = (0,1)[outputTimeInCompoundPattern <= stateT]
#g.warn(str(inputTimeInCompoundPattern) +", "+str(outputTimeInCompoundPattern) +", "+str(stateT)+"\n\n"+str(inputIndexOffset) + ", "+str(outputIndexOffset))
inputBasePos = inputPositionInCompoundPattern + position.rep - Matrix([[inputTimeInCompoundPattern, 0, 0]])
outputBasePos = outputPositionInCompoundPattern + position.rep - Matrix([[outputTimeInCompoundPattern, 0, 0]])
combinedLatticeChangeOfBasis = self.periodVector.blockVertical(pattern.periodVector).rightInverse()
for index in range(inputIndexOffset, outputIndexOffset + int(((inputBasePos - outputBasePos) * combinedLatticeChangeOfBasis)[0,0])):
pattern.place(CosetMatrix(inputBasePos - Matrix([[self.dT, -self.dX, -self.dY]]) * index, pattern.periodLattice), mode)
def placeInput(self, position, index, mode = "or"):
stateT = int(position.rep[0,0])
for inputPattern, inputName, inputTimeToAdd, inputPosition in self.inputs:
indexToUse = index + (1,0)[inputTimeToAdd >= stateT]
inputPattern.place(inputPosition + (position.rep + Matrix([[-inputTimeToAdd, 0, 0]]) - Matrix([[self.dT, -self.dX, -self.dY]]) * indexToUse), mode)
def placeOutput(self, position, index, mode = "or"):
stateT = int(position.rep[0,0])
for outputPattern, outputName, outputTimeToRemove, outputPosition in self.outputs:
indexToUse = index + (1,0)[outputTimeToRemove <= stateT]
outputPattern.place(outputPosition + (position.rep + Matrix([[-outputTimeToRemove, 0, 0]]) + Matrix([[self.dT, -self.dX, -self.dY]]) * indexToUse), mode)
#Place with inputs (and outputs)
def placeWithInputs(self, position, numInputs = 0, numOutputs = 0, mode = "or"):
self.place(position, mode)
for i in range(0,numInputs):
self.placeInput(position, i, mode)
for i in range(0,numOutputs):
self.placeOutput(position, i, mode)
#Find the minimal offset displacement of pat2 linking an output in one PeriodicPattern (or CompoundPattern) to an input in another
# Plus extraSpacing steps worth of space
def findOffset(pat1, pat1OutputIndex, pat2, pat2InputIndex, extraSpacing = 0):
outputPattern, outputName, outputTimeToRemove, outputPosition = pat1.outputs[pat1OutputIndex]
inputPattern, inputName, inputTimeToAdd, inputPosition = pat2.inputs[pat2InputIndex]
if outputPattern != inputPattern:
g.warn("Pattern mismatch!")
#Want to add and remove in the same generation
return CosetMatrix(Matrix([[inputTimeToAdd - outputTimeToRemove,0,0]]) + outputPosition.rep - inputPosition.rep - outputPattern.periodVector * extraSpacing, pat1.periodLattice)
#A chain with certain spacings
def chain(patternInputOutputSpacingInfo):
periodLattice = patternInputOutputSpacingInfo[0][0].periodLattice
cumulativeOffset = CosetMatrix(Matrix.zero(1,3), periodLattice)
componentsWithPositions = [(patternInputOutputSpacingInfo[0][0], patternInputOutputSpacingInfo[0][1], cumulativeOffset)]
for i in range(len(patternInputOutputSpacingInfo) - 1):
outputComponent, _, _, outputComponentOutputName, _ = patternInputOutputSpacingInfo[i]
inputComponent, inputComponentName, inputComponentInputName, _, extraSpacing = patternInputOutputSpacingInfo[i+1]
outputComponentOutputIndex = outputComponent.outputNamesToIndexes[outputComponentOutputName]
inputComponentInputIndex = inputComponent.inputNamesToIndexes[inputComponentInputName]
addedOffset = CompoundPattern.findOffset(outputComponent, outputComponentOutputIndex, inputComponent, inputComponentInputIndex, extraSpacing)
cumulativeOffset += addedOffset
componentsWithPositions.append((inputComponent, inputComponentName, cumulativeOffset))
return CompoundPattern(componentsWithPositions)
#Automatically add in a collection of components with compatible spacings with certain inputs/outputs to form a loop
def addCompatibleComponents(self, componentsToAddData, selfName = "Main"):
newCompoundPatternComponents = [(self, selfName, CosetMatrix(Matrix.zero(1,3), self.periodLattice))]
for component, componentName, componentInputIndicesToSelfOutputIndices, componentOutputIndicesToSelfInputIndices in componentsToAddData:
#Get constraints
baseOffsets = []
stepDisplacements = []
#We expect exactly 2 of these constraints in total
for componentInputName, selfOutputName in componentInputIndicesToSelfOutputIndices.items():
componentInputIndex = component.inputNamesToIndexes[componentInputName]
selfOutputIndex = self.outputNamesToIndexes[selfOutputName]
baseOffsets.append(CompoundPattern.findOffset(self, selfOutputIndex, component, componentInputIndex).rep)
stepDisplacements.append(component.inputs[componentInputIndex][0].periodVector)
for componentOutputName, selfInputName in componentOutputIndicesToSelfInputIndices.items():
componentOutputIndex = component.outputNamesToIndexes[componentOutputName]
selfInputIndex = self.inputNamesToIndexes[selfInputName]
baseOffsets.append(-CompoundPattern.findOffset(component, componentOutputIndex, self, selfInputIndex).rep)
stepDisplacements.append(component.outputs[componentOutputIndex][0].periodVector)
#Our position must be, for all i, of the form v = baseOffsets[i] + n_i * stepDisplacements[i] + m_i * self.periodVector
# In particular, have 0 = baseOffsets[0] - baseOffsets[1] + n_0 * stepDisplacements[0] - n_1 * stepDisplacements[1] + (m_0-m_1)*self.periodVector
# baseOffsets[1] - baseOffsets[0] = Matrix([[n_0,-n_1,m_0-m_1]]) * BlockVertical(stepDisplacements[0], stepDisplacements[1], self.periodVector)
# (baseOffsets[1] - baseOffsets[0]) * blockMatrixInverse = Matrix([[n_0,-n_1,m_0-m_1]])
# ((baseOffsets[1] - baseOffsets[0]) * blockMatrixInverse.col(0))[0,0] = n_0
n0 = ((baseOffsets[1] - baseOffsets[0]) * stepDisplacements[0].blockVertical(stepDisplacements[1]).blockVertical(self.periodVector).inverse().col(0))[0,0]
v = baseOffsets[0] + stepDisplacements[0] * n0
newCompoundPatternComponents.append((component, componentName, CosetMatrix(v, component.periodLattice)))
return CompoundPattern(newCompoundPatternComponents)
#Conversion from a 1x pattern to an nx pattern
# Basically the only thing this has to do is unfold our inputs and outputs
class PeriodMultipliedPattern:
def __init__(self, pattern, multiplier):
self.pattern = pattern
self.multiplier = multiplier
self.dT = pattern.dT * multiplier
self.dX = pattern.dX * multiplier
self.dY = pattern.dY * multiplier
self.periodVector = pattern.periodVector * multiplier
self.periodLattice = Lattice(self.periodVector)
#Register inputs and outputs
self.inputs = [(inputPat,"Instance"+str(i)+"."+inputName,inputTime+pattern.dT*i, CosetMatrix(inputPosition.rep,self.periodLattice)+Matrix([[0,self.pattern.dX,self.pattern.dY]])*i) for inputPat,inputName,inputTime,inputPosition in pattern.inputs for i in range(multiplier)]
self.outputs = [(outputPat,"Instance"+str(i)+"."+outputName,outputTime+pattern.dT*i, CosetMatrix(outputPosition.rep,self.periodLattice)+Matrix([[0,self.pattern.dX,self.pattern.dY]])*i) for outputPat,outputName,outputTime,outputPosition in pattern.outputs for i in range(multiplier)]
self.inputNamesToIndexes = {self.inputs[i][1]:i for i in range(len(self.inputs))}
self.outputNamesToIndexes = {self.outputs[i][1]:i for i in range(len(self.outputs))}
def place(self, position, mode = "or"):
self.pattern.place(CosetMatrix(position.rep, self.pattern.periodLattice), mode)
def placeWithInputs(self, position, numInputs = 0, numOutputs = 0, mode = "or"):
self.pattern.placeWithInputs(CosetMatrix(position.rep, self.pattern.periodLattice), numInputs*self.multiplier, numOutputs*self.multiplier, mode)
#Automatically add in a collection of components with compatible spacings with certain inputs/outputs to form a loop
def addCompatibleComponents(self, componentsToAddData, selfName = "Main"):
newCompoundPatternComponents = [(self, selfName, CosetMatrix(Matrix.zero(1,3), self.periodLattice))]
for component, componentName, componentInputIndicesToSelfOutputIndices, componentOutputIndicesToSelfInputIndices in componentsToAddData:
#Get constraints
baseOffsets = []
stepDisplacements = []
#We expect exactly 2 of these constraints in total
for componentInputName, selfOutputName in componentInputIndicesToSelfOutputIndices.items():
componentInputIndex = component.inputNamesToIndexes[componentInputName]
selfOutputIndex = self.outputNamesToIndexes[selfOutputName]
baseOffsets.append(CompoundPattern.findOffset(self, selfOutputIndex, component, componentInputIndex).rep)
stepDisplacements.append(component.inputs[componentInputIndex][0].periodVector)
for componentOutputName, selfInputName in componentOutputIndicesToSelfInputIndices.items():
componentOutputIndex = component.outputNamesToIndexes[componentOutputName]
selfInputIndex = self.inputNamesToIndexes[selfInputName]
baseOffsets.append(-CompoundPattern.findOffset(component, componentOutputIndex, self, selfInputIndex).rep)
stepDisplacements.append(component.outputs[componentOutputIndex][0].periodVector)
#Our position must be, for all i, of the form v = baseOffsets[i] + n_i * stepDisplacements[i] + m_i * self.periodVector
# In particular, have 0 = baseOffsets[0] - baseOffsets[1] + n_0 * stepDisplacements[0] - n_1 * stepDisplacements[1] + (m_0-m_1)*self.periodVector
# baseOffsets[1] - baseOffsets[0] = Matrix([[n_0,-n_1,m_0-m_1]]) * BlockVertical(stepDisplacements[0], stepDisplacements[1], self.periodVector)
# (baseOffsets[1] - baseOffsets[0]) * blockMatrixInverse = Matrix([[n_0,-n_1,m_0-m_1]])
# ((baseOffsets[1] - baseOffsets[0]) * blockMatrixInverse.col(0))[0,0] = n_0
n0 = ((baseOffsets[1] - baseOffsets[0]) * stepDisplacements[0].blockVertical(stepDisplacements[1]).blockVertical(self.periodVector).inverse().col(0))[0,0]
v = baseOffsets[0] + stepDisplacements[0] * n0
newCompoundPatternComponents.append((component, componentName, CosetMatrix(v, component.periodLattice)))
return CompoundPattern(newCompoundPatternComponents)
#Settings
g.new("13131")
g.setrule("B3/S23")
g.setalgo("HashLife")
#Basic objects
block = PeriodicPattern(g.parse("2o$2o!",0,0), 1, 0, 0)
blinker = PeriodicPattern(g.parse("3o!",-1,0), 2, 0, 0)
SWGlider = PeriodicPattern(g.parse("bo$o$3o!"), 4, -1, 1)
NWGlider = PeriodicPattern(g.parse("2o$obo$o!"), 4, -1, -1)
NEGlider = PeriodicPattern(g.parse("3o$2bo$bo!"), 4, 1, -1)
SEGlider = PeriodicPattern(g.parse("2bo$obo$b2o!"), 4, 1, 1)
NLWSS = PeriodicPattern(g.parse("3o$o2bo$o$o$bobo!",0,0), 4, 0, -2)
NMWSS = PeriodicPattern(g.parse("3o$o2bo$o$o3bo$o$bobo!",0,0), 4, 0, -2)
NHWSS = PeriodicPattern(g.parse("3o$o2bo$o$o3bo$o3bo$o$bobo!",0,0), 4, 0, -2)
WLWSS = PeriodicPattern(g.parse("bo2bo$o$o3bo$4o!",0,0), 4, -2, 0)
'''
#Helix components
helixComponent1 = PeriodicPattern([],31*16,-1*16,-13*16,
[(NEGlider,"NEGlider0",0,CosetMatrix(Matrix([[0,0,4]]), NEGlider.periodLattice)),
(NLWSS,"NLWSS0",0,CosetMatrix(Matrix([[1,5,1]]), NLWSS.periodLattice)),
(NHWSS,"NHWSS0",8,CosetMatrix(Matrix([[2,12,-1]]), NHWSS.periodLattice))],
[(NEGlider,"NEGlider1",17,CosetMatrix(Matrix([[1,12,-5]]), NEGlider.periodLattice))])
helixComponent2 = PeriodicPattern([],31*16,-1*16,-13*16,
[(NEGlider,"NEGlider0",0,CosetMatrix(Matrix([[3,-1,1]]), NEGlider.periodLattice)),
(NLWSS,"NLWSS0",0,CosetMatrix(Matrix([[1,4,2]]), NLWSS.periodLattice)),
(NLWSS,"NLWSS1",10,CosetMatrix(Matrix([[3,2,7]]), NLWSS.periodLattice))],
[(NWGlider,"NWGlider0",19,CosetMatrix(Matrix([[0,-2,1]]), NWGlider.periodLattice))])
helixComponent3 = PeriodicPattern([],31*16,-1*16,-13*16,
[(NWGlider,"NWGlider0",0,CosetMatrix(Matrix([[2,13,4]]), NWGlider.periodLattice)),
(NLWSS,"NLWSS0",0,CosetMatrix(Matrix([[3,7,2]]), NLWSS.periodLattice)),
(NHWSS,"NHWSS0",8,CosetMatrix(Matrix([[0,0,-2]]), NHWSS.periodLattice))],
[(NWGlider,"NWGlider1",17,CosetMatrix(Matrix([[3,1,-5]]), NWGlider.periodLattice))])
helixComponent4 = PeriodicPattern([],31*16,-1*16,-13*16,
[(NWGlider,"NWGlider0",0,CosetMatrix(Matrix([[0,9,5]]), NWGlider.periodLattice)),
(NHWSS,"NHWSS0",0,CosetMatrix(Matrix([[0,3,0]]), NHWSS.periodLattice)),
(NHWSS,"NHWSS1",7,CosetMatrix(Matrix([[0,9,8]]), NHWSS.periodLattice)),
(NLWSS,"NLWSS0",23,CosetMatrix(Matrix([[2,0,4]]), NLWSS.periodLattice)),
(NLWSS,"NLWSS1",40,CosetMatrix(Matrix([[2,4,3]]), NLWSS.periodLattice))],
[(NEGlider,"NEGlider0",29,CosetMatrix(Matrix([[3,6,-6]]), NEGlider.periodLattice)),
(NWGlider,"NWGlider1",39,CosetMatrix(Matrix([[1,-3,-4]]), NWGlider.periodLattice))])
#TODO: Replace indices with names
helixSpacings = [
(helixComponent1,"H0","","NEGlider1",2),
(helixComponent1,"H1","NEGlider0","NEGlider1",2),
(helixComponent1,"H2","NEGlider0","NEGlider1",2),
(helixComponent1,"H3","NEGlider0","NEGlider1",2),
(helixComponent1,"H4","NEGlider0","NEGlider1",2),
(helixComponent1,"H5","NEGlider0","NEGlider1",2),
(helixComponent1,"H6","NEGlider0","NEGlider1",2),
(helixComponent1,"H7","NEGlider0","NEGlider1",2),
(helixComponent2,"H8","NEGlider0","NWGlider0",2),
(helixComponent3,"H9","NWGlider0","NWGlider1",0),
(helixComponent3,"H10","NWGlider0","NWGlider1",2),
(helixComponent3,"H11","NWGlider0","NWGlider1",2),
(helixComponent3,"H12","NWGlider0","NWGlider1",2),
(helixComponent3,"H13","NWGlider0","NWGlider1",2),
(helixComponent3,"H14","NWGlider0","NWGlider1",2),
(helixComponent3,"H15","NWGlider0","NWGlider1",2),
(helixComponent3,"H16","NWGlider0","NWGlider1",2),
(helixComponent3,"H17","NWGlider0","NWGlider1",2),
(helixComponent4,"H18","NWGlider0","NEGlider0",6),
]
helix = CompoundPattern.chain(helixSpacings)
#helix.placeWithInputs(CosetMatrix(Matrix([[0,0,0]]), helix.periodLattice), 20, 20)
#Fanout components
#TODO: Would be nice to figure these input/output values automatically
fanoutComponent1 = PeriodicPattern([],31*16,-1*16,-13*16,
[(NWGlider,"NWGlider0",0,CosetMatrix(Matrix([[1,1,0]]), NWGlider.periodLattice)),
(NLWSS,"NLWSS0",0,CosetMatrix(Matrix([[3,-5,0]]), NLWSS.periodLattice)),
(NLWSS,"NLWSS1",15,CosetMatrix(Matrix([[1,0,5]]), NLWSS.periodLattice)),
(NLWSS,"NLWSS2",16,CosetMatrix(Matrix([[2,6,4]]), NLWSS.periodLattice))],
[(blinker,"Blinker0",28,CosetMatrix(Matrix([[0,4,7]]), blinker.periodLattice)),
(WLWSS,"WLWSS0",29,CosetMatrix(Matrix([[-1,-7,-3]]), WLWSS.periodLattice))])
fanoutComponent2 = PeriodicPattern([],31*16,-1*16,-13*16,
[(blinker,"Blinker0",0,CosetMatrix(Matrix([[1,2,1]]), blinker.periodLattice)),
(NLWSS,"NLWSS0",0,CosetMatrix(Matrix([[1,2,4]]), NLWSS.periodLattice)),
(NLWSS,"NLWSS1",13,CosetMatrix(Matrix([[0,0,6]]), NLWSS.periodLattice))],
[(NWGlider,"NWGlider0",10,CosetMatrix(Matrix([[-1,1,-2]]), NWGlider.periodLattice))])
fanoutComponent3 = PeriodicPattern([],31*16,-1*16,-13*16,
[(NWGlider,"NWGlider0",0,CosetMatrix(Matrix([[2,7,5]]), NWGlider.periodLattice)),
(NHWSS,"NHWSS0",0,CosetMatrix(Matrix([[0,0,0]]), NHWSS.periodLattice)),
(NMWSS,"NMWSS0",10,CosetMatrix(Matrix([[2,9,9]]), NMWSS.periodLattice))],
[(NWGlider,"NWGlider1",26,CosetMatrix(Matrix([[0,2,-5]]), NWGlider.periodLattice))])
fanoutComponent4 = PeriodicPattern([],31*16,-1*16,-13*16,
[(NWGlider,"NWGlider0",0,CosetMatrix(Matrix([[2,5,0]]), NWGlider.periodLattice)),
(NLWSS,"NLWSS0",0,CosetMatrix(Matrix([[0,6,4]]), NLWSS.periodLattice)),
(NLWSS,"NLWSS1",16,CosetMatrix(Matrix([[3,0,7]]), NLWSS.periodLattice))],
[(blinker,"Blinker0",18,CosetMatrix(Matrix([[1,8,5]]), blinker.periodLattice)),
(NWGlider,"NWGlider1",20,CosetMatrix(Matrix([[3,1,4]]), NWGlider.periodLattice))])
fanoutComponent5 = PeriodicPattern([],31*16,-1*16,-13*16,
[(blinker,"Blinker0",0,CosetMatrix(Matrix([[0,1,0]]), blinker.periodLattice)),
(NLWSS,"NLWSS0",0,CosetMatrix(Matrix([[0,3,2]]), NLWSS.periodLattice)),
(NLWSS,"NLWSS0",9,CosetMatrix(Matrix([[3,7,6]]), NLWSS.periodLattice))],
[(NWGlider,"NWGlider0",23,CosetMatrix(Matrix([[2,6,1]]), NWGlider.periodLattice))])
fanoutComponent6 = PeriodicPattern([],31*16,-1*16,-13*16,
[(blinker,"Blinker0",0,CosetMatrix(Matrix([[0,1,2]]), blinker.periodLattice)),
(NLWSS,"NLWSS0",0,CosetMatrix(Matrix([[1,5,1]]), NLWSS.periodLattice)),
(NMWSS,"NMWSS0",25,CosetMatrix(Matrix([[3,9,3]]), NMWSS.periodLattice))],
[(NWGlider,"NWGlider0",37,CosetMatrix(Matrix([[1,5,-3]]), NWGlider.periodLattice))])
#Extra fanout components for the last track builder (slightly cheaper)
fanoutComponent7 = PeriodicPattern([],31*16,-1*16,-13*16,
[(NWGlider,"NWGlider0",0,CosetMatrix(Matrix([[1,4,0]]), NWGlider.periodLattice)),
(NLWSS,"NLWSS0",0,CosetMatrix(Matrix([[2,6,5]]), NLWSS.periodLattice)),
(NLWSS,"NLWSS1",2,CosetMatrix(Matrix([[2,0,4]]), NLWSS.periodLattice))],
[(blinker,"Blinker0",16,CosetMatrix(Matrix([[1,4,1]]), blinker.periodLattice)),
(NWGlider,"NWGlider1",17,CosetMatrix(Matrix([[1,-1,0]]), NWGlider.periodLattice))])
fanoutComponent8 = PeriodicPattern([],31*16,-1*16,-13*16,
[(blinker,"Blinker0",0,CosetMatrix(Matrix([[0,5,0]]), blinker.periodLattice)),
(NLWSS,"NLWSS0",0,CosetMatrix(Matrix([[2,8,2]]), NLWSS.periodLattice)),
(NLWSS,"NLWSS1",7,CosetMatrix(Matrix([[2,0,3]]), NLWSS.periodLattice))],
[(NWGlider,"NWGlider0",14,CosetMatrix(Matrix([[2,3,2]]), NWGlider.periodLattice))])
trackPairBuilderCollision1 = PeriodicPattern([],31*16,-1*16,-13*16,
[(WLWSS,"WLWSS0",0,CosetMatrix(Matrix([[1,1,-1]]), WLWSS.periodLattice)),
(NWGlider,"NWGlider0",0,CosetMatrix(Matrix([[2,3,5]]), NWGlider.periodLattice)),
(NWGlider,"NWGlider1",79,CosetMatrix(Matrix([[1,6,6]]), NWGlider.periodLattice))],
[(block,"Block0",8,CosetMatrix(Matrix([[0,-3,0]]), block.periodLattice)),
(block,"Block1",85,CosetMatrix(Matrix([[0,2,5]]), block.periodLattice))])
#TODO: Want to be able to 'complete a cycle with available components' when possible
#TODO: Want a slightly different last fanout
# First fanout should have different starting displacement, connect to helix
# Last fanout should not produce 'fanout continuation glider': It's cheaper to use an extra LWSS for period multiplication instead
# In fact, could potentially be even cheaper if we instead fan out into two gliders which support our climbers directly!
def fanoutPatSpacingInfo(n,dn, i, isFirstFanout = False, isLastFanout = False):
startSpacing = 207 + (dn-2)*192 - n * 16
if isFirstFanout:
startSpacing = 38 + n * 16
return [
(fanoutComponent1,"F1,"+str(i),"NWGlider0","Blinker0",startSpacing),
(fanoutComponent2,"F2,"+str(i),"Blinker0","NWGlider0",1 + n*24),
(fanoutComponent3,"F3,"+str(i),"NWGlider0","NWGlider1",50 + n*16),
(fanoutComponent4,"F4,"+str(i),"NWGlider0","Blinker0",47),
(fanoutComponent5,"F5,"+str(i),"Blinker0","NWGlider0",10),
(fanoutComponent4,"F6,"+str(i),"NWGlider0","Blinker0",94),
(fanoutComponent6,"F7,"+str(i),"Blinker0","NWGlider0",10),
]
fanoutDeviceSpacingsList = [3]
for i in range(15-1):
#15/12 is probably not the exact optimal value but it's fine
newN = int(math.ceil(fanoutDeviceSpacingsList[0] * 7 / 6 + 15/12))
fanoutDeviceSpacingsList.insert(0,newN)
fanoutPatSpacings = [(helix,"Helix","","H18.NWGlider1",0)]
for i in range(len(fanoutDeviceSpacingsList)):
if i == 0:
fanoutPatSpacings += fanoutPatSpacingInfo(fanoutDeviceSpacingsList[i], 0, i, True)
else:
fanoutPatSpacings += fanoutPatSpacingInfo(fanoutDeviceSpacingsList[i], fanoutDeviceSpacingsList[i-1] - fanoutDeviceSpacingsList[i], i, False)
#Cheaper last fanout
fanoutPatSpacings += [(fanoutComponent7,"F1,16","NWGlider0","Blinker0",68),(fanoutComponent8,"F2,16","Blinker0","NWGlider0",26)]
fanoutTrackPairBuilderConnections = [(trackPairBuilderCollision1, "T"+str(i), {"WLWSS0":"F1,"+str(i)+".WLWSS0", "NWGlider0":"F4,"+str(i)+".NWGlider1"}, {}) for i in range(15)]
periodDemultiplierCrawlerBlockInputs = [(block, "Block"+str(i), 31*i, CosetMatrix(Matrix([[0,6,-2]]) + Matrix([[0,-1,-13]])*i, block.periodLattice)) for i in range(15)]
periodDemultiplierCrawlerGliderOutputs = [(SWGlider, "SWGlider"+str(i), 22+31*i, CosetMatrix(Matrix([[1,0,3]]) + Matrix([[0,-1,-13]])*i, SWGlider.periodLattice)) for i in range(16)]
periodDemultiplierCrawler = PeriodicPattern(g.parse("2b3o$2bo2bo$bo$4b2o$o3b2o$o$2bo$2b3o$3b2o$4bo$2b2o$bobo$o3bo$2b2o!"),31*16,-1*16,-13*16,
periodDemultiplierCrawlerBlockInputs + [(NWGlider, "NWGlider0", 31*15, CosetMatrix(Matrix([[1,7,-2]]) + Matrix([[0,-1,-13]])*15, NWGlider.periodLattice))],
periodDemultiplierCrawlerGliderOutputs)
periodDemultiplierConnections = [(periodDemultiplierCrawler, "Demultiplier0", {"NWGlider0":"Main.F1,16.NWGlider1","Block0":"T0.Block0"}, {}),
(periodDemultiplierCrawler, "Demultiplier1", {"NWGlider0":"Main.F2,16.NWGlider0","Block0":"T0.Block1"}, {})]
fanoutDevice = CompoundPattern.chain(fanoutPatSpacings).addCompatibleComponents(fanoutTrackPairBuilderConnections).addCompatibleComponents(periodDemultiplierConnections)
#g.warn(str(set(fanoutDevice.outputNamesToIndexes)))
#fanoutDevice.placeWithInputs(CosetMatrix(Matrix([[0,0,0]]), fanoutDevice.periodLattice), 320, 3)
'''
#Crawlers on various objects
#TODO: May want to period-multiply periodic or compound patterns by 16
crawlerSWGliders = [0,0,0]
crawlerSWGliders[0] = PeriodicPattern(g.parse("2b3o$bo2bo$o$b3o3$4b2o$4b2o$5bo$5b2o$b3obob2o$b2o$5bo2bo$5b3o!"), 31, -1, -13,
[(SWGlider,"SWGlider0",24,CosetMatrix(Matrix([[0,6,-14]]), SWGlider.periodLattice))],
[(SWGlider,"SWGlider0",15,CosetMatrix(Matrix([[1,1,4]]), SWGlider.periodLattice))])
crawlerSWGliders[1] = PeriodicPattern(g.parse("2b3o$bo2bo$o$b3o3$4b2o$4b2o$5bo$5b2o$b3obob2o$b2o$5bo2bo$5b3o!"), 31, -1, -13,
[(SWGlider,"SWGlider0",24,CosetMatrix(Matrix([[0,6,-15]]), SWGlider.periodLattice))],
[(SWGlider,"SWGlider0",15,CosetMatrix(Matrix([[1,1,4]]), SWGlider.periodLattice))])
crawlerSWGliders[2] = PeriodicPattern(g.parse("2b3o$bo2bo$o$b3o3$4b2o$4b2o$5bo$5b2o$b3obob2o$b2o$5bo2bo$5b3o!"), 31, -1, -13,
[(SWGlider,"SWGlider0",24,CosetMatrix(Matrix([[1,6,-15]]), SWGlider.periodLattice))],
[(SWGlider,"SWGlider0",15,CosetMatrix(Matrix([[1,1,4]]), SWGlider.periodLattice))])
crawlerNWGliders = PeriodicPattern(g.parse("2b3o$bo2bo$o$b3o3$4b2o$4b2o$5bo$5b2o$b3obob2o$b2o$5bo2bo$5b3o!"), 31, -1, -13,
[(NWGlider,"NWGlider0",24,CosetMatrix(Matrix([[1,7,-14]]), NWGlider.periodLattice))],
[(SWGlider,"SWGlider0",15,CosetMatrix(Matrix([[1,1,4]]), SWGlider.periodLattice))])
crawlerBlocks = PeriodicPattern(g.parse("2b3o$bo2bo$o$b3o3$4b2o$4b2o$5bo$5b2o$b3obob2o$b2o$5bo2bo$5b3o!"), 31, -1, -13,
[(block,"Block0",24,CosetMatrix(Matrix([[0,6,-14]]), block.periodLattice))],
[(SWGlider,"SWGlider0",15,CosetMatrix(Matrix([[1,1,4]]), SWGlider.periodLattice))])
SWGliderStreamLattice = crawlerSWGliders[0].periodLattice + SWGlider.periodLattice
SWGliderStreamLaneChangeOfBasis = -Matrix([x.terms[0] for x,y in Lattice(Matrix.id(3)).quotientGeneratorsWithTorsions(SWGliderStreamLattice)]).inverse().col(2)
#The lane shift a SW glider -> SW glider crawler shifts a glider trail by
crawlerGliderLattice = SWGlider.periodLattice + crawlerSWGliders[0].periodLattice
def getGliderShift(pattern, inputIndex = 0, outputIndex = 0):
inputLane = CosetMatrix((pattern.inputs[inputIndex][3] - Matrix([[pattern.inputs[inputIndex][2],0,0]])).rep, crawlerGliderLattice)
outputLane = CosetMatrix((pattern.outputs[inputIndex][3] - Matrix([[pattern.outputs[inputIndex][2],0,0]])).rep, crawlerGliderLattice)
return outputLane - inputLane
gliderShifts = [int((getGliderShift(crawlerSWGliders[i]).rep * SWGliderStreamLaneChangeOfBasis)[0,0]) for i in range(3)] #[69,42,28]
blockLayerTrackDisplacementGoal = int(((Matrix([[0,-12,-12]]) - Matrix([[0,-15,6]])) * SWGliderStreamLaneChangeOfBasis)[0,0]) #237
#237 = 69 + 42*4
#TODO: Would like to figure out the optimal way to do this automatically
startingShiftSpacings = [(crawlerSWGliders[0],"C0","SWGlider0","SWGlider0",1),
(crawlerSWGliders[1],"C1","SWGlider0","SWGlider0",1),
(crawlerSWGliders[1],"C2","SWGlider0","SWGlider0",1),
(crawlerSWGliders[1],"C3","SWGlider0","SWGlider0",1),
(crawlerSWGliders[1],"C4","SWGlider0","SWGlider0",1)]
startingShifter = CompoundPattern.chain(startingShiftSpacings)
#startingShifter.placeWithInputs(CosetMatrix(Matrix([[0,0,0]]), startingShifter.periodLattice), 10, 10)
#TODO: All three pair-track blocklayers and all three pair-track rephasers (the latter can be compound patterns)
pairTrackBlockLayers = [0,0,0]
pairTrackBlockLayers[0] = PeriodicPattern(g.parse("2b3o$2bo2bo$bo$4b2o$o3b2o$o$2bo$2b3o$3b2o$4bo7b3o$2b2o8bo2bo$bobo6bo4bo$o3bo5bo$2b2o5b2o$10b2obo$12bo5$14b2o2$12bo2bo$11bo$11bo2bo$13bo!"),31,-1,-13,
[(SWGlider,"C0.SWGlider0",0,CosetMatrix(Matrix([[0,6,-2]]), SWGlider.periodLattice)),
(SWGlider,"C1.SWGlider0",20,CosetMatrix(Matrix([[0,16,-3]]), SWGlider.periodLattice))],
[(block,"Block0",2,CosetMatrix(Matrix([[0,11,23]]), block.periodLattice)),
(SWGlider,"C0.SWGlider0",22,CosetMatrix(Matrix([[1,0,3]]), SWGlider.periodLattice)),
(SWGlider,"C1.SWGlider0",11,CosetMatrix(Matrix([[1,11,15]]), SWGlider.periodLattice))])
pairTrackBlockLayers[1] = PeriodicPattern(g.parse("2b3o$2bo2bo$bo$4b2o$o3b2o$o$2bo$2b3o$3b2o$4bo7b3o$2b2o8bo2bo$bobo6bo4bo$o3bo5bo$2b2o5b2o$10b2obo$12bo5$14b2o2$12bo2bo$11bo$11bo2bo$13bo!"),31,-1,-13,
[(SWGlider,"C0.SWGlider0",0,CosetMatrix(Matrix([[0,6,-3]]), SWGlider.periodLattice)),
(SWGlider,"C1.SWGlider0",20,CosetMatrix(Matrix([[0,16,-4]]), SWGlider.periodLattice))],
[(block,"Block0",2,CosetMatrix(Matrix([[0,11,23]]), block.periodLattice)),
(SWGlider,"C0.SWGlider0",22,CosetMatrix(Matrix([[1,0,3]]), SWGlider.periodLattice)),
(SWGlider,"C1.SWGlider0",11,CosetMatrix(Matrix([[1,11,15]]), SWGlider.periodLattice))])
pairTrackBlockLayers[2] = PeriodicPattern(g.parse("2b3o$2bo2bo$bo$4b2o$o3b2o$o$2bo$2b3o$3b2o$4bo7b3o$2b2o8bo2bo$bobo6bo4bo$o3bo5bo$2b2o5b2o$10b2obo$12bo5$14b2o2$12bo2bo$11bo$11bo2bo$13bo!"),31,-1,-13,
[(SWGlider,"C0.SWGlider0",0,CosetMatrix(Matrix([[1,6,-3]]), SWGlider.periodLattice)),
(SWGlider,"C1.SWGlider0",20,CosetMatrix(Matrix([[1,16,-4]]), SWGlider.periodLattice))],
[(block,"Block0",2,CosetMatrix(Matrix([[0,11,23]]), block.periodLattice)),
(SWGlider,"C0.SWGlider0",22,CosetMatrix(Matrix([[1,0,3]]), SWGlider.periodLattice)),
(SWGlider,"C1.SWGlider0",11,CosetMatrix(Matrix([[1,11,15]]), SWGlider.periodLattice))])
pairTrackRephasers = [CompoundPattern([(crawlerSWGliders[i],"C0",CosetMatrix(Matrix([[0,0,0]]),crawlerSWGliders[i].periodLattice)),
(crawlerSWGliders[i],"C1",CosetMatrix(Matrix([[-16,11,-2]]),crawlerSWGliders[i].periodLattice))])
for i in range(3)]
#Block layer input for a NW rake
#Remark: The first of these should be able to be either pairTrackBlockLayer1, 2, or 3
blockLayersForNWRakeSpacings = [[(pairTrackBlockLayers[i],"BlockLayer0","C0.SWGlider0","C0.SWGlider0",4),
(pairTrackRephasers[0],"Rephaser0","C0.SWGlider0","C0.SWGlider0",4),
(pairTrackRephasers[0],"Rephaser1","C0.SWGlider0","C0.SWGlider0",1),
(pairTrackRephasers[0],"Rephaser2","C0.SWGlider0","C0.SWGlider0",1),
(pairTrackRephasers[0],"Rephaser3","C0.SWGlider0","C0.SWGlider0",1),
(pairTrackRephasers[0],"Rephaser4","C0.SWGlider0","C0.SWGlider0",1),
(pairTrackBlockLayers[0],"BlockLayer1","C0.SWGlider0","C0.SWGlider0",1),
(pairTrackBlockLayers[0],"BlockLayer2","C0.SWGlider0","C0.SWGlider0",4)] for i in range(3)]
blockLayersForNWRake = [CompoundPattern.chain(blockLayersForNWRakeSpacings[i]) for i in range(3)]
#NW rake from block input
NWRakeCore = PeriodicPattern(g.parse("2b3o$2bo2bo$bo$4b2o$o3b2o$o$2bo$2b3o$3b2o$4bo$2b2o$bobo$o3bo$2b2o5$9bo$9b2o$7bo2b2o$9b2o$7b3o$8bo8$11bo$10bobo4b2o$9b2o6b2o2$16bo$15bobo$15bobo$16b3o$16b3o$18bo$16b2o!")
,31,-1,-13,
[(block,"Block0",0,CosetMatrix(Matrix([[0,6,-2]]), block.periodLattice)),
(SWGlider,"SWGlider0",11,CosetMatrix(Matrix([[0,7,3]]), SWGlider.periodLattice)),
(block,"Block1",15,CosetMatrix(Matrix([[0,18,15]]), block.periodLattice))],
[(NWGlider,"NWGlider0",18,CosetMatrix(Matrix([[0,4,24]]), NWGlider.periodLattice))])
NWRakeFromBlocksSpacings = [(crawlerBlocks,"C0","Block0","SWGlider0",0),
(NWRakeCore,"R0","SWGlider0","NWGlider0",48)]
NWRakeFromBlocks = CompoundPattern.chain(NWRakeFromBlocksSpacings)
def NWRake(i, internalSpacing):
NWRakeSpacings = [(blockLayersForNWRake[i],"BlockLayer","BlockLayer0.C0.SWGlider0","BlockLayer0.Block0",0),
(NWRakeFromBlocks,"NWRakeFromBlocks","C0.Block0","R0.NWGlider0",internalSpacing+55)]
return CompoundPattern.chain(NWRakeSpacings)
#Component which converts a pair track into an easier-to-destroy single track
pairTrackToSingleTrackCore = PeriodicPattern(g.parse("2b3o$2bo2bo$bo$4b2o$o3b2o$o$2bo$2b3o$3b2o$4bo$2b2o$bobo$o3bo$2b2o!"), 31, -1, -13,
[(SWGlider,"SWGlider0",0,CosetMatrix(Matrix([[1,6,-3]]), SWGlider.periodLattice)),
(SWGlider,"SWGlider1",9,CosetMatrix(Matrix([[0,8,13]]), SWGlider.periodLattice))],
[(SWGlider,"SWGlider0",22,CosetMatrix(Matrix([[1,0,3]]), SWGlider.periodLattice))])
pairTrackToSingleTrackSpacings = [(crawlerSWGliders[0],"C0","SWGlider1","SWGlider0",1),
(crawlerSWGliders[0],"C1","SWGlider0","SWGlider0",1),
(crawlerSWGliders[0],"C2","SWGlider0","SWGlider0",1),
(crawlerSWGliders[0],"C3","SWGlider0","SWGlider0",1),
(crawlerSWGliders[0],"C4","SWGlider0","SWGlider0",1),
(pairTrackToSingleTrackCore,"Core","SWGlider1","SWGlider0",3)]
pairTrackToSingleTrack = CompoundPattern.chain(pairTrackToSingleTrackSpacings)
#Component which terminates a pair track
terminationGliderCollision = PeriodicPattern([],31,-1,-13,
[(SWGlider,"SWGlider0",0,CosetMatrix(Matrix([[0,0,0]]), SWGlider.periodLattice)),
(NWGlider,"NWGlider0",0,CosetMatrix(Matrix([[0,4,3]]), NWGlider.periodLattice))])
pairTrackTermination = CompoundPattern.chain([(NWRake(0,29),"NWRake","BlockLayer.BlockLayer0.C0.SWGlider0","BlockLayer.BlockLayer2.C0.SWGlider0",0),
(pairTrackToSingleTrack,"PairToSingle","Core.SWGlider0","Core.SWGlider0",31)]).addCompatibleComponents(
[(terminationGliderCollision, "Termination", {"SWGlider0":"PairToSingle.Core.SWGlider0","NWGlider0":"NWRake.NWRakeFromBlocks.R0.NWGlider0"}, {})])
#pairTrackTermination.placeWithInputs(CosetMatrix(Matrix([[0,0,0]]), pairTrackTermination.periodLattice), 10, 10)
#Track reset
#Spacings in this version are such that you can immediately construct a pair track without further spacing adjustments
minNWRakeSpacings = [297,298,299]
trackResetStart = CompoundPattern.chain([(NWRake(0,29+minNWRakeSpacings[0]+minNWRakeSpacings[1]+8),"NWRake0","BlockLayer.BlockLayer0.C0.SWGlider0","BlockLayer.BlockLayer2.C0.SWGlider0",0),
(NWRake(1,29+minNWRakeSpacings[0]),"NWRake1","BlockLayer.BlockLayer0.C0.SWGlider0","BlockLayer.BlockLayer2.C0.SWGlider0",3),
(pairTrackTermination,"Termination","Main.NWRake.BlockLayer.BlockLayer0.C0.SWGlider0","",3)])
#trackResetStart.placeWithInputs(CosetMatrix(Matrix([[0,0,0]]), trackResetStart.periodLattice), 100, 100)
trackResetEnd = CompoundPattern([(crawlerNWGliders,"C0",CosetMatrix(Matrix([[0,0,0]]),crawlerNWGliders.periodLattice)),
(crawlerNWGliders,"C1",CosetMatrix(Matrix([[-9,6,17]]),crawlerNWGliders.periodLattice))])
def trackReset(spacing):
return CompoundPattern.chain([(trackResetStart,"Start","NWRake0.BlockLayer.BlockLayer0.C0.SWGlider0","NWRake1.NWRakeFromBlocks.R0.NWGlider0",0),
(trackResetEnd,"End","C0.NWGlider0","C0.SWGlider0",spacing)])
#trackReset(512).placeWithInputs(CosetMatrix(Matrix([[0,0,0]]), trackResetStart.periodLattice), 32, 32)
#NE rake
blockLayersForNERakeSpacings = [[(pairTrackBlockLayers[i],"BlockLayer0","C0.SWGlider0","C0.SWGlider0",4),
(pairTrackRephasers[0],"Rephaser0","C0.SWGlider0","C0.SWGlider0",4),
(pairTrackRephasers[0],"Rephaser1","C0.SWGlider0","C0.SWGlider0",1),
(pairTrackRephasers[0],"Rephaser2","C0.SWGlider0","C0.SWGlider0",1),
(pairTrackRephasers[0],"Rephaser3","C0.SWGlider0","C0.SWGlider0",1),
(pairTrackRephasers[0],"Rephaser4","C0.SWGlider0","C0.SWGlider0",1),
(pairTrackRephasers[0],"Rephaser5","C0.SWGlider0","C0.SWGlider0",1),
(pairTrackBlockLayers[0],"BlockLayer1","C0.SWGlider0","C0.SWGlider0",1),
(pairTrackBlockLayers[1],"BlockLayer2","C0.SWGlider0","C0.SWGlider0",12),
(pairTrackBlockLayers[0],"BlockLayer3","C0.SWGlider0","C0.SWGlider0",4)] for i in range(3)]
blockLayersForNERake = [CompoundPattern.chain(blockLayersForNERakeSpacings[i]) for i in range(3)]
NERakeCore = PeriodicPattern(g.parse("2b3o$2bo2bo$bo$4b2o$o3b2o$o$2bo$2b3o$3b2o$4bo$2b2o$bobo$o3bo$2b2o5$9bo$9b2o$7bo2b2o$9b2o$7b3o$8bo!"),
31,-1,-13,
[(block,"Block0",0,CosetMatrix(Matrix([[0,6,-2]]), block.periodLattice)),
(SWGlider,"SWGlider0",11,CosetMatrix(Matrix([[0,7,3]]), SWGlider.periodLattice))],
[(NWGlider,"NWGlider0",18,CosetMatrix(Matrix([[0,4,24]]), NWGlider.periodLattice)),
(NEGlider,"NEGlider0",20,CosetMatrix(Matrix([[0,16,19]]), NEGlider.periodLattice)),
(blinker,"Blinker0",22,CosetMatrix(Matrix([[0,10,27]]), blinker.periodLattice))])
NERakeBlinkerCleanupKickback = PeriodicPattern(g.parse("2b3o$2bo2bo$bo$4b2o$o3b2o$o$2bo$2b3o$3b2o$4bo$2b2o$bobo$o3bo$2b2o!"),
31,-1,-13,
[(block,"Block0",0,CosetMatrix(Matrix([[0,6,-2]]), block.periodLattice)),
(NWGlider,"NWGlider0",8,CosetMatrix(Matrix([[2,8,4]]), NWGlider.periodLattice))],
[(NEGlider,"NEGlider0",14,CosetMatrix(Matrix([[2,5,4]]), NEGlider.periodLattice)),
(SWGlider,"SWGlider0",22,CosetMatrix(Matrix([[1,0,3]]), SWGlider.periodLattice))])
NERakeTerminator = PeriodicPattern([],31,-1,-13,
[(SWGlider,"SWGlider0",0,CosetMatrix(Matrix([[0,3,0]]), SWGlider.periodLattice)),
(block,"Block0",0,CosetMatrix(Matrix([[0,0,3]]), block.periodLattice))])
NERakeTerminator2 = PeriodicPattern([],31,-1,-13,
[(NEGlider,"NEGlider0",0,CosetMatrix(Matrix([[0,0,4]]), NEGlider.periodLattice)),
(blinker,"Blinker0",0,CosetMatrix(Matrix([[1,3,1]]), blinker.periodLattice))])
NERakeFromBlocksSpacings = [(crawlerBlocks,"C0","Block0","SWGlider0",0),
(NERakeCore,"R0","SWGlider0","NWGlider0",45),
(NERakeBlinkerCleanupKickback,"K0","NWGlider0","SWGlider0",15),
(NERakeTerminator,"T0","SWGlider0","",1)]
NERakeFromBlocks = CompoundPattern.chain(NERakeFromBlocksSpacings).addCompatibleComponents([(NERakeTerminator2,"T1",{"NEGlider0":"K0.NEGlider0","Blinker0":"R0.Blinker0"},{})])
def NERake(i, internalSpacing):
NERakeSpacings = [(blockLayersForNERake[i],"BlockLayer","BlockLayer0.C0.SWGlider0","BlockLayer0.Block0",0),
(NERakeFromBlocks,"NERakeFromBlocks","Main.C0.Block0","Main.R0.NWGlider0",internalSpacing+89)]
return CompoundPattern.chain(NERakeSpacings)
#NERake(0,0).placeWithInputs(CosetMatrix(Matrix([[0,0,0]]), blockLayersForNERake[0].periodLattice), 32, 32)
#SE rake (NW rake with kickback)
blockLayersForSERakeSpacings = [[(pairTrackBlockLayers[i],"BlockLayer0","C0.SWGlider0","C0.SWGlider0",4),
(pairTrackRephasers[0],"Rephaser0","C0.SWGlider0","C0.SWGlider0",4),
(pairTrackRephasers[0],"Rephaser1","C0.SWGlider0","C0.SWGlider0",1),
(pairTrackRephasers[0],"Rephaser2","C0.SWGlider0","C0.SWGlider0",1),
(pairTrackRephasers[0],"Rephaser3","C0.SWGlider0","C0.SWGlider0",1),
(pairTrackRephasers[0],"Rephaser4","C0.SWGlider0","C0.SWGlider0",1),
(pairTrackBlockLayers[0],"BlockLayer1","C0.SWGlider0","C0.SWGlider0",1),
(pairTrackBlockLayers[0],"BlockLayer2","C0.SWGlider0","C0.SWGlider0",4),
(pairTrackBlockLayers[1],"BlockLayer3","C0.SWGlider0","Block0",4)] for i in range(3)]
blockLayersForSERake = [CompoundPattern.chain(blockLayersForSERakeSpacings[i]) for i in range(3)]
SERakeKickback = PeriodicPattern([],31,-1,-13,
[(SWGlider,"SWGlider0",0,CosetMatrix(Matrix([[1,0,-1]]), SWGlider.periodLattice)),
(NWGlider,"NWGlider0",0,CosetMatrix(Matrix([[2,5,1]]), NWGlider.periodLattice))],
[(SEGlider,"SEGlider0",6,CosetMatrix(Matrix([[0,2,0]]), SEGlider.periodLattice))])
def SERake(i, internalSpacing):
NWRakeSpacings = [(blockLayersForSERake[i],"BlockLayer","BlockLayer0.C0.SWGlider0","BlockLayer0.Block0",0),
(NWRakeFromBlocks,"NWRakeFromBlocks","C0.Block0","R0.NWGlider0",internalSpacing+76),
(SERakeKickback,"K0","NWGlider0","SEGlider0",16)]
return CompoundPattern.chain(NWRakeSpacings).addCompatibleComponents([(crawlerBlocks,"C0",{"Block0":"BlockLayer.BlockLayer3.Block0"},{"SWGlider0":"K0.SWGlider0"})])
#SERake(0,0).placeWithInputs(CosetMatrix(Matrix([[0,0,0]]), blockLayersForNERake[0].periodLattice), 32, 32)
#A 16x rake
blockLayersForFilterSpacings = [[(pairTrackBlockLayers[i],"BlockLayer0","C0.SWGlider0","C0.SWGlider0",4),
(pairTrackBlockLayers[1],"BlockLayer1","C0.SWGlider0","C0.SWGlider0",61),
(pairTrackBlockLayers[0],"BlockLayer2","C0.SWGlider0","C0.SWGlider0",4),
(pairTrackRephasers[0],"Rephaser0","C0.SWGlider0","C0.SWGlider0",4),
(pairTrackRephasers[0],"Rephaser1","C0.SWGlider0","C0.SWGlider0",1),
(pairTrackRephasers[0],"Rephaser2","C0.SWGlider0","C0.SWGlider0",1),
(pairTrackBlockLayers[0],"BlockLayer3","C0.SWGlider0","C0.SWGlider0",1),
(pairTrackBlockLayers[0],"BlockLayer4","C0.SWGlider0","C0.SWGlider0",7+6*2),
(pairTrackBlockLayers[1],"BlockLayer5","C0.SWGlider0","C0.SWGlider0",4)] for i in range(3)]
blockLayersForFilter = [CompoundPattern.chain(blockLayersForFilterSpacings[i]) for i in range(3)]
NWRakeFromFilterBlocksSpacings = [(crawlerBlocks,"C0","Block0","SWGlider0",0),
(crawlerSWGliders[0],"C0","SWGlider0","SWGlider0",1),
(crawlerSWGliders[0],"C0","SWGlider0","SWGlider0",1),
(crawlerSWGliders[0],"C0","SWGlider0","SWGlider0",1),
(crawlerSWGliders[0],"C0","SWGlider0","SWGlider0",1),
(crawlerSWGliders[0],"C0","SWGlider0","SWGlider0",1),
(crawlerSWGliders[0],"C0","SWGlider0","SWGlider0",1),
(crawlerSWGliders[0],"C0","SWGlider0","SWGlider0",1),
(crawlerSWGliders[0],"C0","SWGlider0","SWGlider0",1),
(NWRakeCore,"R0","SWGlider0","NWGlider0",15)]
NWRakeFromFilterBlocks = CompoundPattern.chain(NWRakeFromFilterBlocksSpacings)
def Filter(i, internalSpacing):
filterSpacings = [(blockLayersForFilter[i],"BlockLayer","BlockLayer0.C0.SWGlider0","BlockLayer0.Block0",0),
(NWRakeFromFilterBlocks,"FilterFromBlocks","C0.Block0","R0.NWGlider0",internalSpacing+112)]
return CompoundPattern.chain(filterSpacings)
def FilteredSERake1XSupport(parity, color):
filteredRakeSpacings = [(Filter(1,342-color*4+parity),"Filter","BlockLayer.BlockLayer0.C0.SWGlider0","BlockLayer.BlockLayer5.C0.SWGlider0",0),
(SERake(color,parity),"Rake","Main.BlockLayer.BlockLayer0.C0.SWGlider0","Main.BlockLayer.BlockLayer3.C0.SWGlider0",6)]
if color == 1:
filteredRakeSpacings.append((pairTrackRephasers[0],"Rephaser0","C0.SWGlider0","C0.SWGlider0",11))
return CompoundPattern.chain(filteredRakeSpacings)
filterBlockPull = PeriodicPattern([],31*16,-1*16,-13*16,
[(block, "Block"+str(i), 31*i, CosetMatrix(Matrix([[0,0,2]]) + Matrix([[0,-1,-13]])*i, block.periodLattice)) for i in range(15)] +
[(NWGlider, "NWGlider"+str(i), 31*i, CosetMatrix(Matrix([[1,4,0]]) + Matrix([[0,-1,-13]])*i, NWGlider.periodLattice)) for i in range(15)],
[(block, "Block"+str(i), 8+31*i, CosetMatrix(Matrix([[0,2,3]]) + Matrix([[0,-1,-13]])*i, block.periodLattice)) for i in range(15)])
filterGliderTurn = PeriodicPattern(g.parse("2b3o$2bo2bo$bo$4b2o$o3b2o$o$2bo$2b3o$3b2o$4bo$2b2o$bobo$o3bo$2b2o!"),31*16,-1*16,-13*16,
[(block,"Block"+str(i), 31*i, CosetMatrix(Matrix([[0,6,-2]]) + Matrix([[0,-1,-13]])*i, block.periodLattice)) for i in range(16)]+
[(NWGlider,"NWGlider0",6,CosetMatrix(Matrix([[0,5,14]]), NWGlider.periodLattice))],
[(SWGlider,"SWGlider0",10,CosetMatrix(Matrix([[1,5,10]]), SWGlider.periodLattice))]+
[(SWGlider,"ExtraSWGlider"+str(i), 22+31*i, CosetMatrix(Matrix([[1,0,3]]) + Matrix([[0,-1,-13]])*i, SWGlider.periodLattice)) for i in range(16)])
filterGliderTurnTerminator = PeriodMultipliedPattern(PeriodicPattern([],31,-1,-13,
[(SWGlider,"SWGlider0",0,CosetMatrix(Matrix([[2,3,-1]]), SWGlider.periodLattice)),
(block,"Block0",0,CosetMatrix(Matrix([[0,0,3]]), block.periodLattice))]), 16)
#The different glider colors use different reactions here
filterGliderBlockCollisionColor = [PeriodicPattern([],31*16,-1*16,-13*16,
[(block, "Block"+str(i), 31*i, CosetMatrix(Matrix([[0,2,4]]) + Matrix([[0,-1,-13]])*i, block.periodLattice)) for i in range(15)] +
[(SEGlider, "SEGlider"+str(i), 31*i, CosetMatrix(Matrix([[3,-1,-1]]) + Matrix([[0,-1,-13]])*i, SEGlider.periodLattice)) for i in range(15)],[]),
PeriodicPattern([],31*16,-1*16,-13*16,
[(block, "Block"+str(i), 31*i, CosetMatrix(Matrix([[0,3,4]]) + Matrix([[0,-1,-13]])*i, block.periodLattice)) for i in range(15)] +
[(SEGlider, "SEGlider"+str(i), 31*i, CosetMatrix(Matrix([[0,0,0]]) + Matrix([[0,-1,-13]])*i, SEGlider.periodLattice)) for i in range(15)],[])]
#Input timing can be fairly variable in this design
def FilteredSERakeWithoutInput(parity,color):
support = PeriodMultipliedPattern(FilteredSERake1XSupport(parity,color), 16)
return support.addCompatibleComponents([
(filterBlockPull,"BlockPull",{"Block0":"Instance0.Filter.BlockLayer.BlockLayer3.Block0","NWGlider0":"Instance0.Filter.FilterFromBlocks.R0.NWGlider0"},{}),
(filterGliderTurn,"GliderTurn",{"Block0":"Instance8.Filter.BlockLayer.BlockLayer4.Block0","NWGlider0":"Instance0.Filter.FilterFromBlocks.R0.NWGlider0"},{})
]).addCompatibleComponents([
(filterGliderTurnTerminator,"GliderTurnTerminator",{"Instance0.Block0":"Main.Instance0.Filter.BlockLayer.BlockLayer5.Block0","Instance0.SWGlider0":"GliderTurn.ExtraSWGlider0"},{}),
(filterGliderBlockCollisionColor[color],"FilterCollision",{"Block0":"BlockPull.Block0","SEGlider0":"Main.Instance0.Rake.Main.K0.SEGlider0"},{})
])
filteredSERakeWithoutInputCache = [[FilteredSERakeWithoutInput(parity,color) for color in range(2)] for parity in range(2)]
filterInputCollision = PeriodicPattern([],31*16,-1*16,-13*16,
[(SWGlider,"SWGlider0",0,CosetMatrix(Matrix([[1,1,-1]]), SWGlider.periodLattice)),
(block,"Block0",0,CosetMatrix(Matrix([[0,0,4]]), block.periodLattice))])
def FilteredSERake(parity,color,inputTiming):
return CompoundPattern.chain([(filteredSERakeWithoutInputCache[parity][color],"Filter","","Main.Main.Instance15.Filter.BlockLayer.BlockLayer3.Block0",0),
(filterInputCollision,"InputCollision","Block0","",inputTiming+66)])
'''
periodLattice = Lattice(Matrix([[31,1,13]])*16)
FilteredSERake(0,0,239).placeWithInputs(CosetMatrix(Matrix([[0,0,0]]), periodLattice), 16, 16)
FilteredSERake(1,0,240).placeWithInputs(CosetMatrix(Matrix([[0,4096,0]]), periodLattice), 16, 16)
FilteredSERake(0,1,238).placeWithInputs(CosetMatrix(Matrix([[0,4096*2,0]]), periodLattice), 16, 16)
FilteredSERake(1,1,239).placeWithInputs(CosetMatrix(Matrix([[0,4096*3,0]]), periodLattice), 16, 16)
'''
#Pass the filter stream along with our track reset
blockLayersForFilterResetsSpacings = [[(pairTrackBlockLayers[i],"BlockLayer0","C0.SWGlider0","C0.SWGlider0",4),
(pairTrackBlockLayers[1],"BlockLayer1","C0.SWGlider0","C0.SWGlider0",61),
(pairTrackBlockLayers[0],"BlockLayer2","C0.SWGlider0","C0.SWGlider0",4),
(pairTrackRephasers[0],"Rephaser0","C0.SWGlider0","C0.SWGlider0",4),
(pairTrackRephasers[0],"Rephaser1","C0.SWGlider0","C0.SWGlider0",1),
(pairTrackRephasers[0],"Rephaser2","C0.SWGlider0","C0.SWGlider0",1),
(pairTrackBlockLayers[0],"BlockLayer3","C0.SWGlider0","C0.SWGlider0",3)] for i in range(3)]
blockLayersForFilterResets = [CompoundPattern.chain(blockLayersForFilterResetsSpacings[i]) for i in range(3)]
filterResetStart = CompoundPattern.chain([(blockLayersForFilterResets[1],"BlockLayer","BlockLayer0.C0.SWGlider0","BlockLayer0.Block0",0),
(NWRakeFromFilterBlocks,"NWRake","C0.Block0","R0.NWGlider0",1178+128)])
def TrackResetWithFilter1XPart(spacing):
componentSpacings = [(filterResetStart,"FilterResetStart","BlockLayer.BlockLayer0.C0.SWGlider0","BlockLayer.BlockLayer3.C0.SWGlider0",0),
(trackReset(spacing),"TrackReset","Start.NWRake0.BlockLayer.BlockLayer0.C0.SWGlider0","End.C0.SWGlider0",4+3),
(pairTrackBlockLayers[0],"BlockLayer0","C0.SWGlider0","C0.SWGlider0",6),
(pairTrackBlockLayers[0],"BlockLayer1","C0.SWGlider0","C0.SWGlider0",4),
(pairTrackRephasers[0],"Rephaser0","C0.SWGlider0","C0.SWGlider0",4),
(pairTrackRephasers[0],"Rephaser1","C0.SWGlider0","C0.SWGlider0",1),
(pairTrackRephasers[0],"Rephaser1","C0.SWGlider0","C0.SWGlider0",1),
(pairTrackRephasers[0],"Rephaser1","C0.SWGlider0","C0.SWGlider0",1),
(pairTrackRephasers[0],"Rephaser1","C0.SWGlider0","C0.SWGlider0",1),
(pairTrackRephasers[0],"Rephaser1","C0.SWGlider0","C0.SWGlider0",1),
(pairTrackRephasers[0],"Rephaser1","C0.SWGlider0","C0.SWGlider0",1)]
return CompoundPattern.chain(componentSpacings)
filterResetBlockCollision = PeriodicPattern([],31*16,-1*16,-13*16,
[(block, "Block"+str(i), 31*i, CosetMatrix(Matrix([[0,0,0]]) + Matrix([[0,-1,-13]])*i, block.periodLattice)) for i in range(15)] +
[(NWGlider, "NWGlider"+str(i), 31*i, CosetMatrix(Matrix([[3,2,4]]) + Matrix([[0,-1,-13]])*i, NWGlider.periodLattice)) for i in range(15)])
filterGliderTurnTerminator2 = PeriodMultipliedPattern(PeriodicPattern([],31,-1,-13,
[(SWGlider,"SWGlider0",0,CosetMatrix(Matrix([[0,3,0]]), SWGlider.periodLattice)),
(block,"Block0",0,CosetMatrix(Matrix([[0,0,3]]), block.periodLattice))]), 16)
def TrackResetWithFilterNoInput(spacing):
support = PeriodMultipliedPattern(TrackResetWithFilter1XPart(spacing), 16)
#I do not like this: The fact that I have to do this kludge indicates a flaw in my approach here
blockInstance = mod((spacing * 4 + 21) // 31 + 9, 16)
return support.addCompatibleComponents([
(filterResetBlockCollision,"BlockCollision",{"Block0":"Instance0.FilterResetStart.BlockLayer.BlockLayer3.Block0","NWGlider0":"Instance0.FilterResetStart.NWRake.R0.NWGlider0"},{}),
(filterGliderTurn,"GliderTurn",{"Block0":"Instance"+str(blockInstance)+".BlockLayer0.Block0","NWGlider0":"Instance0.FilterResetStart.NWRake.R0.NWGlider0"},{})
]).addCompatibleComponents([
(filterGliderTurnTerminator2,"GliderTurnTerminator",{"Instance0.Block0":"Main.Instance0.BlockLayer1.Block0","Instance0.SWGlider0":"GliderTurn.ExtraSWGlider0"},{})
])
#With variable input position
def TrackResetWithFilter(trackResetSpacing, inputTiming):
return CompoundPattern.chain([
(TrackResetWithFilterNoInput(trackResetSpacing),"TrackReset","","Main.Main.Instance15.FilterResetStart.BlockLayer.BlockLayer3.Block0",0),
(filterInputCollision,"InputCollision","Block0","",inputTiming+66)
])
periodLattice = Lattice(Matrix([[31,1,13]])*16)
TrackResetWithFilter(1024,99).placeWithInputs(CosetMatrix(Matrix([[0,0,0]]), periodLattice), 8, 8)
#TODO: Some of these names are getting really long
#Might be good to be able to rename things
#TODO: Would also be nice to have a function for making trees instead of just chains
Added a component for passing the filter along with track resets to the above script.