Disciplines
Engineering disciplines, each a class that owns one domain’s slice of the black box.
Wing, fuselage, nacelles, gear |
|
Span efficiency, airfoil, flaps, envelope |
|
Engine rating and count, installation weight |
|
Empennage shape and tail areas |
|
Load-carrying airframe mass breakdown |
|
Payload, cabin, and the closed weight rollup |
|
The mission, and the field, climb and fuel results |
Every one of them is a class with encapsulated state, reached only through properties and
methods that validate what they are given. None of them computes physics: the physics is inside
the black box. See cdadt.disciplines.base for what that division means and
Architecture for why it is drawn there.
AIRCRAFT_DISCIPLINES is the set that describes the airframe and is composed by
Aircraft. Performance is not in it, because the mission is not a
property of the aircraft.
The classes below are re-exported from this package for convenience. Each is indexed once, under the module that defines it, so that every cross-reference resolves to a single target.
- cdadt.disciplines.AIRCRAFT_DISCIPLINES: tuple[type[Discipline], ...] = (<class 'cdadt.disciplines.geometry.Geometry'>, <class 'cdadt.disciplines.aerodynamics.Aerodynamics'>, <class 'cdadt.disciplines.propulsion.Propulsion'>, <class 'cdadt.disciplines.stability.Stability'>, <class 'cdadt.disciplines.structures.Structures'>, <class 'cdadt.disciplines.weights.Weights'>)
The disciplines that describe the airframe, in the order they are reported.
- class cdadt.disciplines.Aerodynamics[source]
Bases:
DisciplineSpan efficiency, the airfoil, the flap setting and the speed envelope.
Owns the
ac|aero|parameters: the Oswald span efficiency factor that closes the drag polar, the airfoil section maximum lift coefficient and the takeoff flap deflection that together set the two maximum lift coefficients, the maximum operating Mach number, and the landing stall speed.This class writes the inputs to whatever drag model the study installed and reads the two lift coefficients back out. It contains no aerodynamic model of its own and no aerodynamic constant – that invariant still holds, and it is what keeps a discipline an interface rather than a second place physics can live.
What has changed underneath it is which model those inputs reach. On the default path every drag number is computed inside the black box by OpenConcept’s component-by-component parasite buildup and its parabolic polar. A study may instead name one of cdadt’s own – see
cdadt.models.loadsand Supplying your own aerodynamics – in which case the induced drag is computed by a vortex lattice built on this wing andac|aero|polar|ebecomes an unused assumption, because the lattice reports the span efficiency rather than reading it.Either way the ownership is unchanged: this class owns the
ac|aero|parameters, and the model that consumes them is chosen by the case file, not by this discipline.Notes
The drag history itself is per-phase and vector-valued, so it is not reported here. It is read directly from the box by name –
mission.cruise.dragand its siblings – when a run is plotted or exported.- discipline_name: ClassVar[str] = 'aerodynamics'
- description: ClassVar[str] = 'Span efficiency, airfoil and flap settings, speed envelope'
One line saying what the domain covers, used in generated documentation and reports.
- owned_patterns: ClassVar[tuple[str, ...]] = ('ac|aero|*',)
Shell-style patterns matching the black-box variables this discipline may set.
- reported: ClassVar[tuple[Response, ...]] = (Response('CLmax_cruise', 'ac|aero|CLmax_cruise', None), Response('CLmax_takeoff', 'ac|aero|CLmax_TO', None))
Quantities this discipline reads back out of the black box.
- class cdadt.disciplines.Discipline[source]
Bases:
ABCAbstract base for an engineering domain’s slice of the black-box interface.
Subclasses declare three class attributes and add nothing else unless their domain needs it:
discipline_nameShort identifier used as the key in reports and in the results object.
owned_patternsShell-style patterns matching the black-box variables this discipline may set. A discipline that sets nothing declares an empty tuple – see
Structures, which reports a weight breakdown the box computes but has no input of its own.reportedThe
Responseobjects this discipline reads back.
Examples
>>> from cdadt.disciplines import Aerodynamics >>> aero = Aerodynamics() >>> aero.add(Parameter("ac|aero|polar|e", 0.801, source="B738 example estimate")) >>> aero.value("ac|aero|polar|e") 0.801 >>> aero.owns("ac|geom|wing|S_ref") False
- abstract property discipline_name: str
Short identifier for the discipline. Unique across the disciplines of one aircraft.
Abstract because it is the one thing every domain must supply and none can inherit: it is the key the discipline is reported and looked up under. Subclasses satisfy it by assigning a plain class attribute –
discipline_name = "geometry"– which is what makes it readable on the class as well as on an instance, and that in turn is what letsAircraftcompose discipline classes rather than instances.
- description: ClassVar[str] = ''
One line saying what the domain covers, used in generated documentation and reports.
- owned_patterns: ClassVar[tuple[str, ...]] = ()
Shell-style patterns matching the black-box variables this discipline may set.
- reported: ClassVar[tuple[Response, ...]] = ()
Quantities this discipline reads back out of the black box.
- classmethod owns(variable)[source]
Return whether
variablebelongs to this discipline.- Parameters:
variable (str) – Name of a black-box variable, e.g.
"ac|geom|wing|S_ref".- Returns:
bool –
Trueif any ofowned_patternsmatches.- Return type:
bool
- add(parameter)[source]
Take ownership of a parameter.
- Parameters:
parameter (Parameter) – The parameter to hold.
- Raises:
DisciplineError – If the parameter is not owned by this discipline, or is already held. Both are construction errors: the first means the ownership map has a gap, the second means the same variable was declared twice in a case file.
- Return type:
None
- property parameters: Mapping[str, Parameter]
Return the parameters this discipline holds, as a copy.
A copy rather than the live dictionary: the supported way to change a value is
set(), which validates it, and the supported way to add one isadd(), which checks ownership.
- parameter(name)[source]
Return one held parameter.
- Raises:
KeyError – If this discipline does not hold it.
- Parameters:
name (str)
- Return type:
- value(name)[source]
Return the value of one held parameter, in its own units.
- Parameters:
name (str)
- Return type:
float
- units(name)[source]
Return the units of one held parameter.
- Parameters:
name (str)
- Return type:
str | None
- set(name, value)[source]
Set the value of one held parameter, in its existing units.
- Raises:
KeyError – If this discipline does not hold it. There is no implicit creation: a typo that created a new variable would produce a parameter nothing in the box reads.
- Parameters:
name (str)
value (float)
- Return type:
None
- apply(box)[source]
Write every held parameter into the black box.
- Parameters:
box (OpenConceptSizingBox) – Anything with
set(name, value, units).- Return type:
None
- collect(box)[source]
Read every reported response back out of the black box.
- Parameters:
box (OpenConceptSizingBox) – Anything with
get(path, units)andhas(path).- Returns:
dict –
response name -> value. A scalar response is returned as a float, a vector one as an array. Optional responses the box does not publish are omitted; required ones raise.- Raises:
KeyError – If a required response is absent from this black box. That means the box is not the model this discipline was written against, and quietly returning a partial result would let a report claim a quantity it never read.
- Return type:
dict[str, Any]
- missing(box)[source]
Return the names of optional responses this black box does not publish.
- Parameters:
box (Any)
- Return type:
tuple[str, …]
- exception cdadt.disciplines.DisciplineError[source]
Bases:
ExceptionRaised when a discipline is asked to hold something that is not its own.
- class cdadt.disciplines.Geometry[source]
Bases:
DisciplineWing planform, fuselage, nacelles and landing gear.
Owns the shape parameters every other domain is a function of: the wing planform that sets both the lift and the wing weight, the fuselage that sets both the wetted area and the tail lever arm, the nacelles, and the gear.
The empennage is not here. The black box sizes the horizontal and vertical stabilizers from tail volume coefficients rather than taking their areas as inputs, so their shape parameters and their computed areas belong together in
Stability.This class computes nothing. The mean aerodynamic chord and the wetted areas it reports are computed inside the black box; this discipline records that they are geometry, where to read them, and in what units.
Examples
>>> Geometry.owns("ac|geom|wing|S_ref") True >>> Geometry.owns("ac|geom|hstab|AR") False
- discipline_name: ClassVar[str] = 'geometry'
- description: ClassVar[str] = 'Wing planform, fuselage, nacelles and landing gear'
One line saying what the domain covers, used in generated documentation and reports.
- owned_patterns: ClassVar[tuple[str, ...]] = ('ac|geom|wing|*', 'ac|geom|fuselage|*', 'ac|geom|nacelle|*', 'ac|geom|maingear|*', 'ac|geom|nosegear|*')
Shell-style patterns matching the black-box variables this discipline may set.
- reported: ClassVar[tuple[Response, ...]] = (Response('wing_MAC', 'ac|geom|wing|MAC', 'm'), Response('fuselage_wetted_area', 'ac|geom|fuselage|S_wet', 'm**2'), Response('nacelle_wetted_area', 'ac|geom|nacelle|S_wet', 'm**2'), Response('tail_lever_arm', 'tail_lever_arm_estimate.c4_to_wing_c4', 'm'), Response('wing_span', 'wing_span.span', 'm'))
Quantities this discipline reads back out of the black box.
- class cdadt.disciplines.Performance(conditions, ladder=None)[source]
Bases:
DisciplineThe mission the aircraft is sized against, and the results of flying it.
The one discipline whose encapsulated state is not a bag of scalars. What performance owns is the pair OpenConcept’s run scripts write by hand: the initial conditions (
set_values(prob, num_nodes)) and the continuation ladder that converges the hard ones (the tworun_model()calls insideset_mission_profile).What it reports is everything the mission produces, and it is what the certification constraints are written against: balanced field length and its abort distance, the decision and takeoff safety speeds, the engine-out climb gradient, block fuel and fuel with reserves, the range actually flown, and the throttle history of every phase.
- Parameters:
conditions (InitialConditions) – Everything written into the box before it is converged.
ladder (ContinuationLadder, optional) – The rungs walked first. Empty attempts the design mission directly.
- discipline_name: ClassVar[str] = 'performance'
- description: ClassVar[str] = 'The mission flown, and the field, climb and fuel results it produces'
One line saying what the domain covers, used in generated documentation and reports.
- owned_patterns: ClassVar[tuple[str, ...]] = ('mission.*',)
Performance owns the mission-level values. They live in the initial conditions rather than as loose parameters, so
add()refuses them; the pattern is declared so that the ownership check over the box’s settable variables comes out total.
- reported: ClassVar[tuple[Response, ...]] = (Response('block_fuel', 'mission.descent.fuel_burn_integ.fuel_burn_final', 'kg'), Response('total_fuel', 'mission.loiter.fuel_burn_integ.fuel_burn_final', 'kg'), Response('takeoff_field_length', 'mission.bfl.distance_continue', 'ft'), Response('abort_distance', 'mission.bfl.distance_abort', 'ft'), Response('V1', 'mission.takeoff|v1', 'kn'), Response('V2', 'mission.engineoutclimb.takeoff|v2', 'kn'), Response('engine_out_climb_gradient', 'mission.engineoutclimb.gamma', 'rad'), Response('mission_range_flown', 'mission.descent.ode_integ_phase.range_final', 'nmi'), Response('reserve_range_flown', 'mission.resrange.reserverange', 'nmi'), Response('climb_throttle', 'mission.climb.throttle', None), Response('cruise_throttle', 'mission.cruise.throttle', None), Response('descent_throttle', 'mission.descent.throttle', None), Response('climb_duration', 'mission.climb.duration', 'min'), Response('cruise_duration', 'mission.cruise.duration', 'min'), Response('descent_duration', 'mission.descent.duration', 'min'), Response('loiter_duration', 'mission.loiter.duration', 'min'))
Quantities this discipline reads back out of the black box.
- property conditions: InitialConditions
Everything written into the box before it is converged.
- property ladder: ContinuationLadder
The rungs walked before the design mission.
- add(parameter)[source]
Reject loose parameters, with an explanation.
- Raises:
DisciplineError – Always. Mission-level values belong in
initial_conditions, alongside the schedules and the ladder that make them reachable.- Parameters:
parameter (Parameter)
- Return type:
None
- apply(box)[source]
Write the design conditions into the black box, without converging it.
- Parameters:
box (Any)
- Return type:
None
- converge(box, verbose=False)[source]
Walk the continuation ladder and converge the design mission.
- Parameters:
box (Any)
verbose (bool)
- Return type:
None
- class cdadt.disciplines.Propulsion[source]
Bases:
DisciplineEngine rating and engine count, and the propulsion installation weights.
Owns the two
ac|propulsion|parameters. The rating is the sea-level static thrust the engine deck inside the black box is scaled to – OpenConcept calls this a rubberized engine, meaning thrust and fuel flow are scaled from a fixed CFM56 deck rather than recomputed by a cycle analysis. Engine count multiplies both, and is what makes the engine-out cases meaningful: the box multiplies by the number of active engines, which is one fewer in the rejected-takeoff and engine-out climb conditions.Thrust, fuel flow and throttle at every node are computed inside the box and belong to the mission, so they are reported by
Performance. What this discipline reports is the weight of the propulsion installation, which is what the rating and count size.Notes
Because the engine is a scaled deck, a very large change in rating extrapolates the surrogate rather than redesigning an engine. Validation records that limit.
- discipline_name: ClassVar[str] = 'propulsion'
- description: ClassVar[str] = 'Engine rating and count, and the propulsion installation weights'
One line saying what the domain covers, used in generated documentation and reports.
- owned_patterns: ClassVar[tuple[str, ...]] = ('ac|propulsion|*',)
Shell-style patterns matching the black-box variables this discipline may set.
- reported: ClassVar[tuple[Response, ...]] = (Response('engine_weight', 'empty_weight.single_engine.W_engine', 'kg'), Response('engines_weight', 'empty_weight.W_engines', 'kg'), Response('thrust_reverser_weight', 'empty_weight.W_thrust_rev', 'kg'), Response('engine_controls_weight', 'empty_weight.W_eng_control', 'kg'), Response('engine_starter_weight', 'empty_weight.W_eng_start', 'kg'), Response('fuel_system_weight', 'empty_weight.W_fuelsystem', 'kg'))
Quantities this discipline reads back out of the black box.
- class cdadt.disciplines.Stability[source]
Bases:
DisciplineHorizontal and vertical stabilizer shape, and the areas the box sizes from it.
Owns the empennage shape parameters – aspect ratio, quarter-chord sweep, taper and thickness ratio for both surfaces – and reports the two reference areas.
The areas are outputs, not inputs, which is why the empennage is a discipline of its own rather than part of
Geometry. The black box sizes both surfaces by the tail volume coefficient method: the horizontal tail from wing area, mean aerodynamic chord and lever arm, the vertical tail from wing area, span and lever arm. The volume coefficients themselves (1.00 horizontal, 0.09 vertical, Raymer Table 6.4 for jet transports) are options of the components inside the box, not inputs to it, so they cannot be set from cdadt and are documented as fixed by the box. See The OpenConcept black box.That also means this discipline models no stability physics whatsoever – no static margin, no centre of gravity, no minimum control speed. It is named for the domain the tail volume coefficient method belongs to, and Validation states the gap explicitly.
- discipline_name: ClassVar[str] = 'stability'
- description: ClassVar[str] = 'Empennage shape, and the tail areas the box sizes by volume coefficient'
One line saying what the domain covers, used in generated documentation and reports.
- owned_patterns: ClassVar[tuple[str, ...]] = ('ac|geom|hstab|*', 'ac|geom|vstab|*')
Shell-style patterns matching the black-box variables this discipline may set.
- reported: ClassVar[tuple[Response, ...]] = (Response('hstab_area', 'ac|geom|hstab|S_ref', 'm**2'), Response('vstab_area', 'ac|geom|vstab|S_ref', 'm**2'))
Quantities this discipline reads back out of the black box.
- class cdadt.disciplines.Structures[source]
Bases:
DisciplineMass of the load-carrying airframe, broken down by component.
This discipline owns no inputs, and that is the honest answer. Primary structure is sized inside the black box by empirical correlations that read geometry, maximum takeoff weight and maximum landing weight – all of which are owned by
Geometry,StabilityandWeights, or computed by the box itself. There is no structural parameter left for cdadt to set: no material, no load factor, no spar layout. Inventing one here so that the class had state would be a parameter nothing reads.What it does own is the reporting of structure. A structural mass that cannot be broken down cannot be argued with, and a design report that gives only operating empty weight hides where the weight went. This class names the seven component masses the box publishes, where each lives, and in what units to read it.
All of them are marked optional because they exist in the box only as long as the box’s empty-weight model publishes them. A different sizing model, named in the case file, may not; the run then reports them as unavailable rather than failing or, worse, quietly dropping them.
Notes
The ultimate load factor, the structural allowance and every other constant in those correlations are options of components inside the box. They are not settable from cdadt and are documented as fixed by the box in The OpenConcept black box.
- discipline_name: ClassVar[str] = 'structures'
- description: ClassVar[str] = 'Load-carrying airframe mass, component by component'
One line saying what the domain covers, used in generated documentation and reports.
- owned_patterns: ClassVar[tuple[str, ...]] = ()
Structure has no settable parameter of its own; see the class docstring.
- reported: ClassVar[tuple[Response, ...]] = (Response('structure_weight', 'empty_weight.W_structure', 'kg'), Response('wing_weight', 'empty_weight.W_wing', 'kg'), Response('hstab_weight', 'empty_weight.W_hstab', 'kg'), Response('vstab_weight', 'empty_weight.W_vstab', 'kg'), Response('fuselage_weight', 'empty_weight.W_fuselage', 'kg'), Response('main_gear_weight', 'empty_weight.W_mlg', 'kg'), Response('nose_gear_weight', 'empty_weight.W_nlg', 'kg'), Response('nacelle_weight', 'empty_weight.W_nacelle', 'kg'))
Quantities this discipline reads back out of the black box.
- add(parameter)[source]
Reject every parameter, with an explanation.
- Raises:
DisciplineError – Always. The base class would raise anyway, since nothing matches an empty ownership pattern, but the generic message would read as a gap in the ownership map rather than as a deliberate property of this domain.
- Parameters:
parameter (Parameter)
- Return type:
None
- class cdadt.disciplines.Weights[source]
Bases:
DisciplinePayload, occupancy and cabin pressure, and the three weights that close the sizing loop.
Owns the
ac|weights|parameters and the cabin definition – passenger capacity, flight deck and cabin crew, cabin pressure. Those are weight inputs rather than geometry: inside the black box they drive furnishings, oxygen, air conditioning and pressurization, and the pressurized cabin volume that the fuselage weight correlation reads.Reports the rollup the whole model exists to close:
\[\mathrm{MTOW} = \mathrm{OEW}(\mathrm{MTOW}, \text{geometry}) + W_\mathrm{payload} + W_\mathrm{fuel}(\mathrm{MTOW}, \text{mission})\]A heavier aircraft burns more fuel and more fuel makes it heavier. The black box drives that residual to zero with its own Newton solver, at the same time as the mission’s implicit states – phase durations, throttle settings and the decision speed. cdadt supplies the starting guess and reads the answer; the closure itself is the box’s.
Maximum landing weight is reported because it sizes the landing gear, not because it was chosen: the box takes it as 80% of maximum takeoff weight. That is an assumption of the box, not of cdadt, and is recorded in The OpenConcept black box.
- discipline_name: ClassVar[str] = 'weights'
- description: ClassVar[str] = 'Payload, occupancy and cabin, and the closed weight rollup'
One line saying what the domain covers, used in generated documentation and reports.
- owned_patterns: ClassVar[tuple[str, ...]] = ('ac|weights|*', 'ac|num_*', 'ac|cabin_pressure')
Shell-style patterns matching the black-box variables this discipline may set.
- reported: ClassVar[tuple[Response, ...]] = (Response('MTOW', 'ac|weights|MTOW', 'kg'), Response('OEW', 'ac|weights|OEW', 'kg'), Response('MLW', 'ac|weights|MLW', 'kg'), Response('payload', 'ac|weights|W_payload', 'kg'), Response('furnishings_weight', 'empty_weight.W_furnishings', 'kg'), Response('avionics_weight', 'empty_weight.W_avionics', 'kg'), Response('electrical_weight', 'empty_weight.W_electrical', 'kg'), Response('apu_weight', 'empty_weight.W_APU', 'kg'), Response('oxygen_weight', 'empty_weight.W_oxygen', 'kg'), Response('environmental_weight', 'empty_weight.W_ac_pressurize_antiice', 'kg'), Response('flight_controls_weight', 'empty_weight.W_flight_controls', 'kg'), Response('pressurized_volume', 'empty_weight.cabin_volume.V_pressurized', 'm**3'))
Quantities this discipline reads back out of the black box.
The discipline abstraction.
A discipline is one engineering domain – geometry, aerodynamics, propulsion, stability, structures, weights, performance – modeled as a class. Each one owns two things and nothing else:
The parameters it puts into the black box. Which ac| variables belong to its domain,
their values, their units and their provenance. Ownership is declared as a pattern and is
checked to be total and disjoint: every settable variable the black box publishes is owned by
exactly one discipline, and no variable is owned by two. That check runs against the live model,
not against a list written down here, so adding a variable to the black box surfaces as a
failing test rather than as a silently unowned input.
The responses it reads back out. Which quantities the box produces belong to its domain, where they live inside the box, and in what units to read them.
What a discipline is not. It does not compute physics. It has no compute, no residual
and no partial derivative, because the aerodynamics, the weight correlations, the engine deck,
the tail sizing and the whole mission are computed inside the black box by OpenConcept, used
as published. A cdadt discipline is the encapsulated, validated interface to its slice of that
box – the object that knows what its domain is allowed to set, what its domain reports, and
nothing about how either is calculated. Any documentation that implies otherwise is wrong.
There is no global state anywhere in this package. Everything a discipline holds is instance state reached through properties, which is what lets an optimizer hold several aircraft at once and what keeps a discipline testable without building a model.
- class cdadt.disciplines.base.Discipline[source]
Bases:
ABCAbstract base for an engineering domain’s slice of the black-box interface.
Subclasses declare three class attributes and add nothing else unless their domain needs it:
discipline_nameShort identifier used as the key in reports and in the results object.
owned_patternsShell-style patterns matching the black-box variables this discipline may set. A discipline that sets nothing declares an empty tuple – see
Structures, which reports a weight breakdown the box computes but has no input of its own.reportedThe
Responseobjects this discipline reads back.
Examples
>>> from cdadt.disciplines import Aerodynamics >>> aero = Aerodynamics() >>> aero.add(Parameter("ac|aero|polar|e", 0.801, source="B738 example estimate")) >>> aero.value("ac|aero|polar|e") 0.801 >>> aero.owns("ac|geom|wing|S_ref") False
- abstract property discipline_name: str
Short identifier for the discipline. Unique across the disciplines of one aircraft.
Abstract because it is the one thing every domain must supply and none can inherit: it is the key the discipline is reported and looked up under. Subclasses satisfy it by assigning a plain class attribute –
discipline_name = "geometry"– which is what makes it readable on the class as well as on an instance, and that in turn is what letsAircraftcompose discipline classes rather than instances.
- description: ClassVar[str] = ''
One line saying what the domain covers, used in generated documentation and reports.
- owned_patterns: ClassVar[tuple[str, ...]] = ()
Shell-style patterns matching the black-box variables this discipline may set.
- reported: ClassVar[tuple[Response, ...]] = ()
Quantities this discipline reads back out of the black box.
- classmethod owns(variable)[source]
Return whether
variablebelongs to this discipline.- Parameters:
variable (str) – Name of a black-box variable, e.g.
"ac|geom|wing|S_ref".- Returns:
bool –
Trueif any ofowned_patternsmatches.- Return type:
bool
- add(parameter)[source]
Take ownership of a parameter.
- Parameters:
parameter (Parameter) – The parameter to hold.
- Raises:
DisciplineError – If the parameter is not owned by this discipline, or is already held. Both are construction errors: the first means the ownership map has a gap, the second means the same variable was declared twice in a case file.
- Return type:
None
- property parameters: Mapping[str, Parameter]
Return the parameters this discipline holds, as a copy.
A copy rather than the live dictionary: the supported way to change a value is
set(), which validates it, and the supported way to add one isadd(), which checks ownership.
- parameter(name)[source]
Return one held parameter.
- Raises:
KeyError – If this discipline does not hold it.
- Parameters:
name (str)
- Return type:
- value(name)[source]
Return the value of one held parameter, in its own units.
- Parameters:
name (str)
- Return type:
float
- units(name)[source]
Return the units of one held parameter.
- Parameters:
name (str)
- Return type:
str | None
- set(name, value)[source]
Set the value of one held parameter, in its existing units.
- Raises:
KeyError – If this discipline does not hold it. There is no implicit creation: a typo that created a new variable would produce a parameter nothing in the box reads.
- Parameters:
name (str)
value (float)
- Return type:
None
- apply(box)[source]
Write every held parameter into the black box.
- Parameters:
box (OpenConceptSizingBox) – Anything with
set(name, value, units).- Return type:
None
- collect(box)[source]
Read every reported response back out of the black box.
- Parameters:
box (OpenConceptSizingBox) – Anything with
get(path, units)andhas(path).- Returns:
dict –
response name -> value. A scalar response is returned as a float, a vector one as an array. Optional responses the box does not publish are omitted; required ones raise.- Raises:
KeyError – If a required response is absent from this black box. That means the box is not the model this discipline was written against, and quietly returning a partial result would let a report claim a quantity it never read.
- Return type:
dict[str, Any]
- exception cdadt.disciplines.base.DisciplineError[source]
Bases:
ExceptionRaised when a discipline is asked to hold something that is not its own.
The geometry discipline: the shape of the airframe, minus the empennage.
- class cdadt.disciplines.geometry.Geometry[source]
Bases:
DisciplineWing planform, fuselage, nacelles and landing gear.
Owns the shape parameters every other domain is a function of: the wing planform that sets both the lift and the wing weight, the fuselage that sets both the wetted area and the tail lever arm, the nacelles, and the gear.
The empennage is not here. The black box sizes the horizontal and vertical stabilizers from tail volume coefficients rather than taking their areas as inputs, so their shape parameters and their computed areas belong together in
Stability.This class computes nothing. The mean aerodynamic chord and the wetted areas it reports are computed inside the black box; this discipline records that they are geometry, where to read them, and in what units.
Examples
>>> Geometry.owns("ac|geom|wing|S_ref") True >>> Geometry.owns("ac|geom|hstab|AR") False
- discipline_name: ClassVar[str] = 'geometry'
- description: ClassVar[str] = 'Wing planform, fuselage, nacelles and landing gear'
One line saying what the domain covers, used in generated documentation and reports.
- owned_patterns: ClassVar[tuple[str, ...]] = ('ac|geom|wing|*', 'ac|geom|fuselage|*', 'ac|geom|nacelle|*', 'ac|geom|maingear|*', 'ac|geom|nosegear|*')
Shell-style patterns matching the black-box variables this discipline may set.
- reported: ClassVar[tuple[Response, ...]] = (Response('wing_MAC', 'ac|geom|wing|MAC', 'm'), Response('fuselage_wetted_area', 'ac|geom|fuselage|S_wet', 'm**2'), Response('nacelle_wetted_area', 'ac|geom|nacelle|S_wet', 'm**2'), Response('tail_lever_arm', 'tail_lever_arm_estimate.c4_to_wing_c4', 'm'), Response('wing_span', 'wing_span.span', 'm'))
Quantities this discipline reads back out of the black box.
The aerodynamics discipline: the drag and high-lift parameters, and what they produce.
- class cdadt.disciplines.aerodynamics.Aerodynamics[source]
Bases:
DisciplineSpan efficiency, the airfoil, the flap setting and the speed envelope.
Owns the
ac|aero|parameters: the Oswald span efficiency factor that closes the drag polar, the airfoil section maximum lift coefficient and the takeoff flap deflection that together set the two maximum lift coefficients, the maximum operating Mach number, and the landing stall speed.This class writes the inputs to whatever drag model the study installed and reads the two lift coefficients back out. It contains no aerodynamic model of its own and no aerodynamic constant – that invariant still holds, and it is what keeps a discipline an interface rather than a second place physics can live.
What has changed underneath it is which model those inputs reach. On the default path every drag number is computed inside the black box by OpenConcept’s component-by-component parasite buildup and its parabolic polar. A study may instead name one of cdadt’s own – see
cdadt.models.loadsand Supplying your own aerodynamics – in which case the induced drag is computed by a vortex lattice built on this wing andac|aero|polar|ebecomes an unused assumption, because the lattice reports the span efficiency rather than reading it.Either way the ownership is unchanged: this class owns the
ac|aero|parameters, and the model that consumes them is chosen by the case file, not by this discipline.Notes
The drag history itself is per-phase and vector-valued, so it is not reported here. It is read directly from the box by name –
mission.cruise.dragand its siblings – when a run is plotted or exported.- discipline_name: ClassVar[str] = 'aerodynamics'
- description: ClassVar[str] = 'Span efficiency, airfoil and flap settings, speed envelope'
One line saying what the domain covers, used in generated documentation and reports.
- owned_patterns: ClassVar[tuple[str, ...]] = ('ac|aero|*',)
Shell-style patterns matching the black-box variables this discipline may set.
The propulsion discipline: the engine rating and count, and the installed weights.
- class cdadt.disciplines.propulsion.Propulsion[source]
Bases:
DisciplineEngine rating and engine count, and the propulsion installation weights.
Owns the two
ac|propulsion|parameters. The rating is the sea-level static thrust the engine deck inside the black box is scaled to – OpenConcept calls this a rubberized engine, meaning thrust and fuel flow are scaled from a fixed CFM56 deck rather than recomputed by a cycle analysis. Engine count multiplies both, and is what makes the engine-out cases meaningful: the box multiplies by the number of active engines, which is one fewer in the rejected-takeoff and engine-out climb conditions.Thrust, fuel flow and throttle at every node are computed inside the box and belong to the mission, so they are reported by
Performance. What this discipline reports is the weight of the propulsion installation, which is what the rating and count size.Notes
Because the engine is a scaled deck, a very large change in rating extrapolates the surrogate rather than redesigning an engine. Validation records that limit.
- discipline_name: ClassVar[str] = 'propulsion'
- description: ClassVar[str] = 'Engine rating and count, and the propulsion installation weights'
One line saying what the domain covers, used in generated documentation and reports.
- owned_patterns: ClassVar[tuple[str, ...]] = ('ac|propulsion|*',)
Shell-style patterns matching the black-box variables this discipline may set.
- reported: ClassVar[tuple[Response, ...]] = (Response('engine_weight', 'empty_weight.single_engine.W_engine', 'kg'), Response('engines_weight', 'empty_weight.W_engines', 'kg'), Response('thrust_reverser_weight', 'empty_weight.W_thrust_rev', 'kg'), Response('engine_controls_weight', 'empty_weight.W_eng_control', 'kg'), Response('engine_starter_weight', 'empty_weight.W_eng_start', 'kg'), Response('fuel_system_weight', 'empty_weight.W_fuelsystem', 'kg'))
Quantities this discipline reads back out of the black box.
The stability discipline: the empennage, sized by tail volume coefficient.
- class cdadt.disciplines.stability.Stability[source]
Bases:
DisciplineHorizontal and vertical stabilizer shape, and the areas the box sizes from it.
Owns the empennage shape parameters – aspect ratio, quarter-chord sweep, taper and thickness ratio for both surfaces – and reports the two reference areas.
The areas are outputs, not inputs, which is why the empennage is a discipline of its own rather than part of
Geometry. The black box sizes both surfaces by the tail volume coefficient method: the horizontal tail from wing area, mean aerodynamic chord and lever arm, the vertical tail from wing area, span and lever arm. The volume coefficients themselves (1.00 horizontal, 0.09 vertical, Raymer Table 6.4 for jet transports) are options of the components inside the box, not inputs to it, so they cannot be set from cdadt and are documented as fixed by the box. See The OpenConcept black box.That also means this discipline models no stability physics whatsoever – no static margin, no centre of gravity, no minimum control speed. It is named for the domain the tail volume coefficient method belongs to, and Validation states the gap explicitly.
- discipline_name: ClassVar[str] = 'stability'
- description: ClassVar[str] = 'Empennage shape, and the tail areas the box sizes by volume coefficient'
One line saying what the domain covers, used in generated documentation and reports.
- owned_patterns: ClassVar[tuple[str, ...]] = ('ac|geom|hstab|*', 'ac|geom|vstab|*')
Shell-style patterns matching the black-box variables this discipline may set.
The structures discipline: the load-carrying airframe, reported component by component.
- class cdadt.disciplines.structures.Structures[source]
Bases:
DisciplineMass of the load-carrying airframe, broken down by component.
This discipline owns no inputs, and that is the honest answer. Primary structure is sized inside the black box by empirical correlations that read geometry, maximum takeoff weight and maximum landing weight – all of which are owned by
Geometry,StabilityandWeights, or computed by the box itself. There is no structural parameter left for cdadt to set: no material, no load factor, no spar layout. Inventing one here so that the class had state would be a parameter nothing reads.What it does own is the reporting of structure. A structural mass that cannot be broken down cannot be argued with, and a design report that gives only operating empty weight hides where the weight went. This class names the seven component masses the box publishes, where each lives, and in what units to read it.
All of them are marked optional because they exist in the box only as long as the box’s empty-weight model publishes them. A different sizing model, named in the case file, may not; the run then reports them as unavailable rather than failing or, worse, quietly dropping them.
Notes
The ultimate load factor, the structural allowance and every other constant in those correlations are options of components inside the box. They are not settable from cdadt and are documented as fixed by the box in The OpenConcept black box.
- discipline_name: ClassVar[str] = 'structures'
- description: ClassVar[str] = 'Load-carrying airframe mass, component by component'
One line saying what the domain covers, used in generated documentation and reports.
- owned_patterns: ClassVar[tuple[str, ...]] = ()
Structure has no settable parameter of its own; see the class docstring.
- reported: ClassVar[tuple[Response, ...]] = (Response('structure_weight', 'empty_weight.W_structure', 'kg'), Response('wing_weight', 'empty_weight.W_wing', 'kg'), Response('hstab_weight', 'empty_weight.W_hstab', 'kg'), Response('vstab_weight', 'empty_weight.W_vstab', 'kg'), Response('fuselage_weight', 'empty_weight.W_fuselage', 'kg'), Response('main_gear_weight', 'empty_weight.W_mlg', 'kg'), Response('nose_gear_weight', 'empty_weight.W_nlg', 'kg'), Response('nacelle_weight', 'empty_weight.W_nacelle', 'kg'))
Quantities this discipline reads back out of the black box.
- add(parameter)[source]
Reject every parameter, with an explanation.
- Raises:
DisciplineError – Always. The base class would raise anyway, since nothing matches an empty ownership pattern, but the generic message would read as a gap in the ownership map rather than as a deliberate property of this domain.
- Parameters:
parameter (Parameter)
- Return type:
None
The weights discipline: payload and the cabin, and the weight rollup the box closes on.
- class cdadt.disciplines.weights.Weights[source]
Bases:
DisciplinePayload, occupancy and cabin pressure, and the three weights that close the sizing loop.
Owns the
ac|weights|parameters and the cabin definition – passenger capacity, flight deck and cabin crew, cabin pressure. Those are weight inputs rather than geometry: inside the black box they drive furnishings, oxygen, air conditioning and pressurization, and the pressurized cabin volume that the fuselage weight correlation reads.Reports the rollup the whole model exists to close:
\[\mathrm{MTOW} = \mathrm{OEW}(\mathrm{MTOW}, \text{geometry}) + W_\mathrm{payload} + W_\mathrm{fuel}(\mathrm{MTOW}, \text{mission})\]A heavier aircraft burns more fuel and more fuel makes it heavier. The black box drives that residual to zero with its own Newton solver, at the same time as the mission’s implicit states – phase durations, throttle settings and the decision speed. cdadt supplies the starting guess and reads the answer; the closure itself is the box’s.
Maximum landing weight is reported because it sizes the landing gear, not because it was chosen: the box takes it as 80% of maximum takeoff weight. That is an assumption of the box, not of cdadt, and is recorded in The OpenConcept black box.
- discipline_name: ClassVar[str] = 'weights'
- description: ClassVar[str] = 'Payload, occupancy and cabin, and the closed weight rollup'
One line saying what the domain covers, used in generated documentation and reports.
- owned_patterns: ClassVar[tuple[str, ...]] = ('ac|weights|*', 'ac|num_*', 'ac|cabin_pressure')
Shell-style patterns matching the black-box variables this discipline may set.
- reported: ClassVar[tuple[Response, ...]] = (Response('MTOW', 'ac|weights|MTOW', 'kg'), Response('OEW', 'ac|weights|OEW', 'kg'), Response('MLW', 'ac|weights|MLW', 'kg'), Response('payload', 'ac|weights|W_payload', 'kg'), Response('furnishings_weight', 'empty_weight.W_furnishings', 'kg'), Response('avionics_weight', 'empty_weight.W_avionics', 'kg'), Response('electrical_weight', 'empty_weight.W_electrical', 'kg'), Response('apu_weight', 'empty_weight.W_APU', 'kg'), Response('oxygen_weight', 'empty_weight.W_oxygen', 'kg'), Response('environmental_weight', 'empty_weight.W_ac_pressurize_antiice', 'kg'), Response('flight_controls_weight', 'empty_weight.W_flight_controls', 'kg'), Response('pressurized_volume', 'empty_weight.cabin_volume.V_pressurized', 'm**3'))
Quantities this discipline reads back out of the black box.
The performance discipline: the mission flown, and everything the mission produces.
- class cdadt.disciplines.performance.Performance(conditions, ladder=None)[source]
Bases:
DisciplineThe mission the aircraft is sized against, and the results of flying it.
The one discipline whose encapsulated state is not a bag of scalars. What performance owns is the pair OpenConcept’s run scripts write by hand: the initial conditions (
set_values(prob, num_nodes)) and the continuation ladder that converges the hard ones (the tworun_model()calls insideset_mission_profile).What it reports is everything the mission produces, and it is what the certification constraints are written against: balanced field length and its abort distance, the decision and takeoff safety speeds, the engine-out climb gradient, block fuel and fuel with reserves, the range actually flown, and the throttle history of every phase.
- Parameters:
conditions (InitialConditions) – Everything written into the box before it is converged.
ladder (ContinuationLadder, optional) – The rungs walked first. Empty attempts the design mission directly.
- discipline_name: ClassVar[str] = 'performance'
- description: ClassVar[str] = 'The mission flown, and the field, climb and fuel results it produces'
One line saying what the domain covers, used in generated documentation and reports.
- owned_patterns: ClassVar[tuple[str, ...]] = ('mission.*',)
Performance owns the mission-level values. They live in the initial conditions rather than as loose parameters, so
add()refuses them; the pattern is declared so that the ownership check over the box’s settable variables comes out total.
- reported: ClassVar[tuple[Response, ...]] = (Response('block_fuel', 'mission.descent.fuel_burn_integ.fuel_burn_final', 'kg'), Response('total_fuel', 'mission.loiter.fuel_burn_integ.fuel_burn_final', 'kg'), Response('takeoff_field_length', 'mission.bfl.distance_continue', 'ft'), Response('abort_distance', 'mission.bfl.distance_abort', 'ft'), Response('V1', 'mission.takeoff|v1', 'kn'), Response('V2', 'mission.engineoutclimb.takeoff|v2', 'kn'), Response('engine_out_climb_gradient', 'mission.engineoutclimb.gamma', 'rad'), Response('mission_range_flown', 'mission.descent.ode_integ_phase.range_final', 'nmi'), Response('reserve_range_flown', 'mission.resrange.reserverange', 'nmi'), Response('climb_throttle', 'mission.climb.throttle', None), Response('cruise_throttle', 'mission.cruise.throttle', None), Response('descent_throttle', 'mission.descent.throttle', None), Response('climb_duration', 'mission.climb.duration', 'min'), Response('cruise_duration', 'mission.cruise.duration', 'min'), Response('descent_duration', 'mission.descent.duration', 'min'), Response('loiter_duration', 'mission.loiter.duration', 'min'))
Quantities this discipline reads back out of the black box.
- property conditions: InitialConditions
Everything written into the box before it is converged.
- property ladder: ContinuationLadder
The rungs walked before the design mission.
- add(parameter)[source]
Reject loose parameters, with an explanation.
- Raises:
DisciplineError – Always. Mission-level values belong in
initial_conditions, alongside the schedules and the ladder that make them reachable.- Parameters:
parameter (Parameter)
- Return type:
None