coloring_nested_tire_graphs: figures for the rainbow-orbit result (step-2 obs:rainbow)
Adds three figures visualising the rainbow-orbit intersection from tire_fiber_step2.tex (Observation rainbow): k=6, T_1=(m=6, chord=(0,3), SP) vs T_2=(k=3, no chord, SR) |S_1 ∩ S_2| = 6 = the S_3-orbit of (a,b,c,b,c,a) Files: - fig_rainbow_orbit.png: 6-panel grid showing all 6 orbit elements, each as a hexagon coloured by the corresponding σ. - fig_rainbow_pattern.png: abstract pattern abcbca on the shared cycle γ with explicit position-class legend. - fig_rainbow_setup.png: geometric setup — T_1 outside γ with its antipodal chord (v_0, v_3) ∈ O_1, T_2 inside γ (a triangle), and the orbit element σ = (1,2,3,2,3,1) shown on γ. Adds experiments/draw_rainbow_orbit.py generator script. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,314 @@
|
||||
"""Visualise the 'rainbow orbit' intersection from
|
||||
tire_fiber_step2.tex, Observation rainbow:
|
||||
|
||||
k = 6, T_1 = (6, (0,3), SP) vs T_2 = (3, -, SR)
|
||||
|S_1 ∩ S_2| = 6
|
||||
The 6 elements are exactly {(a, b, c, b, c, a) : {a,b,c} = {1,2,3}}
|
||||
--- the S_3 orbit of the pattern abstractly written abcbca.
|
||||
|
||||
Produces three figures:
|
||||
|
||||
fig_rainbow_pattern.png:
|
||||
The abstract pattern abcbca on the shared 6-cycle, showing the
|
||||
three antipodal/positional symmetry classes (positions {0,5},
|
||||
{1,3}, {2,4}).
|
||||
|
||||
fig_rainbow_orbit.png:
|
||||
All six concrete colourings in the S_3 orbit, drawn as small
|
||||
hexagons with positions colored. Highlights how each permutation
|
||||
of {1,2,3} gives one element.
|
||||
|
||||
fig_rainbow_setup.png:
|
||||
The geometric setup: shared cycle gamma (the hexagon), T_1
|
||||
outside (with its antipodal chord), T_2 inside (the triangle),
|
||||
spoke edges crossing gamma. Shows why the antipodal chord in T_1
|
||||
pins the pattern.
|
||||
"""
|
||||
import math
|
||||
import os
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.patches as patches
|
||||
|
||||
|
||||
# Color palette: a 3-color set used consistently across all figures.
|
||||
COLORS = {
|
||||
1: '#1f77b4', # blue ("color 1")
|
||||
2: '#d62728', # red ("color 2")
|
||||
3: '#2ca02c', # green ("color 3")
|
||||
}
|
||||
NAMES = {1: '1', 2: '2', 3: '3'}
|
||||
|
||||
|
||||
def hex_positions(n=6, radius=1.0, angle_offset=math.pi / 2):
|
||||
"""Return positions of n evenly-spaced points on a circle of given radius."""
|
||||
return [
|
||||
(radius * math.cos(angle_offset - 2 * math.pi * i / n),
|
||||
radius * math.sin(angle_offset - 2 * math.pi * i / n))
|
||||
for i in range(n)
|
||||
]
|
||||
|
||||
|
||||
def draw_hex_with_pattern(ax, pattern, *, radius=1.0, label_size=10,
|
||||
dot_size=20, ring_color='#888888'):
|
||||
"""Draw a 6-cycle with edges in ring_color and vertices colored by pattern
|
||||
(length-6 list of colors). pattern[i] is the color of position i.
|
||||
"""
|
||||
pos = hex_positions(6, radius=radius)
|
||||
# cycle edges
|
||||
for i in range(6):
|
||||
x1, y1 = pos[i]
|
||||
x2, y2 = pos[(i + 1) % 6]
|
||||
ax.plot([x1, x2], [y1, y2], color=ring_color, linewidth=1.6, zorder=1)
|
||||
# vertices
|
||||
for i, c in enumerate(pattern):
|
||||
x, y = pos[i]
|
||||
ax.plot(x, y, 'o', color=COLORS[c], markersize=dot_size, zorder=2,
|
||||
markeredgecolor='black', markeredgewidth=0.8)
|
||||
# tiny label of position
|
||||
lx, ly = 1.32 * x, 1.32 * y
|
||||
ax.annotate(str(i), (lx, ly), color='#444', ha='center', va='center',
|
||||
fontsize=label_size - 1, zorder=3)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Figure 1: the abstract pattern abcbca on a hexagon.
|
||||
# ---------------------------------------------------------------------------
|
||||
def fig_pattern(outdir):
|
||||
fig, ax = plt.subplots(figsize=(6.0, 5.4))
|
||||
pos = hex_positions(6, radius=1.0)
|
||||
|
||||
# Draw cycle edges
|
||||
for i in range(6):
|
||||
x1, y1 = pos[i]
|
||||
x2, y2 = pos[(i + 1) % 6]
|
||||
ax.plot([x1, x2], [y1, y2], color='#888888', linewidth=2.0, zorder=1)
|
||||
|
||||
classes = {0: 'a', 1: 'b', 2: 'c', 3: 'b', 4: 'c', 5: 'a'}
|
||||
class_colors = {
|
||||
'a': '#bdbdbd',
|
||||
'b': '#ffe082',
|
||||
'c': '#a5d6a7',
|
||||
}
|
||||
|
||||
for i in range(6):
|
||||
x, y = pos[i]
|
||||
cls = classes[i]
|
||||
ax.plot(x, y, 'o', color=class_colors[cls], markersize=34, zorder=2,
|
||||
markeredgecolor='black', markeredgewidth=1.0)
|
||||
ax.annotate(cls, (x, y), color='black', ha='center', va='center',
|
||||
fontsize=15, fontweight='bold', zorder=3)
|
||||
lx, ly = 1.34 * x, 1.34 * y
|
||||
ax.annotate(f'$v_{i}$', (lx, ly), color='#333', ha='center',
|
||||
va='center', fontsize=11, zorder=3)
|
||||
|
||||
# Legend showing the position-class assignments
|
||||
legend = (
|
||||
r"$\sigma = (a, b, c, b, c, a)$" + "\n"
|
||||
+ r"position classes:" + "\n"
|
||||
+ r" $a$: $\{v_0, v_5\}$" + "\n"
|
||||
+ r" $b$: $\{v_1, v_3\}$" + "\n"
|
||||
+ r" $c$: $\{v_2, v_4\}$"
|
||||
)
|
||||
ax.text(1.85, 0.0, legend, fontsize=11, color='#222',
|
||||
family='monospace',
|
||||
verticalalignment='center',
|
||||
bbox=dict(boxstyle='round,pad=0.5', facecolor='#f8f8f8',
|
||||
edgecolor='#bbb'))
|
||||
|
||||
ax.set_xlim(-1.6, 4.0)
|
||||
ax.set_ylim(-1.7, 1.7)
|
||||
ax.set_aspect('equal')
|
||||
ax.axis('off')
|
||||
ax.set_title(r"Rainbow pattern $(a,b,c,b,c,a)$ on the shared cycle $\gamma$"
|
||||
+ " (length $k=6$)",
|
||||
fontsize=11)
|
||||
|
||||
out = os.path.join(outdir, 'fig_rainbow_pattern.png')
|
||||
plt.savefig(out, dpi=160, bbox_inches='tight')
|
||||
plt.close()
|
||||
print(f"wrote {out}")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Figure 2: the 6 concrete colourings in the S_3 orbit.
|
||||
# ---------------------------------------------------------------------------
|
||||
def fig_orbit(outdir):
|
||||
# The 6 permutations of (a, b, c) -> (1, 2, 3) instantiated into the
|
||||
# pattern (a, b, c, b, c, a).
|
||||
perms = [
|
||||
(1, 2, 3),
|
||||
(1, 3, 2),
|
||||
(2, 1, 3),
|
||||
(2, 3, 1),
|
||||
(3, 1, 2),
|
||||
(3, 2, 1),
|
||||
]
|
||||
|
||||
fig, axes = plt.subplots(2, 3, figsize=(10.5, 7.5))
|
||||
|
||||
for idx, (a, b, c) in enumerate(perms):
|
||||
ax = axes[idx // 3, idx % 3]
|
||||
pattern = [a, b, c, b, c, a]
|
||||
draw_hex_with_pattern(ax, pattern, dot_size=24)
|
||||
ax.set_xlim(-1.7, 1.7)
|
||||
ax.set_ylim(-1.7, 1.7)
|
||||
ax.set_aspect('equal')
|
||||
ax.axis('off')
|
||||
# title shows pattern explicitly
|
||||
title = (f"$(a,b,c) = ({a},{b},{c})$\n"
|
||||
+ r"$\sigma = ($" + ",".join(str(x) for x in pattern) + r"$)$")
|
||||
ax.set_title(title, fontsize=10)
|
||||
|
||||
fig.suptitle(r"The 6-element rainbow orbit: $S_3$ acting on pattern "
|
||||
+ r"$(a,b,c,b,c,a)$, the entire $|S_1 \cap S_2| = 6$ in step-2"
|
||||
+ "\n"
|
||||
+ r"$T_1 = (m{=}6, \mathrm{chord}{=}(0,3), \mathrm{SP})$ vs. "
|
||||
+ r"$T_2 = (k{=}3, \mathrm{SR})$ on shared cycle of length $k{=}6$",
|
||||
fontsize=11, y=1.00)
|
||||
plt.tight_layout()
|
||||
out = os.path.join(outdir, 'fig_rainbow_orbit.png')
|
||||
plt.savefig(out, dpi=160, bbox_inches='tight')
|
||||
plt.close()
|
||||
print(f"wrote {out}")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Figure 3: geometric setup --- T_1 (hexagon + antipodal chord) outside gamma,
|
||||
# T_2 (triangle) inside gamma, the 6 spoke edges crossing gamma.
|
||||
# ---------------------------------------------------------------------------
|
||||
def fig_setup(outdir):
|
||||
fig, ax = plt.subplots(figsize=(8.5, 7.5))
|
||||
|
||||
# Shared cycle gamma at radius R_gamma
|
||||
R_gamma = 1.4
|
||||
gamma_pos = hex_positions(6, radius=R_gamma)
|
||||
|
||||
# T_1 lives outside gamma. Its "B_in" is gamma; its "B_out" is a bigger
|
||||
# hexagon at R_out, with an antipodal chord across T_1's B_in side.
|
||||
# For visualization, draw T_1's annular region as an annulus around gamma.
|
||||
R_out = 2.5
|
||||
R_in = 0.55
|
||||
T1_outer = hex_positions(6, radius=R_out)
|
||||
|
||||
# Draw T_1 outer boundary (B_out for T_1)
|
||||
for i in range(6):
|
||||
x1, y1 = T1_outer[i]
|
||||
x2, y2 = T1_outer[(i + 1) % 6]
|
||||
ax.plot([x1, x2], [y1, y2], color='#444', linewidth=2.0, zorder=2)
|
||||
|
||||
# Draw the annular region for T_1 (shaded)
|
||||
outer_poly = patches.Polygon(T1_outer + gamma_pos[::-1], closed=True,
|
||||
facecolor='#e3f2fd', edgecolor='none',
|
||||
alpha=0.55, zorder=0)
|
||||
ax.add_patch(outer_poly)
|
||||
|
||||
# Draw spokes between gamma_pos[i] and T1_outer[i] (representing radial spokes
|
||||
# in T_1's annular triangulation, schematically).
|
||||
for i in range(6):
|
||||
x1, y1 = gamma_pos[i]
|
||||
x2, y2 = T1_outer[i]
|
||||
ax.plot([x1, x2], [y1, y2], color='#a0a0a0', linewidth=0.9,
|
||||
linestyle=':', zorder=2)
|
||||
|
||||
# T_1's antipodal chord across its B_in (= gamma):
|
||||
# this chord is in the O of T_1, conceptually living outside gamma in T_1's
|
||||
# interior outerplanar O. For the diagram we represent it as a curved chord
|
||||
# outside gamma connecting two opposite gamma vertices.
|
||||
chord_p1 = gamma_pos[0]
|
||||
chord_p2 = gamma_pos[3]
|
||||
# Draw outside gamma using an arc above
|
||||
chord_arc = patches.FancyArrowPatch(chord_p1, chord_p2,
|
||||
connectionstyle='arc3,rad=0.55',
|
||||
arrowstyle='-', color='#9c27b0',
|
||||
linewidth=2.2, linestyle=(0, (3, 2)),
|
||||
zorder=4)
|
||||
ax.add_patch(chord_arc)
|
||||
|
||||
# T_2 lives inside gamma. T_2's B_out = gamma (length 6),
|
||||
# T_2's B_in = a triangle of length 3.
|
||||
triangle_pos = hex_positions(3, radius=R_in, angle_offset=math.pi / 2)
|
||||
for i in range(3):
|
||||
x1, y1 = triangle_pos[i]
|
||||
x2, y2 = triangle_pos[(i + 1) % 3]
|
||||
ax.plot([x1, x2], [y1, y2], color='#444', linewidth=2.0, zorder=2)
|
||||
|
||||
# Draw T_2's annular region (between gamma and triangle, shaded)
|
||||
inner_poly = patches.Polygon(gamma_pos + triangle_pos[::-1], closed=True,
|
||||
facecolor='#fff3e0', edgecolor='none',
|
||||
alpha=0.55, zorder=0)
|
||||
ax.add_patch(inner_poly)
|
||||
|
||||
# Draw shared cycle gamma (on top of everything)
|
||||
for i in range(6):
|
||||
x1, y1 = gamma_pos[i]
|
||||
x2, y2 = gamma_pos[(i + 1) % 6]
|
||||
ax.plot([x1, x2], [y1, y2], color='#333', linewidth=3.0, zorder=3)
|
||||
|
||||
# Place vertices of gamma colored by one of the rainbow patterns: (1,2,3,2,3,1)
|
||||
pattern = [1, 2, 3, 2, 3, 1]
|
||||
for i in range(6):
|
||||
x, y = gamma_pos[i]
|
||||
ax.plot(x, y, 'o', color=COLORS[pattern[i]], markersize=26, zorder=4,
|
||||
markeredgecolor='black', markeredgewidth=1.0)
|
||||
ax.annotate(f"$v_{i}$", (x * 1.0, y * 1.0), color='white',
|
||||
ha='center', va='center', fontsize=10, fontweight='bold',
|
||||
zorder=5)
|
||||
|
||||
# Triangle vertices
|
||||
for i, (x, y) in enumerate(triangle_pos):
|
||||
ax.plot(x, y, 'o', color='#666', markersize=14, zorder=4,
|
||||
markeredgecolor='black', markeredgewidth=0.6)
|
||||
|
||||
# Annotate which regions are T_1 and T_2 (placed off to the side with arrows)
|
||||
ax.annotate(r'$T_1$ annulus' + '\n'
|
||||
+ r'$m_1 = 6$,' + '\n'
|
||||
+ r'chord $(v_0, v_3) \in O_1$' + '\n'
|
||||
+ r'Steiner-poor',
|
||||
xy=(R_out - 0.1, -0.5), xytext=(3.7, -1.4),
|
||||
fontsize=10, color='#1565c0',
|
||||
ha='left', va='center',
|
||||
arrowprops=dict(arrowstyle='->', color='#1565c0', lw=1.0))
|
||||
ax.annotate(r'$T_2$ annulus' + '\n'
|
||||
+ r'$k_2 = 3$, no chord' + '\n'
|
||||
+ r'Steiner-rich',
|
||||
xy=(R_in + 0.2, 0.0), xytext=(3.7, 0.8),
|
||||
fontsize=10, color='#e65100',
|
||||
ha='left', va='center',
|
||||
arrowprops=dict(arrowstyle='->', color='#e65100', lw=1.0))
|
||||
ax.annotate(r'shared cycle $\gamma$' + '\n' + r'(length $k = 6$)',
|
||||
xy=(R_gamma * 0.9, R_gamma * 0.4), xytext=(3.7, 2.1),
|
||||
fontsize=10, color='#222',
|
||||
ha='left', va='center',
|
||||
arrowprops=dict(arrowstyle='->', color='#222', lw=1.0))
|
||||
ax.annotate(r'antipodal chord' + '\n' + r'$(v_0, v_3)$ in $O_1$',
|
||||
xy=(-0.55, 1.7), xytext=(-3.2, 2.4),
|
||||
fontsize=10, color='#9c27b0',
|
||||
ha='left', va='center',
|
||||
arrowprops=dict(arrowstyle='->', color='#9c27b0', lw=1.0))
|
||||
|
||||
ax.set_xlim(-3.6, 5.4)
|
||||
ax.set_ylim(-2.8, 3.2)
|
||||
ax.set_aspect('equal')
|
||||
ax.axis('off')
|
||||
ax.set_title(r"Geometric setup behind the rainbow orbit: $T_1$ outside $\gamma$,"
|
||||
+ r" $T_2$ inside $\gamma$" + "\n"
|
||||
+ r"shown with the spoke configuration $\sigma = (1,2,3,2,3,1)$"
|
||||
+ r" from the orbit",
|
||||
fontsize=11)
|
||||
|
||||
out = os.path.join(outdir, 'fig_rainbow_setup.png')
|
||||
plt.savefig(out, dpi=160, bbox_inches='tight')
|
||||
plt.close()
|
||||
print(f"wrote {out}")
|
||||
|
||||
|
||||
def main():
|
||||
here = os.path.dirname(os.path.abspath(__file__))
|
||||
notes = os.path.normpath(os.path.join(here, '..', 'notes'))
|
||||
fig_pattern(notes)
|
||||
fig_orbit(notes)
|
||||
fig_setup(notes)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 110 KiB |
Reference in New Issue
Block a user