Solving a Geometry Quiz with JuMP

4 minute read

Published:

Introduction

In this post I want to show you how easy and fun it is to model a little geometry quiz using Julia.

My goal is to give you a quick glance at the basic syntax needed to formulate and solve the problem.

Target audience: beginners in numerical optimisation or newcomers to the Julia ecosystem.

The Problem

Consider the shapes shown in Figure 1 and the following constraints:

  • All rectangles (A, B, C, D, and E) have the same area;
  • Their geometric layout makes up a square; and
  • The height of rectangle A is 2.

What is the area of the square?

You can try to solve this quiz on your own first, using a pen and a piece of paper.

quiz image
Figure 1. Layout of the rectangles.

The Solution

There are many ways to reach the solution for this problem. The way I solved it was by realising that rectangles C and D are exactly the same. Why? We know all rectangles have the same area, and we also know that C and D share an edge. So, surely, they have the same height! That means that the height of B is two times the height of C or D. After knowing that, it is possible to solve for the height of B.

Constrained Optimisation

Now I want to show you how you could reach the solution by formulating a constrained optimisation problem. I am going to use JuMP, a language for modeling mathematical optimisation problems.

First, we import JuMP and Ipopt. Ipopt is a free solver for these kind of problems:

using JuMP
using Ipopt

Afterwards, we create a new model and declare the problem variables (the height and the width of each rectangle):

model = Model(Ipopt.Optimizer)

@variables(model, begin
    0 <= Ah ; 0 <= Bh ; 0 <= Ch ; 0 <= Dh ; 0 <= Eh
    0 <= Aw ; 0 <= Bw ; 0 <= Cw ; 0 <= Dw ; 0 <= Ew
end)

Then, we give an initial guess for those values, and fix the value of the height of A:

# Specifying an initial guess
set_start_value.([Ah Bh Ch Dh Eh
                  Aw Bw Cw Dw Ew], 2)

fix(Ah, 2, force=true)  # Given in the problem

Now, using the @constraint macro, we define the problem constraints:

# Areas relationship: A = B = C = D = E
@constraint(model, Ah * Aw == Bh * Bw)
@constraint(model, Bh * Bw == Ch * Cw)
@constraint(model, Ch * Cw == Dh * Dw)
@constraint(model, Dh * Dw == Eh * Ew)

# Total area relationship
@constraint(model, 5 * Ah * Aw == Eh * Eh)

# Width relationships
@constraint(model, Cw == Dw)
@constraint(model, Aw == Bw + Cw)

# Height relationships
@constraint(model, Bh == Ch + Dh)
@constraint(model, Eh == Ah + Bh)

Notice that, in the constraints defined above, I never told the solver that Ch = Bh / 2. I don’t have to! All of the constraints I declared are very straightforward, and directly observed from Figure 1—and that should be sufficient for the solver to figure everything out. How cool is that?!

Finally, we just need to call

optimize!(model)

Once the solver is done, you can print the answer to the quiz and the values found for every edge with:

area = round(value(Eh * Eh), digits=3)

println("Total area of the square is $(area).")

round.(value.([Ah Bh Ch Dh Eh
               Aw Bw Cw Dw Ew]), digits=3) |> display

Which should output the following:

Total area of the square is 64.0.

2×5 Array{Float64,2}:
 2.0  6.0    3.0    3.0    8.0
 6.4  2.133  4.267  4.267  1.6

Plotting the Results

After having solved the problem, we can use the values to plot Figure 1 (the image shown on the top of this page) with the code below and using Plots.jl.

using Plots

rectangle(x, y, w, h) = Shape(x .+ [0,w,w,0], y .+ [0,0,h,h])

plot(axis=false, ticks=false, legend=nothing,
     aspect_ratio=:equal, size=(600, 600),
     background_color=:transparent,
     foreground_color=:black,
     palette=:Pastel1)

plot!(rectangle(        0, value(Bh), value(Aw), value(Ah)), linewidth=2)
plot!(rectangle(        0,         0, value(Bw), value(Bh)), linewidth=2)
plot!(rectangle(value(Bw), value(Dh), value(Cw), value(Ch)), linewidth=2)
plot!(rectangle(value(Bw),         0, value(Dw), value(Dh)), linewidth=2)
plot!(rectangle(value(Aw),         0, value(Ew), value(Eh)), linewidth=2)

font_size = 20
annotate!(            value(Aw) / 2, value(Bh) + value(Ah) / 2, text("A", font_size))
annotate!(            value(Bw) / 2,             value(Bh) / 2, text("B", font_size))
annotate!(value(Bw) + value(Cw) / 2, value(Dh) + value(Ch) / 2, text("C", font_size))
annotate!(value(Bw) + value(Dw) / 2,             value(Dh) / 2, text("D", font_size))
annotate!(value(Aw) + value(Ew) / 2,             value(Eh) / 2, text("E", font_size))

annotate!(0.10 * value(Aw), value(Bh) + value(Ah) / 2, text("2", font_size))
quiver!([0.05 * value(Aw)], [value(Bh) + value(Ah) / 2], quiver=([0], [ value(Ah) / 2.1]), color=:black, linewidth=2)
quiver!([0.05 * value(Aw)], [value(Bh) + value(Ah) / 2], quiver=([0], [-value(Ah) / 2.1]), color=:black, linewidth=2)

savefig("./square_quiz.svg")

The End

This is the end of the post. Thank you for getting this far! Feel free to leave a comment below.

Comments