Mini repository that contains various ways to calculate the steering angles for
Ackermann geometry. The goal is to find the approximation with the least error
for MuJoCo cars — specifically, a quartic polynomial that maps the central
reference steering angle to a front-wheel steer joint, emitted as a MuJoCo
joint-equality polycoef="a0 a1 a2 a3 a4" vector.
- Calculate quartic
polycoefcoefficients for Ackermann steering in MuJoCo, via two methods:- Taylor expansion of the exact geometry around centre (
taylor_differentiation.py) - Least-squares fit over the steering range (
numpy_polyfit.py) — fits over a symmetric range of±delta_deg(function default±30°, driven bymax_angle_degfrom the CLI), sampled atnum_samplesevenly-spaced points (default200)
- Taylor expansion of the exact geometry around centre (
- Print an error table (SSE / RMSE / max error) comparing the methods
- Graphical comparison of how each method fits the exact geometry
- Ideal for automotive engineers, designers, and enthusiasts working on steering design
The project uses a conda environment named ackermann.
# Create the environment from the spec
conda env create -f environment.yml
conda activate ackermannAlready have the ackermann env? Update it in place instead:
conda env update -f environment.yml --prunePrefer plain pip? Use requirements.txt (note: Tkinter must come from your
Python install — it is not a pip package):
pip install -r requirements.txtpython main.py [L] [W] [max_angle_deg] [n_samples]L— wheelbase (default1.2)W— track width (default1.0)max_angle_deg— steering lock, the +/- range to fit over (default23)n_samples— number of sample points (default400)
Running it prints the error table and the polycoef strings, then pops up the
comparison figure. Example:
python main.py 1.2 1.0 23 400The error table reports three numbers, but RMSE (root mean square error) is the one to optimize for. Here's the reasoning:
- SSE (sum of squared errors) depends on how many sample points you take. Double
n_samplesand the SSE roughly doubles, even though the fit hasn't changed at all. That makes it useless for comparing runs with different sampling — it measures the grid as much as the fit. - Max error captures only a single worst-case point. It's noisy: it swings with where that one peak lands and ignores how the fit behaves everywhere else. A method can have a slightly worse max error but be better across the entire range.
- RMSE is the square root of the average squared error, so it's independent of the sample count (unlike SSE) and it reflects the whole steering range rather than a single point (unlike max error). The squaring also penalizes large deviations more than small ones, which matches what we care about: a fit that's a little off everywhere is fine, but a fit that's badly off anywhere causes visible steering misalignment.
Because RMSE is in the same units as the data (degrees), it also reads directly as "the typical steering error you should expect from this fit" — which is exactly the quantity a MuJoCo car model cares about.
| File | Responsibility |
|---|---|
main.py |
CLI entry point: prints the error table and the polycoef strings |
graph.py |
Renders the comparison figure (curves + error) |
src/ackermann.py |
Core geometry/error math shared by the table and the plot |
src/methods/taylor_differentiation.py |
Taylor-expansion coefficients |
src/methods/numpy_polyfit.py |
Least-squares fit coefficients |
Run python main.py from the repository root so the src package resolves.
- Python 3.14
- NumPy 2.4.6
- Matplotlib 3.10.9 (with the Tk backend for the interactive window)
See environment.yml for the conda spec, or requirements.txt for pip.
Formula for Ackermann steering angles — https://raw.org/book/kinematics/ackerman-steering/