Skip to main content

DRA2303 Airfoil Environments

This module provides DRA2303 airfoil CFD environments for reinforcement learning with two simulation scenarios at Ma=0.2 and Ma=0.7 with chord-based Re=400000.

Two actuation strategies share the DRA2303Base class:

DRA2303Jet Jet-based actuation. Actions lie in [−MAX_CONTROL, +MAX_CONTROL] and are passed directly to the CFD solver.

DRA2303SurfaceWave Actuated traveling surface wave parameterized by [amplitude, speed, wavelength]. Features two wave zones (pressure side and suction side) that are both actuated using the same wave parameters. All three parameters are strictly positive, which requires a per-action asymmetric action space and a non-zero reset; see the class docstring for config details.

Both environments receive the same solver force output [F_x, F_y, F_z, C_P, area] (nDim + 2 elements), where C_P is the power coefficient (0.0 for the jet case) and area is the wetted wall area used for normalisation. Forces and power are pre-normalised by the solver by dynamic pressure (q_∞ = ρ_∞ · ½ · U_∞²) and q_∞ · U_∞ respectively, so dividing by area yields the final dimensionless coefficients. Observation sizing and normalization for this extended force vector are handled in DRA2303Base.

DRA2303Base Objects

class DRA2303Base(MaiaFlowEnv)

Base class for DRA2303 airfoil environments.

Uses the structured-grid m-AIA solver (MAIA_STRCTRD).

Supports two simulation scenarios:

  • Ma = 0.2 with chord-based Re = 400000
  • Ma = 0.7 with chord-based Re = 400000

Both actuation variants (jet and surface wave) receive the solver force output [F_x, F_y, F_z, C_P, area] (nDim + 2 elements). Forces F_x … F_z and C_P are pre-normalised by the solver using the dynamic pressure q_∞ = ρ_∞ · ½ · U_∞² (forces) and q_∞ · U_∞ (power), so dividing by the returned area gives the final dimensionless drag, lift, and power coefficients.

This class provides:

  • configure_observations() – sizes the force observation slot to nDim + 2 per boundary.
  • setup_normalization() – handles the C_P and area elements in the 'U_inf' normalization strategy.
  • get_reward() – unified reward for both actuation variants:

.. math::

R = -C_D - \omega \cdot C_P

where C_D = F_x / area and C_P = forces[3] / area. For the jet case C_P = 0, so the reward reduces to -C_D.

configure_observations

def configure_observations() -> None

Configure the number of observations.

Overrides the base class to account for the extended solver force output [F_x, F_y, F_z, C_P, area] (nDim + 2 elements per boundary instead of nDim).

setup_normalization

def setup_normalization() -> None

Set up observation normalization.

Overrides the base class to handle the extended force vector [F_x, F_y, F_z, C_P, area] (nDim + 2 elements per boundary).

For the 'U_inf' strategy, free-stream values computed from the isentropic relations in __init__ are used:

  • velocities scaled by U_T (total free-stream speed),
  • density by rho_inf,
  • pressure by the dynamic pressure q_inf = ½ · rho_inf · U_T².

Force-slot entries use loc = 0, scale = 1 since the solver already normalises them by q_inf (forces) / q_inf·U_T (power).

get_reward

def get_reward() -> Tuple[float, Dict]

Compute the unified reward: :math:R = -C_D - \omega \cdot C_P.

The solver returns [F_x, F_y, F_z, C_P, area]. Forces and power are pre-normalised by q_∞ and q_∞ · U_∞ respectively; dividing by area yields the final dimensionless coefficients::

C_D = forces[0] / area C_P = forces[3] / area

For the jet environment C_P = 0 and the reward reduces to -C_D.

Returns:

Tuple of (reward, info_dict). info_dict contains 'forces', 'C_D', and 'C_P'.

DRA2303Jet Objects

class DRA2303Jet(DRA2303Base)

DRA2303 airfoil with jet-based flow control.

Uses the same actuation setup as the :class:~hydrogym.maia.envs.Cube environment: actions lie in [−MAX_CONTROL, +MAX_CONTROL] (scaled by the base-class step()) and are passed directly to the CFD solver. The number of jet actuators is read from fvNoJets in the property file.

The solver returns [F_x, F_y, F_z, C_P, area] with C_P = 0.0, so the reward reduces to -C_D. Observation sizing, normalization, and reward computation are all provided by :class:``3.

Attributes:

  • ``4 int - Number of jet boundary conditions.

convert_action

def convert_action(action: np.ndarray) -> np.ndarray

Pass the (already MAX_CONTROL-scaled) jet actuation to the solver.

Arguments:

  • action - Action array scaled by MAX_CONTROL.

Returns:

Action array for the CFD solver.

DRA2303SurfaceWave Objects

class DRA2303SurfaceWave(DRA2303Base)

DRA2303 airfoil with actuated traveling surface wave.

The surface is driven by a traveling wave with three strictly positive control parameters:

============= ============================================= Parameter Description ============= ============================================= amplitude Wave amplitude speed Wave propagation speed wavelength Spatial period of the wave ============= =============================================

Two wave zones: This environment features two wave zones, one on the pressure side and one on the suction side of the airfoil. Both zones are actuated using the same wave parameters [amplitude, speed, wavelength].

Differences from :class:DRA2303Jet:

  • Action space – Per-action [lower_bound, upper_bound] read from maia.action_lower_bounds / maia.action_upper_bounds in the config. The MAX_CONTROL scaling in step() is not applied.
  • Reset – Uses [amplitude_init, speed_init, wavelength_init] from config (zeros would crash the CFD solver).
  • Reward – :math:5 (shared with :class:`DRA2303Jet` via :class:7; here C_P > 0 because the surface wave performs work on the fluid).

Required additional entries in the maia section of the YAML config:

.. code-block:: yaml

maia: amplitude_init: 0.05 speed_init: 1.0 wavelength_init: 2.0 action_lower_bounds: [0.01, 0.1, 0.5] action_upper_bounds: [0.2, 3.0, 10.0] omega: 0.1 wave_zone_ids: [10, 11] # pressure side, suction side BC IDs

set_observation_action_spaces

def set_observation_action_spaces() -> None

Override to use a normalized [0, 1] action space per parameter.

The RL agent always operates in [0, 1]; physical values are recovered inside :meth:convert_action via the per-parameter affine mapping lower + action * (upper - lower).

convert_action

def convert_action(action: np.ndarray) -> List[float]

Scale a normalized [0, 1] action to physical wave parameters.

Applies the per-parameter affine map::

physical[i] = lower[i] + action[i] * (upper[i] - lower[i])

The same physical parameters are applied to both wave zones (pressure side and suction side).

Arguments:

  • action - Normalized action array with values in [0, 1], corresponding to [amplitude, speed, wavelength].

Returns:

Physical actuation values for the CFD solver.

step

def step(
action: Optional[np.ndarray] = None
) -> Tuple[np.ndarray, float, bool, bool, Dict]

Advance the environment by one step.

Overrides the base class to skip MAX_CONTROL scaling. The incoming action is in [0, 1] and is converted to physical units by :meth:convert_action. The same physical wave parameters are applied to both wave zones (pressure and suction side).

Arguments:

  • action - Normalized action in [0, 1] for [amplitude, speed, wavelength].

Returns:

Tuple of (observation, reward, terminated, truncated, info).

reset

def reset(seed: Optional[int] = None,
options: Optional[Dict] = None) -> Tuple[np.ndarray, Dict]

Reset the environment with physically valid initial wave parameters.

Overrides the base-class reset to avoid setting zero control actions, which would crash the CFD solver for the surface-wave boundary condition. init_action is passed in physical units directly, bypassing the [0, 1] → physical scaling used during normal steps. The same initial parameters are applied to both wave zones.

Arguments:

  • seed - Optional random seed.
  • options - Optional reset options (unused).

Returns:

Tuple of (initial_observation, info).