In [1]:
# This notebook is a semi top-down explanation. This cell needs to be
# executed first so that the operators and helper functions are defined
# All of this is explained in the later half of the notebook

using Compose, Interact
Compose.set_default_graphic_size(2inch, 2inch)

points_f = [
    (.1, .1),
    (.9, .1),
    (.9, .2),
    (.2, .2),
    (.2, .4),
    (.6, .4),
    (.6, .5),
    (.2, .5),
    (.2, .9),
    (.1, .9),
    (.1, .1)
]

f = compose(context(), stroke("black"), line(points_f))

rot(pic) = compose(context(rotation=Rotation(-deg2rad(90))), pic)
flip(pic) = compose(context(mirror=Mirror(deg2rad(90), 0.5w, 0.5h)), pic)
above(m, n, p, q) =
    compose(context(),
            (context(0, 0, 1, m/(m+n)), p),
            (context(0, m/(m+n), 1, n/(m+n)), q))

above(p, q) = above(1, 1, p, q)

beside(m, n, p, q) =
    compose(context(),
            (context(0, 0, m/(m+n), 1), p),
            (context(m/(m+n), 0, n/(m+n), 1), q))

beside(p, q) = beside(1, 1, p, q)

over(p, q) = compose(context(),
                (context(), p), (context(), q))

rot45(pic) =
    compose(context(0, 0, 1/sqrt(2), 1/sqrt(2),
        rotation=Rotation(-deg2rad(45), 0w, 0h)), pic)

# Utility function to zoom out and look at the context
zoomout(pic) = compose(context(),
                (context(0.2, 0.2, 0.6, 0.6), pic),
                (context(0.2, 0.2, 0.6, 0.6), fill(nothing), stroke("black"), strokedash([0.5mm, 0.5mm]),
                    polygon([(0, 0), (1, 0), (1, 1), (0, 1)])))

function read_path(p_str)
    tokens = [try parsefloat(x) catch symbol(x) end for x in split(p_str, r"[\s,]+")]
    path(tokens)
end

fish = compose(context(units=UnitBox(260, 260)), stroke("black"),
            read_path(strip(readall("fish.path"))))

rotatable(pic) = @manipulate for θ=0:0.001:2π
    compose(context(rotation=Rotation(θ)), pic)
end

blank = compose(context())

fliprot45(pic) = rot45(compose(context(mirror=Mirror(deg2rad(-45))),pic))

# Hide this cell.
display(MIME("text/html"), """<script>
var cell = \$(".container .cell").eq(0), ia = cell.find(".input_area")
if (cell.find(".toggle-button").length == 0) {
ia.after(
    \$('<button class="toggle-button">Toggle hidden code</button>').click(
        function (){ ia.toggle() }
        )
    )
ia.hide()
}
</script>""")

Functional Geometry

Functional Geometry is a paper by Peter Henderson (original (1982), revisited (2002)) which deconstructs the MC Escher woodcut Square Limit

Square Limit

A picture is an example of a complex object that can be described in terms of its parts. Yet a picture needs to be rendered on a printer or a screen by a device that expects to be given a sequence of commands. Programming that sequence of commands directly is much harder than having an application generate the commands automatically from the simpler, denotational description.

A picture is a denotation of something to draw.

e.g. The value of f here denotes the picture of the letter F

In [2]:
f
Out[2]:

Basic Operations on Pictures

We begin specifying the algebra of pictures we will use to describe Square Limit with a few operations that operate on pictures to give other pictures, namely:

  • rot : picture → picture
  • flip : picture → picture
  • rot45 : picture → picture
  • above : picture × picture → picture
  • above : int × int × picture × picture → picture
  • beside : picture × picture → picture
  • beside : int × int × picture × picture → picture
  • over : picture → picture

Rotate and flip

rot : picture → picture

Rotate a picture anti-clockwise by 90°

In [3]:
rot(f)
Out[3]:

flip : picture → picture

Flip a picture along its virtical center axis

In [4]:
flip(f)
Out[4]:
In [5]:
rot(flip(f))
Out[5]:

fliprot45 : picture → picture

rotate the picture anti-clockwise by 45°, then flip it across the new virtical axis. In the paper this is implemented as $flip(rot45(fish))$. This function is rather specific to the problem at hand.

In [6]:
fliprot45(fish)              |> zoomout # zoomout shows the bounding box
Out[6]:

Juxtaposition

above : picture × picture → picture

place a picture above another.

In [7]:
above(f, f)
Out[7]:

above : int × int × picture × picture → picture

given m, n, picture1 and picture2, return a picture where picture1 is placed above picture2 such that their heights occupy the total height in m:n ratio

In [8]:
above(1, 2, f, f)
Out[8]:

beside : picture × picture → picture

Similar to above but in the left-to-right direction.

In [9]:
beside(f, f)
Out[9]:

beside : int × int × picture × picture → picture

In [10]:
beside(1, 2, f, f)
Out[10]:
In [11]:
above(beside(f, f), f)
Out[11]:

Superposition

over : picture → picture

place a picture upon another

In [12]:
over(f, flip(f))
Out[12]:

Square Limit

The Fish

We will now study some of the properties of the fish.

In [13]:
fish |> zoomout
Out[13]:
In [14]:
rotatable(fish |> zoomout)
Out[14]:
In [15]:
over(fish, rot(rot(fish))) |> zoomout
Out[15]: