pterasoftware.problems

Contains the SteadyProblem, UnsteadyProblem, AeroelasticUnsteadyProblem, and FreeFlightUnsteadyProblem classes.

Classes

SteadyProblem

A class used to contain steady aerodynamics problems.

UnsteadyProblem

A class used to contain unsteady aerodynamics problems.

FreeFlightUnsteadyProblem

A class used to contain problems with coupled unsteady aerodynamics and rigid body dynamics.

AeroelasticUnsteadyProblem

A subclass of _CoupledUnsteadyProblem used to couple aeroelastic wing deformations with unsteady aerodynamics.

Module Contents

class pterasoftware.problems.SteadyProblem(airplanes: list[pterasoftware.geometry.airplane.Airplane], operating_point: pterasoftware.operating_point.OperatingPoint)[source]

A class used to contain steady aerodynamics problems.

Contains the following methods:

reynolds_numbers: A tuple of Reynolds numbers, one for each Airplane in the SteadyProblem.

Parameters:
  • airplanes – The list of the Airplanes for this SteadyProblem.

  • operating_point – The OperatingPoint for this SteadyProblem.

Returns:

None

property reynolds_numbers: tuple[float, Ellipsis]

A tuple of Reynolds numbers, one for each Airplane in the SteadyProblem.

Notes:

The Reynolds number is calculated as: Re = (V x L) / nu, where V is the freestream speed, observed from the Earth frame (vCg__E from OperatingPoint, m/s), L is the characteristic length (c_ref from Airplane, m), and nu is the kinematic viscosity (nu from OperatingPoint, m^2/s).

These Reynolds numbers only consider the freestream speed, not any apparent velocity due to prescribed motion, so be careful interpreting it for cases where this SteadyProblem corresponds to one time step in an UnsteadyProblem.

Returns:

A tuple of Reynolds numbers, one for each Airplane.

class pterasoftware.problems.UnsteadyProblem(movement: pterasoftware.movements.movement.Movement, only_final_results: bool | numpy.bool_ = False)[source]

A class used to contain unsteady aerodynamics problems.

Contains the following methods:

only_final_results: Determines whether the solver will only calculate loads for the final time step or final cycle.

num_steps: The number of time steps.

delta_time: The time step size in seconds.

first_averaging_step: The first time step included in cycle averaging.

first_results_step: The first time step for which loads are calculated.

max_wake_rows: The maximum chordwise wake rows per Wing.

movement: The Movement that contains this UnsteadyProblem’s OperatingPointMovement and AirplaneMovements.

steady_problems: A tuple of SteadyProblems, one for each time step.

Parameters:
  • movement – The Movement that contains this UnsteadyProblem’s OperatingPointMovement and AirplaneMovements.

  • only_final_results – Determines whether the Solver will only calculate loads for the final time step (for static Movements) or (for non static Movements) for will only calculate loads for the time steps in the final complete motion cycle (of the Movement’s sub Movement with the longest period), which increases simulation speed. Can be a bool or a numpy bool and will be converted internally to a bool. The default is False.

Returns:

None

class pterasoftware.problems.FreeFlightUnsteadyProblem(movement: pterasoftware.movements.free_flight_movement.FreeFlightMovement, mass: float | int, I_BP1_CgP1: numpy.ndarray | collections.abc.Sequence[collections.abc.Sequence[float | int]], external_loads_fn: collections.abc.Callable[[pterasoftware.operating_point.OperatingPoint, pterasoftware.geometry.airplane.Airplane], tuple[numpy.ndarray, numpy.ndarray]] | None = None, extra_xml: dict[str, str] | None = None, mujoco_assets: dict[str, bytes] | None = None)[source]

Bases: _CoupledUnsteadyProblem

A class used to contain problems with coupled unsteady aerodynamics and rigid body dynamics.

Contains the following methods:

only_final_results: Determines whether the solver will only calculate loads for the final time step or final cycle.

num_steps: The number of time steps.

delta_time: The time step size in seconds.

first_averaging_step: The first time step included in cycle averaging.

first_results_step: The first time step for which loads are calculated.

max_wake_rows: The maximum chordwise wake rows per Wing.

movement: The FreeFlightMovement that defines the motion parameters for this FreeFlightUnsteadyProblem.

steady_problems: A tuple of SteadyProblems, one for each time step that has been initialized so far.

get_steady_problem: Gets the SteadyProblem at a specified time step.

initialize_next_problem: Initializes the next time step’s SteadyProblem from rigid body dynamics.

mass: The mass of the Airplane in kilograms.

I_BP1_CgP1: The inertia matrix of the Airplane (in the first Airplane’s body axes, relative to the first Airplane’s CG) in kilogram square meters.

external_loads_fn: A callable that computes additional forces and moments to apply to the Airplane during the simulation, or None.

mujoco_model: The MuJoCoModel used for rigid body dynamics integration.

Parameters:
  • movement – The FreeFlightMovement that defines the prescribed Airplane geometry for this FreeFlightUnsteadyProblem. The initial Airplane and OperatingPoint are derived from the FreeFlightMovement at the first time step. The FreeFlightMovement must contain exactly one FreeFlightAirplaneMovement; multi-airplane free flight is not supported in this release.

  • mass – A number (int or float) representing the mass of the Airplane. It must be greater than zero and will be converted internally to a float. The units are in kilograms. It must satisfy weight == mass * |g_E| within floating point tolerance, where weight is the Airplane’s weight and g_E is the OperatingPoint’s gravitational acceleration, which keeps the Airplane’s weight, the supplied mass, and the gravitational field mutually consistent.

  • I_BP1_CgP1 – An array-like object of numbers (int or float) with shape (3,3) representing the inertia matrix of the Airplane (in the first Airplane’s body axes, relative to the first Airplane’s CG). It must be symmetric. Can be a tuple, list, or ndarray. Values are converted to floats internally. The units are in kilogram square meters.

  • external_loads_fn – A callable that computes additional forces and moments to apply to the Airplane during the simulation. It takes an OperatingPoint and an Airplane and returns a tuple of two (3,) ndarrays of floats: the additional force (in wind axes, in Newtons) and the additional moment (in wind axes, relative to the first Airplane’s CG, in Newton meters). The return value is validated on the callable’s first invocation; a return that is not a pair of (3,) finite numeric vectors raises a descriptive error. The physical correctness of the forces and moments themselves is not checked. Setting this to None applies no additional loads. The default is None.

  • extra_xml – A dict mapping injection point names to XML fragment strings to inject into the MuJoCo model’s XML. Supported keys are “default”, “asset”, “visual”, “worldbody”, and “body”. Setting this to None injects no extra XML. The default is None. The argument is checked to be a dict (or None) whose keys are supported injection points and whose values are strings; the XML fragments themselves are not validated, which is left to MuJoCo, so this is an advanced-user parameter.

  • mujoco_assets – A dict mapping virtual filenames to their binary contents for the MuJoCo model. Setting this to None provides no extra assets. The default is None. The argument is checked to be a dict (or None) mapping string filenames to bytes; whether a referenced asset is actually supplied is left to MuJoCo, so this is an advanced-user parameter.

Returns:

None

initialize_next_problem(solver: pterasoftware._coupled_unsteady_ring_vortex_lattice_method.CoupledUnsteadyRingVortexLatticeMethodSolver, step: int) None[source]

Initializes the next time step’s SteadyProblem from rigid body dynamics.

On every step except the last one, steps the MuJoCo dynamics forward, extracts the new state, and creates the next SteadyProblem with the new OperatingPoint and the prescribed Airplane geometry for the next step. During the free flight phase (once the step index reaches the movement’s prescribed_num_steps), it first transforms the aerodynamic loads into Earth axes and applies them, along with weight and any external loads, to the MuJoCo model before stepping. During the prescribed phase, those loads are withheld so the body coasts at its initial trimmed condition while the wake develops. On every step (including the last one), records the current step’s loads in the load history lists.

Parameters:
  • solver – The CoupledUnsteadyRingVortexLatticeMethodSolver instance providing aerodynamic data from the current time step.

  • step – The current time step index (zero indexed).

Returns:

None

get_steady_problem(step: int) SteadyProblem

Get the SteadyProblem at a given time step.

Parameters:

step – The time step index (zero indexed). Must be greater than or equal to zero and less than the total number of time steps.

Returns:

The SteadyProblem at the specified time step.

class pterasoftware.problems.AeroelasticUnsteadyProblem(movement: pterasoftware.movements.aeroelastic_movement.AeroelasticMovement, wing_density: float, spring_constant: float, damping_constant: float, aero_scaling: float = 1.0, step_discards: int = 5, moment_scaling_factor: float = 1.0, plot_flap_cycle: bool = False)[source]

Bases: _CoupledUnsteadyProblem

A subclass of _CoupledUnsteadyProblem used to couple aeroelastic wing deformations with unsteady aerodynamics.

This class couples aerodynamic loads with wing structural dynamics (spring-mass- damper system) to simulate aeroelastic deformation. Each time step, wing deformations are calculated based on the combined effects of aerodynamic moments, inertial forces, and spring-damper restoring forces.

Contains the following methods:

calculate_wing_panel_accelerations: Computes panel accelerations from finite difference of positions.

calculate_mass_matrix: Generates the mass distribution matrix for wing panels.

calculate_wing_deformation: Computes cumulative wing deformation for the current step.

calculate_spring_moments: Solves the torsional spring-damper ODE for each spanwise section, returning angular states.

calculate_torsional_spring_moment: Solves the torsional spring-damper ODE for a single span section.

generate_inertial_torque_function: Creates a torque function from prescribed wing motion.

spring_numerical_ode: Numerically integrates the spring-damper differential equation.

plot_flap_cycle_curves: Visualizes moment and deformation time histories.

Notes:

The aeroelastic coupling assumes a torsional spring-mass-damper model for each spanwise section. Wing motion is prescribed through wing flapping, and aerodynamic moments from the solver are combined with inertial and spring restoring forces via ODE integration to produce structural deformations.

Sets up the aeroelastic problem with structural parameters for the torsional spring-mass-damper model applied to each wing spanwise section. Initializes storage for aerodynamic loads, deformations, moments, and solver state.

See _CoupledUnsteadyProblem’s initialization method for descriptions of inherited parameters.

Parameters:
  • movement – An AeroelasticMovement object containing the prescribed motion and aerodynamic setup for the aeroelastic simulation.

  • wing_density – The mass per unit span area of the wing (kg/m^2). Used to distribute wing mass across panels for inertial calculations.

  • spring_constant – The torsional spring stiffness for the spring-mass- damper model (N*m/rad). Controls the restoring torque opposing deformation.

  • damping_constant – The torsional damping coefficient (N*m*s/rad). Controls the viscous damping in the spring-mass-damper system.

  • aero_scaling – A scaling factor applied to aerodynamic moments (unitless). The default is 1.0. Use values less than 1 to reduce aerodynamic influence.

  • step_discards – The number of initial time steps to discard for numerical stability (there are inconsistent startup effects from the UVLM solver). During these steps, the solver will run but the results will not be applied to the deformation of the wings. The default is 5.

  • moment_scaling_factor – A scaling factor applied to the computed wing deformation angles (unitless). The default is 1.0. Useful for adjusting the magnitude of structural response.

  • plot_flap_cycle – If True, plots time histories of moments and deformations at the end of the simulation. The default is False.

Returns:

None

property wing_movement: pterasoftware.movements.aeroelastic_wing_movement.AeroelasticWingMovement

Return the primary wing movement definition used by the aeroelastic model.

calculate_wing_panel_accelerations(wing_idx: int = 0) numpy.ndarray[source]

Compute panel accelerations using finite difference of stored positions.

Calculates second-order accelerations using the finite difference formula: a = (p[n] - 2*p[n-1] + p[n-2]) / dt^2.

Parameters:

wing_idx – The index of the wing in airplane.wings whose positions are used for the acceleration calculation. The default is 0.

Returns:

An (N_chordwise, N_spanwise, 3) ndarray of floats representing panel center accelerations in the global frame. Returns zeros if fewer than 3 position snapshots are available.

calculate_mass_matrix(wing: pterasoftware.geometry.wing.Wing) numpy.ndarray[source]

Generate the mass distribution matrix for all wing panels.

Distributes the total spanwise mass (wing_density) across panel areas to form a panel-by-panel mass matrix. Each panel’s mass is proportional to its area times the specified wing_density.

Parameters:

wing – A Wing object whose panels define the mass distribution.

Returns:

An (N_chordwise, N_spanwise, 3) ndarray of floats representing the mass at each panel. The three components are identical (mass scalar replicated for x, y, z axes).

initialize_next_problem(solver: pterasoftware._coupled_unsteady_ring_vortex_lattice_method.CoupledUnsteadyRingVortexLatticeMethodSolver, step: int) None[source]

Initialize the next time step’s SteadyProblem and perform per step work.

Subclasses must override this method. It is invoked by the solver on every step, so subclasses are responsible for guarding any work that depends on a next step existing (such as building the next SteadyProblem) with step < self.num_steps - 1. Per step work that should run on every step (such as recording the current step’s loads) belongs outside that guard.

Parameters:
  • solver – The CoupledUnsteadyRingVortexLatticeMethodSolver instance providing aerodynamic data from the current time step.

  • step – The current time step index (zero indexed).

Returns:

None

Raises:

NotImplementedError – Always. Subclasses must override this method.

calculate_wing_deformation(solver: pterasoftware.aeroelastic_unsteady_ring_vortex_lattice_method.AeroelasticUnsteadyRingVortexLatticeMethodSolver, step: int) list[numpy.ndarray | None][source]

Compute cumulative wing deformation for the current time step.

Loops over every Wing in the current airplane. For wings backed by an AeroelasticWingMovement, orchestrates the inertial moment, aero moment, and spring ODE integration, updates internal per-wing state, and returns the deformation array. For wings backed by a standard WingMovement, records null entries in the history lists and returns None for that wing.

Parameters:
  • solver – The solver instance providing aerodynamic moment data (moments_GP1_Slep and stack_leading_edge_points).

  • step – The current time step index (0-indexed).

Returns:

A list of length len(airplane.wings) where each element is either an (N_spanwise+1, 3) ndarray of cumulative deformation angles for an aeroelastic wing or None for a non-aeroelastic wing.

calculate_spring_moments(num_spanwise_panels: int, wing: pterasoftware.geometry.wing.Wing, mass_matrix: numpy.ndarray, aero_moments: numpy.ndarray, step: int, wing_idx: int, wing_movement: pterasoftware.movements.aeroelastic_wing_movement.AeroelasticWingMovement) tuple[numpy.ndarray, numpy.ndarray][source]

Solve the torsional spring-damper ODE for each spanwise section.

Solves the torsional spring-damper ODE independently for each spanwise section, accounting for aerodynamic moments, inertial forces, and structural properties. Uses the parallel axis theorem to compute rotational inertia about the flapping axis.

Parameters:
  • num_spanwise_panels – Number of spanwise panel rows in the wing.

  • wing – The Wing object containing geometric and structural definitions.

  • mass_matrix – An (N_chordwise, N_spanwise, 3) ndarray of panel masses.

  • aero_moments – An (N_chordwise, N_spanwise, 3) ndarray of aerodynamic moments from the aerodynamic solver.

  • step – The current time step index.

  • wing_idx – Index of the wing in airplane.wings.

  • wing_movement – The AeroelasticWingMovement providing the prescribed flapping parameters used for inertial torque generation.

Returns:

A tuple of two ndarrays: - thetas: (N_spanwise+1,) ndarray of torsional angles (radians) at each station. - omegas: (N_spanwise+1,) ndarray of angular velocities (rad/s) at each station. Notes: The rotational inertia is computed as: I = (1/12)*M*(L^2 + W^2) + M*d^2, where M is panel mass, L is chord, W is span width, and d is distance from the flapping axis (computed cumulatively using the parallel axis theorem).

calculate_torsional_spring_moment(dt: float, I: float, theta0: float, omega0: float, aero_span_moment: float, step: int, span_I: float, wing_movement: pterasoftware.movements.aeroelastic_wing_movement.AeroelasticWingMovement | None = None, num_steps: int = 2) tuple[float, float][source]

Solve the torsional spring-damper ODE for a single wing section.

Integrates the forced torsional damped harmonic oscillator equation: I*d(omega)/dt = tau_aero + tau_inertial - k*theta - c*omega

Returns the angular displacement and velocity at the end of the time step.

Parameters:
  • dt – The time step duration (seconds).

  • I – The rotational inertia about the flapping axis (kg*m^2).

  • theta0 – Initial torsional angle at the start of the time step (radians).

  • omega0 – Initial angular velocity at the start of the time step (rad/s).

  • aero_span_moment – The y-component aerodynamic moment summed over chordwise panels for this spanwise section (N*m).

  • step – The current time step index (used for inertial torque evaluation).

  • span_I – The rotational inertia including parallel axis theorem (kg*m^2). This is the actual inertia used in the ODE solver.

  • wing_movement – The AeroelasticWingMovement whose prescribed flapping parameters are used. When None, falls back to self.wing_movement. The default is None.

  • num_steps – Number of time sub-steps for numerical integration. The default is 2.

Returns:

A tuple of (theta, omega) where: - theta: Final torsional angle (radians). - omega: Final angular velocity (rad/s).

generate_inertial_torque_function(span_I: float, wing_movement: pterasoftware.movements.aeroelastic_wing_movement.AeroelasticWingMovement | None = None)[source]

Generate the prescribed wing motion inertial torque function.

Extracts the prescribed flapping motion from the wing_movement definition and creates a callable inertial torque function tau_inertial = I * d^2(theta_prescribed)/dt^2. Supports sinusoidal and custom spacing functions.

Parameters:
  • span_I – The rotational inertia of the wing span section about the flapping axis (kg*m^2).

  • wing_movement – The AeroelasticWingMovement whose prescribed flapping parameters are used. When None, falls back to self.wing_movement. The default is None.

Returns:

A callable function that accepts time and returns the inertial torque (N*m) due to the prescribed wing motion acceleration. Notes: For sinusoidal spacing: tau = -I * b^2 * sin(b*t + h) * A, where b = 2*pi/period, h = phase, A = amplitude. For custom spacing, uses the wing movement’s spacingAnglesSecondDerivative_Gs_to_Wn_ixyz, which its constructor guarantees is present whenever the spacing is a custom callable.

spring_numerical_ode(t: numpy.ndarray, k: float, c: float, I: float, theta0: float, omega0: float, aero_torque: float, inertial_torque_func) tuple[float, float][source]

Numerically integrate the torsional spring-damper ODE.

Solves the second-order forced ODE: I * d^2(theta)/dt^2 = tau_aero + tau_inertial(t) - k*theta - c*d(theta)/dt

using scipy.integrate.solve_ivp with strict tolerances.

Parameters:
  • t – A (N,) ndarray of time points for integration evaluation.

  • k – Spring constant (N*m/rad).

  • c – Damping constant (N*m*s/rad).

  • I – Rotational inertia (kg*m^2). This parameter is present for potential alternative models of inertia.

  • theta0 – Initial angular displacement (radians).

  • omega0 – Initial angular velocity (rad/s).

  • aero_torque – Constant aerodynamic torque acting on the section (N*m).

  • inertial_torque_func – A callable function of time that returns the inertial torque from prescribed motion acceleration (N*m).

Returns:

A tuple of (theta, omega) representing the final angle and angular velocity at the last time point in t.

plot_flap_cycle_curves(data: list, title: str, flap_cycle=None) None[source]

Visualize time histories of moments, deformations, or forces.

Creates a multi-curve line plot showing moment or deformation values across all time steps, with optional overlay of a reference flap cycle.

Parameters:
  • data – A list of lists where each inner list represents a curve to plot. Values in each curve are plotted against step number.

  • title – The title for the plot and the output PNG filename (spaces replaced with underscores).

  • flap_cycle – Optional reference curve to overlay on the plot. If provided, should be a list of values to plot with label “Flap Cycle” in black. The default is None.

Returns:

None Notes: The plot is saved as a PNG file with the title as the filename. The plot window is displayed to the user. Figure size is 12x6 inches at 200 DPI.

get_steady_problem(step: int) SteadyProblem

Get the SteadyProblem at a given time step.

Parameters:

step – The time step index (zero indexed). Must be greater than or equal to zero and less than the total number of time steps.

Returns:

The SteadyProblem at the specified time step.