Classes and Immutability¶
This document describes the consistent pattern of immutability and lazy caching across the following core data and geometry classes in the Ptera Software codebase:
CoreUnsteadyProblem/UnsteadyProblem_CoupledUnsteadyProblemAeroelasticUnsteadyProblemFreeFlightUnsteadyProblemCoreMovement/MovementCoreAirplaneMovement/AirplaneMovementCoreWingMovement/WingMovementCoreWingCrossSectionMovement/WingCrossSectionMovementCoreOperatingPointMovement/OperatingPointMovementSteadyProblemOperatingPointAirplaneWingWingCrossSectionAirfoilPanelMuJoCoModel
The Core* classes live in pterasoftware/_core.py and own the shared slots and properties. The public classes extend their core parents, sometimes adding additional slots and sometimes inheriting everything with an empty __slots__. See each class section below for details on which attributes are defined at which level.
Design Principles¶
Class Attribute Categories¶
Most attribute falls into one of these categories:
Category |
Pattern |
|---|---|
Immutable |
Read-only property (no setter), set once in |
Derived (Immutable) |
Manual lazy caching (check |
Set Once |
Property with setter that raises |
Mutable |
Property with setter, or plain attribute |
Derived (Set Once) |
Manual lazy caching, depends on set once attributes |
Construction-Only Parameters¶
A constructor parameter is not always retained as an attribute. Some parameters shape how an object is built but are deliberately discarded once construction finishes: they have no __slots__ entry, appear in no attribute-category table, and are not serialized or deep copied, because nothing reads them after __init__. They are still validated exactly like their stored counterparts.
Wing’s explode_into_strips is one example. When True, it triggers Wing.explode_wing to replace the supplied wing cross sections with single-strip cross sections, then plays no further role. Storing it would only create a stale provenance flag, since an exploded Wing is indistinguishable from one defined with single-strip cross sections directly. So the validated value lives in a local variable inside __init__ and is never assigned to self, while still passing through boolLike_return_bool, the same check applied to the stored bool-likes symmetric and mirror_only.
Key Decisions¶
No cache invalidation for immutable/set once attributes: Since these are only set once, we don’t need invalidation logic in setters.
Enforce set once semantics at runtime: Set once properties raise
AttributeErrorif assigned a second time. This catches bugs early where code incorrectly attempts to modify values that should be immutable after initial assignment.Use manual lazy caching for all derived properties: This approach:
Works consistently for properties derived from both immutable and set once attributes
Is compatible with
__slots__(no dependency on__dict__)Simplifies
__deepcopy__(cache variables can be copied directly)Maintains the existing pattern already used throughout the codebase
Solver mutable attributes remain mutable: Properties that the solver needs to set keep their setters.
__slots__on every class: All classes in the package define__slots__, which eliminates per-instance__dict__overhead and prevents accidental dynamic attribute assignment. This catches typos likeself.num_panles = 5at runtime with anAttributeErrorinstead of silently creating a new attribute.
NumPy Array Mutability¶
Even with read-only properties, numpy arrays can still be mutated in place via the getter (e.g., panel.Frpp_G_Cg[0] = 999.0). To prevent this, all numpy arrays that should be immutable are set to read-only using arr.flags.writeable = False:
Immutable arrays: Set in
__init__immediately after assignmentSet once arrays: Set in the setter after assignment
Derived cached arrays: Set in the lazy property after computation (since numpy operations like subtraction create new writable arrays regardless of input writability)
Deepcopy: Use
.copy()then setflags.writeable = Falseon the copy
Deepcopy Cache Handling¶
When implementing __deepcopy__, handle cached derived properties based on their source:
Derived from Immutable -> Preserve: Copy the cached values (they remain valid since the source immutable attributes are also copied). For numpy arrays, use
.copy()then setflags.writeable = False.Derived from Set Once -> Reset to None: These depend on values that will be set fresh by the solver or meshing, so reset them.
Serialization Attribute Handling¶
The _serialization module uses the same object.__new__() + object.__setattr__() pattern as the __deepcopy__ methods but with a simpler strategy: the logical state of each object is preserved exactly as it was at save time, including all cached values on primary slots. For these primary attributes, no values are reset to None during deserialization.
Some redundant or alias slots (for example, solver alias slots and Movement._airplanes / Movement._operating_points) are treated specially: they are serialized as null and then deterministically rebuilt from their canonical sources during deserialization. This preserves object graph identity and avoids duplicating equivalent objects while still yielding the same effective state as the original instance.
This works because:
Deserialized objects are fully formed snapshots. There is no subsequent
__init__, solver run, or meshing step that would populate set once attributes, so there is no conflict with set once guards.Cached derived values remain valid because the immutable and set once attributes they depend on are also restored with their original values, and any alias slots are rebuilt to match.
NumPy array writeable flags are preserved through serialization, maintaining the same mutability guarantees as the original objects.
When adding or renaming __slots__ on any class, both __deepcopy__ and _serialization are affected. The serialization module discovers attributes generically via __slots__, so new slots are automatically serialized (subject to the intentional omission of redundant/alias slots described above). However, adding or removing slots requires incrementing _FORMAT_VERSION in _serialization.py to ensure old files are not loaded with incompatible code.
List Collection Immutability¶
Store collections as tuples internally to prevent external mutation via .append(), .pop(), etc.
CoreUnsteadyProblem / UnsteadyProblem Class (_core.py, problems.py)¶
UnsteadyProblem extends CoreUnsteadyProblem. CoreUnsteadyProblem owns all attributes except movement and steady_problems, which are defined on UnsteadyProblem.
Attribute Classification¶
Immutable (set in __init__, never modified)¶
Attribute |
Type |
Defined On |
Notes |
|---|---|---|---|
|
|
|
Movement definition |
|
|
|
Results flag |
|
|
|
Copied from Movement |
|
|
|
Copied from Movement |
|
|
|
Computed during init |
|
|
|
Computed during init |
|
|
|
Copied from Movement |
|
|
|
Generated during init |
Mutable (populated by solver)¶
Attribute |
Type |
Notes |
|---|---|---|
|
|
Final forces |
|
|
Final force coefficients |
|
|
Final moments |
|
|
Final moment coefficients |
|
|
Cycle averaged forces |
|
|
Cycle averaged coefficients |
|
|
Cycle averaged moments |
|
|
Cycle averaged moment coefficients |
|
|
RMS forces |
|
|
RMS force coefficients |
|
|
RMS moments |
|
|
RMS moment coefficients |
Note: The mutable solver result lists are defined on CoreUnsteadyProblem and must remain mutable as they are populated after initialization by the solver. These are initialized as empty lists and appended to during the solve.
_CoupledUnsteadyProblem Class (problems.py)¶
_CoupledUnsteadyProblem is a private middle-layer class that extends CoreUnsteadyProblem. It is the base for concrete subclasses (AeroelasticUnsteadyProblem and FreeFlightUnsteadyProblem, both documented below) whose per-step SteadyProblem depends on the solver’s results from the previous step: deformed wing geometry for aeroelasticity, updated rigid body state for free flight. Unlike UnsteadyProblem, which builds all SteadyProblems up front from a pre-generated Movement, the coupled subclasses grow their SteadyProblem collection one step at a time during the solve.
All CoreUnsteadyProblem attributes (documented in the section above) are inherited unchanged. The additions are:
Attribute Classification¶
Immutable (set in __init__, never modified)¶
Attribute |
Type |
Notes |
|---|---|---|
|
|
Source of |
|
|
Read-only view of the |
Note on steady_problems: The parent class’s steady_problems property is doubly immutable. The returned tuple is read-only and its value never changes over the lifetime of the UnsteadyProblem. On _CoupledUnsteadyProblem, the first guarantee still holds (callers cannot mutate the tuple), but the second does not. The backing slot _steady_problems is a list[SteadyProblem] seeded at init with a single entry built from initial_airplanes and initial_operating_point. Subclass initialize_next_problem overrides append to this list as each step is initialized during the solve, so calling steady_problems at different points can yield different-length tuples. External code that needs a consistent snapshot should read steady_problems once after the solver has completed.
AeroelasticUnsteadyProblem Class (problems.py)¶
AeroelasticUnsteadyProblem extends _CoupledUnsteadyProblem. It couples aerodynamic loads with a torsional spring-mass-damper structural model so that each wing’s deformation at a given time step is driven by the previous step’s aerodynamic, inertial, and spring-restoring moments. All _CoupledUnsteadyProblem and CoreUnsteadyProblem attributes (documented in the sections above) are inherited unchanged. The additions are the structural configuration (set once at construction) and the per-wing structural state (populated as the solve advances).
Attribute Classification¶
Immutable (set in __init__, never modified)¶
Each is stored in a _-prefixed backing slot and exposed through a read-only property of the unprefixed name.
Attribute |
Type |
Backing Slot |
Notes |
|---|---|---|---|
|
|
|
Mass per unit span area (kg/m^2) |
|
|
|
Torsional spring stiffness (N*m/rad) |
|
|
|
Torsional damping coefficient (Nms/rad) |
|
|
|
Scaling factor applied to aerodynamic moments |
|
|
|
Scaling factor applied to deformation angles |
|
|
|
Number of initial steps discarded for stability |
|
|
|
Whether to plot time histories at the end of the solve |
Derived from Immutable (read-only property, no backing slot)¶
Property |
Depends On |
Notes |
|---|---|---|
|
|
Typed-narrow cast of the inherited |
|
|
The first airplane movement’s first wing movement, cast to |
Mutable (populated by solver)¶
These lists are allocated in __init__ with one entry per wing in the initial airplane, then appended to or reassigned element-wise by the solver during the run. They are plain slots rather than read-only properties because the solver must update them after construction, mirroring the mutable-result-list treatment on CoreUnsteadyProblem.
Attribute |
Type |
Notes |
|---|---|---|
|
|
Current cumulative deformation angles, per wing |
|
|
Current angular velocity state, per wing |
|
|
Panel center position history, indexed |
|
|
Inertial moment history, indexed |
|
|
Aerodynamic moment history, indexed |
|
|
Cumulative deformation snapshots, indexed |
|
|
Angular velocity snapshots, indexed |
|
|
Wing deflection offsets, indexed |
|
|
Undeformed baseline panel positions, per wing |
FreeFlightUnsteadyProblem Class (problems.py)¶
FreeFlightUnsteadyProblem extends _CoupledUnsteadyProblem. It couples aerodynamic loads with six-degree-of-freedom rigid body dynamics, integrated by a MuJoCoModel, so that the Airplane’s position, orientation, and velocity at a given time step are driven by the previous step’s aerodynamic loads, gravity, and any external loads. The wing geometry stays prescribed; it is the per-step OperatingPoint (body pose and rates) that the dynamics update. All _CoupledUnsteadyProblem and CoreUnsteadyProblem attributes (documented in the sections above) are inherited unchanged. The additions are the rigid body configuration (set once at construction) and the per-step aerodynamic load history (populated as the solve advances).
Attribute Classification¶
Immutable (set in __init__, never modified)¶
Each is stored in a _-prefixed backing slot and exposed through a read-only property of the unprefixed name.
Attribute |
Type |
Backing Slot |
Notes |
|---|---|---|---|
|
|
|
Inertia matrix in the first Airplane’s body axes about its CG (kg*m^2); the array itself is set read-only |
|
|
|
Mass of the first Airplane (kg) |
|
|
|
Optional callback returning additional (force, moment) loads to apply each step, or None |
|
|
|
Rigid body dynamics engine; the reference is fixed while the engine’s own state advances during the solve (see the MuJoCoModel section) |
Derived from Immutable (read-only property, no backing slot)¶
Property |
Depends On |
Notes |
|---|---|---|
|
|
Typed-narrow cast of the inherited |
Mutable (populated by solver)¶
These are allocated in __init__ (the four load-history lists as empty lists, the validation guard as False) and updated by the problem’s initialize_next_problem as the solver advances. They are plain slots rather than read-only properties because they must change after construction, mirroring the mutable-result-list treatment on CoreUnsteadyProblem.
Attribute |
Type |
Notes |
|---|---|---|
|
|
Per-step aerodynamic force history, in wind axes |
|
|
Per-step aerodynamic force coefficient history |
|
|
Per-step aerodynamic moment history, in wind axes about the CG |
|
|
Per-step aerodynamic moment coefficient history |
|
|
Once-only guard; flips to |
Construction-only parameters¶
extra_xml and mujoco_assets are constructor parameters, not attributes: both are validated here for structural shape (each a dict or None; extra_xml keys restricted to the permitted injection points and values to strings; mujoco_assets mapping string filenames to bytes), then forwarded to the MuJoCoModel constructed in __init__ and not stored on the problem, so neither has a slot or an attribute-category entry above. They are the only raw user input reaching the MuJoCoModel, which performs no validation of its own; deeper XML and asset-reference correctness is left to MuJoCo. See Construction-Only Parameters under Design Principles.
CoreMovement / Movement Class (_core.py, movements/movement.py)¶
Movement extends CoreMovement. CoreMovement owns the shared slots (airplane_movements, operating_point_movement, delta_time, num_steps, max_wake_rows) and derived properties (lcm_period, max_period, min_period, static). Movement adds cycle/chord counting, wake sizing parameters, and batch pre-generation of Airplanes and OperatingPoints.
Attribute Classification¶
Immutable (set in __init__, never modified)¶
Attribute |
Type |
Defined On |
Notes |
|---|---|---|---|
|
|
|
Tuple prevents mutation |
|
|
|
Operating point changes |
|
|
|
Time step |
|
|
|
Total time steps |
|
|
|
Max wake rows per Wing |
|
|
|
Number of cycles |
|
|
|
Number of chord lengths |
|
|
|
Max wake in chord lengths |
|
|
|
Max wake in motion cycles |
|
|
|
Generated Airplanes |
|
|
|
Generated OperatingPoints |
Derived from Immutable (use manual lazy caching)¶
Property |
Depends On |
Defined On |
Notes |
|---|---|---|---|
|
|
|
Cached |
|
|
|
Cached |
|
|
|
Cached |
|
|
|
Cached |
Note on airplanes and operating_points: These are defined on Movement and generated during __init__ by calling the child movements’ generate_* methods. They are stored as nested tuples to prevent modification after generation.
FreeFlightMovement and AeroelasticMovement variant additions¶
FreeFlightMovement and AeroelasticMovement extend CoreMovement directly, as feature-specific siblings of Movement. They inherit every attribute above unchanged and add the immutable state below, each stored in a _-prefixed backing slot and exposed through a read-only property of the unprefixed name. The pre-generation is asymmetric: FreeFlightMovement pre-generates only Airplanes (the solver produces OperatingPoints from the rigid body dynamics), while AeroelasticMovement pre-generates only OperatingPoints (deformed Airplane geometry depends on the solver’s structural response).
Attribute |
Type |
Defined On |
Notes |
|---|---|---|---|
|
|
|
Prescribed-flight time steps before the free-flight phase |
|
|
|
Free-flight time steps after the prescribed phase |
|
|
|
Pre-generated prescribed Airplane geometry, indexed |
|
|
|
Pre-generated prescribed OperatingPoints |
CoreAirplaneMovement / AirplaneMovement Class (_core.py, movements/airplane_movement.py)¶
AirplaneMovement extends CoreAirplaneMovement. All slots are defined on CoreAirplaneMovement; AirplaneMovement has empty __slots__ and only narrows the wing_movements type to require WingMovement children.
Attribute Classification¶
Immutable (set in __init__, never modified)¶
Attribute |
Type |
Notes |
|---|---|---|
|
|
Base geometry |
|
|
Tuple prevents mutation |
|
|
CG position amplitudes |
|
|
CG position periods |
|
|
CG position spacing |
|
|
CG position phases |
Derived from Immutable (use manual lazy caching)¶
Property |
Depends On |
Notes |
|---|---|---|
|
Own periods + child |
Tuple of unique non zero periods (cached) |
|
Own periods + child |
Scalar float, longest period (cached) |
CoreWingMovement / WingMovement Class (_core.py, movements/wing_movement.py)¶
WingMovement extends CoreWingMovement. All slots are defined on CoreWingMovement; WingMovement has empty __slots__ and only narrows the wing_cross_section_movements type to require WingCrossSectionMovement children.
Attribute Classification¶
Immutable (set in __init__, never modified)¶
Attribute |
Type |
Notes |
|---|---|---|
|
|
Base geometry |
|
|
Tuple prevents mutation |
|
|
Position amplitudes |
|
|
Position periods |
|
|
Position spacing |
|
|
Position phases |
|
|
Angle amplitudes |
|
|
Angle periods |
|
|
Angle spacing |
|
|
Angle phases |
|
|
Rotation point offset |
Derived from Immutable (use manual lazy caching)¶
Property |
Depends On |
Notes |
|---|---|---|
|
Own periods + child |
Tuple of unique non zero periods (cached) |
|
Own periods + child |
Scalar float, longest period (cached) |
AeroelasticWingMovement variant additions¶
AeroelasticWingMovement extends CoreWingMovement directly, as a feature-specific sibling of WingMovement. It inherits every attribute above unchanged and adds the one immutable slot below, stored in a _-prefixed backing slot and exposed through a read-only property of the unprefixed name.
Attribute |
Type |
Notes |
|---|---|---|
|
|
Per-basis-direction (x, y, z) analytical second time derivative of the matching custom angular spacing; an entry is a callable when its |
CoreWingCrossSectionMovement / WingCrossSectionMovement Class (_core.py, movements/wing_cross_section_movement.py)¶
WingCrossSectionMovement extends CoreWingCrossSectionMovement. All slots are defined on CoreWingCrossSectionMovement; WingCrossSectionMovement has empty __slots__.
Attribute Classification¶
Immutable (set in __init__, never modified)¶
Attribute |
Type |
Notes |
|---|---|---|
|
|
Base geometry |
|
|
Position amplitudes |
|
|
Position periods |
|
|
Position spacing |
|
|
Position phases |
|
|
Angle amplitudes |
|
|
Angle periods |
|
|
Angle spacing |
|
|
Angle phases |
Derived from Immutable (use manual lazy caching)¶
Property |
Depends On |
Notes |
|---|---|---|
|
Period arrays |
Tuple of unique non zero periods (cached) |
|
Period arrays |
Scalar float, longest period (cached) |
CoreOperatingPointMovement / OperatingPointMovement Class (_core.py, movements/operating_point_movement.py)¶
OperatingPointMovement extends CoreOperatingPointMovement. All slots are defined on CoreOperatingPointMovement; OperatingPointMovement has empty __slots__.
Attribute Classification¶
Immutable (set in __init__, never modified)¶
Attribute |
Type |
Notes |
|---|---|---|
|
|
Base operating conditions |
|
|
Amplitude |
|
|
Period |
|
|
Spacing function |
|
|
Phase offset |
Derived from Immutable (use manual lazy caching)¶
Property |
Depends On |
Notes |
|---|---|---|
|
|
Cached |
FreeFlightOperatingPointMovement variant additions¶
FreeFlightOperatingPointMovement extends CoreOperatingPointMovement directly, as a feature-specific sibling of OperatingPointMovement. It inherits every attribute above unchanged and adds the one mutable slot below, since its OperatingPoints are produced by the solver’s rigid body dynamics integration rather than prescribed.
Attribute |
Type |
Notes |
|---|---|---|
|
|
Mutable OperatingPoint history; seeded with the base OperatingPoint at step 0, then the solver appends one per step. A plain mutable slot rather than a read-only property, mirroring the mutable-result-list treatment on |
SteadyProblem Class (problems.py)¶
Attribute Classification¶
Immutable (set in __init__, never modified)¶
Attribute |
Type |
Notes |
|---|---|---|
|
|
Tuple prevents external mutation |
|
|
Operating conditions |
Derived from Immutable (use manual lazy caching)¶
Property |
Depends On |
Notes |
|---|---|---|
|
|
Tuple of Re for each Airplane (cached) |
OperatingPoint Class (operating_point.py)¶
Attribute Classification¶
Immutable (set in __init__, never modified)¶
Attribute |
Type |
Notes |
|---|---|---|
|
|
Fluid density |
|
|
CG speed |
|
|
Angle of attack |
|
|
Sideslip angle |
|
|
Earth-to-body orientation |
|
|
CG position in Earth axes |
|
|
Image surface normal |
|
|
Image surface point |
|
|
External force |
|
|
Kinematic viscosity |
|
|
Gravity in Earth axes |
|
|
Body angular velocity |
Derived from Immutable (use manual lazy caching)¶
Property |
Depends On |
Notes |
|---|---|---|
|
|
Dynamic pressure (cached) |
|
(constant) |
Geometry-to-body matrix (cached) |
|
|
Inverse of above (cached) |
|
|
Body-to-wind matrix (cached) |
|
|
Inverse of above (cached) |
|
|
Geometry-to-wind matrix (cached) |
|
|
Inverse of above (cached) |
|
|
Earth-to-body matrix (cached) |
|
|
Inverse of above (cached) |
|
|
Earth-to-geometry matrix (cached) |
|
|
Inverse of above (cached) |
|
|
Wind-to-Earth matrix (cached) |
|
|
Inverse of above (cached) |
|
|
Surface normal in GP1 (cached) |
|
|
Surface point in GP1 (cached) |
|
|
Active reflection matrix (cached) |
|
|
Freestream direction (cached) |
|
|
Freestream velocity (cached) |
Note on caching: While vInfHat_GP1__E and vInf_GP1__E are simple computations once the transformation matrix is cached, they are cached for consistency with the overall pattern and because they are called repeatedly during solver operations. The same is true for qInf__E.
Note on T_pas_GP1_CgP1_to_BP1_CgP1: This matrix is a constant (180-degree rotation about y) and does not depend on any __init__ parameters. It is still lazily cached for consistency with the overall pattern and to avoid recomputing it on every access.
Note on transformation decomposition: The geometry-to-wind transformation (T_pas_GP1_CgP1_to_W_CgP1) is composed from T_pas_GP1_CgP1_to_BP1_CgP1 and T_pas_BP1_CgP1_to_W_CgP1 via compose_T_pas. Similarly, T_pas_E_CgP1_to_GP1_CgP1 is composed from T_pas_E_CgP1_to_BP1_CgP1 and T_pas_BP1_CgP1_to_GP1_CgP1, and T_pas_W_CgP1_to_E_CgP1 is composed from T_pas_W_CgP1_to_BP1_CgP1 and T_pas_BP1_CgP1_to_E_CgP1. Decomposing through body axes lets each non-body chain reuse the body-relative matrices rather than computing fresh rotations.
Airplane Class (geometry/airplane.py)¶
Attribute Classification¶
Immutable (set in __init__, never modified)¶
Attribute |
Type |
Notes |
|---|---|---|
|
|
Processed for symmetry during init (tuple prevents mutation) |
|
|
Airplane identifier |
|
|
CG position in formation coordinates |
|
|
Aircraft weight in Newtons |
|
|
Reference wetted area |
|
|
Reference chord length |
|
|
Reference span |
Derived from Immutable (use manual lazy caching)¶
Property |
Depends On |
Notes |
|---|---|---|
|
|
Sum of wing panel counts |
|
|
Transformation matrix |
Mutable (set by solver)¶
Attribute |
Type |
Notes |
|---|---|---|
|
|
Forces in wind axes |
|
|
Force coefficients |
|
|
Moments in wind axes |
|
|
Moment coefficients |
Wing Class (geometry/wing.py)¶
Attribute Classification¶
Immutable (set in __init__, never modified)¶
Attribute |
Type |
Notes |
|---|---|---|
|
|
Wing cross sections (tuple prevents mutation) |
|
|
Wing identifier |
|
|
Leading edge root position |
|
|
Rotation angles |
|
|
Chordwise panel count |
|
|
“cosine” or “uniform” |
Derived from Immutable (use manual lazy caching)¶
Property |
Depends On |
Notes |
|---|---|---|
|
Immutable attrs |
Transformation matrix |
|
Above |
Inverse transformation |
|
Above |
Basis vectors |
|
Cross sections |
Child transformations |
Set Once (set by generate_mesh, never modified after)¶
Attribute |
Type |
Set By |
Notes |
|---|---|---|---|
|
|
|
1, 2, 3, or 4 |
|
|
|
Total spanwise count |
|
|
|
Total panel count |
|
|
|
Panel matrix |
Derived from Set Once (use manual lazy caching)¶
Property |
Depends On |
Notes |
|---|---|---|
|
|
Projected area |
|
|
Wetted area |
|
|
Average aspect ratio |
|
Wing cross sections, |
Wing span |
|
|
Standard mean chord |
|
|
MAC |
Note on caching: Most derived properties iterate over panels or wing cross sections. For large meshes, caching projected_area, wetted_area, and span provides meaningful performance gains if they’re accessed multiple times. Since their source attributes are immutable or set once, these are cached after first computation without invalidation logic.
Mutable (modified by process_wing_symmetry for type 5 symmetry)¶
Attribute |
Type |
Notes |
|---|---|---|
|
|
Modified to False for type 5 |
|
|
Modified to False for type 5 |
|
|
Modified to None for type 5 |
|
|
Modified to None for type 5 |
Note: These are modified by Airplane.process_wing_symmetry() when type 5 symmetry is detected. The original Wing becomes a type 1 wing and a new reflected Wing is created with type 3 symmetry.
Mutable (modified during simulation for wake)¶
Attribute |
Type |
Notes |
|---|---|---|
|
|
Wake vortex array, grows |
|
|
Wake vortex positions, grows |
Construction-only parameters¶
explode_into_strips is a constructor parameter, not an attribute: when True it triggers explode_wing during initialization and is then discarded, so it has no slot and no attribute-category entry above. See Construction-Only Parameters under Design Principles.
WingCrossSection Class (geometry/wing_cross_section.py)¶
Attribute Classification¶
Immutable (set in __init__, never modified)¶
Attribute |
Type |
Notes |
|---|---|---|
|
|
Wing cross section airfoil |
|
|
Spanwise panel count |
|
|
Chord length |
|
|
Position in parent axes |
|
|
Rotation angles |
|
|
Hinge location (0-1) |
|
|
Deflection in degrees |
|
|
“cosine” or “uniform” |
Derived from Immutable (use manual lazy caching)¶
Property |
Depends On |
Notes |
|---|---|---|
|
|
Transformation matrix |
|
Above |
Inverse transformation |
Set Once (set by parent Wing)¶
Attribute |
Type |
Set By |
Notes |
|---|---|---|---|
|
|
|
Validation flag |
|
|
|
Inherited symmetry type |
Mutable (modified by process_wing_symmetry)¶
Attribute |
Type |
Notes |
|---|---|---|
|
|
Set to None for type 5 symmetry |
Note: Modified at when type 5 symmetry is split into two wings.
Airfoil Class (geometry/airfoil.py)¶
Attribute Classification¶
Immutable (set in __init__, never modified)¶
Attribute |
Type |
Notes |
|---|---|---|
|
|
Airfoil identifier |
|
|
Outline coordinates |
|
|
Resampling flag |
|
|
Points per side for resampling |
|
|
Mean camber line coordinates |
Note: The add_control_surface method creates and returns a new Airfoil instance rather than modifying the existing one. This is the correct immutable pattern.
Panel Class (_panel.py)¶
Attribute Classification¶
Immutable (set in __init__, never modified)¶
Attribute |
Type |
Notes |
|---|---|---|
|
|
Front right corner (geometry axes) |
|
|
Front left corner (geometry axes) |
|
|
Back left corner (geometry axes) |
|
|
Back right corner (geometry axes) |
|
|
Edge flag |
|
|
Edge flag |
Derived from Immutable (use manual lazy caching)¶
Property |
Depends On |
Notes |
|---|---|---|
|
|
Right leg vector |
|
|
Front leg vector |
|
|
Left leg vector |
|
|
Back leg vector |
|
|
Front right bound vortex point |
|
|
Front left bound vortex point |
|
Multiple corners |
Collocation point |
|
All corners |
Unit normal vector |
|
All corners |
Panel area |
|
All legs |
Aspect ratio |
Set Once (set after construction by meshing or Problem)¶
Attribute |
Type |
Set By |
Notes |
|---|---|---|---|
|
|
Meshing |
Edge flag |
|
|
Meshing |
Edge flag |
|
|
Meshing |
Grid position |
|
|
Meshing |
Grid position |
|
|
Problem |
Front right (formation axes) |
|
|
Problem |
Front left (formation axes) |
|
|
Problem |
Back left (formation axes) |
|
|
Problem |
Back right (formation axes) |
Derived from Set Once (use manual lazy caching)¶
Property |
Depends On |
Notes |
|---|---|---|
|
|
Right leg (formation axes) |
|
|
Front leg (formation axes) |
|
|
Left leg (formation axes) |
|
|
Back leg (formation axes) |
|
|
Front right BVP (formation) |
|
|
Front left BVP (formation) |
|
Multiple GP1 corners |
Collocation point (formation) |
|
All GP1 corners |
Unit normal (formation) |
Mutable (set by solver)¶
Attribute |
Type |
Notes |
|---|---|---|
|
|
Computed forces |
|
|
Computed moments |
|
|
Forces in wind axes |
|
|
Moments in wind axes |
MuJoCoModel Class (_mujoco_model.py)¶
MuJoCoModel is a private class that wraps MuJoCo’s MjModel and MjData objects. It is constructed by FreeFlightUnsteadyProblem and provides methods for applying aerodynamic loads, stepping the rigid body dynamics, and extracting the updated state. Users pass raw scalars and arrays to FreeFlightUnsteadyProblem and never interact with MuJoCoModel directly.
Attribute Classification¶
Immutable (set in __init__, never modified)¶
Attribute |
Type |
Notes |
|---|---|---|
|
|
Generated MuJoCo XML |
|
|
Compiled MuJoCo model |
|
|
MuJoCo body ID for the Airplane |
|
|
MuJoCo key frame ID for initial conditions |
|
|
Initial generalized positions (computed during init) |
|
|
Initial generalized velocities (computed during init) |
Mutable¶
Attribute |
Type |
Notes |
|---|---|---|
|
|
Mutated by |
Construction-only parameters¶
extra_xml and mujoco_assets are constructor parameters, not attributes: both shape the generated model during initialization and are then discarded, so neither has a slot or an attribute-category entry above. extra_xml is folded into xml_str, so its content survives indirectly through the stored XML, while mujoco_assets is passed to MuJoCo’s from_xml_string and not retained at all, which is why an asset-based model cannot be rebuilt from xml_str alone. MuJoCoModel does not validate them: it is private and validates nothing, so they arrive already validated for structural shape from FreeFlightUnsteadyProblem (the only constructor), with deeper XML and asset-reference correctness left to MuJoCo. See Construction-Only Parameters under Design Principles.
Solver Classes (Not Covered Above)¶
The six solver classes (SteadyHorseshoeVortexLatticeMethodSolver, SteadyRingVortexLatticeMethodSolver, UnsteadyRingVortexLatticeMethodSolver, CoupledUnsteadyRingVortexLatticeMethodSolver, AeroelasticUnsteadyRingVortexLatticeMethodSolver, and FreeFlightUnsteadyRingVortexLatticeMethodSolver) are intentionally omitted from the immutability and lazy caching patterns described in this document. Unlike the data and geometry classes above, the solver classes are algorithmic classes whose attributes are internal mutable working state in a procedural computation pipeline. They are not shared data that external code accesses or modifies, so immutable properties, set once enforcement, and lazy caching would add significant boilerplate with no meaningful safety benefit. The solver classes do still use __slots__, like all other classes in the package, to protect against dynamic attribute assignment typos.
One narrow exception is UnsteadyRingVortexLatticeMethodSolver.steady_problems. It is not internal working state but results data that external code reads after a run, so rather than holding a separate mutable copy it is a read-only property that returns the underlying UnsteadyProblem.steady_problems directly. This keeps the problem as the single source of truth and removes any chance of the solver’s view going stale, which matters for coupled problems whose steady_problems grows step by step during the solve.