Skip to content

Conversation

@camUrban
Copy link
Owner

@camUrban camUrban commented Jan 30, 2026

Description

This PR implements a comprehensive refactoring of the Ptera Software codebase to enforce attribute immutability across all core classes. The refactoring introduces read-only properties, set-once semantics, and lazy caching patterns throughout the Panel, Vortex, Geometry, Movement, OperatingPoint, and Problem class hierarchies. The class structure and attribute immutability decisions are documented in docs/CLASSES_AND_IMMUTABILITY.md.

Motivation

The original codebase allowed mutable attributes, which led to:

  1. Performance issues: Derived properties were recalculated repeatedly even when source data hadn't changed
  2. Data integrity risks: External code could accidentally mutate internal state, causing subtle bugs
  3. Cache invalidation complexity: Mutable attributes required complex cache invalidation logic that was error-prone

This refactoring addresses these issues by:

  • Making most attributes immutable after initialization (read-only properties backed by private variables)
  • Implementing manual lazy caching for derived properties
  • Making numpy arrays read-only to prevent in-place mutation
  • Using tuples instead of lists for collections to prevent external mutation
  • Adding proper __deepcopy__ methods to support safe copying with cache preservation/reset

The result is a 25-40x performance improvement for complex variable geometry simulations, with simpler and more maintainable code.

Relevant Issues

Continues work from #93, which referenced #92.

Changes

Phase 1: Panel and Vortex Classes

  • Renamed _aerodynamics.py to _aerodynamics_functions.py
  • Created new _vortices/ package and moved vortex classes:
    • LineVortex to _vortices/_line_vortex.py
    • HorseshoeVortex to _vortices/horseshoe_vortex.py
    • RingVortex to _vortices/ring_vortex.py
  • Made LineVortex endpoints immutable (removed setters)
  • Made HorseshoeVortex geometry immutable (front points, leg vector, leg length)
  • Made RingVortex corners immutable; updated UVLM solver to create new objects instead of mutating
  • Refactored Panel class with immutable and set-once attributes
  • Set all cached numpy arrays to read-only in Panel class
  • Added comprehensive unit tests for immutability and caching behavior

Phase 2: Geometry Classes

  • Made Airfoil numpy arrays immutable with private attributes and read-only properties
  • Made WingCrossSection attributes immutable with set-once enforcement for validated and symmetry_type
  • Cached transformation matrices in WingCrossSection (T_pas_Wcsp_Lpp_to_Wcs_Lp, T_pas_Wcs_Lp_to_Wcsp_Lpp)
  • Made Wing attributes immutable with read-only and set-once enforcement
  • Made Airplane attributes immutable (wings, name, Cg_GP1_CgP1, weight, s_ref, c_ref, b_ref)
  • Added Airplane.deep_copy_with_Cg_GP1_CgP1() method for creating copies with a custom global CG position
  • Changed wings collections to tuples instead of lists
  • Added lazy caching for derived properties (num_panels, T_pas_G_Cg_to_GP1_CgP1)
  • Fixed sign error in Airplane transformation matrix calculation
  • Updated __deepcopy__ methods to preserve or reset caches appropriately
  • Added comprehensive unit tests for immutability, caching, and deep copy behavior

Phase 3: Movement and Problem Classes

  • Made OperatingPoint attributes immutable with read-only properties and lazy caching
  • Made OperatingPointMovement attributes immutable
  • Made WingCrossSectionMovement immutable with custom __deepcopy__
  • Made WingMovement immutable with deep copy support
  • Made AirplaneMovement immutable with manual caching for derived properties (all_periods, max_period)
  • Made Movement attributes immutable; changed sequences to tuples
  • Made SteadyProblem attributes and outputs immutable; reynolds_numbers returns cached tuple
  • Made UnsteadyProblem attributes immutable; steady_problems stored as tuple
  • Fixed trim functions that were mutating newly immutable OperatingPoint attributes
  • Updated type hints for tuples of Airplane objects
  • Added comprehensive unit tests for all immutability and caching behavior

New Dependencies

None.

Change Magnitude

Major: Large change that adds significant new functionality, changes existing behavior, or may affect many parts of the codebase.

This refactoring touches nearly every core class in the codebase and changes the fundamental mutability contract of the API. While the public interface remains largely the same (properties are accessed the same way), code that previously mutated attributes directly will now raise AttributeError.


Checklist

  • I am familiar with the current contribution guidelines.
  • My branch is based on main and is up to date with the upstream main branch.
  • All calculations use S.I. units.
  • Code is formatted with black (line length = 88).
  • Code is well documented with block comments where appropriate.
  • Any external code, algorithms, or equations used have been cited in comments or docstrings.
  • All new modules, classes, functions, and methods have docstrings in reStructuredText format, and are formatted using docformatter (--in-place --black). See the style guide for type hints and docstrings for more details.
  • All new classes, functions, and methods in the pterasoftware package use type hints. See the style guide for type hints and docstrings for more details.
  • If any major functionality was added or significantly changed, I have added or updated tests in the tests package.
  • Code locally passes all tests in the tests package.
  • After pushing, PR passes all automated checks (codespell, black, mypy, and tests).
  • PR description links all relevant issues and follows this template.

Refactored the Airfoil class to store numpy arrays as private attributes and expose them via read-only properties. All numpy arrays are now made immutable to prevent external mutation, and deep copies are enforced during validation and copying. This improves encapsulation and data safety for Airfoil geometry data.
Refactored WingCrossSection to make key attributes immutable via private fields and read-only properties, with numpy arrays set as read-only. Added set-once enforcement for validated and symmetry_type attributes. Updated fixtures and tests to verify immutability and deep copy behavior, ensuring attributes cannot be modified after initialization except for control_surface_symmetry_type.
Refactored the Wing class to store key attributes as private and enforce immutability via read-only properties and set-once setters. Numpy arrays for geometry are now made read-only, and mesh metadata is set-once. Updated deepcopy logic to respect immutability and set-once semantics. Added caching for derived properties. Updated unit tests to use fresh fixtures for each test and added tests for immutability, set-once enforcement, and tuple immutability.
Introduces a temporary comprehensive benchmarking script to measure UnsteadyProblem object creation time under various configurations, including static and variable geometry, different panel counts, and cycle numbers. Results are saved as JSON for later comparison, and the script supports listing and comparing benchmark runs.
… vortex planning doc

Added a section explaining how numpy arrays are made immutable using `arr.flags.writeable = False` to prevent in-place mutation via read-only properties. Also updated code examples to use `cast(np.ndarray, ...)` for clarity and type safety.
Refactors the Panel class to make key geometric attributes immutable and read-only, and introduces set-once properties for mesh and global geometry metadata. Removes setters for corner points, enforces single-assignment for mesh metadata and global positions, and updates all internal logic to use the new attribute structure. This improves data integrity, prevents accidental mutation, and clarifies the distinction between immutable, set-once, and cached properties.
Moved Panel-related test fixtures from geometry_fixtures.py to a new panel_fixtures.py module and updated imports accordingly. Expanded panel_fixtures.py with additional fixture functions for various panel configurations. Updated test_panel.py to use the new fixtures and added comprehensive tests for Panel immutability, set-once properties, and deep copy behavior.
The LineVortex class was moved from _aerodynamics_functions.py to a new module _vortices/_line_vortex.py. All references and imports throughout the codebase, including in horseshoe_vortex.py, ring_vortex.py, movement.py, fixtures, and tests, were updated to use the new location and class name. Documentation was updated to reflect the new class name and location.
Added `flags.writeable = False` to all derived cached numpy arrays in the Panel class to enforce immutability. Updated documentation to reflect this change and clarified the immutability strategy for all vortex-related classes.
Refactors the LineVortex class to make the start and end points immutable after initialization, removing their property setters and related cache invalidation logic. Updates tests to remove cases that relied on mutating these attributes.
Refactors HorseshoeVortex to make geometric attributes (front points, leg vector, leg length) immutable after construction, removing their setters and related cache invalidation logic. Updates property access and internal caching accordingly. Removes tests that relied on mutability of these attributes, retaining only strength mutability and its propagation to legs.
Refactored RingVortex to make corner positions immutable after construction, removing setters and related cache invalidation logic. Updated unsteady_ring_vortex_lattice_method to create new RingVortex objects with updated corners instead of mutating existing ones. Removed unit tests that relied on mutating corner positions and cache invalidation.
Introduces new test classes to verify attribute immutability and lazy caching behavior for LineVortex objects. Updates __init__.py files to include new test and fixture modules for LineVortex.
Add new test classes for immutability and lazy caching behavior of HorseshoeVortex attributes. Remove redundant or superseded tests and improve clarity of test names and docstrings.
Introduces new test classes to verify attribute immutability and lazy caching behavior in RingVortex. Removes redundant tests for initial age and area property.
Expanded and clarified the GEOMETRY_PLAN.md to detail immutable, set-once, and mutable attribute patterns for Airplane, Wing, WingCrossSection, and Airfoil classes. Added guidance on numpy array immutability, manual lazy caching for derived properties, and correct deepcopy handling for caches. Updated PANEL_VORTEX_PLAN.md to reflect implementation status. Added new unsteady problem benchmark results.
…_PLAN.md

Standardized and clarified the __deepcopy__ implementation for Airfoil, improving docstrings and attribute handling. Updated section comments for consistency and readability.
…ate with GEOMETRY_PLAN.md

Refactored WingCrossSection to lazily evaluate and cache the derived transformation matrices T_pas_Wcsp_Lpp_to_Wcs_Lp and T_pas_Wcs_Lp_to_Wcsp_Lpp. Updated __deepcopy__ to preserve cached matrices, improved docstrings, and removed redundant property implementations. This change improves performance by avoiding repeated computation of transformation matrices.
…GEOMETRY_PLAN.md changes

Reorganized the Wing class to use manual lazy caching for derived properties, separating caches for immutable and set-once attributes. Moved property definitions and docstrings for set-once attributes and their derived properties, and updated the deepcopy method to preserve or reset caches appropriately. Improved code clarity and maintainability by grouping related logic and updating comments.
Refactored the Airplane class to make core attributes (wings, name, Cg_GP1_CgP1, weight, s_ref, c_ref, b_ref) immutable and read-only, storing them as private variables with property accessors. Updated derived properties (num_panels, T_pas_G_Cg_to_GP1_CgP1) to use lazy evaluation and caching. Removed unit tests that relied on mutability and direct assignment of these attributes.
Introduces Airplane.deep_copy_with_Cg_GP1_CgP1 to create deep copies with a specified Cg_GP1_CgP1 position, supporting immutable airplane instances with different positions. Updates AirplaneMovement to use this method instead of deepcopy and manual attribute assignment, improving clarity and correctness.
…data

Added comprehensive unit tests for the Wing.average_panel_aspect_ratio property and the Wing.get_plottable_data method, including input validation, caching, and output structure. Also added tests for transformation matrix caching and immutability. Removed obsolete test comments and updated docstrings for clarity.
Corrects the sign of the translation vector in the T_pas_G_Cg_to_GP1_CgP1 property to ensure the transformation matrix is computed correctly. Also removes outdated test comments.
Introduces comprehensive unit tests for Airplane attribute immutability, deep copy with Cg_GP1_CgP1, transformation matrix property, plottable data, and draw methods. Adds a follower airplane fixture for transformation tests. Updates documentation to indicate geometry class plan is implemented.
…es from PANEL_VORTEX_PLAN.md and GEOMETRY_PLAN.md
Added docs/OPERATING_POINT_MOVEMENT_PROBLEM_PLAN.md detailing the planned refactor for immutability and lazy caching patterns in OperatingPoint, movement, and problem classes.
Refactored OperatingPoint to use private immutable attributes with read-only properties. Added manual lazy caching for derived properties and ensured cached numpy arrays are read-only. Expanded test coverage with new fixtures for edge cases and added tests for immutability, caching, and numerical stability.
Refactored SteadyProblem to store airplanes and operating_point as private, immutable attributes and expose them as read-only properties. Changed reynolds_numbers to return a cached tuple instead of a list, ensuring immutability. Updated documentation and tests to reflect these changes, and added new tests for immutability and caching behavior.
Refactored OperatingPointMovement to store initialization parameters as private attributes and expose them via read-only properties, ensuring immutability. Updated tests to verify immutability and type conversion of parameters. Removed redundant tests and improved test coverage for attribute access.
Implements a custom __deepcopy__ method for WingCrossSectionMovement to ensure numpy arrays remain read only and caches are reset after copying. Refactors attributes to use private variables with read only properties, updates documentation to reflect deepcopy requirements, and adds comprehensive unit tests for immutability, caching, and deepcopy behavior.
Refactors WingMovement to store all attributes as private, immutable fields, exposes them via read-only properties, and ensures all numpy arrays are read-only. Adds a __deepcopy__ method for safe deep copying, including resetting internal caches. Updates and expands unit tests to verify immutability, caching, and deep copy behavior.
Refactored AirplaneMovement to store all attributes as private, immutable fields and expose them via read-only properties. Added __deepcopy__ method to support deep copying of AirplaneMovement instances, ensuring independence of internal state. Implemented manual caching for derived properties (all_periods, max_period). Updated tests to verify immutability, caching, and deepcopy behavior, and added new test classes for validation and immutability.
Refactored the Movement class to store key attributes as private and expose them via read-only properties, ensuring immutability. Airplane and operating point sequences are now tuples to prevent external mutation. Added new unit tests for immutability and caching behavior, and updated existing tests to reflect the new attribute types and access patterns.
Refactored UnsteadyProblem to store key attributes as private and expose them via read-only properties, ensuring immutability. Updated steady_problems to be a tuple instead of a list to prevent external mutation. Added comprehensive unit tests to verify immutability of these attributes and tuple behavior, as well as tests for multiple Airplanes and numpy bool handling.
…int attributes

All three phases of the properties refactoring plan (see *_PLAN.md files in docs) are now complete.
…ne from this branch

The final results show massive performance gains across almost all test cases, in particular ones with many WingCrossSections.
@camUrban camUrban self-assigned this Jan 30, 2026
@camUrban camUrban added bug Something isn't working maintenance Improvements or additions to documentation, testing, or robustness feature New feature or request labels Jan 30, 2026
@codecov
Copy link

codecov bot commented Jan 30, 2026

Codecov Report

❌ Patch coverage is 95.42484% with 70 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.32%. Comparing base (6ee4f2d) to head (1450142).
⚠️ Report is 54 commits behind head on main.

Files with missing lines Patch % Lines
pterasoftware/geometry/wing.py 84.30% 54 Missing ⚠️
pterasoftware/geometry/airfoil.py 94.31% 5 Missing ⚠️
pterasoftware/geometry/airplane.py 95.55% 4 Missing ⚠️
pterasoftware/trim.py 71.42% 4 Missing ⚠️
pterasoftware/movements/movement.py 98.85% 1 Missing ⚠️
...terasoftware/movements/operating_point_movement.py 97.29% 1 Missing ⚠️
...erasoftware/unsteady_ring_vortex_lattice_method.py 90.90% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #94      +/-   ##
==========================================
+ Coverage   89.29%   91.32%   +2.02%     
==========================================
  Files          28       32       +4     
  Lines        5259     5818     +559     
==========================================
+ Hits         4696     5313     +617     
+ Misses        563      505      -58     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@camUrban camUrban requested a review from Copilot January 30, 2026 16:33
@camUrban camUrban marked this pull request as ready for review January 30, 2026 16:34
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements a comprehensive refactoring to enforce attribute immutability across all core classes in Ptera Software. The changes introduce read-only properties, set-once semantics, and lazy caching patterns to improve performance (25-40x for complex simulations) and data integrity.

Changes:

  • Refactored core classes (Panel, Vortex, Geometry, Movement, OperatingPoint, Problem) to use immutable attributes with read-only properties backed by private variables
  • Implemented manual lazy caching for derived properties with cache reset in __deepcopy__ methods
  • Changed mutable collections (lists) to immutable collections (tuples) throughout the API
  • Moved vortex classes to new _vortices/ package structure
  • Updated trim functions to create new OperatingPoint instances instead of mutating existing ones
  • Added comprehensive unit tests for immutability, caching, and deepcopy behavior

Reviewed changes

Copilot reviewed 56 out of 57 changed files in this pull request and generated no comments.

Show a summary per file
File Description
test_wing_movement.py Added immutability, caching, and deepcopy tests for WingMovement
test_wing_cross_section_movement.py Added immutability, caching, and deepcopy tests for WingCrossSectionMovement
test_problems.py Added immutability tests and updated for tuple returns
test_operating_point_movement.py Added immutability tests for OperatingPointMovement
test_operating_point.py Added comprehensive immutability and caching tests
test_movement.py Added immutability, caching tests and LCM method tests
test_line_vortex.py Added immutability and caching tests, removed mutation tests
ring_vortex_fixtures.py Updated imports to new _vortices package
problem_fixtures.py Added multi-airplane unsteady problem fixture
panel_fixtures.py New fixture file for Panel testing
operating_point_fixtures.py Added 8 new fixtures for edge cases
line_vortex_fixtures.py Updated imports to new _vortices package
horseshoe_vortex_fixtures.py Updated imports to new _vortices package
geometry_fixtures.py Added new fixtures and refactored existing ones
fixtures/init.py Updated module documentation
unit/init.py Updated module documentation
operating_point.py Refactored to immutable with lazy caching
problems.py Refactored SteadyProblem and UnsteadyProblem to immutable
movements/*.py Refactored all movement classes to immutable with deepcopy support
_vortices/*.py New package with immutable vortex classes
trim.py Updated to create new OperatingPoints instead of mutating
solver files Updated for new vortex package and tuple types
docs/*.md Updated documentation for conventions and structure
Files not reviewed (1)
  • .idea/dictionaries/project.xml: Language not supported

@camUrban camUrban merged commit 201b989 into main Jan 30, 2026
18 checks passed
@camUrban camUrban deleted the feature/properties branch January 30, 2026 16:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working feature New feature or request maintenance Improvements or additions to documentation, testing, or robustness

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant