In today’s post, I write a Python script to optimize a chain of planetary gears to achieve a specific gear ratio.

Background

Planetary gear chains

Planetary gear sets have a sun gear (yellow), planet gears (green), and a ring gear (blue). One gear is usually held fixed, and the others rotate around, achieving mechanical advantage.

Image of a planetary gear set

Planetary gear sets have the benefit of being entirely self-contained, and easy to fit into assemblies where conventional gearboxes would be incredibly inconvenient. However, depending on the application, they can pack a ton of bulk for mediocre gear ratios. My application is an angle meter, for which I’d like to make the ring gear into a dial of sorts. Keeping this assembly tight enough will require chaining planetary gear sets together.

We chain planetary gear sets by holding one element constant (the sun gear, the ring gear, or the arrangement of planet gears as a whole) and feeding it into the next ring. For my particular application, I’m interested in keeping the sun gear constant and feeding the ring gear into the planet gear arrangement on each subsequent level.

Governing equations

Here are several governing concepts for planetary gear design. In each one:

  • SS represents the number of teeth on the sun gear.
  • YY represents the number of teeth on each planet gear. (Three planet gears are often linked with a “Y”-shaped connector, hence the name!)
  • RR represents the number of teeth on the ring gear.
  • NN represents the number of planet gears.

Tooth Counts

Beyond having the same tooth module, a planetary gear set has a number of constraints on the tooth count of each element.

First, in order to fit, the ring gear must have as many teeth as the sun gear plus twice the planet gears:

2Y+S=R2Y + S = R

Image showing 2 planetary gears and a sun gear adding up to the ring gear

In addition, if I want the planets to be evenly spaced, the sum of the ring and sun gear teeth must be evenly divisible by the number of planets:

(R+S)mod  N=0(R + S) \mod N = 0

Gear Ratios

Gear ratios are defined by:

(R+S)Ty=RTr+TsS(R+S)T_y = RT_r+T_sS

where Ts,Ty,TrT_s, T_y, T_r are turns of the sun, planet, and ring gears, respectively.

As mentioned above, a gear ratio can be calculated by holding one gear constant. For example, if I wanted to solve for the gear ratio between the ring and the planet gear with the sun held constant, I’d solve for TrTy\frac{T_r}{T_y} after setting Ts=0T_s=0.:

(R+S)Ty=RTr+TsS(R+S)T_y = RT_r+T_sS

Set Ts=0T_s=0:

(R+S)Ty=RTr(R+S)T_y = RT_r

Divide both sides by TyT_y:

R+S=RTrTyR+S = \frac{RT_r}{T_y}

Divie both sides by RR:

R+SR=TrTy\frac{R+S}{R} = \frac{T_r}{T_y}

Un-grouping fractions:

TrTy=1+SR\frac{T_r}{T_y} = 1+\frac{S}{R}

Therefore, the gear ratio from the ring to the planets is one plus the sun teeth divided by the ring teeth.

Credits

All credit for the planetary math goes to this fantastic page on woodgears.ca!

Method

For a brute-force solution, I selected a random number of layers and sun/ring teeth, checking the above conditions.

What I’m specifically trying to optimize for is the shortest gear chain. As such, once my program finds a chain of length N, it only searches for chains that are of length N or shorter.

The full script is reproduced here:

"""
Calculate planetary gear train configurations, assuming the sun gear is held constant
"""

import random

# (R+S)Ty = R*Tr+Ts*S
# Since I'm feeding the ring forward into the "Y" (the planets), I want to find Tr/Ty, holding Ts at 0
# (R+S)Ty = R*Tr
# (R+S)/R = Tr/Ty
# .: gear ratio is 1 + Sun/Ring

MIN_PLANET_TEETH = 6

MIN_TEETH_SUN = 6
MAX_TEETH_SUN = 20

MIN_TEETH_RING = 20
MAX_TEETH_RING = 50

MIN_LAYERS = 1
MAX_LAYERS = 6

DESIRED_RATIO = 3.6
THRESHOLD = 0.05 / 360

# I am solving for S (the number of gears on the sun) and (for example) three Rs
# Each R must satisfy S+R being divisible by 2 or 3, since I'm fine with 2, 3, or 4 planets
# At 3 layers, (1+S/R1)*(1+S/R2)*(1+S/R3) must equal desired_ratio

best_length = MAX_LAYERS

while True:
    sun = random.randint(MIN_TEETH_SUN, MAX_TEETH_SUN)
    rings = [random.randint(MIN_TEETH_RING, MAX_TEETH_RING) for _ in range(random.randint(MIN_LAYERS, best_length))]
    valid = True
    for ring in rings:
        planet = (ring-sun)/2
        # To have evenly spaced planets, (S+R) must be divisible by the number of gears
        # I am willing to have 2, 3, or 4 planets
        if (not (sun+ring) % 2 == 0 or (sun+ring) % 3 == 0) or (planet < MIN_PLANET_TEETH) or (planet % 1 != 0):
            valid = False
            break
    if valid:
        ratio = 1
        for ring in rings:
            ratio *= 1 + (sun/ring)
        if abs(ratio - DESIRED_RATIO) < THRESHOLD and len(rings) <= best_length:
            print(f"Candidate: {ratio} with {sun}-tooth sun and ring teeth {','.join([str(r) for r in rings])}")
            best_length = len(rings)

For all the solvable inputs I tested, my script produced an satisfactory solution within minutes.

Next steps

While the script is good enough for basic engineering analysis, its efficiency could certainly be improved by:

  • Limiting the randomness of ring tooth selection based on the sun tooth constraint.
  • Removing the randomness altogether, if the solution space is small enough.

I would hesitate to immediately rewrite it in a faster language, since I believe effort is better spent optimizing algorithms than implementations.

Thanks for reading!