Mean-line designers¶
Classes linking mean-line flow fields to design variables.
A MeanLineDesign turns aerodynamic variables into a MeanLine
flow field and back again. Writing the mean-line design for a new machine
is a single subclass:
class MyStage(MeanLineDesign):
type: ClassVar[str] = "my_stage"
n_row: ClassVar[int] = 2
psi: float
phi: float
Po1: float = 1e5
def forward(self, fluid: ember.fluid.Fluid):
ml = self.allocate(fluid)
...
return ml
def backward(self, ml): ...
The Tutorial works through such a class in full, from an empty file to a designed fan; this page document the class in more detail. The data structure storing the flow field is documented at Mean-line flow field.
The design contract¶
A design declares two class variables, writes two methods, and inherits the rest:
|
The name an input file asks for, under mean_line:. |
|
Number of blade rows the design describes. The mean line it builds has
shape |
Written by the design: build a mean line from the design variables. |
|
Written by the design: recover the design variables from a mean line. |
|
Provided: an empty mean line of the right size and fluid. |
|
Provided: run |
|
Provided: drive unknowns until |
A built-in design defined in the turbigen package is registered
automatically. A new user-created design need not be installed: it is picked up
from any turbigen_plugins directory beside the input file, or in any
directory above it.
In addition to the class variables, type and n_row, a design declares its
design variables as dataclass fields. Values for the
design variables are taken from under the mean_line:
key in the input file, converted to the annotated type and rejected if that
fails. A field with
no default is required, and omitting it from the input file is an error.
A field with a default is optional, and the defaulted value is recorded in
output.yaml for future reproducibility.
Design process¶
Loading an input file converts its mean_line: mapping
into an instance of the class named by type, with defaults filled in.
turbigen then calls design() on that instance,
passing the working fluid specified by fluid:, and that
method runs the design:
backward()inverts it, and the result is compared against the nominal design variables;mass is checked to be conserved through the machine;
the
MeanLineis frozen, so that every stage which follows — annulus, blades, mesher, post-processing — reads it and cannot write to it.
Because the check runs there, a nominal MeanLine
that exists is the requested design, and there is no third state between what
was asked for and what was achieved.
forward() should start with
allocate(), which returns an empty
MeanLine of the right shape and working fluid,
fills it in using the setters documented at Mean-line flow field, and returns it.
It should make no assumption about the equation of state: a design written in
terms of enthalpy and entropy works for a perfect gas and a real one alike.
Forward method builds one line by line from the design equations.
The fluid it is passed is the equation of state named by fluid:, whose interface is documented in ember.fluid.
Thermodynamic properties come
from its two method families: a set_X_Y returns the density and internal
energy pair for the two properties named —
set_P_T(), set_P_s(),
set_P_h(), set_h_s() and the
rest — and a get_Z evaluates one property from that pair, so
get_h(), get_s(),
get_T(), get_a() and so on.
The whole interface is documented in ember.fluid.
backward() goes the other way, returning a plain dict
keyed by field name. A key that is not a field is reported for information but
never checked, so a design is free to return whatever else is worth printing
next to a CFD solution; a field mapped to None declares itself deliberately
not invertible, and is skipped; a field with no key at all warns once, naming
the variable that can no longer be checked or reported.
backward() can run on a nominal design, or a mixed-out CFD
solution — it is the single definition of what each design variable means.
Backward method writes one for the design variables of a fan.
Implicit design problems¶
Where possible, forward() should build the mean line
explicitly from the design variables, but there are often situations where
the mean line cannot be built directly from the natural choice of design variables. solve_for() adjusts unknowns until
the residual calculated through backward() meets the targets asked for, thus solving implicit design problems.
For example, a turbine stage at given stator exit Mach number cannot be built
in one pass: that Mach number depends on the temperature, which depends on the
static state and loss. So the
design puts the whole construction in a closure over the quantities it does not
yet know — here the blade speed and the three swirl velocities — and asks
for the values of those which make backward() report the
design variables asked for:
def build(U, Vt1, Vt2, Vt3_rel):
"""Fill in `ml` for one trial set of unknowns."""
...
self.solve_for(
ml,
build,
unknowns={"U": U0, "Vt1": Vt1_0, "Vt2": Vt2_0, "Vt3_rel": Vt3_rel_0},
targets={
"psi": self.psi,
"Ma2": self.Ma2,
# Repeating stage: the flow leaves as it entered
"Alpha1": "Alpha3",
},
name="stage",
)
build is called as build(**unknowns) and writes into the same
MeanLine every time.
The values in unknowns are initial guesses, which may be scalars or arrays,
and the guess must itself give a valid mean line (but not necessarily one that
meets the targets). During iteration, any calls to backward() that error have a penalty residual applied.
There must be at least as many targets as unknowns, or the solve is refused as
underdetermined. On success the mean line is left rebuilt at the solution, so
forward can return it directly, and the solved unknowns are returned as a
dict for a design that wants to keep them.
A numeric target is a value that key must take; a string target names
another key of backward()’s output that it must equal.
Thermodynamic datum¶
A design that expects high temperatures and pressures should move the fluid dynamic datum before allocating the mean line. For example, if the inlet stagnation conditions are specified as design variable fields:
ml = self.allocate(fluid.change_datum(P_dtm=self.Po1, T_dtm=self.To1))
See Datum state for more detail on this part of the fluid API.
- class turbigen.design.MeanLineDesign¶
Bases:
NodeBase for mean-line designers.
- forward(fluid: Fluid)¶
Return a mean line built from this design’s variables.
Use
allocate()for the empty mean line, fill it in, and return it.
- backward(ml)¶
Return the design variables represented by mean line ml.
- allocate(fluid: Fluid) MeanLine¶
Return an empty mean line of the right size, ready to fill in.
- Parameters:
fluid (ember.fluid.Fluid) – The equation of state, already built from the config. A design never sees the config node, only the fluid object it describes.
- design(fluid: Fluid) MeanLine¶
Return a mean line built from this design.
Checks that the result inverts back to the design variables that asked for it, then freezes it at the earliest opportunity, so that every stage which follows – annulus, blades, mesher, post-processing – reads the mean line and cannot write to it.
- solve_for(ml, build, unknowns, targets, *, rtol=0.0001, max_iter=100, name='')¶
Adjust unknowns until
backward()reports targets.Use this for the parts of a design that cannot be constructed explicitly, where some quantity has to be guessed and then corrected. Everything else should be built directly in
forward.- Parameters:
ml (MeanLine) – The mean line being designed. build writes into it, and
backward()is read from it.build (callable) –
build(**unknowns), constructing ml for one trial set of values. Its return value is ignored.unknowns (dict) – Quantities to solve for, mapped to their initial guesses. Values may be scalars or arrays.
targets (dict) –
Maps a key of
backward()’s output to the value it must take. A number means that key must equal it. A string names another key ofbackward()’s output that it must equal, which is how conditions like a repeating stage are written:targets={"psi": psi, "Alpha1": "Alpha3"}
rtol (float) – Largest acceptable scaled residual. A mean line stores its state as float32, and targets are derived quantities, so residuals below about 1e-6 are not reachable however many iterations are spent: do not tighten this towards float64 tolerances.
max_iter (int) – Iteration limit, in units of Jacobian evaluations.
name (str) – Label for this solve, used in error messages.
- Returns:
The solved values, in the same shapes as unknowns.
- Return type:
- Raises:
DesignError – If the system is underdetermined, or the solve does not converge.