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.

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:
- represents the number of teeth on the sun gear.
- represents the number of teeth on each planet gear. (Three planet gears are often linked with a “Y”-shaped connector, hence the name!)
- represents the number of teeth on the ring gear.
- 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:

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:
Gear Ratios
Gear ratios are defined by:
where 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 after setting .:
Set :
Divide both sides by :
Divie both sides by :
Un-grouping fractions:
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!