Configuration
The case file: one YAML document that describes an entire study.
The layout follows OpenConcept’s own B738 run scripts, block for block, so that anyone who can
read B738.py can read a cdadt case.
design_variablesB738.pyline 86 reads “Define a bunch of design variables and airplane-specific parameters” and then lists everyac|variable throughdv_comp.add_output_from_dict(...). That block is this one: every design variable, with its value, its units and where the number came from. A variable becomes free for the optimizer by gaining anoptimize:entry with bounds – nothing else moves between sections, and a study that frees one more variable differs by three lines.initial_conditionsset_values(prob, num_nodes)inB738.py, and the first half ofset_mission_profile(prob)inB738_sizing.py: the per-phase speed and vertical-speed schedules, the cruise altitude, the range, and the solver’s starting guesses. Written with the same names those functions use.continuationThe second half of
set_mission_profile: the easy mission converged first, then stepped up. OpenConcept writes it as tworun_model()calls between blocks of assignments; here it is a list of rungs.driver,constraints,objectiveWhat the examples that do optimize –
B738_aerostructural.py,B738_VLM_drag.py– write asadd_constraint/add_objectivecalls. Every form those calls accept is expressible: one-sided, two-sided, equality, with any of OpenMDAO’s scaling and index arguments.
Every section rejects keys it does not recognise. A silently ignored key in a configuration file means the run succeeds and answers a different question than the one that was asked.
- class cdadt.config.BlackBoxConfig(model, num_nodes, options=None)[source]
Bases:
objectWhich sizing analysis to drive, on what grid, and with what options.
- Parameters:
model (str) –
"module:ClassName"naming the analysis.num_nodes (int) – Analysis points per phase.
options (mapping, optional) –
Anything else the analysis declares as an OpenMDAO option, passed straight to its constructor. This is how a case file configures a black box that has choices to make – cdadt’s own
SizingMissionAnalysistakesaerodynamic_loadsthis way, naming which aerodynamics to fly with.Not validated here, deliberately. cdadt does not know what options an arbitrary analysis declares, and OpenMDAO already refuses an option a group does not recognise, by name. A list of allowed keys here would be a second source of truth that could only ever be more wrong than the group itself.
- ALLOWED = ('model', 'num_nodes', 'options')
- classmethod from_section(section)[source]
Build from the
black_boxsection.- Parameters:
section (CaseFileSection)
- Return type:
- property options: dict[str, Any]
Extra options passed to the analysis, as a copy so a caller cannot mutate the case.
- property model: str
The
module:Classstring naming the analysis.
- property num_nodes: int
Analysis points per mission phase.
- class cdadt.config.Bounds(lower=None, upper=None, equals=None)[source]
Bases:
objectThe bound of a constraint: an upper limit, a lower limit, both, or an equality.
Every form
add_constraintaccepts, because every one appears in OpenConcept’s own examples: a one-sided limit, a two-sided band such aslower: 0.01, upper: 1.05on a throttle history, and an equality such asequals: 0.- Parameters:
lower (Any)
upper (Any)
equals (Any)
- property lower: Any
The lower side, if there is one.
- property upper: Any
The upper side, if there is one.
- property equals: Any
The equality value, if this is an equality.
- property is_equality: bool
Whether this is an equality constraint.
- property is_two_sided: bool
Whether both sides are bounded.
- property magnitude: float
A representative size of the bound, used to scale the constraint by default.
- class cdadt.config.CaseFileSection(data, where)[source]
Bases:
objectOne block of a case file, together with the address it lives at.
Every reader in this module goes through one of these. The point is that a section carries its own address, so a message can say where in the file the mistake is without every call site passing that string along by hand – and so a nested block cannot be given the wrong address, because it derives its own from its parent.
A case file is the only thing a user of cdadt writes by hand, and it fails late or not at all if it is read leniently: a misspelled key means the run succeeds and answers a different question. So a section validates on construction, rejects keys it does not recognise, and names the block in every message it raises.
- Parameters:
data (Any) – The parsed block. Must be a mapping; anything else is the error this raises.
where (str) – The address, as it should read in a message –
"solver","design_variables.ac|geom|wing|AR","constraints[0]".
- Raises:
ConfigError – If
datais not a mapping, namingwhereand what was found instead.
Examples
>>> section = CaseFileSection({"value": 124.6, "units": "m**2"}, "design_variables.S_ref") >>> section.require("value") 124.6 >>> section.child_of("units") Traceback (most recent call last): ConfigError: 'design_variables.S_ref.units' must be a mapping; got str.
- property where: str
The address this section lives at, as it reads in a message.
- get(key, default=None)[source]
Return
key’s raw value, ordefaultif the block does not have it.- Parameters:
key (str)
default (Any)
- Return type:
Any
- require(key)[source]
Return
key’s raw value.- Raises:
ConfigError – If the block does not have it, naming the block and the key.
- Parameters:
key (str)
- Return type:
Any
- reject_unknown(allowed)[source]
Raise if the block carries any key outside
allowed.- Raises:
ConfigError – Naming the offending keys and listing what is allowed. A silently ignored key means the study runs and answers a different question than the one that was written.
- Parameters:
allowed (Sequence[str])
- Return type:
None
- number_or_vector(key)[source]
Return
keyas a float, or an array when the file gave a sequence.- Parameters:
key (str)
- Return type:
Any
- child_of(key, default=ABSENT, *, where=None)[source]
Return the block at
keyas a section of its own.- Parameters:
key (str) – Which block to descend into.
default (Any, optional) – Used when the key is absent, so an optional block reads the same as a present one – pass
{}for a block whose keys all have defaults of their own. Omit it to make the block required, in which case an absent key raises naming this section.where (str, optional) – Address to give the child. Defaults to
"<this section>.<key>", which is what a nested block wants. The top-level blocks of a case file pass their own bare name instead: a reader is looking for'objective'in the message, not'the case file.objective'.
- Return type:
- entries()[source]
Iterate
(name, section)for a block whose keys are themselves blocks.This is what
design_variablesandinitial_conditionsare: a mapping from a black-box variable name to its entry.- Return type:
Iterator[tuple[str, CaseFileSection]]
- series(key)[source]
Return the list at
keyas sections addressedkey[0],key[1], …Absent means empty, because a case file that constrains nothing simply omits the block.
- Raises:
ConfigError – If the value is present but is not a list.
- Parameters:
key (str)
- Return type:
tuple[CaseFileSection, …]
- static as_number(value, where)[source]
Return
valueas a float, or raise namingwhereit came from.- Parameters:
value (Any)
where (str)
- Return type:
float
- static as_number_or_vector(value, where)[source]
Return a float, or an array when the case file gave a sequence.
A sequence is how a per-node schedule is written –
[2300, 400]for the endpoints of a climb rate – and how a vector design variable is bounded element by element, which is what OpenConcept’s aerostructural example does for a spanwise thickness distribution.- Parameters:
value (Any)
where (str)
- Return type:
Any
- class cdadt.config.Config(black_box, design_variables, initial_conditions=None, continuation=None, solver=None, driver=None, constraints=(), objective=None, mission_path='mission', path=None)[source]
Bases:
objectA whole case file.
Examples
>>> config = Config.from_yaml("cases/b738.yaml") >>> config.black_box.num_nodes 21 >>> [name for name, spec in config.design_variables.items() if spec.is_free] ['ac|geom|wing|S_ref', ...]
- Parameters:
black_box (BlackBoxConfig)
design_variables (Mapping[str, VariableSpec])
initial_conditions (Mapping[str, VariableSpec] | None)
continuation (ContinuationLadder | None)
solver (SolverConfig | None)
driver (DriverConfig | None)
constraints (Sequence[ConstraintSpec])
objective (ObjectiveSpec | None)
mission_path (str)
path (Path | None)
- ALLOWED = ('black_box', 'solver', 'mission_path', 'design_variables', 'initial_conditions', 'continuation', 'driver', 'constraints', 'objective')
- classmethod from_dict(data, path=None)[source]
Build a configuration from an already-parsed mapping.
This is where the raw document becomes a
CaseFileSection; every reader below this point takes a section and therefore knows its own address in the file.- Parameters:
data (Mapping[str, Any])
path (Path | None)
- Return type:
- classmethod from_section(section, path=None)[source]
Build a configuration from the top-level section of a case file.
- Parameters:
section (CaseFileSection)
path (Path | None)
- Return type:
- property black_box: BlackBoxConfig
Which sizing analysis to drive.
- property solver: SolverConfig
How hard to converge the box.
- property driver: DriverConfig
Which optimizer to use.
- property mission_path: str
Subsystem the mission lives under inside the black box.
- property design_variables: dict[str, VariableSpec]
Every design variable, free or fixed, as
B738.pylists them.
- property initial_conditions_specs: dict[str, VariableSpec]
Every initial condition, as
set_valueswrites them.
- property free_variables: dict[str, VariableSpec]
The variables that carry an
optimize:entry, in declaration order.
- property constraints: tuple[ConstraintSpec, ...]
The constraints, in the order the case file states them.
- property objective: ObjectiveSpec | None
The objective, or
Nonefor a sizing-only case.
- property continuation: ContinuationLadder
The ladder walked before the design mission.
- property path: Path | None
Where the case file was read from.
- property is_optimization: bool
Whether this case declares an objective to drive.
- initial_conditions()[source]
Return everything that is written into the box before it is converged.
Both blocks: the design variables carry values too, and a value is a value whichever block it was declared in. Keeping them in separate sections is about what a reader is being told, not about what the box is sent.
- Return type:
- exception cdadt.config.ConfigError[source]
Bases:
ExceptionRaised when a case file is missing something, has something extra, or has it wrong.
- class cdadt.config.ConstraintSpec(name, bounds, units=None, scaling=None, indices=None, linear=False, regulation='', source='', title='')[source]
Bases:
objectOne constraint on the design.
- Parameters:
name (str) – Either a response name any discipline reports –
takeoff_field_length,climb_throttle– or a raw black-box path. Response names are preferred: they carry their own units.bounds (Bounds) – Any form
add_constraintaccepts.units (str or None, optional) – Units the bounds are stated in.
scaling (Scaling, optional) – Driver scaling. Defaults to the magnitude of the bound, so that constraints of wildly different magnitudes – a field length in thousands of feet and a climb gradient in hundredths of a radian – are comparable to the optimizer.
indices (sequence of int or None, optional) – Which elements of a vector response are constrained.
linear (bool, optional) – Whether the constraint is linear in the design variables. Default
False.regulation (str, optional) – Optional provenance. Supplying both is what puts a constraint in the traceability matrix as a defensible requirement rather than as a bare number; leaving them out is allowed, and the matrix says so.
source (str, optional) – Optional provenance. Supplying both is what puts a constraint in the traceability matrix as a defensible requirement rather than as a bare number; leaving them out is allowed, and the matrix says so.
title (str, optional) – One line naming the constraint in a report. Defaults to the variable name.
- ALLOWED = ('name', 'lower', 'upper', 'equals', 'units', 'indices', 'linear', 'regulation', 'source', 'title', 'ref', 'ref0', 'scaler', 'adder')
- classmethod from_section(section)[source]
Build one constraint entry.
- Parameters:
section (CaseFileSection)
- Return type:
- property name: str
Response name or black-box path being constrained.
- property units: str | None
Units the bounds are stated in.
- property indices: list[int] | None
Which elements of a vector response are constrained.
- property linear: bool
Whether the constraint is linear in the design variables.
- property regulation: str
The regulation this constraint comes from, if it was given one.
- property source: str
Where the number came from, if it was given.
- property title: str
One line naming the constraint in a report.
- property is_traceable: bool
Whether this constraint names both a regulation and a source.
- class cdadt.config.DriverConfig(name='SLSQP', maxiter=50, tol=1e-06, derivative_mode='fwd', options=None)[source]
Bases:
objectWhich optimizer to use, and how hard to drive it.
- Parameters:
name (str)
maxiter (int)
tol (float)
derivative_mode (str)
options (Mapping[str, Any] | None)
- ALLOWED = ('name', 'maxiter', 'tol', 'derivative_mode', 'options')
- MODES = ('auto', 'fwd', 'rev')
- classmethod from_section(section)[source]
Build from the
driversection.- Parameters:
section (CaseFileSection)
- Return type:
- property name: str
Optimizer name.
- property maxiter: int
Optimizer iteration limit.
- property tol: float
Optimizer convergence tolerance.
- property derivative_mode: str
Total-derivative mode.
- property options: Mapping[str, Any]
Extra optimizer settings, passed through to pyOptSparse.
- class cdadt.config.ObjectiveSpec(name, units=None, scaling=None, sense='minimize', index=None)[source]
Bases:
objectWhat the optimizer minimizes or maximizes.
- Parameters:
name (str) – A response name or a raw black-box path.
units (str or None, optional) – Units to optimize in.
scaling (Scaling, optional) – Driver scaling. It matters: SciPy’s SLSQP takes its finite-difference step and its convergence test on the scaled objective, so an objective of order 1e4 is effectively converged before it starts.
sense (str, optional) –
"minimize"or"maximize". Default"minimize".index (int or None, optional) – Which element of a vector output is the objective.
- ALLOWED = ('name', 'units', 'sense', 'index', 'ref', 'ref0', 'scaler', 'adder')
- SENSES = ('minimize', 'maximize')
- classmethod from_section(section)[source]
Build from the
objectivesection.- Parameters:
section (CaseFileSection)
- Return type:
- property name: str
Response name or black-box path being optimized.
- property units: str | None
Units the objective is optimized in, if the case file fixed them.
- property sense: str
"minimize"or"maximize".
- property index: int | None
Which element of a vector output is the objective.
- property direction: float
Return
+1to minimize or-1to maximize.
- as_kwargs(units)[source]
Return the keyword arguments OpenMDAO’s
add_objectivetakes.The sense is folded into the scaling, because OpenMDAO drivers only minimize. Both spellings are handled, so a case file may scale with
refor withscalerand still saysense: maximize.- Parameters:
units (str | None)
- Return type:
dict[str, Any]
- class cdadt.config.OptimizeSpec(bounds, scaling=None, indices=None)[source]
Bases:
objectThe
optimize:entry that frees a design variable for the driver.- Parameters:
bounds (Bounds) – The interval the variable may move in.
scaling (Scaling, optional) – Driver scaling. Defaults to
refat the larger bound magnitude, which is nearly always the right order and is the difference between an optimizer that converges and one that stalls when a wing area in square metres shares a design space with an aspect ratio.indices (sequence of int or None, optional) – Which elements of a vector variable are free.
Nonefrees all of them.
- ALLOWED = ('lower', 'upper', 'indices', 'ref', 'ref0', 'scaler', 'adder')
- classmethod from_section(section)[source]
Build one
optimize:entry.- Parameters:
section (CaseFileSection)
- Return type:
- property lower: Any
Lower bound.
- property upper: Any
Upper bound.
- property indices: list[int] | None
Which elements are free, or
Nonefor all of them.
- property ref: float
The reference this variable is scaled by, defaulted from its bounds if not given.
- class cdadt.config.Scaling(ref=None, ref0=None, scaler=None, adder=None)[source]
Bases:
objectHow a quantity is scaled for the driver:
ref/ref0orscaler/adder.OpenMDAO accepts either pair and refuses both, because they are two spellings of the same affine map. cdadt refuses both here instead, where the offending key can be named.
- Parameters:
ref (float | None)
ref0 (float | None)
scaler (float | None)
adder (float | None)
- ALLOWED = ('ref', 'ref0', 'scaler', 'adder')
- classmethod from_section(section)[source]
Build from whichever of the four keys an entry carries.
Scaling shares its block with whatever else the entry declares – bounds, indices, a regulation – so this reads the four keys it owns and leaves the rest to the caller.
- Parameters:
section (CaseFileSection)
- Return type:
- property ref: float | None
Value the driver should see as one, if given.
- property ref0: float | None
Value the driver should see as zero, if given.
- property scaler: float | None
Multiplicative scaling, if given.
- property adder: float | None
Additive offset, if given.
- property is_empty: bool
Whether no scaling at all was given.
- class cdadt.config.SolverConfig(maxiter=20, atol=1e-09, rtol=1e-09, iprint=-1, err_on_non_converge=True)[source]
Bases:
objectHow hard to converge the black box. Defaults are OpenConcept’s own sizing settings.
- Parameters:
maxiter (int)
atol (float)
rtol (float)
iprint (int)
err_on_non_converge (bool)
- ALLOWED = ('maxiter', 'atol', 'rtol', 'iprint', 'err_on_non_converge')
- classmethod from_section(section)[source]
Build from the
solversection, which may be empty.- Parameters:
section (CaseFileSection)
- Return type:
- settings()[source]
Return the
SolverSettingsthis section describes.- Return type:
- property maxiter: int
Newton iteration limit.
- class cdadt.config.VariableSpec(name, value, units=None, source='', optimize=None)[source]
Bases:
objectOne entry of
design_variablesorinitial_conditions.- Parameters:
name (str) – Name of a variable the black box publishes.
value (float or sequence of float) – Its value. A sequence is a per-node schedule: two entries are interpolated across the phase exactly as
np.linspacedoes in OpenConcept’s own run script.units (str or None, optional) – Units of the value.
source (str, optional) – Where the number came from. A number with no provenance is indistinguishable from a guess in the report that quotes it.
optimize (OptimizeSpec or None, optional) – Present makes this variable a design variable the driver may move.
- ALLOWED = ('value', 'units', 'source', 'optimize')
- classmethod from_section(name, section)[source]
Build one entry.
- Parameters:
name (str)
section (CaseFileSection)
- Return type:
- property name: str
Name of the black-box variable.
- property units: str | None
Units of the value.
- property source: str
Where the number came from.
- property optimize: OptimizeSpec | None
The
optimize:entry, if this variable is free.
- property is_free: bool
Whether the driver may move this variable.