Modular arithmetic on a circle


Inspired by David Richeson's applet on GeoGebra the Python script below plots the lines joining pairs of points, evenly spaced on the perimeter of a circle according to the following algorithm: label the $n$ points $k=0, 1, 2, \ldots n-1$; then join point $k$ to point $mk\,\mathrm{mod}\,n$.

The line segments inside and outside the circle can be plotted in different colours to make pleasing images. Pass $m$ and $n$ as arguments on the command line, e.g.

python 82 110

m=82, n=110

python 20 40

m=20, n=40

import sys
import numpy as np
import matplotlib.pyplot as plt

def add_lines(ax, m, n, line_segments=True, c='w', lw=0.5, *args, **kwargs):
    ax.add_patch(plt.Circle((0, 0), 1, fc='none', ec='w', lw=2))
    # Pop off oc ("outer line colour") if presentm otherwise set to the
    # same as the inside colour.
    oc = kwargs.pop('oc', c)

    dtheta = 2 * np.pi / n
    theta = np.arange(0, 2 * np.pi, dtheta)
    pts = np.array([np.cos(theta), np.sin(theta)]).T

    def get_bounded_coords(X, x1, y1, g):
        Get the coordinates of the intersection of the line through
        (x1, y1) with gradient g at with x-coordinate X, unless this
        has a y-coordinate outside the range (YMIN, YMAX), in which case
        set y appropriately and determine the correspinding x-coordinate.

        def get_x_at_y(Y):
            """Return the x-coordinate at y=Y for the line."""
            return (Y - y1) / g + x1

        yA = y1 + g * (X - x1)
        if yA > YMAX:
            return get_x_at_y(YMAX), YMAX
        elif yA < YMIN:
            return get_x_at_y(YMIN), YMIN
        return X, yA

    for k, pt1 in enumerate(pts):
        pt2 = pts[m * k % n]
        x1, y1 = pt1
        x2, y2 = pt2
        if x1 > x2:
            x1, y1, x2, y2 = x2, y2, x1, y1
        if line_segments:
            # Just join the line segments inside the circle.
            plt.plot([x1, x2], [y1, y2], c=c, lw=lw, **kwargs)

        if x1 == x2:
            # Vertical lines are a special case.
            xA, xB, yA, yB = x1, x2, YMIN, YMAX
            # The gradient of the line.
            g = (y2 - y1) / (x2 - x1)
            # Get the start and end points of the line on the boundary
            # rectangle, (XMIN, YMIN), (XMAX, YMAX).
            xA, yA = get_bounded_coords(XMIN, x1, y1, g)
            xB, yB = get_bounded_coords(XMAX, x1, y1, g)

        if oc == c:
            # Same colour inside and outside the circle.
            plt.plot([xA, xB], [yA, yB], c=c, lw=lw, **kwargs)
            plt.plot([xA, x1], [yA, y1], c=oc, lw=lw, **kwargs)
            plt.plot([x1, x2], [y1, y2], c=c, lw=lw, **kwargs)
            plt.plot([x2, xB], [y2, yB], c=oc, lw=lw, **kwargs)

if __name__ == '__main__':
    m, n = map(int, sys.argv[1:3])
    XMIN, XMAX, YMIN, YMAX = -4, 4, -3, 3
    fig, ax = plt.subplots(facecolor='k')

    add_lines(ax, m, n, c='y', line_segments=False, oc='r')

    ax.set_xlim(XMIN, XMAX)
    ax.set_ylim(YMIN, YMAX)
Current rating: 4


Comments are pre-moderated. Please be patient and your comment will appear soon.

There are currently no comments

New Comment


required (not published)