Wednesday, March 4th, 2026¶

On Monday, we discussed Pythagorean triples and the numpy module.

Primitive Pythagorean triples¶

It will be useful for the project to consider what are called, "primitive Pythagorean triples". We say that a Pythagorean triple $(a,b,c)$ is primitive if the greatest common divisor of $a$, $b$, and $c$ is $1$.

Math exercise: A Pythagorean triple $(a,b,c)$ is primitive if and only if the greatest common divisor of $a$ and $b$ is $1$.

Recall from the project page that a pair of integers $(a,b)$ is a Pythagorean double if there as integer $c$ such that $(a,b,c)$ is a Pythagorean triple. In light of the math exercise above, we will call a Pythagorean double $(a,b)$ primitive if $a$ and $b$ have greatest common divisor $1$.

In addition to plotting all Pythagorean doubles in a given range, it will be useful to also plot only the primitive Pythagorean doubles. To that end, can we calculated greatest common divisors? Let's write our own greatest_common_divisor function.

In [6]:
def greatest_common_divisor(a,b):
    for d in range(min(abs(a),abs(b)), 0, -1):
        if (a % d == 0) and (b % d == 0):
            return d
In [7]:
greatest_common_divisor(10,15)
Out[7]:
5
In [8]:
greatest_common_divisor(99,54)
Out[8]:
9
In [9]:
greatest_common_divisor(10,-15)
Out[9]:
5

Another method of calculating greatest common divisors is known as the Euclidean algorithm. The algorithm is based on the following facts.

  • If $d$ divides both $a$ and $b$, then $d$ must also divide $b - a$.
  • If $d$ divides both $a$ and $b-a$, then $d$ must also divide $b$.

Putting these together, we see that the greatest common divisor of $a$ and $b$ is exactly the same as the greatest common divisor of $a$ and $b-a$. The algorithm then proceeds as follows.

  1. Start with integers $a$ and $b$, and assume that $a \leq b$.
  2. Subtract the smaller $a$ from the larger $b$, and replace the larger $b$ with the difference $b - a$.
  3. Repeat step 2 until one of the numbers is $0$. The other will be the greatest common divisor.

Exercise: Write a function to implement the Euclidean algorithm

In [10]:
def Euclidean_algorithm(a,b):
    a = abs(a)
    b = abs(b)
    while (a > 0) and (b > 0):
        if a > b: 
            a,b = b,a
        b = b % a
    return a
In [11]:
Euclidean_algorithm(10,15)
Out[11]:
5
In [12]:
Euclidean_algorithm(99,54)
Out[12]:
9
In [13]:
Euclidean_algorithm(-10,15)
Out[13]:
5
In [14]:
import time
In [16]:
t0 = time.time()
print(greatest_common_divisor(10**6*455, 10**6*455 + 1))
t1 = time.time()
print(t1 - t0,'seconds')
1
72.49251222610474 seconds
In [17]:
t0 = time.time()
print(Euclidean_algorithm(10**6*455, 10**6*455 + 1))
t1 = time.time()
print(t1 - t0,'seconds')
1
0.0008251667022705078 seconds

Exercise: Generate a list of Pythagorean triples using the get_ptriples function. Then generate a slice consisting of only the primitive Pythagorean triples (use list comprehension if you can).

In [18]:
ptriples = [[3, 4, 5],
[4, 3, 5],
[5, 12, 13],
[6, 8, 10],
[8, 6, 10],
[8, 15, 17],
[9, 12, 15],
[12, 5, 13],
[12, 9, 15],
[12, 16, 20],
[15, 8, 17],
[15, 20, 25],
[16, 12, 20],
[20, 15, 25]]
In [21]:
for ptriple in ptriples[:10]:
    print(ptriple)
[3, 4, 5]
[4, 3, 5]
[5, 12, 13]
[6, 8, 10]
[8, 6, 10]
[8, 15, 17]
[9, 12, 15]
[12, 5, 13]
[12, 9, 15]
[12, 16, 20]
In [25]:
a = 6
b = 8
c = 10

gcd_ab = Euclidean_algorithm(a,b)
gcd = Euclidean_algorithm(gcd_ab, c)

print(gcd)
2
In [ ]:
def is_primitive(ptriple):
    
In [ ]:
 

Figures and axes with matplotlib¶

We have been using matplotlib.pyplot to generate plots in Python. Up to now, we've mostly let matplotlib choose most of the formatting options automatically when generating the figure. Consider the following example where this can give undesireable results.

Suppose we want to draw the unit circle (i.e. the circle of radius $1$ centered at the origin). Recall from MTH 142 that we can describe the unit circle by the parametric equation $$x = \cos t \qquad\qquad y = \sin t \qquad \qquad 0 \leq t \leq 2\pi.$$

We can use np.linspace, np.pi, np.sin, and np.cos to plot the unit circle using the parametrization above.

In [26]:
import matplotlib.pyplot as plt
import numpy as np
In [27]:
t = np.linspace(0, 2*np.pi, 1000)

x = np.cos(t)
y = np.sin(t)

plt.plot(x,y)
Out[27]:
[<matplotlib.lines.Line2D at 0x29d4e546710>]
No description has been provided for this image

Notice: the graph does not look circular. We need to adjust the aspect ratio (i.e. the horizontal:vertical ratio) to correct this. First, a little more information on the structure of figures in matplotlib. When plotting with matplotlib.pyplot:

  • Each figure starts with a figure object.
  • Within that figure object, we can place an axes object (or several).
  • On an axes object, we can draw things (lines, points, etc.).

When we call plt.plot (in the absence of other surrounding commands), matplotlib automatically generates a figure and axes object. We can also take control of that process ourselves:

  • Calling plt.figure() will generate a figure object.

Exercise: Create two figures, one which graphs $x$ vs $y$ (as in the code above), and another that graphs both $x(t)$ and $y(t)$ vs $t$.

In [29]:
plt.figure()
plt.plot(x,y)
plt.title('$x$ vs $y$')
plt.xlabel('$x$')
plt.ylabel('$y$')

plt.figure()
plt.plot(t,x, label='$x(t)$')
plt.plot(t,y, label='$y(t)$')
plt.xlabel('$t$')
plt.legend()
Out[29]:
<matplotlib.legend.Legend at 0x29d500982f0>
No description has been provided for this image
No description has been provided for this image

By defining the figure object ourselves, we are able to take more control and can include numerous optional arguments to change the default behavior. For example, the optional argument figsize=(<horizontal size>, <vertical size>) can be used to change the size of the figure. Let's graph the circle again, but this time specify a figure size that is square.

In [33]:
plt.figure(figsize=(4,4))
plt.plot(x,y)
plt.title('$x$ vs $y$')
plt.xlabel('$x$')
plt.ylabel('$y$')

plt.figure(figsize=(4,2))
plt.plot(t,x, label='$x(t)$')
plt.plot(t,y, label='$y(t)$')
plt.xlabel('$t$')
plt.legend()
Out[33]:
<matplotlib.legend.Legend at 0x29d506d2ad0>
No description has been provided for this image
No description has been provided for this image

Now suppose we want to graph a second circle centered at $(3,0)$.

In [39]:
plt.figure(figsize=(4,4))
plt.plot(x,y)
plt.plot(x + 3, y)
Out[39]:
[<matplotlib.lines.Line2D at 0x29d51b48550>]
No description has been provided for this image

We see that neither circle is circular. Unfortunately, using the figure size is not a robust way to set aspect ratios. Instead, we can take control of the aspect ratio through the axes object. To do so, we will need to add an axes object ourselves. Calling the plt.subplot function is one mechanism for adding an axes object to a figure.

The plt.subplot function can take in an optional argument, aspect. We can use aspect='equal' to force an equal aspect ratio.

Note: by default, matplotlib will shrink the figure to (approximately) the extents of the data. As a result, when forcing an equal aspect ratio, the resulting figure may end up smaller than the specified figsize.

In [48]:
plt.figure(figsize=(12,4))

plt.subplot(aspect='equal')

plt.plot(x,y)
plt.plot(x + 3,y)
plt.xlim(-4, 10)
Out[48]:
(-4.0, 10.0)
No description has been provided for this image