--- jupyter: description: 'Dudeney''s haberdasher''s puzzle with planex2d: an equilateral triangle cut by his ruler-and-compass construction into four pieces, hinged in one constraint sketch and swung by one angle into a square of side a 3^(1/4)/2, with the area constant and no two pieces overlapping on the way.' --- # The haberdasher's puzzle Can an equilateral triangle be cut into pieces that form a square? Henry Dudeney's answer, the haberdasher's puzzle, has four pieces. They can be joined by three hinges into a chain that folds one way into the triangle and the other way into the square. Demaine, Kamata and Uehara proved that three pieces are not enough if they may not be turned over. The example cuts the triangle by Dudeney's construction, hinges the four pieces in one constraint sketch and swings them by one angle. The square must have the side $a \cdot 3^{1/4}/2$, the area must stay the same in every position, and no two pieces may overlap on the way. ![The four pieces swinging on their hinges from the triangle into the square](../_static/gallery/haberdasher.gif){.result} ```python import itertools import math import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation from IPython.display import HTML import planex as px ``` ```python nbsphinx="hidden" from docs_figures import CYAN, GUIDE, LIGHT, PLANEX, Row ``` ## Dudeney's construction The triangle $ABC$ has the side $a = 100$, and $D$ and $E$ are the midpoints of $AB$ and $BC$. The construction finds the side of the square first and then the cuts: 1. Extend $AE$ beyond $E$ to $F$, with $EF = EB$, and let $G$ be the midpoint of $AF$. 2. The circle around $G$ through $A$ meets the line $EB$ beyond $B$ in $H$. 3. The circle around $E$ through $H$ meets $AC$ in $J$. 4. $K$ lies on $AC$ with $JK = EB$, and $L$ and $M$ are the feet of the perpendiculars from $D$ and $K$ on $EJ$. The cuts are $EJ$, $DL$ and $KM$. `along` and `foot` are the two helpers the steps need, and planex2d intersects the circles with the lines (Figure 1). Since $H$ lies on the circle over the diameter $AF$, $EH^2 = AE \cdot EF$, which is the triangle's area. So $EH$ is the side of the square, $a \cdot 3^{1/4}/2$, which the print compares: ```python a = 100.0 A, B, C = (0.0, 0.0), (a, 0.0), (a / 2, a * math.sqrt(3) / 2) def along(p, q, t): """the point a fraction t of the way from p to q""" return (p[0] + t * (q[0] - p[0]), p[1] + t * (q[1] - p[1])) def foot(p, q, x): """the foot of the perpendicular from x on the line pq""" dx, dy = q[0] - p[0], q[1] - p[1] return along(p, q, ((x[0] - p[0]) * dx + (x[1] - p[1]) * dy) / (dx * dx + dy * dy)) D, E = along(A, B, 0.5), along(B, C, 0.5) F = along(A, E, 1 + math.dist(E, B) / math.dist(A, E)) G = along(A, F, 0.5) h = px.Circle(G, math.dist(G, A)).Intersections(px.Line(E, along(E, B, 2)))[0] # E lies inside the circle H = (h.x, h.y) j = px.Circle(E, math.dist(E, H)).Intersections(px.Line(A, C))[0] J = (j.x, j.y) K = along(A, C, (math.dist(A, J) + math.dist(E, B)) / a) L, M = foot(E, J, D), foot(E, J, K) print("EH", math.dist(E, H), "square side", a * 3**0.25 / 2) ``` ```python nbsphinx="hide-input" triangle = px.FromPolyline([A, B, C], closed=True).Face() names = dict(A=A, B=B, C=C, D=D, E=E, F=F, G=G, H=H, J=J, K=K, L=L, M=M) offsets = dict(A=(-6, -4), B=(6, -4), C=(0, 7), D=(0, -7), E=(7, 1), F=(6, 4), G=(5, -6), H=(6, -5), J=(-7, 0), K=(-7, 2), L=(-6, -6), M=(4, 7)) Row((triangle, "The construction, the cuts in cyan", {"dashed": [px.Circle(G, math.dist(G, A)), px.Circle(E, math.dist(E, H)), px.Line(A, F), px.Line(B, H)], "highlight": [e for cut in (px.Line(E, J), px.Line(D, L), px.Line(K, M)) for e in cut.edges], "labels": [(n, (p[0] + offsets[n][0], p[1] + offsets[n][1])) for n, p in names.items()], "dots": list(names.values()), "view": ((-20, 135), (-40, 110))}), caption="Dudeney's construction") ``` ## The four pieces The three cuts split the triangle into four pieces, each named by the corners it has in the triangle, in order around it (Figure 2). Glued with the three cuts, the triangle falls into four faces, and their areas add up to the triangle's area $\sqrt{3}\,a^2/4$: ```python pieces = {"a": [A, D, L, J], "b": [D, B, E, M, L], "c": [E, C, K, M], "d": [K, J, L, M]} faces = {name: px.FromPolyline(corners, closed=True).Face() for name, corners in pieces.items()} cut = px.Glue([triangle, px.Line(E, J), px.Line(D, L), px.Line(K, M)]) print(len(cut.faces), "faces of", sorted(round(f.mass, 6) for f in cut.faces)) print("pieces", sorted(round(f.mass, 6) for f in faces.values())) print("sum", sum(f.mass for f in faces.values()), "triangle", math.sqrt(3) / 4 * a**2) ``` ```python nbsphinx="hide-input" COLORS = {"a": LIGHT, "b": GUIDE, "c": PLANEX, "d": CYAN} Row((triangle, "The pieces a, b, c and d", {"fills": [(faces[n], COLORS[n]) for n in pieces], "labels": [(n, faces[n].center[:2]) for n in pieces]}), caption="The four pieces") ``` ## A hinged sketch In one constraint sketch, each piece is a closed chain of lines whose ids name the piece and the corner they start from: `b1` runs from $B$ to $E$. `RigidGroup` keeps each piece's shape, and piece a stays where it is, with $A$ at the origin and its side on $AB$ horizontal. The hinges are the corners that two pieces share, $D$, where `a0` ends and `b0` starts, $E$ between `b1` and `c0`, and $K$ between `c1` and `d0`. At each hinge, the two lines lie on one side of the triangle, and the angle between them, 0 in the triangle, is the hinge's angle. Expressions turn the three hinges together, so the angle at $D$ is the one dimension that moves them all: ```python sk = px.Sketch() for name, corners in pieces.items(): sk.Move(corners[0]) for k, p in enumerate(corners[1:] + corners[:1]): sk.Line(p, id=f"{name}{k}") sk.RigidGroup([f"{name}{k}" for k in range(len(corners))], id=f"piece_{name}") sk.Constrain("a0.start", sk.origin, px.Coincident) sk.Constrain("a0", px.Horizontal) for end, start in (("a0.end", "b0.start"), ("b1.end", "c0.start"), ("c1.end", "d0.start")): sk.Constrain(end, start, px.Coincident) sk.Constrain("a0", "b0", px.Angle, 0, id="hinge_D") sk.Constrain("b1", "c0", px.Angle, 0, id="hinge_E") sk.Constrain("c1", "d0", px.Angle, 0, id="hinge_K") sk.Expression("hinge_E = hinge_D") sk.Expression("hinge_K = hinge_D") print("status", sk.status, "| degrees of freedom", sk.dof) ``` ## Swinging into the square `Update` turns the hinge at $D$ in steps of 5°, each step solved from the one before, and `placed` reads the pieces back from their solved corners. A negative angle turns clockwise. Turned this way, all three hinges swing the pieces past each other. Turned the other way, they run into each other. The area of the four pieces must stay the triangle's in every position, and no two of them may overlap: ```python def placed(): return [px.FromPolyline([sk.Location(f"{name}{k}.start") for k in range(len(corners))], closed=True).Face() for name, corners in pieces.items()] frames, overlap = [], 0.0 for t in range(0, 181, 5): sk.Update({"hinge_D": -t}) frames.append(placed()) overlap = max([overlap] + [(p * q).mass for p, q in itertools.combinations(frames[-1], 2)]) areas = [sum(f.mass for f in frame) for frame in frames] print(len(frames), "positions, area from", min(areas), "to", max(areas), "| largest overlap", overlap) ``` After half a turn, the pieces fit together into one face with four corners. Its four sides have the length $a \cdot 3^{1/4}/2$, and its two diagonals are $\sqrt{2}$ times longer, so it is a square: ```python square = px.Unify(frames[-1]).Face() # Face raises if the union has more than one face corners = [(e.start.x, e.start.y) for e in square.outer.edges] print("one face with", len(corners), "corners") print("sides", [math.dist(corners[i], corners[i - 1]) for i in range(4)], "exact", a * 3**0.25 / 2) print("diagonals", math.dist(corners[0], corners[2]), math.dist(corners[1], corners[3]), "exact", math.sqrt(2) * a * 3**0.25 / 2) ``` ```python fig, ax = plt.subplots(figsize=(6, 5)) colors = ["#6fb0cf", "#d98c2b", "#0f7ea8", "#00b4d8"] def frame(k): ax.clear() for face, color in zip(frames[min(k, len(frames) - 1)], colors): face.Plot(ax=ax, face_col=color, legend=False) ax.set_aspect("equal", adjustable="box") ax.set_xlim(-40, 110) ax.set_ylim(-60, 95) ax.axis("off") animation = FuncAnimation(fig, frame, frames=len(frames) + 12, interval=80) plt.close(fig) HTML(animation.to_jshtml()) ``` See [Symmetry and patterns](../core/constraints/symmetry.md) for `RigidGroup` and [Expressions: design rules](../core/constraints/expressions.md) for dimensions that follow other dimensions. ```python nbsphinx="hidden" import sys sys.path.insert(0, "..") from gallery import save_animation save_animation(animation, "haberdasher") ```