Problem Statement
The movement hierarchy stores base geometry references independently at every level, so nothing enforces that wing_movements[i].base_wing is the same object as base_airplane.wings[i], or that wing_cross_section_movements[i].base_wing_cross_section is the same object as base_wing.wing_cross_sections[i]. The same data is reachable through two paths with no guarantee they agree, so a caller can construct a movement hierarchy whose base geometry contradicts itself. This has a concrete consequence today: AeroelasticAirplaneMovement.__init__() rejects an AeroelasticWingMovement whose base Wing has type 4 symmetry (its mirrored half has Panels but no WingCrossSections, so its strips cannot deform), but a base Wing that was never meshed and is not shared with the base Airplane has no symmetry type yet, passes that check, and fails later with an uninformative IndexError deep in the structural solve (see the review discussion on #190).
Location(s): pterasoftware/_core.py, pterasoftware/movements/aeroelastic_airplane_movement.py, pterasoftware/movements/aeroelastic_wing_movement.py
Proposed Solution
- In
CoreAirplaneMovement.__init__(), validate that wing_movements[i].base_wing is base_airplane.wings[i] for every index. Placing the check at the Core level covers all three hierarchies: the standard classes, AeroelasticAirplaneMovement (which subclasses CoreAirplaneMovement directly), and free flight (whose FreeFlightMovement holds standard AirplaneMovements).
- In
CoreWingMovement.__init__(), validate that wing_cross_section_movements[i].base_wing_cross_section is base_wing.wing_cross_sections[i] for every index, which likewise covers WingCrossSectionMovement and AeroelasticWingCrossSectionMovement.
- Audit the test fixtures and any examples that construct movements around geometry objects not taken from a constructed
Airplane, and rework them to satisfy the identity checks.
- Because an
Airplane's constructor meshes all of its Wings, these checks guarantee that every base Wing reaching AeroelasticAirplaneMovement.__init__() has its symmetry type set, which removes the type 4 rejection's blind spot and converts the IndexError failure mode into the existing clear construction-time ValueError.
Additional Context
This is one instance of a broader parallel-ownership pattern in the movement and problem classes (for example, the coupled problems now derive their initial Airplanes from their movements rather than accepting them as separate arguments). The identity checks proposed here are the narrow, construction-time piece of that cleanup and do not depend on the larger class restructuring.
Problem Statement
The movement hierarchy stores base geometry references independently at every level, so nothing enforces that
wing_movements[i].base_wingis the same object asbase_airplane.wings[i], or thatwing_cross_section_movements[i].base_wing_cross_sectionis the same object asbase_wing.wing_cross_sections[i]. The same data is reachable through two paths with no guarantee they agree, so a caller can construct a movement hierarchy whose base geometry contradicts itself. This has a concrete consequence today:AeroelasticAirplaneMovement.__init__()rejects anAeroelasticWingMovementwhose baseWinghas type 4 symmetry (its mirrored half hasPanels but noWingCrossSections, so its strips cannot deform), but a baseWingthat was never meshed and is not shared with the baseAirplanehas no symmetry type yet, passes that check, and fails later with an uninformativeIndexErrordeep in the structural solve (see the review discussion on #190).Location(s):
pterasoftware/_core.py,pterasoftware/movements/aeroelastic_airplane_movement.py,pterasoftware/movements/aeroelastic_wing_movement.pyProposed Solution
CoreAirplaneMovement.__init__(), validate thatwing_movements[i].base_wing is base_airplane.wings[i]for every index. Placing the check at the Core level covers all three hierarchies: the standard classes,AeroelasticAirplaneMovement(which subclassesCoreAirplaneMovementdirectly), and free flight (whoseFreeFlightMovementholds standardAirplaneMovements).CoreWingMovement.__init__(), validate thatwing_cross_section_movements[i].base_wing_cross_section is base_wing.wing_cross_sections[i]for every index, which likewise coversWingCrossSectionMovementandAeroelasticWingCrossSectionMovement.Airplane, and rework them to satisfy the identity checks.Airplane's constructor meshes all of itsWings, these checks guarantee that every baseWingreachingAeroelasticAirplaneMovement.__init__()has its symmetry type set, which removes the type 4 rejection's blind spot and converts theIndexErrorfailure mode into the existing clear construction-timeValueError.Additional Context
This is one instance of a broader parallel-ownership pattern in the movement and problem classes (for example, the coupled problems now derive their initial
Airplanes from their movements rather than accepting them as separate arguments). The identity checks proposed here are the narrow, construction-time piece of that cleanup and do not depend on the larger class restructuring.