The Fibonacci tiling

In this page, we explain how we can define a substitution tiling, and use this library to generate pictures of it. We do this in the example of the Fibonacci tiling.

using SubstitutionTilings
using SubstitutionTilings.NumFields
using StructEquality
using Luxor

In order to be able to do frequency computation, we need to work in a group with exact equality, so we can't use floats as coordinates. We work in our own implementation of the field $\mathbb{Q}(\tau)$ instead, where $\tau = \frac{1 + \sqrt{5}}{2}$. If you want to define number fields for your purposes, I recommend looking into Nemo.jl and Hecke.jl.

@NumFields.simple_number_field_concrete Qτ [1,1] τ
Base.promote_rule(::Type{Qτ}, ::Type{<:Integer}) = Qτ

Now we define the coordinate group of the tiles (which is just a wrapper around $\mathbb{Q}(\tau)$) in this case) and the prototiles

@struct_hash_equal_isequal struct FibElem <: DGroupElem
    a :: Qτ
end

@enum FibTile begin
    A=1
    B=2
end

function Base.:*(x :: FibElem, y :: FibElem)
    return FibElem(x.a + y.a)
end

function SubstitutionTilings.dilate(λ :: Qτ, x :: FibElem)
    return FibElem(λ*x.a)
end

function Base.inv(x :: FibElem)
    return FibElem(-x.a)
end

function SubstitutionTilings.id(::Type{FibElem})
    return FibElem(0)
end

We can draw the tiles as follows:

function SubstitutionTilings.embed_aff(g :: FibElem)
    fτ = (1+sqrt(5))/2
    return [1, 0, 0, 1, embed_field(float,fτ, g.a), 0]
end

function SubstitutionTilings.draw(ptile::FibTile, action)
    fτ = (1+sqrt(5))/2
    if ptile == A
        return Luxor.poly([
            Point(-fτ*0.9,-10),
            Point(-fτ*0.9,10),
            Point(fτ*0.9,10),
            Point(fτ*0.9,-10)
        ], close = true, action)
    else
        return Luxor.poly([
            Point(-0.9,-10),
            Point(-0.9,10),
            Point(0.9,10),
            Point(0.9,-10)
        ], close = true, action)
    end
end

Then the substitution is defined by

fib = SubSystem(Dict(A => [FibElem(Qτ(-1)//2) => A, FibElem(τ//2) => B], B => [FibElem(0) => A]),τ)
SubSystem{Main.FibElem, Main.Qτ, Main.FibTile}(Dict{Main.FibTile, Vector{Pair{Main.FibElem, Main.FibTile}}}(Main.B => [Main.FibElem(Main.Qτ([0, 0], 1)) => Main.A], Main.A => [Main.FibElem(Main.Qτ([-1, 0], 2)) => Main.A, Main.FibElem(Main.Qτ([0, 1], 2)) => Main.B]), Main.Qτ([0, 1], 1))

And we can calculate substitutions:

fib_tiling = substitute(fib, Dict([FibElem(0) => A]), 3)
Dict{Main.FibElem, Main.FibTile} with 5 entries:
  FibElem(Qτ([-1, -1], 2)) => B
  FibElem(Qτ([1, 3], 2))   => B
  FibElem(Qτ([-1, -1], 1)) => A
  FibElem(Qτ([0, 1], 1))   => A
  FibElem(Qτ([0, 0], 1))   => A
function color(tile :: Pair{FibElem,FibTile})
    return (tile[2] == A) ? 1 : 2
end

width = 800
height = 20
sc = 5
@png begin
    colors = ["#DD93FC", "#E7977A"]
    first_tile = [FibElem(0) => A]
    tiling = substitute(fib, Dict([FibElem(0) => A]), 16)
    setline(1)

    for tile in tiling
        origin()
        draw(tile, sc, colors[color(tile)], :fill)
    end
end width height
Example block output

In order to calculate frequencies, we need to be able to know when a tile is interior to a patch and what its collar is:

function SubstitutionTilings.is_interior(tiling :: Dict, t :: FibElem)
    return haskey(tiling, t) && (haskey(tiling, FibElem(t.a-τ)) || haskey(tiling, FibElem(t.a-(1+τ)//2))) && (haskey(tiling, FibElem(t.a+τ)) || haskey(tiling, FibElem(t.a+(1+τ)//2)))
end

function SubstitutionTilings.collar_in(tiling :: Dict, t :: FibElem)
    if !is_interior(tiling, t)
        throw(UnrecognizedCollar)
    end
    collar = Dict{FibElem, FibTile}([])
    shifts = ([0, -τ, -(1+τ)//2, τ, (1+τ)//2])
    for shift in shifts
        if haskey(tiling, FibElem(t.a+shift))
            collar[FibElem(t.a+shift)] = tiling[FibElem(t.a+shift)]
        end
    end
    return collar
end

The total collaring SubstitutionTilings.jl computes has 4 collars:

initial_collar = collar_in(fib_tiling, FibElem(0))
(collars, fib_c) = total_collaring(fib, initial_collar)

collars
4-element Vector{Dict{Main.FibElem, Main.FibTile}}:
 Dict(Main.FibElem(Main.Qτ([-1, -1], 2)) => Main.B, Main.FibElem(Main.Qτ([0, 1], 1)) => Main.A, Main.FibElem(Main.Qτ([0, 0], 1)) => Main.A)
 Dict(Main.FibElem(Main.Qτ([0, -1], 1)) => Main.A, Main.FibElem(Main.Qτ([1, 1], 2)) => Main.B, Main.FibElem(Main.Qτ([0, 0], 1)) => Main.A)
 Dict(Main.FibElem(Main.Qτ([-1, -1], 2)) => Main.A, Main.FibElem(Main.Qτ([1, 1], 2)) => Main.A, Main.FibElem(Main.Qτ([0, 0], 1)) => Main.B)
 Dict(Main.FibElem(Main.Qτ([-1, -1], 2)) => Main.B, Main.FibElem(Main.Qτ([1, 1], 2)) => Main.B, Main.FibElem(Main.Qτ([0, 0], 1)) => Main.A)

For example, we'll compute the frequency of the following collar:

width = 800
height = 40
sc = 20
@png begin
    colors = ["#DD93FC", "#E7977A"]
    first_tile = [FibElem(0) => A]
    tiling = collars[4]
    setline(1)

    for tile in tiling
        origin()
        draw(tile, sc, colors[color(tile)], :fill)
    end
end width height
Example block output
frequency(fib, initial_collar, collars[4], 4)
0.0