Page 1 of 1

Thread for custom aperiodic (substitution) tilings

Posted: April 17th, 2021, 12:33 am
by GUYTU6J
It has been a while since @FWCamelship (in a PM), @yujh, @Schiaparelliorbust, and @BokaBB sent their congratulations to my 20th birthday, but I was unfortunately busy yesterday so I could not respond earlier — anyway, thank you!

Back to topic, this thread is made because of a great gallery, Dirk Frettlöh's Tilings Encyclopedia. Not having any mathematical knowledge on this field, I tried to invent my own substitution rules and see how delicate the resulting tilings are.

Previously I constructed my patterns with (don't ask) ChemDraw by manually putting together polygons one by one, and of course it's a time-consuming process prone to random errors. Once I asked, and got a reply:
dvgrn wrote: September 11th, 2019, 8:16 am
GUYTU6J wrote:I download the software Ready under gollygang umbrella, and found it is capable of generating finite Penrose tiling by subdivision. Can I modify anything so that it can be applied to custom subdivision rules and effectively leading to other aperiodic 2d tilings?
Interesting question. ...
[details about how Ready works omitted]
The coding for this kind of thing isn't as painful as it might seem, though. For example, see illustrations in p29-30 of Volume 1 of the G4G13 Gift Exchange book, for some recursive subdivisions of Robert Ammann's golden-bee / chair tiling. I have the Python code for generating those meshes, if you're interested. Here's a sample -- runs in Golly, for no particularly good reason; only fifty-some lines of code, including the SVG generation:

Code: Select all

import golly as g
import math
import svgwrite
from svgwrite import cm, mm

def goldpt(coord1, coord2):
  x1, y1 =  coord1; x2, y2 = coord2
  x3, y3 = x1+golden*(x2-x1), y1+golden*(y2-y1)
  return (x3, y3)
  
level = 12

dwg = svgwrite.Drawing(filename='c:/your/path/here/goldenbee.svg', debug=True)
scale = 500.0
margin = scale/10
golden = math.sqrt(5)/2.0-0.5
top = 0
left = 0
bottom=scale
right = math.sqrt(golden)*scale
ledge = bottom - golden*(bottom-top)
vert = left+golden*(right-left)
coord1 = (left,top)
coord2 = (left,bottom)
coord3 = (right,bottom)
coord4 = (right, ledge)
coord5 = (vert, ledge)
coord6 = (vert, top)

dwg.viewbox(left-margin, top-margin, right+margin, bottom+margin)
dwg.add(dwg.g(id='demo'))
tilelist = [([coord1, coord2, coord3, coord4, coord5, coord6], 1, 0)]
while level>0:
  level -= 1
  newtilelist=[]
  for tile in tilelist:
      coord1, coord2, coord3, coord4, coord5, coord6 = tile[0]
      coord7 = goldpt(coord3, coord1)
      coord9 = goldpt(coord1, coord2)
      coordZ = goldpt(coord3, coord4)
      coord8 = goldpt(coordZ, coord9)
      othercolor = tile[2]+1 -(3 if tile[2]==2 else 0)
      newtilelist.append(([coord2, coord3, coord4, coord7, coord8, coord9],tile[1]+1, tile[2]))
      newtilelist.append(([coord9, coord1, coord6, coord5, coord7, coord8], tile[1]+2, othercolor))
  tilelist=newtilelist[:]
for tile in tilelist[::-1]:
  if tile[2]==0: s="rgb(90%,90%,100%)"
  elif tile[2]==1: s="rgb(70%,90%,50%)"
  else: s="rgb(100%,80%,90%)"
  tiles = dwg.add(dwg.g(stroke='green', fill=s, stroke_width=10.0/tile[1]))
  tiles.add(dwg.polygon(tile[0]))

dwg.save()
g.show("Wrote output to goldenbee.svg.")
You'll probably have to do

Code: Select all

pip install svgwrite
before the script will run.
...
However, the coding work for my own substitutions hasn't become possible until my recent decision to take a Python course and actually look into programming.

Re: Thread for custom aperiodic (substitution) tilings

Posted: April 17th, 2021, 12:45 am
by GUYTU6J
My earliest invention was based on marked regular hexagons and Gosper island, which I dubbed "Graphene's dream" due to its resemblance of a partially hydrogenated fragment of graphene mulecules:
Graphene's dream.png
Graphene's dream.png (61.56 KiB) Viewed 1246 times
With some python-coding ability and Visual Studio Code, I hacked dvgrn's code and wrote this inefficiently:

Code: Select all

import svgwrite

def one_third(coord1, coord2):
    x1, y1 =  coord1; x2, y2 = coord2
    x3, y3 = (2*x1)/3+x2/3, (2*y1)/3+y2/3
    return (x3, y3)

def one_half(coord1, coord2):
    x1, y1 =  coord1; x2, y2 = coord2
    x3, y3 = 0.5*x1+0.5*x2, 0.5*y1+0.5*y2
    return (x3, y3)

level = 3
dwg = svgwrite.Drawing(filename='d:/Mathematics/substitution/custom1.svg', debug=True)
scale = 1000.0
margin = scale/10
top = 0
left = 0
bottom = scale
right = scale
coord1 = (left,top)
coord2 = (left,bottom)
coord3 = (right,bottom)
coord4 = (right,top)

dwg.viewbox(left-margin, top-margin, right+margin, bottom+margin)
dwg.add(dwg.g(id='demo'))
tilelist = [([coord1, coord2, coord3, coord4], 0)]
while level>0:
    level -= 1
    newtilelist=[]
    for tile in tilelist:
        coord1, coord2, coord3, coord4 = tile[0]
        if tile[1] == 0: #square
            coord5 = one_third(coord1, coord2)
            coord6 = one_third(coord2, coord3)
            coord7 = one_third(coord3, coord4)
            coord8 = one_third(coord4, coord1)
            coordW = one_third(coord2, coord1)
            coordX = one_third(coord3, coord2)
            coordY = one_third(coord4, coord3)
            coordZ = one_third(coord1, coord4)
            coord9 = one_half(coord5, coordX)
            coord10 = one_half(coord6, coordY)
            coord11 = one_half(coord7, coordZ)
            coord12 = one_half(coord8, coordW)
            newtilelist.append(([coord9, coord10, coord11, coord12], 0))
            newtilelist.append(([coord1, coord5, coord11, coord8], 1))
            newtilelist.append(([coord2, coord6, coord12, coord5], 2))
            newtilelist.append(([coord3, coord7, coord9, coord6], 1))
            newtilelist.append(([coord4, coord8, coord10, coord7], 2))
        else: #rectangles in both orientation
            coord5 = one_third(coord1, coord2)
            coord6 = one_half(coord2, coord3)
            coord7 = one_third(coord3, coord4)
            coord8 = one_half(coord4, coord1)
            coord9 = one_third(coord8, coord1)
            coord10 = one_third(coord1, coord8)
            coord11 = one_third(coord2, coord6)
            coord12 = one_third(coord6, coord3)
            coord13 = one_third(coord3, coord6)
            coord14 = one_third(coord4, coord8)
            coord15 = one_third(coord10, coord11)
            coord16 = one_third(coord11, coord10)
            coord17 = one_third(coord13, coord14)
            coord18 = one_third(coord14, coord13)
            coord19 = one_third(coord8, coord6)
            coord20 = one_third(coord6, coord8)
            coord21 = one_third(coord19, coord5)
            coord22 = one_half(coord16, coord20)
            coord23 = one_third(coord20, coord7)
            coord24 = one_half(coord18, coord19)
            newtilelist.append(([coord2, coord11, coord15, coord5], tile[1]))
            newtilelist.append(([coord16, coord22, coord9, coord10], tile[1]))
            newtilelist.append(([coord4, coord14, coord17, coord7], tile[1]))
            newtilelist.append(([coord18, coord24, coord12, coord13], tile[1]))
            if tile[1] == 1:
                othercolor = 2
                newtilelist.append(([coord1, coord5, coord15, coord10], 0))
                newtilelist.append(([coord9, coord21, coord19, coord8], 0))
                newtilelist.append(([coord3, coord7, coord17, coord13], 0))
                newtilelist.append(([coord12, coord23, coord20, coord6], 0))
            else:
                othercolor = 1
                newtilelist.append(([coord5, coord15, coord10, coord1], 0))
                newtilelist.append(([coord21, coord19, coord8, coord9], 0))
                newtilelist.append(([coord7, coord17, coord13, coord3], 0))
                newtilelist.append(([coord23, coord20, coord6, coord12], 0))
            newtilelist.append(([coord8, coord19, coord18, coord14], othercolor))
            newtilelist.append(([coord21, coord22, coord23, coord24], othercolor))
            newtilelist.append(([coord16, coord11, coord6, coord20], othercolor))
        tilelist=newtilelist[:]

for tile in tilelist[::-1]:
    if tile[1]==2: s="rgb(255,204,102)"
    elif tile[1]==1: s="rgb(204,204,204)"
    else: s="rgb(0,0,98)"
    tiles = dwg.add(dwg.g(stroke='black', fill=s, stroke_width=1.0))
    tiles.add(dwg.polygon(tile[0]))

dwg.save()
print("Image created successfully.")
which is based on another sketch:
custom1_info.png
custom1_info.png (24.28 KiB) Viewed 1246 times
---
One of my main reasons to write this post is that LifeWiki doesn't allow any SVG files to be uploaded. I thought forum threads could, but I got this upon trying:

Code: Select all

ERROR
It was not possible to determine the dimensions of the image. Please verify that the URL you entered is correct.

Re: Thread for custom aperiodic (substitution) tilings

Posted: April 18th, 2021, 9:13 pm
by pcallahan
GUYTU6J wrote: April 17th, 2021, 12:45 am My earliest invention was based on marked regular hexagons and Gosper island, which I dubbed "Graphene's dream" due to its resemblance of a partially hydrogenated fragment of graphene mulecules
Have you considered making the marks more visible? I am curious how they change over time, but it's hard to see against the hexagons. Removing the hexagons completely is one option, though I guess it will not look like graphene.

Re: Thread for custom aperiodic (substitution) tilings

Posted: April 23rd, 2021, 12:11 pm
by GUYTU6J
pcallahan wrote: April 18th, 2021, 9:13 pm
GUYTU6J wrote: April 17th, 2021, 12:45 am My earliest invention was based on marked regular hexagons and Gosper island, which I dubbed "Graphene's dream" due to its resemblance of a partially hydrogenated fragment of graphene m[o]lecules
Have you considered making the marks more visible? I am curious how they change over time, but it's hard to see against the hexagons. Removing the hexagons completely is one option, though I guess it will not look like graphene.
I carried out cyclopropanation to these double bonds (not with any carbene, but with math tools) so that you could see some triangles as marks:
cyclopropanated.png
cyclopropanated.png (89.57 KiB) Viewed 1170 times
The script for its generation got figured out today, and here I used some fancy colors:

Code: Select all

#N Graphene's dream colored tiling generator
#O GUYTU6J, April 23 2021
import svgwrite
import math
def rotate60(coord0, coord1):
    """Rotates vector from coord0 to coord1 counterclockwise by pi/3 in mathematical coordinate system;
    in screen coordinates it is clockwise"""
    global a
    a = math.sqrt(3)
    x1, y1 =  coord0; x2, y2 = coord1
    x3, y3 = ((a*(y1-y2))+(x2-x1))/2 + x1, ((a*(x2-x1))+(y2-y1))/2 + y1
    return (x3, y3)

def gosper(coord0, coord1):
    """Scale vector from coord0 to coord1 by 1/sqrt(7), then rotate counterclockwise by arctan((sqrt(3))/5)
    in mathematical coordinate system; in screen coordinates it is clockwise"""
    x1, y1 =  coord0; x2, y2 = coord1
    x3, y3 = ((a*(y1-y2))+5*(x2-x1))/14 + x1, ((a*(x2-x1))+5*(y2-y1))/14 + y1
    return (x3, y3)

def one_half(coord1, coord2):
    """Find midpoint"""
    x1, y1 =  coord1; x2, y2 = coord2
    x3, y3 = 0.5*x1+0.5*x2, 0.5*y1+0.5*y2
    return (x3, y3)

level = 4
dwg = svgwrite.Drawing(filename='d:/Mathematics/substitution/graphene_dream/graphene_dream.svg', debug=True)
scale = 2000.0
margin = scale/10
top = 0
left = 0
bottom = scale
right = scale
coord0 = (scale/2, scale/2)
coord1 = (scale, scale/2)
coord2 = rotate60(coord0, coord1)
coord3 = rotate60(coord0, coord2)
coord4 = rotate60(coord0, coord3)
coord5 = rotate60(coord0, coord4)
coord6 = rotate60(coord0, coord5)

dwg.viewbox(left-margin, top-margin, right+margin, bottom+margin)
dwg.add(dwg.g(id='demo'))
tilelist = [([coord0, coord1, coord2, coord3, coord4, coord5, coord6], 0)]
while level>0:
    level -= 1
    newtilelist=[]
    for tile in tilelist:
        coord0, coord1, coord2, coord3, coord4, coord5, coord6 = tile[0]
        coord7 = gosper(coord0, coord1)
        coord8 = gosper(coord0, coord2)
        coord9 = gosper(coord0, coord3)
        coord10 = gosper(coord0, coord4)
        coord11 = gosper(coord0, coord5)
        coord12 = gosper(coord0, coord6)
        coord13 = one_half(coord12, coord1)
        coord14 = one_half(coord7, coord2)
        coord15 = one_half(coord8, coord3)
        coord16 = one_half(coord9, coord4)
        coord17 = one_half(coord10, coord5)
        coord18 = one_half(coord11, coord6)
        coord19 = rotate60(coord14, coord7)
        coord20 = rotate60(coord14, coord19)
        coord21 = rotate60(coord15, coord8)
        coord22 = rotate60(coord15, coord21)
        coord23 = rotate60(coord16, coord9)
        coord24 = rotate60(coord16, coord23)
        coord25 = rotate60(coord17, coord10)
        coord26 = rotate60(coord17, coord25)
        coord27 = rotate60(coord18, coord11)
        coord28 = rotate60(coord18, coord27)
        coord29 = rotate60(coord13, coord12)
        coord30 = rotate60(coord13, coord29)
        other = 1 - tile[1]
        newtilelist.append(([coord0, coord7, coord8, coord9, coord10, coord11, coord12], tile[1]))
        newtilelist.append(([coord13, coord7, coord12, coord29, coord30, coord1, coord19], other))
        newtilelist.append(([coord16, coord10, coord9, coord23, coord24, coord4, coord25], other))
        newtilelist.append(([coord17, coord11, coord10, coord25, coord26, coord5, coord27], other))
        newtilelist.append(([coord14, coord8, coord7, coord19, coord20, coord2, coord21], 1))
        if tile[1]==0: 
            newtilelist.append(([coord15, coord9, coord8, coord21, coord22, coord3, coord23], other))
            newtilelist.append(([coord18, coord12, coord11, coord27, coord28, coord6, coord29], other))
        else: 
            newtilelist.append(([coord15, coord23, coord9, coord8, coord21, coord22, coord3], other))
            newtilelist.append(([coord18, coord27, coord28, coord6, coord29, coord12, coord11], 1))
    tilelist=newtilelist[:]

for tile in tilelist[::-1]:
    if tile[1]==0: 
        del tile[0][0]
    else:
        coord0, coord1, coord2, coord3, coord4, coord5, coord6 = tile[0]
        tilelist.append(([coord0, coord1, coord6], 2))

for tile in tilelist[::-1]:    
    if tile[1]==0: s="rgb(203,160,214)" #Full hexagon
    elif tile[1]==1: s="rgb(20,55,87)" #Partial hexagon
    elif tile[1]==2: s="rgb(216,88,145)" #Triangle in the partial hexagon
    tiles = dwg.add(dwg.g(stroke='black', fill=s, stroke_width=1.0))
    tiles.add(dwg.polygon(tile[0]))
dwg.save()
print("Image created successfully.")

These some 100-line code seems a bit messy due to issues with screen coordinates where y-axis goes downwards. The original scheme was
index_scheme.png
index_scheme.png (26.97 KiB) Viewed 1170 times
When debugging, I realized that there is an alternative definition where the non-decorated hexagon is substituted in another chirality.