Disciplines

Engineering disciplines, each a class that owns one domain’s slice of the black box.

Geometry

Wing, fuselage, nacelles, gear

Aerodynamics

Span efficiency, airfoil, flaps, envelope

Propulsion

Engine rating and count, installation weight

Stability

Empennage shape and tail areas

Structures

Load-carrying airframe mass breakdown

Weights

Payload, cabin, and the closed weight rollup

Performance

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: Discipline

Span 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.loads and Supplying your own aerodynamics – in which case the induced drag is computed by a vortex lattice built on this wing and ac|aero|polar|e becomes 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.drag and 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: ABC

Abstract 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_name

Short identifier used as the key in reports and in the results object.

owned_patterns

Shell-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.

reported

The Response objects 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 lets Aircraft compose 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 variable belongs to this discipline.

Parameters:

variable (str) – Name of a black-box variable, e.g. "ac|geom|wing|S_ref".

Returns:

boolTrue if any of owned_patterns matches.

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 is add(), which checks ownership.

parameter(name)[source]

Return one held parameter.

Raises:

KeyError – If this discipline does not hold it.

Parameters:

name (str)

Return type:

Parameter

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) and has(path).

Returns:

dictresponse 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: Exception

Raised when a discipline is asked to hold something that is not its own.

class cdadt.disciplines.Geometry[source]

Bases: Discipline

Wing 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: Discipline

The 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 two run_model() calls inside set_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:
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: Discipline

Engine 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: Discipline

Horizontal 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: Discipline

Mass 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, Stability and Weights, 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: Discipline

Payload, 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: ABC

Abstract 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_name

Short identifier used as the key in reports and in the results object.

owned_patterns

Shell-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.

reported

The Response objects 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 lets Aircraft compose 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 variable belongs to this discipline.

Parameters:

variable (str) – Name of a black-box variable, e.g. "ac|geom|wing|S_ref".

Returns:

boolTrue if any of owned_patterns matches.

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 is add(), which checks ownership.

parameter(name)[source]

Return one held parameter.

Raises:

KeyError – If this discipline does not hold it.

Parameters:

name (str)

Return type:

Parameter

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) and has(path).

Returns:

dictresponse 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.base.DisciplineError[source]

Bases: Exception

Raised 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: Discipline

Wing 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: Discipline

Span 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.loads and Supplying your own aerodynamics – in which case the induced drag is computed by a vortex lattice built on this wing and ac|aero|polar|e becomes 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.drag and 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.

The propulsion discipline: the engine rating and count, and the installed weights.

class cdadt.disciplines.propulsion.Propulsion[source]

Bases: Discipline

Engine 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: Discipline

Horizontal 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.

The structures discipline: the load-carrying airframe, reported component by component.

class cdadt.disciplines.structures.Structures[source]

Bases: Discipline

Mass 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, Stability and Weights, 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: Discipline

Payload, 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: Discipline

The 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 two run_model() calls inside set_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:
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