Parabolic mirrors made simple(r)

A couple of days ago, Numberphile released a video on YouTube in which Tom Crawford explained why parabolic mirrors reflect incoming light to the focal point. His explanation was, I thought, needlessly complicated, so I decided to write up a simpler one. No implicit differentiation, no vector algebra, no messing around with square roots of polynomials.

Here’s Tom’s explanation:

First, I don’t think Tom does his audience any favors by starting with a parabola that opens to the right. Most people are used to parabolas that open upward and have this simple equation

\[y = bx^2\]

where b controls the spread of the parabola. Using this equation is especially helpful when you want to get the slope,

\[\frac{dy}{dx} = 2bx\]

because this is one of the first derivatives you learn in calculus.

Sometimes, doing the unexpected can lead to nice simplifications, but that doesn’t happen in Tom’s analysis, so let’s make our parabola follow the equation above.

Parabola construction with tangent, focal point, and light rays

As you can see, I’ve added the focal point (or focus), which is on the y-axis at a distance \(1/4b\) above the vertex. If it’s been a while since you took analytic geometry, you may have forgotten where the focus is, but that’s the spot. (If you’re wondering about the directrix, it’s parallel to the x-axis and \(1/4b\) below it, but we won’t need that in our analysis.)

The figure also includes a line tangent to the parabola at some general point, \((x, bx^2)\) and some signal (a light or sound wave) that comes in parallel to the y-axis, bounces off the parabola and goes through the focal point. Physics says the incident and reflected beams are at the same angle, \(\theta\), to the tangent line. We get the other \(\theta\), the one on the lower right, from geometry: it’s on the opposite side of intersecting lines from the upper \(\theta\).

Our job is to prove that the above construction is true, that the reflected beam passes through the focal point for any point on the parabola. To do that, let’s redraw the triangles from the above diagram a little bigger and identify some other dimensions and angles.

Triangles from parabola construction

Here, \(\alpha\) is the angle of the line from the reflection point to the focus and \(\beta\) is the angle of the tangent line through the reflection point. As you can see from the larger triangle,

\[2 \theta = 90^\circ - \alpha\]

and from the smaller triangle,

\[\theta = 90^\circ - \beta\]

Combining these equations gives us this relationship between \(\alpha\) and \(\beta\):

\[2\beta = 90^\circ + \alpha\]

If we prove that this is true, we’ve shown that the reflected beam goes through the focus.

We know that the tangent of \(\beta\) is the derivative of the parabola, \(2bx\), and we can work out the tangent of \(\alpha\) from the dimensions given in the figure. So let’s take the tangent of both sides of the above equation and see what happens.

For the left side, we use this double angle formula for tangent:

\[\tan (2\beta) = \frac{2 \tan \beta}{1 - \tan^2 \beta} = \frac{2 (2bx)}{1 - (2bx)^2} = \frac{4 b x}{1 - 4 b^2 x^2}\]

For the right side, we use this trig identity,

\[\tan (90^\circ + \alpha) = - \cot \alpha\]

and the lengths given for the sides of the larger triangle to get

\[-\cot \alpha = -\frac{x}{b x^2 - \frac{1}{4b}} = -\frac{4bx}{4b^2x^2 - 1} = \frac{4 b x}{1 - 4 b^2 x^2}\]

This proves that

\[\tan (2\beta) = \tan (90^\circ + \alpha )\]

and therefore

\[2\beta = 90^\circ + \alpha\]

and that’s what we set out to prove. Because this is true for any value of x, any vertical beam hitting the parabola will be reflected to the focal point.

When Tom turned his lines into vectors and started taking dot products, I thought he was doing something clever to make his later algebra easier. But as you saw from his messing around with square roots of polynomials, it didn’t make the algebra easier, it made it harder.

Should I point out that Tom’s flashlight probably doesn’t work the way he says? That the part behind the LED is milky white and not a shiny reflector? That its beam clearly doesn’t come out parallel to the axis but spreads quite widely? No, I’m sure you already noticed all that.


Yesterday, I listened to the most recent Mac Power Users episode, “20 Mac Apps Under $20.” Although MPU is probably best known for its deep dive episodes, I always like these more rapid-fire discussions. As usual, this one has a good mix of apps that are new to me and those I already know about.1 One of Stephen’s picks is ImageOptim (discussion starts at 1:00:50), an app I use all the time but haven’t talked about here.

As suggested by its name, ImageOptim optimizes images by reducing their file size. It doesn’t resize an image in the sense of changing its width or height in pixels. What ImageOptim leaves you with is an image that takes up less space on your disk; it doesn’t take up less space on your screen.

I’m sure you already know that a JPEG’s file size can be reduced by lowering its “quality.” ImageOptim can do this kind of optimization, but so can lots of apps. what ImageOptim really excels at is reducing the file size without changing the image quality. And it can do this for PNGs, which is the format I use for the screenshots I post here. Generally, I get 25–40% smaller files after running them through ImageOptim.

Fundamentally, ImageOptim is a front end for a set of open source image optimization programs. You can see and choose the programs in either the Preferences or the Tools menu.

ImageOptim Tools menu

The given image is run through each of the checked programs appropriate for its file type, and the one that provides the smallest file is selected. The image file is overwritten with that optimized version and you’re told how much smaller the file became. You’ll note that I don’t have my images run through Zopfli or PNGOUT, as these programs run slower than the others, and I don’t think the extra time is worth slight extra reduction in file size.

This is all very well and good, but I wouldn’t think as highly of ImageOptim if it weren’t for one very useful feature: it can be run as a command line program. Which means that my Keyboard Maestro macro for taking screenshots and uploading them to the blog’s server can reduce their file size during the process.

(I used to use OptiPNG for this, but at some point a couple of years ago it started running very slowly, and the delay was disrupting even the slow pace of my writing. I just ran a few tests, and OptiPNG seems to be running fine on my Macs now, but I’m sticking with ImageOptim.)

My screenshot-and-upload macro, SnapSCP, looks like this:

SnapSCP KM macro

The Python script in Step 1 is this:

 1:  #!/usr/bin/env python3
 3:  import Pashua
 4:  import tempfile
 5:  from PIL import Image
 6:  import sys, os, os.path
 7:  import subprocess
 8:  import urllib.parse
 9:  from datetime import date
10:  import sys
12:  # Parameters
13:  dstring ='%Y%m%d')
14:  year ='%Y')
15:  type = "png"
16:  localdir = os.environ['HOME'] + "/Pictures/Screenshots"
17:  tf, tfname = tempfile.mkstemp(suffix='.'+type, dir=localdir)
18:  bgcolor = (61, 101, 156)
19:  border = 16
20:  optimizer = '/Applications/'
21:  server = f'{year}/'
22:  port = '9876'
24:  # Dialog box configuration
25:  conf = b'''
26:  # Window properties
27:  *.title = Snapshot
29:  # File name text field properties
30:  fn.type = textfield
31:  fn.default = Snapshot
32:  fn.width = 264
33:  fn.x = 50
34:  fn.y = 40
35:  fnl.type = text
36:  fnl.default = Name:
37:  fnl.x = 0
38:  fnl.y = 42
40:  # Border checkbox properties
41:  bd.type = checkbox
42:  bd.label = Add background
43:  bd.x = 10
44:  bd.y = 0
46:  # Default button
47:  db.type = defaultbutton
48:  db.label = Save
50:  # Cancel button
51:  cb.type = cancelbutton
52:  '''
54:  # Capture a portion of the screen and save it to a temporary file.
55:  status =["screencapture", "-io", "-t", type, tfname])
57:  # Exit if the user canceled the screencapture.
58:  if not status.returncode == 0:
59:    os.remove(tfname)
60:    print("Canceled")
61:    sys.exit()
63:  # Open the dialog box and get the input.
64:  dialog =
65:  if dialog['cb'] == '1':
66:    os.remove(tfname)
67:    print("Canceled")
68:    sys.exit()
70:  # Add a desktop background border if asked for.
71:  snap =
72:  if dialog['bd'] == '1':
73:    # Make a solid-colored background bigger than the screenshot.
74:    snapsize = tuple([ x + 2*border for x in snap.size ])
75:    bg ='RGBA', snapsize, bgcolor)
76:    bg.alpha_composite(snap, dest= (border, border))
79:  # Rename the temporary file using today's date (yyyymmdd) and the
80:  # name provided by the user.
81:  name = dialog['fn'].strip()
82:  fname =  f'{localdir}/{dstring}-{name}.{type}'
83:  os.rename(tfname, fname)
84:  bname = os.path.basename(fname)
86:  # Optimize the PNG.
87:[optimizer, fname], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
89:  # Upload the file via scp.
90:['scp', '-P', port, fname, server])
92:  # Generate the URL of the uploaded image.
93:  bname = urllib.parse.quote(bname)
94:  print(f'{year}/{bname}', end='')

How this script uses Pashua, screencapture, and scp are described in an older post. The only thing that’s different now is the use of ImageOptim. The full path to the executable is given in Line 20,

optimizer = '/Applications/'

and it’s run via the subprocess library in Line 87,

python:[optimizer, fname], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

When the optimized screenshot is done uploading and its URL is on the clipboard, the macro plays the Glass sound to alert me that I can paste the URL wherever I need it. It typically takes 3–5 seconds.

So ImageOptim gives you the best of both worlds: a nice app for optimizing images through the GUI and a command-line tool for doing the same thing in scripts and macros. It’s even better than Stephen said it was.

  1. What’s good about a discussion of apps I’m familiar with? Well, I’m usually not as familiar with them as I think. There’s often a feature or two I never knew about. 

Feet, inches, and averaging

After Purdue’s historically embarrassing loss to Fairleigh Dickinson on Friday night, many sportswriters awakened to Purdue’s longstanding poor performance in the NCAA tournament and wrote articles like this one in Slate. As I read that article, I saw one error that should embarrass Slate and then saw another statement that seemed like an error but wasn’t. I figured they were worth a short blog post.

The embarrassing error concerns the height difference between Purdue and FDU. Purdue’s top player, Zach Edey, is 7′ 4″. According to Slate, FDU has “nobody taller than 6-foot-6.” Slate apparently doesn’t know how to use foot and inch marks, but I don’t consider that an error. And even though a quick perusal of FDU’s roster clearly shows a 6′ 7″ player, I’m going to give them a pass on that, too, as Pier-Olivier Racine doesn’t get much court time and didn’t play at all in the Purdue game. No, the error comes later when, after establishing that Edey is 7′ 4″ and the tallest players on FDU are 6′ 6″, the article says

Edey is 8 inches taller than anyone who guarded him.

My guess is that the writer, Alex Kirshner, did the calculation in his head this way,

74 – 66 = 8

and everyone at Slate who read the article before publication just went along with it. I’m also pretty sure that all my non-American readers are nodding their heads and congratulating themselves on not using those awful and confusing US customary units. But honestly, is it really that hard to see that 7′ 4″ is actually 10″ taller than 6′ 6″? I bet even Purdue’s engineering students would get that one right.

Here’s the thing that seemed like a mistake but wasn’t:

There are 363 Division I men’s basketball teams. FDU is the shortest one of them all, according to Ken Pomeroy’s data, with an average height of 6-foot-1.

This wasn’t the first time I’d heard that Fairleigh Dickinson’s team had an average height of 6′ 1″—it’s in several of the Purdue-excoriating articles. And it wasn’t the first time I thought that can’t be right. I don’t need KenPom to tell me the team’s average height (and a good thing, too, because it’s behind a paywall), all the heights are on the afore-linked roster.

Scrolling through the list of players, there are only two under 6′ 1″, one who is 6′ 1″, and ten others who are taller. Even without doing a single calculation, I knew the average had to be over 6′ 1″. And it is. To the nearest half-inch,1 the average height of the thirteen players on FDU’s roster is 6′ 3″.

The thing is, while I can believe that Slate would make a mistake in calculating a team’s average height, I can’t believe Ken Pomeroy would. So how did he come up with 6′ 1″? The answer is on his site, and it’s a good one:

Overall average height is computed by taking the average listed height of every player on the team, weighted by minutes played. Players that have played less than 10% of their team’s minutes are not included.

Pomeroy wants to give his readers the average height of the team on the court, and weighting the players’ heights by minutes played is probably the best way to do that. And dropping out those who play in “garbage time” makes sense, too.2

So I made a little spreadsheet with the FDU players, their heights, and the minutes they played in the Purdue game. Here’s a screenshot:

FDU height spreadsheet

As you can see, the team’s shorter players, Roberts and Singleton, also got the most minutes, so the time-weighted average, rounded to the nearest half-inch, for this particular game was 6′ 1½″. Close enough. I wouldn’t be surprised to learn that FDU had its taller players on the court more than usual in this game.

The spreadsheet also includes the naive average of 6′ 3″ and the 6′ 2″ average of the nine FDU players who played in the Purdue game.

By the way, I didn’t enter the heights in feet and inches as shown in the second column. I entered the heights in inches in the third column and converted to feet and inches for presentation with this formula for the player heights

FIXED(FLOOR(C2÷12,1), 0) & "′ " &
FIXED(MOD(C2, 12), 0) & "″"

and this formula for the average heights.

FIXED(FLOOR(C16÷12,1), 0) & "′ " &
FIXED(MROUND(MOD(C16, 12), 0.5), 1) & "″"

I needed the MROUND function in the latter formula to get the results rounded to the nearest half-inch.

The “played average” was calculated this way:


I’m not a big spreadsheet user, and I think this is the first time I ever used MROUND or AVERAGEIF. So thanks to Slate and KenPom for that.

  1. The Slate article says Purdue’s average height is 6′ 5½″, so I assume KenPom gives average heights with that resolution or finer. 

  2. One might argue that someone who averages 4 minutes per game is getting more than garbage time, but the principle is sound. 

Better MathJax equations

If some of the equations in my posts look a little off—poorly spaced or, more likely, with a wobbly baseline—there may be something you can do about it. I learned this on Mastodon through a combination of Andy Napper and Mimmo.

To some extent, the formatting of equations depends on the browser you use, but I’m not going to tell you switch from your favorite browser just to read this blog. Instead, you can change how MathJax renders equations in whatever browser you use by tweaking a setting or two.

Here’s an example of an uneven baseline from a recent post:

MathJax equation rendered with CHTML

This is how Safari renders the equation when MathJax is using the “CHTML” (CSS and HTML) renderer. But if you right-click on the equation and work your way through the MathJax settings, you can switch to the SVG renderer

MathJax renderer setting

to get a more even baseline

MathJax equation rendered with SVG

The nice thing about this is that even though you change the setting on one equation, it applies to all the equations. Even better, it’s persistent—you don’t have to keep changing it every time you open a different post with equations.

If you find yourself zooming in on equations often, you might want to set up a “Zooom Trigger” to show a magnified view of any equation you click on:

MathJax zoom trigger setting

The problem with the Zoom Trigger is that it shows the magnified equation in a box that might not be big enough to show the whole equation at once:

MathJax magnified equation in a box

Even though you can scroll around within the box to see the rest of the equation, I never use this feature. I find it much better to zoom the entire web page by either spreading two fingers on the trackpad1 or pressing ⌘+ a few times.

It’s been a long time since I reviewed ANIAT’s MathJax configuration, and I’m sure there are things I can do to improve the rendering for everyone. Until I get around to that, have a look through all of MathJax’s settings to see if any of its customizations strike your fancy.

  1. I refuse to call this “pinching,” no matter what Apple and everyone else does.