Speed reducer
The problem
A speed reducer is a gearbox. A small gear, the pinion, on one shaft drives a larger gear on a second shaft, which turns slower with a larger torque. Golinski posed the design of the lightest such gearbox (1970, Journal of Mechanisms 5(3): 287-309; 1973). There are seven design variables, in cm except for the teeth:
| Variable | Gene | Meaning | Bounds |
|---|---|---|---|
| b | x₁ | face width of the gears | [2.6, 3.6] |
| m | x₂ | module of the teeth (the pitch diameter per tooth) | [0.7, 0.8] |
| z | x₃ | number of teeth on the pinion, an integer | [17, 28] |
| l₁ | x₄ | length of shaft 1 between its bearings | [7.3, 8.3] |
| l₂ | x₅ | length of shaft 2 between its bearings | [7.8, 8.3] |
| d₁ | x₆ | diameter of shaft 1 | [2.9, 3.9] |
| d₂ | x₇ | diameter of shaft 2 | [5.0, 5.5] |
The weight to minimize adds up the gears and the two shafts:
0.7854 b m² (3.3333 z² + 14.9334 z − 43.0934) − 1.508 b (d₁² + d₂²)
+ 7.4777 (d₁³ + d₂³) + 0.7854 (l₁ d₁² + l₂ d₂²)
Eleven constraints, written g(x) ≤ 0, keep the gearbox sound:
g1 = 27 / (b m² z) − 1 bending stress of the teeth
g2 = 397.5 / (b m² z²) − 1 surface stress of the teeth
g3 = 1.93 l₁³ / (m z d₁⁴) − 1 deflection of shaft 1
g4 = 1.93 l₂³ / (m z d₂⁴) − 1 deflection of shaft 2
g5 = √((745 l₁ / (m z))² + 16.9·10⁶) / (110 d₁³) − 1 stress in shaft 1
g6 = √((745 l₂ / (m z))² + 157.5·10⁶) / (85 d₂³) − 1 stress in shaft 2
g7 = m z / 40 − 1 size of the pinion
g8 = 5 m / b − 1 face width at least 5 modules
g9 = b / (12 m) − 1 face width at most 12 modules
g10 = (1.5 d₁ + 1.9) / l₁ − 1 shaft 1 long enough for its diameter
g11 = (1.1 d₂ + 1.9) / l₂ − 1 shaft 2 long enough for its diameter
The first two keep the teeth from breaking or wearing: their bending stress and the contact stress on their surfaces. g3 to g6 limit how far each shaft bends and the stress in it. g7 to g11 are rules of proportion: g7 limits the pinion's pitch diameter, m z, to 40.
genoxide's SpeedReducer uses the definition, bounds and best known solution that Cagnina,
Esquivel and Coello Coello restate (2008, Informatica 32: 319-326, appendix, problem E03). The best
known weight is 2996.348165, at b = 3.5, m = 0.7, z = 17, l₁ = 7.3, l₂ = 7.8, d₁ = 3.350214 and d₂ =
5.286683. It isn't proven optimal. The published design, printed to 6 digits, exceeds g5 by 6.0e-7
and g6 by 1.3e-7, and weighs 2996.347849.
The literature's formulations differ (Ray, 2003, AIAA Journal 41(3): 556-558). Some print 7.477 for 7.4777 and 1.5079 for 1.508, which don't give this value. Some let l₂ go down to 7.3, where the minimum is 2994.471 (Lin, Tsai, Hu and Chang, 2013, Mathematical Problems in Engineering 419043). A weight from another paper is comparable only if its formulation is the same.
What makes it hard
The number of teeth is an integer, and the weight is a step function of it. One more tooth on the pinion, 18 instead of 17, adds 177 to the weight of the best design.
The feasible region is small. Of 2 million designs drawn at random within the bounds, 0.2 % are feasible. g8 alone rules out 99 %: the face width must be at least 5 modules, b ≥ 5m, and with b ≤ 3.6 that leaves only m ≤ 0.72 of the range [0.7, 0.8].
The minimum lies on a vertex of the feasible region, where seven limits hold at once: four genes at their lower bounds (m, z, l₁ and l₂) and three constraints (g5, g6 and g8). The shaft stresses set the shaft diameters, and g8 sets the face width to 5 modules. A search has to reach this corner from inside the feasible region, where a step toward it can cross a constraint.
Representation
A Real genome of 7 genes, (b, m, z, l₁, l₂, d₁, d₂), within the bounds above. SpeedReducer
rounds the third gene to the nearest integer when it evaluates a genome, and its design method
gives the rounded design. Any real-valued algorithm can then search the genes: a gene of 17.13 is 17
teeth.
The fitness is the weight and the total constraint violation, the sum of max(0, g) over the eleven constraints, 0 for a feasible design. genoxide compares fitnesses with Deb's feasibility rules (Deb, 2000, Computer Methods in Applied Mechanics and Engineering 186: 311-338): a feasible design beats an infeasible one, two feasible ones compare by weight, and two infeasible ones by violation. The rules need no penalty weights.
Algorithm
SHADE (Tanabe and Fukunaga, 2013, IEEE CEC 2013: 71-78), a differential evolution that adapts its scale factor and crossover rate from successful trials, with genoxide's defaults: its published population of 100, and a restart after 200 generations without progress. It stops once the weight is within 1e-10 of the best known weight, relative to it, or after 50,000 evaluations.
Differential evolution suits a minimum at the bounds: genoxide's DE sets a trial gene that falls outside its bounds halfway between its parent's gene and the bound. The population can then close in on a bound, halving the distance at each such step, without leaving the box.
Output
The first line gives the weight of the best design, the evaluations, and the best known weight. The second gives its
constraint violation; 0 means it's feasible. The next three give the design: the face width, the
module and the number of teeth, then each shaft's length and diameter. The last names the
constraints at their limit, within 1e-6 of 0. In Python, run evaluates the problem in Rust, so
both versions print the same.
The plot shows each variable on its range, and each constraint's value g: satisfied with its slack, active (within 1e-6 of its limit) or violated. The best of the first 100 random designs exceeds g5, the stress in shaft 1; within 300 evaluations, the best design is feasible. Between about 10,500 and 13,000 evaluations, g8, then g5 and g6, reach their limits, where all three stay from about 13,000 on. After about 16,000 evaluations, the design matches the best known one to the 6 digits that the plot shows. The progress curve shows the weight's error to the best known weight, on a log scale: the best design's falls to about 1e-4 by 16,000 evaluations, and the population goes on refining the design, without a restart, until the run stops after 23,200 evaluations, 2.9e-7 above the best known weight.
The project page plays this run back.
Good results
The run finds a feasible design of weight 2996.348165, the best known value: the design b = 3.5, m = 0.7, z = 17, l₁ = 7.3, l₂ = 7.8, d₁ = 3.350215 and d₂ = 5.286683. The shaft stresses (g5, g6) and the least face width (g8) are at their limits, and d₁ and d₂ hold g5 and g6 exactly: that's the minimum at this vertex.
Runs with seeds 2 to 5 end at the same design and weight, after 22,200 to 23,700 evaluations.
Known optimum: 2996.348165 (weight), best known
Source: examples/speed_reducer
Interactive run: tachsin.gr/projects/genoxide/examples/speed-reducer
cargo run --release --example speed_reducer
//! Golinski's speed reducer (1970, 1973): the lightest gearbox whose gear teeth and shafts stay
//! within their stress and deflection limits. A constrained mixed discrete-continuous problem,
//! whose best known weight is 2996.348165.
//!
//! The variables are the face width of the gears, the module of their teeth, the number of teeth
//! on the pinion, an integer, and the lengths and diameters of the two shafts. genoxide's
//! `SpeedReducer` rounds the number of teeth when it evaluates a genome, and its fitness is the
//! weight and the violation of the eleven constraints, which Deb's feasibility rules compare.
//! SHADE, a differential evolution, searches the genes, and the example prints the best design
//! and the constraints at their limits.
//!
//! With `GENOXIDE_TRACE=<file>`, it also writes a trace of its run for the plot on the example's
//! page, with `trace.rs`.
//!
//! ```text
//! cargo run --release --example speed_reducer
//! ```
mod trace;
use genoxide::prelude::*;
use genoxide::problems::Problem;
use genoxide::problems::engineering::SpeedReducer;
// a constraint within this of 0 is at its limit: active
const ACTIVE: f64 = 1e-6;
fn main() -> Result<()> {
let problem = SpeedReducer;
let best_known = problem.optimum().expect("known").value();
let de = De::builder(problem.representation())
.minimize()
.seed(1)
.build()?;
// with GENOXIDE_TRACE=<file>, a trace of the run for the plot on the example's page
let mut trace = trace::Trace::from_env();
let outcome = Engine::new(de, problem)
.stop_when(Stop::target(best_known * (1.0 + 1e-10)).or(Stop::evaluations(50_000)))
.on_generation(|snapshot| trace.record(snapshot))
.run()?;
let best = outcome.best_fitness();
let [b, m, z, l1, l2, d1, d2] = problem.design(outcome.best_genome());
let constraints = problem.constraints(outcome.best_genome());
let active: Vec<String> = (constraints.inequalities().iter().enumerate())
.filter(|(_, g)| g.abs() <= ACTIVE)
.map(|(i, _)| format!("g{}", i + 1))
.collect();
println!(
"weight {:.6} after {} evaluations (the best known: {best_known:.6})",
best.score().unwrap_or(f64::NAN),
outcome.evaluations()
);
println!("violation {:.6}", best.violation());
println!("face width {b:.6}, module {m:.6}, teeth {z}");
println!("shaft 1: length {l1:.6}, diameter {d1:.6}");
println!("shaft 2: length {l2:.6}, diameter {d2:.6}");
println!("active constraints: {}", active.join(", "));
trace.write();
Ok(())
}
python examples/speed_reducer/main.py
"""Golinski's speed reducer (1970, 1973): the lightest gearbox whose gear teeth and shafts stay
within their stress and deflection limits. A constrained mixed discrete-continuous problem, whose
best known weight is 2996.348165.
The variables are the face width of the gears, the module of their teeth, the number of teeth on
the pinion, an integer, and the lengths and diameters of the two shafts. genoxide's
``SpeedReducer`` rounds the number of teeth when it evaluates a genome, and its fitness is the
weight and the violation of the eleven constraints, which Deb's feasibility rules compare. SHADE,
a differential evolution, searches the genes, and the example prints the best design and the
constraints at their limits.
With ``GENOXIDE_TRACE=<file>``, it also writes a trace of its run for the plot on the example's
page, with trace.py.
python examples/speed_reducer/main.py
"""
import genoxide as gx
from trace import Trace
# a constraint within this of 0 is at its limit: active
ACTIVE = 1e-6
problem = gx.problems.engineering.SpeedReducer()
best_known = problem.optimum.value
de = gx.De(problem.genome, objective=problem.objective, seed=1)
# with GENOXIDE_TRACE=<file>, a trace of the run for the plot on the example's page
trace = Trace(problem)
result = de.run(
problem,
target=best_known * (1.0 + 1e-10),
evaluations=50_000,
on_generation=trace.on_generation,
)
b, m, z, l1, l2, d1, d2 = problem.design(result.best_genome).tolist()
constraints = problem.constraints(result.best_genome).tolist()
active = [f"g{i + 1}" for i, g in enumerate(constraints) if abs(g) <= ACTIVE]
print(
f"weight {result.best_fitness:.6f} after {result.evaluations} evaluations "
f"(the best known: {best_known:.6f})"
)
print(f"violation {result.violation:.6f}")
print(f"face width {b:.6f}, module {m:.6f}, teeth {z:.0f}")
print(f"shaft 1: length {l1:.6f}, diameter {d1:.6f}")
print(f"shaft 2: length {l2:.6f}, diameter {d2:.6f}")
print(f"active constraints: {', '.join(active)}")
trace.write()
What it prints, from a seeded run:
weight 2996.348165 after 23200 evaluations (the best known: 2996.348165)
violation 0.000000
face width 3.500000, module 0.700000, teeth 17
shaft 1: length 7.300000, diameter 3.350215
shaft 2: length 7.800000, diameter 5.286683
active constraints: g5, g6, g8