Skip to content

Polish the aeroelastic API for v5.1.0#222

Open
camUrban wants to merge 11 commits into
mainfrom
aeroelastic_api
Open

Polish the aeroelastic API for v5.1.0#222
camUrban wants to merge 11 commits into
mainfrom
aeroelastic_api

Conversation

@camUrban

@camUrban camUrban commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Description

This PR gives the aeroelastic feature's public surface the same pre-tag polish the free-flight feature received in PR #206 and PR #207. It is a naming and visibility pass, not a behavior change: the structural spring-mass-damper model, its coupling to the solver, and every computed result are unchanged, and the two deformation examples' expected outputs needed no regeneration (the plotting example's was regenerated only because its plot was renamed and retitled). The changes fall into three groups: names brought to the documented conventions (most visibly, radians-valued names now declare their units), internal working state and helper methods made private, and a small amount of redundant or unused surface removed.

@JonahJ27: most of the touched code is your aeroelastic work, so this PR's review is meant to double as a walkthrough. Since re-adding public surface before the tag is additive and free, everything removed here is easy to bring back: if any removed method, property, or recorded quantity should stay, flag it in review and I will happily restore it.

Motivation

None of the aeroelastic problem, solver, or movement API has shipped in a release yet, so the window before the v5.1.0 tag is the only time this surface can be adjusted without breaking changes; after the tag, every public name becomes a compatibility commitment. The naming work continues the direction set by PR #190 and PR #198: after those units fixes, marking radians-based units in the names themselves, rather than only in docstrings, is the systematic guard against the same class of mismatch, and the convention docs now state the rules the renames follow. The visibility work keeps the deliberately public read surface small and matches the free-flight classes: what stays public is what a user or the coupled problem genuinely reads (the per-wing deformation time series, the structural constants, and the solver's per-panel moments about the strip leading edge points), while the internals stay free to evolve.

Relevant Issues

None.

Changes

  • Consolidate AeroelasticUnsteadyProblem's per-wing structural state: the nine *_per_wing lists are now two seeded per-wing time series, the public listDeformationAnglesYRad_Wcsp_to_Wcs_ixyz and the private _listDeformationAnglesDerivativeYRad_Wcsp_to_Wcs_ixyz. The old lists were state/history pairs recording the same quantities twice, plus feeders for the plotting machinery removed below, so the two series subsume them. The record-versus-hold behavior under step_discards is unchanged and is now pinned by new unit tests of _record_structural_state.
  • Remove the flap-cycle plotting and recording machinery from problems.py: plot_flap_cycle_curves, the plot_flap_cycle parameter, calculate_wing_panel_accelerations, the finite-difference inertial-moment recording, and the module-level matplotlib import. The finite-difference moments never fed the deformation ODE, whose inertial forcing is the analytic prescribed-motion function, so this is behavior-neutral, and it also stops the meshing of a throwaway undeformed reference Airplane every time step, a cost previously paid even with plotting off. The supported plotting pattern (read the public time series and plot user-side) lives in examples/aeroelastic_unsteady_first_order_plotting.py.
  • Underscore-prefix the six structural methods on AeroelasticUnsteadyProblem (_calculate_mass_matrix, _calculate_wing_deformation, _calculate_spring_moments, _calculate_torsional_spring_moment, _generate_inertial_moment_function, and _spring_numerical_ode); nothing outside the class called them. generate_inertial_torque_function is also renamed to _generate_inertial_moment_function to match the moment vocabulary used everywhere else.
  • Delete the wing_movement property, which was hardcoded to airplane_movements[0].wing_movements[0] and therefore arbitrary for any multi-wing configuration (the shipped examples carry three WingMovements). The two structural methods that read prescribed flapping parameters now take their AeroelasticWingMovement explicitly, and the plotting example computes its flap-angle overlay from the same module-level constants that parameterize its wing movement constructions.
  • Privatize seven of the nine strip-leading-edge-point slots on AeroelasticUnsteadyRingVortexLatticeMethodSolver. moments_GP1_Slep stays public as the structural coupling output that AeroelasticUnsteadyProblem reads as its aerodynamic forcing, matching how FreeFlightUnsteadyProblem drives its solver through public surface, and the solver's class docstring now presents it that way.
  • Delete AeroelasticOperatingPointMovement and let AeroelasticMovement accept a standard OperatingPointMovement, mirroring PR Delete free-flight movement markers #207's free-flight marker deletions: the class had empty __slots__ and no overrides, so it only forced users to re-wrap an identical configuration under a different name. A FreeFlightOperatingPointMovement is still rejected.
  • Apply the radians markers across the affected names: spring_constant_rad and damping_constant_rad on the constructor, properties, and slots; the deformation state and structural solve names carry Rad per the angle vector convention; and the last unmarked radians-valued names in _oscillation.py (b_rad and h_rad) and geometry/airfoil.py (thetaSlopeRad_Ax_to_MCL and chord_angle_rad) follow the same rule.
  • Expand the convention docs the renames follow: ANGLE_VECTORS_AND_TRANSFORMATIONS.md (angle vector components, time derivative markers, and the Rad rule), AXES_POINTS_AND_FRAMES.md (component letters, point ID capitalization, and the stack, grid, and list collection prefixes), CODE_STYLE.md (the snake case _rad rule), and WRITING_STYLE.md. CLASSES_AND_IMMUTABILITY.md's tables track the new attribute set.
  • Rework examples/aeroelastic_unsteady_first_order_plotting.py around the public time series and regenerate its expected output (now wing_tip_twist_spring_constant.png, with the pre-solve seed entry plotted), repointing docs/website/examples/aeroelastic_unsteady_first_order_plotting.md in the same pass. All three aeroelastic examples now construct an OperatingPointMovement directly.
  • Bump _FORMAT_VERSION from 16 to 19 across the slot renames and the serialization registry change; no released build ever sees the intermediate integers.
  • Update the tests throughout: the marker class's dedicated test and fixture modules are removed, the structural and solver tests read the renamed or privatized attributes, and the serialization and slots suites cover the new attribute set.

Dependency Updates

None.

Change Magnitude

Moderate: Medium-sized change that adds or modifies a feature without large-scale impact.

Checklist (check each item when completed or not applicable)

  • I am familiar with the current contribution guidelines.
  • PR description links all relevant issues and follows this template.
  • 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.
  • This PR passes the ReadTheDocs build check (this runs automatically with the other workflows).
  • This PR passes the ascii-only, black, codespell, docformatter, isort, and pre-commit-hooks GitHub actions.
  • This PR passes the mypy GitHub action.
  • This PR passes all the tests GitHub actions.

camUrban added 8 commits July 11, 2026 00:56
The aeroelastic naming pass leaned on several conventions that were
only implicit in the codebase: component letters with singular or
plural stems, names for angle component time derivatives, the stack,
grid, and list collection prefixes, capitalized point-ID stems, and the
rule that radians-based values must be marked in the name itself (Rad
in camelCase portions, _rad on snake case names). Record them in
AXES_POINTS_AND_FRAMES.md, ANGLE_VECTORS_AND_TRANSFORMATIONS.md, and
CODE_STYLE.md so future code follows them deliberately.

Also make "time step" and "step" interchangeable in prose and variable
names, exempt compound units like N*m from operator spacing, and
repoint both style docs' convention references from the superseded
AXES_AND_COORDINATE_SYSTEMS.md to the current pair of vector docs.
Replace the nine per-wing state lists on AeroelasticUnsteadyProblem
with two seeded structural time series, because the old current-state
arrays and history lists recorded the same quantities twice and the
remaining lists only fed diagnostics that are now deleted: the
flap-cycle plotting machinery and the finite-difference inertial-moment
and flap-point recording. Those inertial moments never fed the
deformation ODE, whose inertial forcing is the analytic
prescribed-motion function, and their recording meshed a throwaway
undeformed reference Airplane every step, so deleting them is
behavior-neutral and removes that per-step cost. Bump the serialization
format version for the slot changes.

Rename the structural surface to the naming conventions: the
deformation angle series and the structural solve's locals and
parameters carry component letters, Rad markers, and the Wcsp-to-Wcs
sequence IDs, spring_constant_rad and damping_constant_rad declare
their radians-based units in the public constructor, and
generate_inertial_torque_function becomes
generate_inertial_moment_function. Sweep the aeroelastic docstrings and
comments so every mention of a vector-valued quantity or component
names its axes, points, and rotation sequence, replacing references to
the nonexistent SLEP and global panel frames with the first Airplane's
geometry axes and the strip leading edge points.

Adapt the tests, fixtures, and examples to the new surface, and add
unit tests pinning that _record_structural_state records fresh state
after the discard window and holds the previous state within it.
The plotting example now frames its result as the wing-tip twist
history, names its output wing_tip_twist_spring_constant.png, and
includes the pre-solve seed entry in the plotted series, so regenerate
its expected output and repoint the docs page's image reference in the
same change to keep the docs build coherent. Also correct the image's
alt text, which misread the old filename's curve index of 16 as a
spring constant of 16 and now describes the overlaid sweep.
Underscore-prefix AeroelasticUnsteadyProblem's six structural solve
methods, which only the class itself calls: the mass matrix, wing
deformation, spring moments, torsional spring moment, inertial moment
function, and spring ODE routines. Land the change while the
aeroelastic problem API is still unreleased, so retracting the methods
from the public surface is non-breaking and the RTD page stops
advertising coupling internals as user-facing API.

Rewrite the class docstring's method listing, which named only the
now-private methods, to cover the full public surface per the docstring
convention for public subclasses of private parents, mirroring
FreeFlightUnsteadyProblem's wording for the inherited entries and
adding the aeroelastic properties.
Remove AeroelasticUnsteadyProblem's wing_movement property, which was
hardcoded to the first airplane movement's first wing movement: the
name promised a single primary wing movement that multi-wing
configurations do not have, and the production call chain already
passes each Wing's own movement explicitly, leaving the property's
fallback role in the structural methods unreachable outside tests. Land
the removal while the aeroelastic problem API is still unreleased, so
it is non-breaking.

Require the wing_movement parameter on the two structural methods that
read prescribed flapping parameters, dropping the None default and
fallback, so no trace of the first-wing assumption remains on the
class. Rework the plotting example to compute its flap-angle overlay
from module-level flap constants that also parameterize its wing
movement constructions, so the prescribed motion is stated once.
Underscore-prefix seven of the nine SLEP slots on
AeroelasticUnsteadyRingVortexLatticeMethodSolver: the index mapping,
the five relative-position arrays, and the per-panel SLEP position
stack are internal working state that no production code outside the
solver reads. Keep moments_GP1_Slep public, since
AeroelasticUnsteadyProblem's structural solve reads it as its
aerodynamic forcing, and the free-flight precedent has the problem
drive its solver through public surface. Present it in the class
docstring as the structural coupling output, mirroring the free-flight
solver's interface paragraph.

Bump _FORMAT_VERSION from 17 to 18, since the renamed slots change the
field names in saved solver states. The aeroelastic solver API is new
in v5.1.0, so the renames are non-breaking ahead of the tag.
AeroelasticOperatingPointMovement was a pure marker: empty __slots__,
no method overrides, existing only for naming consistency. Its
constructor strictness forced users to mechanically re-wrap an
identical OperatingPointMovement configuration. Delete it and loosen
AeroelasticMovement to accept OperatingPointMovement directly, matching
the free-flight family, whose marker classes were deleted under the
same logic. AeroelasticMovement requires the concrete standard type, so
a FreeFlightOperatingPointMovement is still rejected. Bump
_FORMAT_VERSION from 18 to 19 for the serialization registry change.
An unmarked angle name implies degrees under the naming convention, and
noting radians-based units in a docstring or comment alone is not
enough, so the few radians-valued names that predate the rule are owed
markers. Rename the sinusoid formula locals in the oscillation
functions to b_rad and h_rad, matching the names their aeroelastic
mirror in problems.py already uses, mark the airfoil geometry's mean
camber line slope angles and chord angle with the convention's Rad
camelCase marker and _rad suffix, and mark the aeroelastic plotting
example's spring and damping sweep constants with _RAD to match the
spring_constant_rad and damping_constant_rad parameters they feed.

The renames are local to their files and change no behavior, no public
API, and no plotted output, so no serialization format bump or
expected-output regeneration is owed.
@camUrban camUrban added maintenance Improvements or additions to documentation, testing, robustness, or tooling performance An opportunity to improve performance labels Jul 12, 2026
@camUrban camUrban self-assigned this Jul 12, 2026
@camUrban camUrban added this to the v5.1.0 milestone Jul 12, 2026
PyCharm flags four structural methods on AeroelasticUnsteadyProblem as
candidates for static declaration and three locals as shadowing outer
scope names, all in code this branch touches. The four methods
(_extract_aero_moments, _build_deformation_vector,
_generate_inertial_moment_function, and _spring_numerical_ode) read no
instance state, so declare them static, following
_build_next_operating_point in the same module. Every call site
accesses them through an instance, which binds identically to a static
method, so no call site changes.

Rename _spring_numerical_ode's integration results to final_theta_rad
and final_theta_derivative_rad so they no longer collide with the inner
ODE function's state names, and rename the plotting example's
function-local problem to solved_problem, out of the way of the
module-level sweep variable of the same name.
@codecov

codecov Bot commented Jul 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.72%. Comparing base (3f3a60b) to head (b5671fc).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #222      +/-   ##
==========================================
- Coverage   93.79%   93.72%   -0.07%     
==========================================
  Files          44       43       -1     
  Lines        8600     8506      -94     
==========================================
- Hits         8066     7972      -94     
  Misses        534      534              

☔ View full report in Codecov by Harness.
📢 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 marked this pull request as ready for review July 12, 2026 20:04
@camUrban camUrban requested a review from Copilot July 12, 2026 20:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Polishes the unreleased aeroelastic API ahead of v5.1.0 without changing structural behavior.

Changes:

  • Renames radians-based APIs and privatizes implementation details.
  • Consolidates structural state into per-wing deformation time series.
  • Removes redundant plotting infrastructure and marker classes while updating tests, examples, serialization, and conventions.

Reviewed changes

Copilot reviewed 30 out of 32 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
pterasoftware/problems.py Refines aeroelastic API and state handling.
pterasoftware/aeroelastic_unsteady_ring_vortex_lattice_method.py Privatizes internal SLEP arrays.
pterasoftware/_serialization.py Updates registry and format version.
pterasoftware/_oscillation.py Marks radians-valued local names.
pterasoftware/geometry/airfoil.py Clarifies radians-valued angle names.
pterasoftware/movements/__init__.py Removes deleted marker export.
pterasoftware/movements/aeroelastic_movement.py Accepts standard operating-point movements.
pterasoftware/movements/aeroelastic_airplane_movement.py Clarifies deformation documentation.
pterasoftware/movements/aeroelastic_wing_movement.py Clarifies deformation and moment terminology.
pterasoftware/movements/aeroelastic_operating_point_movement.py Deletes redundant marker class.
examples/aeroelastic_unsteady_first_order_plotting.py Plots the public deformation time series.
examples/aeroelastic_unsteady_first_order_deformation.py Uses the polished API.
examples/aeroelastic_unsteady_first_order_deformation_mixed_wings.py Uses the polished API.
docs/examples_expected_output/aeroelastic_unsteady_first_order_plotting/wing_tip_twist_spring_constant.png Updates plotting example output.
docs/website/examples/aeroelastic_unsteady_first_order_plotting.md References the renamed plot.
docs/ANGLE_VECTORS_AND_TRANSFORMATIONS.md Documents angle-component naming.
docs/AXES_POINTS_AND_FRAMES.md Documents components and collections.
docs/CLASSES_AND_IMMUTABILITY.md Updates aeroelastic attributes.
docs/CODE_STYLE.md Adds radians naming rules.
docs/WRITING_STYLE.md Refines terminology rules.
CLAUDE.md Removes deleted files from the tree.
tests/integration/test_aeroelastic_unsteady_ring_vortex_lattice_method.py Updates physics regressions for the new state API.
tests/unit/test_aeroelastic_movement.py Tests standard operating-point movements.
tests/unit/test_aeroelastic_operating_point_movement.py Removes obsolete marker tests.
tests/unit/test_aeroelastic_unsteady_problem.py Tests renamed methods and state recording.
tests/unit/test_aeroelastic_unsteady_ring_vortex_lattice_method.py Updates private SLEP assertions.
tests/unit/test_serialization.py Verifies renamed state serialization.
tests/unit/test_slots.py Updates slot and property coverage.
tests/unit/fixtures/problem_fixtures.py Uses renamed constructor parameters.
tests/unit/fixtures/movement_fixtures.py Uses standard operating-point movements.
tests/unit/fixtures/aeroelastic_operating_point_movement_fixtures.py Removes obsolete fixtures.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pterasoftware/problems.py Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

@JonahJ27 JonahJ27 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Overall this looks great. My only notes are that the inertial moments while much cleaner, still could use being in their own function for clarity. Also wondering if M for moments is the standard over tau which I usually see for torque

@camUrban

Copy link
Copy Markdown
Owner Author

@JonahJ27 Thanks for the review! I'll factor those out into a new function for clarity. I'm not wedded to either the tau vs. M change, it was just a consistency nit because we refer to this quantities as a moment everywhere else. Happy to revert the local variables back :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

maintenance Improvements or additions to documentation, testing, robustness, or tooling performance An opportunity to improve performance

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants