Lior.
← All projects

Rocket Optimizer

A desktop app that searches thousands of solid rocket motor designs in openMotor's ballistics engine and returns the ones that satisfy your limits.

Year
2026
Role
Sole developer
Stack
Python, pymoo, scikit-learn, openMotor, FastAPI, Plotly, pywebview, PyInstaller
Links
Code ↗Download ↗
Rocket Optimizer mid-search, a scatter of simulated motors with the best trade-off curve drawn in orange

Why

Designing a solid motor in openMotor is trial and error. A BATES motor has nine dimensions worth changing, six core diameters plus the nozzle throat, exit and throat length, and every change gets checked by eye against peak pressure, Kn, mass flux, port-to-throat ratio and core Mach. I wanted the computer to run that search for the hobby rocketry team I’m part of, and hand back .ric files that open straight in openMotor.

The full name is Lior’s Really Good™ Rocket Optimizer. It ships as a macOS and Windows app, keeps its files under Documents, and never sends anything off the machine.

How the search works

The full search runs NSGA-II, via pymoo, directly against openMotor, so every candidate is a real simulation. The surrogate search spends half its budget on a space-filling sample, then works in rounds: fit scikit-learn models to every burn so far, run NSGA-II against the models, simulate the designs the models propose. Each round puts the burns where the trade-off curve is, which is exactly where the models need to be accurate. On the reference motor it reaches a fuller curve than the full search on the same budget.

Models influence which designs are proposed, never which are reported. Every design in a result has been re-simulated at a timestep five times finer than the search, with all search-time safety margins removed. A single search is not guaranteed to find the global front, so a run splits its budget across independent searches and reports the non-dominated set of everything they found, up to ninety designs spread along the curve.

Making the space smaller before searching it

Grain order does not change impulse, pressure or burn time in openMotor’s model; only the multiset of core diameters does. Storing cores sorted removes a 720-fold degeneracy, and a script in CI checks that claim against the simulator on every push rather than taking it on trust. Port-to-throat ratio, initial Kn, propellant mass and ignition pressure are closed-form for BATES geometry, so they are computed rather than learned, and used to rule out regions no legal motor can occupy before a single simulation runs.

The search and verification timesteps disagree slightly, and in different directions per metric: peak mass flux is a finite difference and grows as the step shrinks, total impulse is an integral, pressure and Kn are invariant. The app measures that ratio on the loaded motor and tightens the search-time limits by it, so a design sitting on a limit during the search still satisfies it after verification.

Grain count is not a dimension like the others, since a five-grain design vector and an eight-grain one have different lengths. It gets a search of its own in three passes: a closed-form screen drops counts that no core and throat can make legal, a short search ranks the survivors on the hypervolume of their verified front, and the best two counts get the full budget.

What comes out

A trade-off curve where any point can be clicked to see its thrust curve, chamber pressure, Kn and a scaled cross-section. A PDF report derived entirely from the run. One .ric per design for openMotor, a design sheet per design with the dimensions to machine to, and a RASP .eng that OpenRocket reads as a separate motor per design. An empty result is a finding rather than a failure: the report names the limit that could not be met and, where burning area is closed-form, proves that no core diameter would have satisfied it.

The results page: download buttons for the report, design sheets, .eng and .ric files, above the optimized thrust curve against the baseline and the chamber pressure and Kn traces

Shipping it

FastAPI serves a vanilla JavaScript interface with Plotly loaded from disk, so it works with no network. pywebview wraps it in a native window and PyInstaller freezes it; a GitHub Actions workflow builds the macOS and Windows zips on every tag, runs the frozen build headlessly to prove it starts, and attaches both to a release. One rule held throughout: no time estimate is shown until the app has timed this machine at these settings, because a rate the program has not measured is a guess.