Python library · v0.7.0 · MIT · Shanthosh KV
A method-of-characteristics design code for rocket nozzles. You give it a gas, a throat radius, and one expansion target. It marches Mach waves, returns a wall you can send to CAD, and draws the characteristic net the wall came from.
Mach, throat to exit C+ characteristics C− characteristics Ideal bell · γ = 1.2 · Me = 3 · Rt = 50 mm
What this program is
rockmoc builds the inviscid wall of a supersonic nozzle by the method of characteristics. It does not solve the Navier–Stokes equations, and it does not replace a hot-fire test. It is the step before those: a contour whose exit Mach, area ratio, and thrust coefficient you can defend from the mesh.
A gas (constant γ, a γ(M) table, or a frozen CEA / Cantera / CoolProp state), throat radius in metres, and exactly one of exit Mach, area ratio, or pe/pc. Angles in the API are radians. The geometric throat sits at x = 0, r = Rt.
A wall polyline with θ and M, a characteristic net if you ask for it, length, geometric area ratio, vacuum
and ambient thrust coefficient, and specific impulse. result.summary(),
result.report(), and result.plot() are the three ways to read it.
CSV for a report, a header-less point list for CAD, an R12 DXF, and an OpenFOAM axisymmetric-wedge
blockMeshDict. The same call can grow the wall by a displacement thickness before you export
it.
Every figure on this page was drawn by the library. Two design points are in use, and the captions
say which. Feature figures in images/rockmoc/ are γ = 1.2, Me = 3, Rt = 50
mm, usually
40 characteristics (website/make_images.py, make_images_extra.py). Dashboard figures
in images/rockmoc/ are γ = 1.25, Me = 3, Rt = 50 mm, 48 characteristics
(tests/generate_all_graphs.py), regenerated from the 0.7 solver. Do not read a metric box on a γ =
1.25 plot as the γ = 1.2 table.
One design point
γ = 1.2, Me = 3.0, Rt = 50 mm, 40 characteristics. One-dimensional area ratio is ε = Ae/A* = 6.74. Length below is L/Rt. Thrust is the exit-plane integral of momentum and pressure, divided by the one-dimensional vacuum CF.
| Design | L / Rt | CF,vac / 1-D |
|---|---|---|
| MLN | 10.45 | 100.0% |
| Ideal | 11.26 | 99.5% |
| TIC, 80% of ideal | 9.01 | 99.3% |
| Rao, 80% of a 15° cone | 4.80 | 97.0% |
At this Mach number Rao’s length convention (80% of the 15° cone of the same ε) is much shorter than a minimum-length contour. The thrust gap is mostly exit divergence, not a mass-flow error. A 15° cone at the same point keeps 96.9% of one-dimensional vacuum CF.
design_mln · axisymmetric
A sharp corner at the throat launches a Prandtl–Meyer fan. The corner angle θmax is solved so the last left-running wave from that corner hits the axis at the design Mach. It is not νe/2. That half-angle rule is the planar one, and using it on an axisymmetric nozzle was a real bug.
For Me = 3 and γ = 1.4 the axisymmetric corner is about 11.7°. The old solver reported 25.9°. Planar theory at the same point wants νe/2 ≈ 24.9°. The gap is the axisymmetric source term, not a fudge factor.
Downstream of the kernel, a Goursat problem turns the flow onto a uniform axial exit. The wall is the streamline that carries the throat mass flow, so exit area is an output of mass conservation. On this page’s design point the field-integrated vacuum thrust matches the one-dimensional value.
The wall angle rises a few degrees past the corner and then comes back. That overshoot is converged and shock-free. It is listed in the known limits.
design_ideal
The ideal bell replaces the sharp corner with a circular-arc throat. The march starts on a Sauer transonic initial-value line lying on that arc, then runs the full characteristic lattice out to a uniform axial exit.
Version 0.7 stopped building this shape by stretching an MLN and gluing an arc on the front. The lattice is the design. Exit area again follows the mass-carrying streamline, so geometric ε reflects the discharge of that start line rather than a forced rescale onto the one-dimensional A/A*.
Sauer’s series is the published one (Mendelson, NASA TM X-53084). It is comfortable for throat curvature
Rc/Rt ≳ 2. Smaller radii or low exit Mach fall back to a uniform near-sonic start, and
that fallback is written into meta["warnings"].
On the shared design point the ideal bell is the longest of the four, L/Rt = 11.26, and keeps 99.5% of one-dimensional vacuum thrust.
design_tic · truncate_fraction
A TIC is the ideal family cut at a fraction of its length. truncate_fraction is that fraction:
0.80 means the axial station at 80% of the ideal length. The exit angle is then whatever the ideal wall was
doing there, so you pay a divergence loss for the mass you saved.
At 80% on this design point, L/Rt drops from 11.26 to 9.01 and vacuum thrust moves from 99.5% to 99.3% of one-dimensional. The interesting part of the trade is shorter than that. The sweep from 50% to 100% is the first figure below.
Flight bells are often this compromise. The code keeps the parent MOC field so you can still see which waves were thrown away with the missing length.
design_rao · design_rao_control · design_rao_toc
Rao’s 1958 contour maximises thrust at a prescribed length. In rockmoc that length is a fraction of the 15°
cone with the same area ratio. length_fraction=0.8 is the usual engineering choice, which is why
this bell is 4.80 Rt long here while the MLN is 10.45.
The 0.7 solver builds it as MOC. A control surface satisfies Rao’s conditions (V cos(θ−μ)/cos μ held in the thrust balance, and r ρ V² sin²θ tan μ matched), mass flow closes the exit radius, and the wall is a Goursat solution off that surface. The previous version ignored its own kernel and drew a cubic Hermite through two angles.
Length and exit radius land within about 0.1% of the request, and Mach rises monotonically along the wall. If you ask for a bell shorter than the lattice can support, the nearest feasible length is used and a warning is stored.
design_rao_toc is the length-referenced TOC variant: the length is taken from an ideal MOC bell
rather than from the cone. Attachment angle stays above exit angle, which is the TOP/TOC signature (a
validation case records θn = 26.8° and θe = 10.6°).
The other geometries
design_conical is a straight wall at a constant half-angle, 15° unless you change it. Length is
(Re − Rt) / tan θc with Re from the one-dimensional area ratio.
Divergence efficiency is about (1 + cos θc)/2. It exists so a comparison has a textbook baseline.
On this design point its field-integrated vacuum thrust is 96.9% of one-dimensional, close to the Rao bell and
well below the MLN.
design_planar_mln_nasa follows NASA TM-X-1502 (Vanco and Goldman, 1968): two-dimensional, sharp
throat, θmax = νe/2, exit half-height equal to the one-dimensional area ratio. The
figure uses Me = 3, γ = 1.4, where that corner is 24.88°. The paper’s own sample,
Me = 4.05 and γ = 1.36, gives θmax = 34.96° and ye/ht = A/A* =
12.98, with the throat at (0, 1). Planar results did not move in the 0.7 axisymmetric fix.
design_spike builds a Lee / Vernacchia plug. The spike angle at the lip satisfies δ = π/2 −
νe. Truncation, a base-pressure model, and a primary cowl are separate calls:
design_spike_truncated, truncated_spike_with_base,
design_spike_with_cowl. The figure is pc = 5 MPa, Tc = 3300 K, γ = 1.2,
molar mass 22, lip radius 0.12 m, expansion ratio 18: Isp = 254 s, CF = 1.445, exit Mach
3.69. That is a constant-γ isentropic plug, not a reacting plume.
plot_aerospike, the figure result dashboards use
for a plug.What the march actually solves
Irrotational, isentropic, perfect-gas (or frozen-table) flow. The equations live in
src/rockmoc/moc/equations.py and were checked against the exact conical source flow.
Interior points are C+ ∩ C−. The axis point forces θ = 0. Near r = 0 the source looks singular; sinθ/r stays finite and the axis unit process handles it. Unit processes use predictor–corrector with averaged angles, in the style of TM-X-1502 and Zucrow & Hoffman.
Local error on the exact conical source flow falls as h³: 7.1×10⁻⁵, 8.9×10⁻⁶, 1.1×10⁻⁶, 1.4×10⁻⁷ as the step goes from 0.08 down to 0.01.
gamma_at_mach at each point.
Core installs ship PerfectGas and
VariableGammaGas. Teaching presets are frozen engineering estimates, not flight thermochemistry.
Optional extras replace the chamber γ, R, and Tc and then the MOC still runs frozen.
| Preset | γ | R, J/(kg·K) | What the name means |
|---|---|---|---|
| air | 1.40 | 287 | Dry air, the teaching default |
| air_hot | 1.33 | 287 | Hot-air stand-in |
| lox_rp1 | 1.22 | 355 | LOX/RP-1 near stoichiometric, frozen |
| lox_rp1_fuel_rich | 1.25 | 370 | Fuel-rich LOX/RP-1 estimate |
| lox_lh2 | 1.20 | 550 | LOX/LH2 frozen estimate |
| lox_ch4 | 1.20 | 380 | Methalox frozen estimate |
| n2o_htpb | 1.25 | 320 | Hybrid-ish teaching estimate |
| monoprop_h2o2 | 1.30 | 380 | High-test peroxide decomposition |
RocketCEA, Cantera, and CoolProp are extras: pip install "rockmoc[cea]",
[cantera], [coolprop]. Tests skip them when the package is absent.
design_production
One function runs the accuracy stack. Pick kind as mln, ideal, tic, or rao. The rest
is optional and each piece can be turned off.
(γ_c, γ_t, γ_e) such as
(1.25, 1.22, 1.18).prod.bl.corrected is the wall you machine or mesh.mesh_certificate and
monte_carlo_mln quantify grid and γ uncertainty.
Trades and uncertainty
Vacuum CF uses Sutton’s one-dimensional form as the reference. Divergence loss uses the
exit angle, not a tax on how curved the wall looked upstream. When a net exists,
field_integrated_cf integrates the exit plane.
plot_performance_vs_altitude. Not the same contour as the γ = 1.2 altitude figure in the
previous card.
analysis.grid_convergence also reports the grid convergence index.
rockmoc._kernels (Numba if you install [fast]). One ideal design
at N = 40: 16.1 s → 0.6 s.Figure atlas
Click a figure to enlarge it. Filters only hide cards. Nothing here is a stock render: each file
is an output of make_images.py, make_images_extra.py, or
tests/generate_all_graphs.py.
Version 0.7
The axisymmetric numbers moved because the physics was wrong, not because a mesh default changed. Planar NASA results are the same as before.
Compatibility on C− had the wrong sign, and the invariants θ±ν were swapped in every unit process. The axis point was being reached along an upstream C+. The source-flow test above is the check.
θmax is solved so the last corner wave hits the axis at Me. For Me = 3, γ = 1.4 that is 11.7°, not the old 25.9° blend of a partial source correction and a 0.52·νe patch. Exit area is no longer rescaled to hide the miss.
The ideal contour is a characteristic lattice from a Sauer line on the circular-arc throat. It is not an MLN with an arc glued on and a 1.55× stretch.
Rao TOC uses the control-surface ODE, a mass-matched exit, and a Goursat wall. It hits the requested length and exit radius to about 0.1%.
CF integrates momentum and pressure on the exit plane of the net and divides by the field mass flow. The old “exit ray” was synthetic. An MLN reproduces the one-dimensional vacuum value.
The throat initial-value line is Sauer as published. A made-up Hall factor was removed. Hall’s higher-order series is still not implemented.
The deep run is 43 cases, 170 metrics, 12 sources, median error about 0%.
Thermodynamic table errors sit under about 0.02%. The largest intentional disagreement is axisymmetric
θmax versus planar νe/2, which is about 4% by construction and is not a failure. Re-run
with py -3.12 tests/run_deep_validation.py. A separate offline suite,
run_all_validation(), records 50 of 50 checks.
| Anchor | What is checked |
|---|---|
| Anderson, Modern Compressible Flow | ν(M), A/A*, pe/pc at γ = 1.4. M = 2: ν = 26.380°. M = 3: ν = 49.757°, A/A* = 4.2346. |
| NACA Report 1135 (Ames, 1953) | Compressible-flow equations and tables behind those digits. |
| NASA TM-X-1502, Vanco & Goldman, 1968 | Planar sharp-throat MLN. Sample Me = 4.05, γ = 1.36: θmax = νe/2 = 34.96°, ye = A/A*. |
| NACA RM E8J12, Shames & Seashore, 1948 | Two-dimensional sharp-edge design data, five cases from Me = 2 to 4. |
| NASA TM-X-2343, Goldman & Vanco, 1971 | MLN plus boundary-layer displacement. The δ* module follows that spirit. |
| Rao, 1958, Jet Propulsion | Optimum-thrust contour. Length versus the 15° cone, and θn > θe. |
| Lee, 1963 / Vernacchia spike notes | Annular plug. δ = π/2 − νe, tip radius toward zero, positive vacuum thrust. |
| Sutton & Biblarz | Ideal one-dimensional thrust coefficient. |
| Zucrow & Hoffman | Axisymmetric unit processes. The source-term signs above. |
| Huzel & Huang | 15° conical nozzle as the industry baseline. |
| Sauer / Kliegel, via Mendelson TM X-53084 | Transonic throat initial-value line. M > 1 on the line, θ = 0 on the axis. |
| Shapiro | Prandtl–Meyer function and νmax as M → ∞. |
| M | ν reference | ν solver | A/A* reference | A/A* solver |
|---|---|---|---|---|
| 1.5 | 11.905° | 11.90521° | 1.1762 | 1.17617 |
| 2 | 26.380° | 26.37976° | 1.6875 | 1.68750 |
| 3 | 49.757° | 49.75735° | 4.2346 | 4.23457 |
| 4 | 65.785° | 65.78482° | 10.7188 | 10.71875 |
| 5 | 76.920° | 76.92022° | 25.000 | 25.00000 |
From a checkout to a wall file
Python 3.10 or newer. Core dependencies are NumPy and SciPy. Plots need
rockmoc[plot]. Interactive Plotly fields need [viz]. Numba kernels need
[fast].
from rockmoc import DesignSpec, PerfectGas, design_ideal from rockmoc.export import export_dxf, export_blockmesh gas = PerfectGas(gamma=1.2, R=350.0) spec = DesignSpec(gas, throat_radius=0.05, exit_mach=3.0, n_characteristics=60) r = design_ideal(spec, store_net=True) print(r.summary()) r2 = r.with_boundary_layer(pc=5e6, Tc=3300) export_dxf(r2.contour, "nozzle.dxf", units="mm") export_blockmesh(r2.contour, "case/system/blockMeshDict") r.plot(show=False, save_dir="figures") # needs rockmoc[plot]
from rockmoc import ( DesignSpec, PerfectGas, design_mln, design_tic, design_rao, design_conical, design_planar_mln_nasa, design_spike, design_production, compare_nozzle_designs, ) spec = DesignSpec(PerfectGas(1.25, R=350), throat_radius=0.05, exit_mach=3.0) mln = design_mln(spec, store_net=True) tic = design_tic(DesignSpec(PerfectGas(1.25), throat_radius=0.05, exit_mach=3.0, truncate_fraction=0.80)) rao = design_rao(spec, length_fraction=0.80) cone = design_conical(spec, half_angle_deg=15) # NASA TM-X-1502 sample: Me = 4.05, gamma = 1.36 planar = design_planar_mln_nasa(4.05, 1.36, dnu_deg=0.1, throat_half_height=0.05) spike = design_spike(Pc=5e6, Tc=3200, gamma=1.25, molar_mass=22, Re=0.15, expansion_ratio=25, n_points=5000) prod = design_production(spec, kind="mln", apply_bl=True, multi_gamma=(1.25, 1.22, 1.18), pc=5e6, Tc=3200) print(compare_nozzle_designs(spec).to_markdown())
# CLI needs no extra dependency beyond the install rockmoc design --type ideal --Me 3 --gamma 1.2 --Rt 0.05 --export csv,dxf,openfoam --units mm -o out rockmoc design --type rao --area-ratio 25 --length 0.8 --export cad -o out rockmoc run nozzle.toml rockmoc --version
# nozzle.toml [gas] gamma = 1.2 R = 350.0 [design] type = "rao" throat_radius = 0.05 exit_mach = 3.5 length_fraction = 0.85 [export] formats = ["csv", "cad", "dxf", "openfoam"] units = "mm"
from rockmoc import DesignSpec, design_mln, compare_nozzle_designs, explainer spec = DesignSpec.student(Me=3.0, gas_name="lox_rp1", Rt_mm=50) mln = design_mln(spec) print(mln.report(length_unit="mm")) # millimetres and degrees mln.lab_report("lab.md", student_name="Alex", course="AE") print(explainer("mln")) print(compare_nozzle_designs(spec).to_markdown())
CSV keeps x, r, θ, and M. CAD points are a header-less x y z list for a
curve-through-points feature in SolidWorks or CATIA. DXF is R12 so older CAD importers accept
it. OpenFOAM is an axisymmetric wedge blockMeshDict.
Plots can stay on screen or go to a directory: result.plot(show=False, save_dir="out"). Pass a
light theme when the figure has to survive a printed report. SVG is the default vector output.
thermo/ gases and expansion tables. moc/ unit processes, planar NASA, axisymmetric
kernel, transition, ideal, Rao. designers/ the public builders. aerospike/ the plug.
analysis/ boundary layer, throat, atmosphere, uncertainty, comparison. performance/
one-dimensional and field thrust. plot/ the dark figures. student/ reports, presets,
explainers. validate/ the literature suite.
export_blockmesh writes: one wedge, collapsed axis, spline wall, grading toward
the wall. The DXF path is an R12 polyline with wall and axis layers, checked with ezdxf.| Call | Returns |
|---|---|
| design_mln / design_ideal / design_tic | Axisymmetric wall, metrics, optional net |
| design_rao / design_rao_control / design_rao_toc | Thrust-optimised or length-referenced bell |
| design_conical | Straight wall at a fixed half-angle |
| design_planar_mln_nasa | TM-X-1502 planar MLN |
| design_spike, design_spike_truncated, design_spike_with_cowl, design_primary_cowl, truncated_spike_with_base | Plug, truncated plug, base pressure, cowl |
| design_production | Fine mesh, table, throat, optional BL, field CF, altitude |
| compare_nozzle_designs | Family table, including the cone. Markdown, LaTeX, or CSV |
| field_integrated_cf, integrated_performance, off_design_altitude_sweep | Exit-plane thrust, one-dimensional metrics, CF(h) |
| correct_wall_bl, sauer_throat_ivl | δ* wall, transonic start line |
| monte_carlo_mln, mesh_certificate, sensitivity_one_at_a_time | Uncertainty and a mesh pass/fail |
| mesh_convergence_planar, mesh_convergence_axisym, grid_convergence | Mesh studies and Richardson order |
| example_gas, explainer, KNOWN_LIMITS, run_all_validation | Teaching presets, short explainers, the check suite |
result.report(length_unit="mm") prints millimetres and degrees. lab_report writes
a Markdown skeleton with a checklist. explainer("rao") is a short “why this shape” note.
Passing both an exit Mach and an area ratio raises DesignError and shows a working line. Only
one expansion target is legal, because the other two are determined by γ.
Four tutorials under examples/notebooks: a first MLN, a family comparison, altitude
performance, and the NASA planar sample. Worked problems are in docs/student_problems.md.
Symbols are in docs/glossary.md.
Read this before you quote a number
Homework, a first sizing, a family trade at fixed γ, a mesh study, a wall to drop into CAD or a CFD mesher, and a lab figure with the characteristic net still visible. Axisymmetric bells, the planar NASA MLN, and the constant-γ aerospike each state their assumptions in the result metadata.
Hall’s higher-order throat series is not in the code. Sauer is the start, and it is the right start for Rc/Rt ≳ 2. Below that, the ideal solver says it used a uniform near-sonic line. A very short Rao request can fall off the feasible lattice; you get the nearest length plus a warning. The axisymmetric MLN wall angle bumps up a few degrees after the corner before it turns back. A planar ideal can show about a 1° first-order bump after the arc attaches.
Shocks, separation, side loads, film cooling, regenerative-channel conjugate heat transfer, structural thickness, multi-phase or reacting flow, and anything you would sign as flight hardware. Chemistry backends freeze a γ and an R. They do not integrate a reacting nozzle flow. The boundary layer is an integral estimate.
The order of work the docs recommend is: converge the mesh, export the wall, then CFD, thermal, and
structural analysis. KNOWN_LIMITS is importable so a generated report can print this list
instead of forgetting it.