Thread for custom aperiodic (substitution) tilings

A forum where anything goes. Introduce yourselves to other members of the forums, discuss how your name evolves when written out in the Game of Life, or just tell us how you found it. This is the forum for "non-academic" content.
Post Reply
GUYTU6J
Posts: 2200
Joined: August 5th, 2016, 10:27 am
Location: 拆哪!I repeat, CHINA! (a.k.a. 种花家)
Contact:

Thread for custom aperiodic (substitution) tilings

Post by GUYTU6J » April 17th, 2021, 12:33 am

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.

GUYTU6J
Posts: 2200
Joined: August 5th, 2016, 10:27 am
Location: 拆哪!I repeat, CHINA! (a.k.a. 种花家)
Contact:

Re: Thread for custom aperiodic (substitution) tilings

Post by GUYTU6J » 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:
Graphene's dream.png
Graphene's dream.png (61.56 KiB) Viewed 1050 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 1050 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.

User avatar
pcallahan
Posts: 845
Joined: April 26th, 2013, 1:04 pm

Re: Thread for custom aperiodic (substitution) tilings

Post by pcallahan » 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 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.

GUYTU6J
Posts: 2200
Joined: August 5th, 2016, 10:27 am
Location: 拆哪!I repeat, CHINA! (a.k.a. 种花家)
Contact:

Re: Thread for custom aperiodic (substitution) tilings

Post by GUYTU6J » April 23rd, 2021, 12:11 pm

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 974 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 974 times
When debugging, I realized that there is an alternative definition where the non-decorated hexagon is substituted in another chirality.

Post Reply