codexpast-exam-of-the-mathematics-course-of-the-university-of-cambridge2016iiipaper-117-propellers.py
past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2016/iii/paper-117-propellers.py
"""Draw reflected seven-triangle propellers; PNG is written only to cwd.
Tested with Python 3.14.4, numpy 2.3.5 and matplotlib 3.10.7.
"""
from pathlib import Path
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
PAIRINGS = [
[[(0, 1), (2, 5)], [(0, 2), (4, 3)], [(0, 4), (1, 6)]],
[[(0, 4), (2, 3)], [(0, 1), (4, 6)], [(0, 2), (1, 5)]],
]
COLOURS = ['#cc5533', '#2477ad', '#35854b']
def reflected_tiles(edges):
tiles = {0: np.array([[0., 0.], [1.45, 0.], [.32, 1.18]])}
while len(tiles) < 7:
old_size = len(tiles)
for colour, pairs in enumerate(edges):
for i, j in pairs:
if i not in tiles and j in tiles:
i, j = j, i
if i in tiles and j not in tiles:
tile = tiles[i]
u, v = [tile[k] for k in range(3) if k != colour]
direction = (v - u) / np.linalg.norm(v - u)
delta = tile[colour] - u
reflected = tile.copy()
reflected[colour] = u + 2 * np.dot(delta, direction) * direction - delta
tiles[j] = reflected
assert len(tiles) > old_size
return tiles
def main():
fig, axes = plt.subplots(1, 2, figsize=(10, 4.8), dpi=100, facecolor='white')
for ax, pairings, name in zip(axes, PAIRINGS, ['Left propeller', 'Right propeller']):
tiles = reflected_tiles(pairings)
for i in range(7):
tile = tiles[i]
ax.add_patch(Polygon(tile, facecolor='#fff1bf' if i == 0 else '#eaf0f5', edgecolor='none'))
for colour in range(3):
u, v = [tile[k] for k in range(3) if k != colour]
internal = any(i in pair for pair in pairings[colour])
ax.plot([u[0], v[0]], [u[1], v[1]], color=COLOURS[colour], lw=1.8, ls='--' if internal else '-')
xy = tile.mean(axis=0)
ax.text(*xy, str(i), ha='center', va='center', fontsize=14, weight='bold')
cloud = np.concatenate(list(tiles.values()))
ax.set_xlim(cloud[:, 0].min() - .18, cloud[:, 0].max() + .18)
ax.set_ylim(cloud[:, 1].min() - .18, cloud[:, 1].max() + .18)
ax.set_aspect('equal')
ax.axis('off')
ax.set_title(name, fontsize=14)
from matplotlib.lines import Line2D
handles = [Line2D([0], [0], color=c, lw=2, label=f'Side {s}') for c, s in zip(COLOURS, 'abc')]
fig.legend(handles=handles, loc='lower center', ncol=3, frameon=False)
fig.subplots_adjust(left=.03, right=.98, top=.89, bottom=.10, wspace=.11)
fig.savefig(Path.cwd() / 'paper-117-propellers.png', facecolor='white', transparent=False)
plt.close(fig)
if __name__ == '__main__':
main()