codex/conic-sections-double-cone-plane-cuts.py

conic-sections-double-cone-plane-cuts.py
"""Four exact cone/plane intersections. Write the original PNG to cwd."""
from pathlib import Path
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np


def intersection(m):
    if m == 0:
        t = np.linspace(0, 2*np.pi, 501)
        return [(np.cos(t), np.sin(t), np.ones_like(t))]
    if m < 1:
        t = np.linspace(0, 2*np.pi, 501)
        h = 1-m*m
        x = m/h + np.cos(t)/h
        y = np.sin(t)/np.sqrt(h)
        return [(x, y, 1+m*x)]
    if m == 1:
        y = np.linspace(-2.15, 2.15, 501)
        x = (y*y-1)/2
        return [(x, y, 1+x)]
    t = np.linspace(-1, 1, 501)
    h = m*m-1
    lines = []
    for sign in [-1, 1]:
        x = -m/h + sign*np.cosh(t)/h
        y = np.sinh(t)/np.sqrt(h)
        lines.append((x, y, 1+m*x))
    return lines


def main():
    fig = plt.figure(figsize=(11.2, 9.1), facecolor='white')
    titles = [('Circle', 0, r'$m=0$'), ('Ellipse', .6, r'$m=3/5$'),
              ('Parabola', 1., r'$m=1$'), ('Hyperbola', 1.4, r'$m=7/5$')]
    angle, z = np.meshgrid(np.linspace(0, 2*np.pi, 29), np.linspace(-3.25, 3.25, 25))
    cx, cy = np.abs(z)*np.cos(angle), np.abs(z)*np.sin(angle)
    for index, (title, m, label) in enumerate(titles, 1):
        ax = fig.add_subplot(2, 2, index, projection='3d')
        ax.plot_wireframe(cx, cy, z, color='#9eafb9', alpha=.44, lw=.48,
                          rstride=3, cstride=3)
        px, py = np.meshgrid(np.linspace(-2.65, 2.65, 10), np.linspace(-2.65, 2.65, 10))
        pz = 1+m*px
        pz = np.where(np.abs(pz)<=3.25, pz, np.nan)
        ax.plot_surface(px, py, pz, color='#79b7d4', alpha=.17, linewidth=0)
        for x, y, zc in intersection(m):
            # Every displayed point satisfies both equations independently.
            assert np.max(np.abs(zc*zc-x*x-y*y)) < 3e-14
            assert np.max(np.abs(zc-1-m*x)) < 3e-15
            ax.plot(x, y, zc, color='#b84d25', lw=3, zorder=8)
        ax.scatter(0, 0, 0, color='#485561', s=14)
        ax.set_xlim(-3.35, 3.35)
        ax.set_ylim(-3.35, 3.35)
        ax.set_zlim(-3.35, 3.35)
        ax.set_box_aspect((1, 1, 1))
        ax.set_xticks([-2, 0, 2])
        ax.set_yticks([-2, 0, 2])
        ax.set_zticks([-2, 0, 2])
        ax.set_xlabel('x', labelpad=1)
        ax.set_ylabel('y', labelpad=1)
        ax.set_zlabel('z', labelpad=1)
        ax.view_init(elev=25, azim=-110)
        ax.set_title(title+'  '+label, fontsize=15, weight='bold', pad=0)
        ax.set_facecolor('white')
        for axis in (ax.xaxis, ax.yaxis, ax.zaxis):
            axis.pane.set_facecolor((1, 1, 1, 1))
    fig.suptitle('Four plane cuts of the same double cone', fontsize=19, weight='bold', y=.98)
    fig.text(.5, .935, r'Cone: $z^2=x^2+y^2$     Cutting plane: $z=1+mx$',
             ha='center', fontsize=14)
    fig.subplots_adjust(top=.89, bottom=.08, left=.02, right=.98, hspace=.10, wspace=.01)
    fig.text(.5, .025, 'Orange: exact intersection. Blue: plane patch. Gray: cone mesh. Each panel has equal spatial scales.',
             ha='center', fontsize=10.5, color='#405063')
    fig.savefig(Path.cwd() / (Path(__file__).stem+'.png'), dpi=120, facecolor='white')
    plt.close(fig)


if __name__ == '__main__':
    main()