Build a stator blade from a two-phase nozzle#
This example solves a cyclopentane expansion using the stator design conditions in Cioffi et al. [2026], then uses its wall to construct a stator blade profile. You will see how nozzle flow design and blade geometry fit together, and export both the source wall and the fitted blade.
Configure the nozzle and blade#
The nozzle starts at 2.513 bar with a vapor mass fraction of 0.025 and expands toward a back pressure of 0.95 bar using the conventional solver and the curved Sauer initial line. Its throat half-height is 0.01 m. These are the paper’s operating conditions; the current package’s blade parametrization differs from the paper’s control-point construction, as discussed in stator theory.
Blade input |
Value |
Meaning |
|---|---|---|
|
|
Camberline angle at the convergent-section inlet. |
|
|
Camberline angle at the throat. |
|
|
Radius used to construct the trailing edge. |
|
|
Number of repeated profiles drawn by the plotter. |
|
|
Hide the spline control polygon in the figure. |
parametrize_stator_blade takes the nozzle wall in meters and constructs
the blade in millimeters. It adds the convergent geometry and pressure side,
then fits the blade contour with a B-spline. The plot repeats this geometry
at the calculated pitch; n_blades changes only the display.
Run and inspect the script#
Run from the repository root after following the source installation instructions:
poetry run python examples/moc_design/stator_design.py
The code below is included directly from the example. The final display block
is omitted; the script opens the figures with plt.show() during normal runs.
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
import turbo_moc
from turbo_moc.reporting import logging_context
from turbo_moc.geometry import parametrize_stator_blade
# 1. Cyclopentane stator design conditions from Cioffi et al. (2026),
# Table 3. This uses the current package geometry, not an exact reproduction
# of the paper's control-point construction.
# Show the solver's progress on the console (INFO; use level="DEBUG" for details).
with logging_context():
data = turbo_moc.design_nozzle(
fluid_name="Cyclopentane",
P0=2.513e5, # Stagnation pressure [Pa].
Q0=0.025, # Two-phase inlet: 2.5% vapor by mass.
p_back=0.95e5, # Design exit pressure [Pa].
solver="conventional",
use_true_sauer_line=True, # Smooth sonic transition within the mixture.
)
if not data["converged"] or not data["wall_final"]["x"]:
raise RuntimeError("The nozzle design did not converge.")
wall = data["wall_final"]
# Nozzle coordinates are in meters; the stator geometry is returned in mm.
blade = parametrize_stator_blade(
wall["x"],
wall["y"],
metal_angle_in=0.0,
metal_angle_out=70.0,
r_trailing=0.5,
inlet_opening_ratio=1.5,
)
curve = blade["blade_curve"]
if not np.isfinite(np.column_stack((curve["x"], curve["y"]))).all():
raise RuntimeError("The stator design produced invalid coordinates.")
# Plotting helpers return (figure, axes); keep the figures for SVG export.
figures = {
"nozzle_contour": turbo_moc.mpl.plot_nozzle_contour(data)[0],
"characteristics_mesh": turbo_moc.mpl.plot_characteristics_mesh(data)[0],
"axis_properties": turbo_moc.mpl.plot_axis_properties(data)[0],
"wall_properties": turbo_moc.mpl.plot_wall_properties(data)[0],
}
figures["stator_blade"] = turbo_moc.mpl.plot_blade(
blade,
n_blades=2,
show_control_points=False,
)[0]
print(f"Stator pitch: {blade['pitch']:.4f} mm")
# Resolve output relative to this script, regardless of the working directory.
outdir = Path(__file__).resolve().parents[1] / "output" / "stator_design"
outdir.mkdir(parents=True, exist_ok=True)
np.savetxt(
outdir / "nozzle_wall.csv",
np.column_stack((wall["x"], wall["y"])),
delimiter=",",
header="x_m,y_m",
comments="",
)
for name in ("blade_curve", "suction", "pressure", "trailing_edge"):
surface = blade[name]
np.savetxt(
outdir / f"{name}.csv",
np.column_stack((surface["x"], surface["y"])),
delimiter=",",
header="x_mm,y_mm",
comments="",
)
# Save vector figures that can also be embedded in the documentation.
for name, figure in figures.items():
figure.savefig(outdir / f"{name}.svg", bbox_inches="tight")
print(f"Coordinates and figures saved to: {outdir}")
Read the figures#
Two stator profiles repeated at the design pitch, with coordinates in millimeters.#
The spacing between the two profiles forms the passage. The fitted contour
and separate trailing-edge curve show the blade geometry assembled from
the nozzle wall. The script prints the pitch of the resulting profile.
Set show_control_points=True to see the points controlling the spline fit.
Source nozzle used to construct the stator, with coordinates in meters.#
This is the flow-design input to the blade parametrization. Its wall provides the divergent-section suction-side geometry before the blade construction transforms and supplements it.
Characteristic solution for the source nozzle.#
The mesh belongs to the nozzle calculation. The subsequent blade parametrization constructs geometry from that result; it does not compute a new flow solution through the complete stator passage.
Centerline pressure and Mach number for the source nozzle.#
Pressure uses the left axis in bar and Mach number uses the right axis. These curves describe the nozzle expansion that underlies the blade design.
Wall pressure and Mach number for the source nozzle.#
These are the flow states along the original nozzle wall. The horizontal coordinate is the nozzle axial position in meters, before the geometry is transformed into the stator profile.
Use the exported results#
Results are written to examples/output/stator_design/, and the script
prints the absolute path. Five SVG figures accompany these coordinate files:
File |
Contents |
Units |
|---|---|---|
|
Source nozzle wall, from throat to exit. |
m |
|
Fitted B-spline contour. |
mm |
|
Constructed suction-side points. |
mm |
|
Constructed pressure-side points. |
mm |
|
Separate trailing-edge curve. |
mm |
Keep the trailing-edge curve with the fitted contour when using the exported profile. CSV headers explicitly identify the units; the source nozzle wall must be scaled before combining it with blade coordinates.
Try changing metal_angle_out or r_trailing while leaving the nozzle
inputs fixed. The blade geometry and pitch change, while the source nozzle
solution stays the same.