Analysis¶
In this part we perform a mathematical analysis of the family of curves we found previously.
Definitions¶
Let \(p \in \mathbb N, p > 0\) and \(q \in \mathbb N, q > 0\). Let \(d = \gcd(p, q)\), \(p' = \frac{p}{d}\) and \(q' = \frac{q}{d}\). Let \(r \in \mathbb R\) and \(\delta_r \in \mathbb R\) such that \(0 < \delta_r < r\). Let’s define the following family of functions:
Let \(\Gamma_k\) be the graph of \(r_k\) in polar coordinates, that is the graph of \(\vec{r_k} : \theta \mapsto r_k(\theta) \cdot \vec u(\theta)\) where \(\vec u(\theta)\) is the unit vector at polar angle \(\theta\).
Intersections¶
For \((m, n) \in \mathbb Z^2\), \(\Gamma_m\) and \(\Gamma_n\) intersect if and only if \(\exists \theta_1, \theta_2 \in \mathbb R^2, \vec{r_m}(\theta_1) = \vec{r_n}(\theta_2)\).
\(0 < \delta_r < r\) and \(\cos\) is between \(-1\) and \(1\) so \(r_k(\theta) > 0\) and we can drop the second case.
\(\vec u\) is \(2 \pi \mbox{-periodic}\) and \(\cos\) is even and \(2 \pi \mbox{-periodic}\) so:
Case 1¶
So if we can find \(a\) and \(b\) such that \(n - m = a \cdot p + b \cdot q\), then \(\Gamma_m\) and \(\Gamma_n\) have an infinite number of intersection points and are identical.
According to Bézout’s identity, \(\exists (a, b) \in \mathbb Z, n - m = a \cdot p + b \cdot q\) if and only if \(n - m\) is a multiple of \(d\).
Reduction of the family¶
Applying this to \(n = m + d\) proves that \(\Gamma_{n + d}\) is identical to \(\Gamma_n\). It’s sufficient to consider intersections between \(\Gamma_m\) and \(\Gamma_n\) for \((m, n) \in [0, d-1]^2\) and \(m \le n\). And it’s enough to draw \(\Gamma_k\) for \(k \in [0, d-1]\).
Periodicity of \(r_k\)¶
By construction, \(r_k\) is \(2 \cdot q \cdot \pi \mbox{-periodic}\).
For \(m = n\), we can use \(a = q'\) and \(b = -p'\). Then we have \(\theta_2 = \theta_1 + 2 \cdot q' \cdot \pi\). So \(r_k\) is \(2 \cdot q' \cdot \pi \mbox{-periodic}\). It’s sufficient to consider intersections where \((\theta_1, \theta_2) \in \left[0, 2 \cdot q' \cdot \pi\right[^2\). And it’s enough to draw each \(\Gamma_k\) on \(\theta \in \left[0, 2 \cdot q' \cdot \pi\right[\).
Case 2¶
So this case correspond to “real”, individual intersection points.
Let’s reduce the domain we need to search for \(a\) and \(b\). So far, we have:
Replacing \(\theta_1\) and \(\theta_2\) in \(-2 \cdot q' \cdot \pi \lt \theta_2 - \theta_1 \lt 2 \cdot q' \cdot \pi\) and isolating \(a\), we obtain \(-q' + 1 \le a \lt q'\).
Then, replacing \(\theta_1\) and \(\theta_2\) in \(0 \le \theta_1 \lt 2 \cdot q' \cdot \pi\) and \(0 \le \theta_2 \lt 2 \cdot q' \cdot \pi\) and isolating b, we obtain:
We can regroup those two conditions in:
So to obtain all intersections, it’s enough to iterate on \(a\) and \(b\) in those ranges. Note that if \(m = n\), this will give us each intersection twice. We can consider only the cases where \(\theta_1 \lt \theta_2\), which reduces the range of \(a\) to \(1 \le a \lt q'\).
This is just a necessary condition on \(a\) and \(b\). We have proven that we obtain all intersections, but @todoc we might want to prove that we don’t get duplicates.
import fractions
import math
import matplotlib.pyplot as plt
import numpy as np
P = 7
Q = 8
plt.figure(figsize=(P, Q))
for p in range(1, P + 1):
for q in range(1, Q + 1):
d = fractions.gcd(p, q)
p_prime = p / d
q_prime = q / d
r = []
for k in range(d):
r.append(
lambda theta, k=k: 2 + np.cos((p * theta - 2 * k * np.pi) / q)
)
theta = np.arange(0, 2 * q_prime * np.pi, 0.01)
intersections = []
# Note that these 4 imbricated loops are only O(p*q)
for m in range(d):
for n in range(m, d):
if m == n:
min_a = 1
else:
min_a = -q_prime + 1
max_a = q_prime
for a in range(min_a, max_a):
min_b = int(math.ceil(float(abs(a) * p - m - n) / q))
max_b = 2 * p_prime - int(math.floor(float(abs(a) * p + m + n) / q))
for b in range(min_b, max_b):
theta_1 = (b * q - a * p + m + n) * np.pi / p
theta_2 = (b * q + a * p + m + n) * np.pi / p
assert 0 <= theta_1 < 2 * q_prime * np.pi
assert 0 <= theta_2 < 2 * q_prime * np.pi
assert m != n or theta_1 < theta_2
intersections.append((theta_1, r[m](theta_1)))
assert len(intersections) == p * (q - 1) # @todoc Explain
sp = plt.subplot(Q, P, (q - 1) * P + p, polar=True)
for k in range(d):
sp.plot(theta, r[k](theta))
sp.plot(
[theta for theta, r in intersections],
[r for theta, r in intersections],
"r."
)
sp.set_rmin(0)
sp.set_rmax(3.1)
sp.set_yticks([1, 2, 3])
sp.set_yticklabels([])
sp.set_xticklabels([])
sp.spines['polar'].set_visible(False)
plt.tight_layout()