Skip to main content
Version: Next

OpenAeroStruct Aerostructural Analysis

This tutorial demonstrates two approaches for running an OAS aerostructural analysis through Philote: a monolithic discipline that wraps the full coupled analysis, and a split decomposition with geometry, aerodynamics, and structures as three independent gRPC services coupled on the client side.

By the end of this tutorial you will be able to:

  • Wrap a coupled OAS aerostructural analysis as a single Philote discipline.
  • Decompose the analysis into three separate disciplines with client-side coupling.
  • Configure wing geometry and material properties via discipline options.
  • Run both approaches and compare results.

Prerequisites

From the repository root:

pip install -e .

This installs philote-mdo, openmdao, openaerostruct, and numpy as dependencies.

Monolithic Example

Architecture

The OasAerostructDiscipline wraps the complete OAS AerostructGeometry + AerostructPoint pipeline as a single Philote discipline. The internal NonlinearBlockGS solver handles the aero-structural coupling automatically.

┌──────────────────────────────────────────────────────────────┐
│ OasAerostructDiscipline (port 50051) │
│ │
│ AerostructGeometry ──▶ AerostructPoint (coupled VLM + FEM) │
│ │
│ 11 inputs ──▶ 6 outputs │
└──────────────────────────────────────────────────────────────┘

Running the analysis

cd examples/oas_aerostruct
python run_analysis.py

Expected output:

OAS aerostructural analysis: v=248.136 m/s, alpha=5.0 deg, M=0.84, Re=1e+06/m, rho=0.38 kg/m^3
CL = 0.5700
CD = 0.037955
CM = [0.0000, -0.7041, 0.0000]
Fuel burn = 241347.34 kg
Failure = -0.8922
Structural mass = 252490.91 kg

Standalone server

Terminal 1:

python server.py

Terminal 2:

from run_analysis import run

cl, cd, cm, fuelburn, failure, struct_mass = run(start_server=False)

Key inputs and outputs

Inputs -- flight conditions and mission parameters:

NameUnitsDescription
vm/sFreestream velocity
alphadegAngle of attack
Mach_number--Mach number
re1/mReynolds number per unit length
rhokg/m^3Air density
CT1/sThrust-specific fuel consumption
RmRange
W0kgOperating empty weight
speed_of_soundm/sSpeed of sound
load_factor--Load factor
empty_cgmEmpty-weight center of gravity (3-vector)

Outputs:

NameDescription
CLLift coefficient
CDDrag coefficient
CMMoment coefficient (3-vector)
fuelburnFuel burn (kg)
failureStructural failure index (< 0 means safe)
structural_massStructural mass (kg)

Split Example

Architecture

The split example decomposes the analysis into three independent Philote disciplines:

┌──────────────┐
│ Geometry │ (port 50051)
│ twist_cp │
│ thickness_cp│
└──────┬───────┘
mesh, nodes, │ stiffness, radius,
t_over_c │ thickness
┌────────────┴────────────┐
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ Aerodynamics │ │ Structures │
│ (port 50052) │ │ (port 50053) │
│ │loads│ │
│ VLM + disp/load │────▶│ Beam FEM solve │
│ transfer │◀────│ │
│ │disp │ │
└──────────────────┘ └──────────────────┘

Coupled via NonlinearBlockGS (client-side)

The geometry discipline runs once to produce the mesh and structural setup. The aero and struct disciplines iterate inside a coupled group until the displacements and loads converge.

Running the analysis

cd examples/oas_aerostruct_split
python run_analysis.py

Expected output:

Split OAS aerostructural analysis: v=248.136 m/s, alpha=5.0 deg, M=0.84, Re=1e+06/m, rho=0.38 kg/m^3
CL = 0.5700
CD = 0.037955
Failure = -0.8922

Results match the monolithic case exactly.

Standalone servers

Terminal 1: python geom_server.py Terminal 2: python aero_server.py Terminal 3: python struct_server.py Terminal 4:

from run_analysis import run

cl, cd, failure = run(start_servers=False)

Client-side coupling

The client script assembles the three disciplines in OpenMDAO:

# Coupled group with Gauss-Seidel solver
coupled = om.Group()
coupled.add_subsystem("struct", RemoteExplicitComponent(channel=struct_channel))
coupled.add_subsystem("aero", RemoteExplicitComponent(channel=aero_channel))

coupled.connect("aero.loads", "struct.loads")
coupled.connect("struct.disp", "aero.disp")

coupled.nonlinear_solver = om.NonlinearBlockGS(use_aitken=True)
coupled.nonlinear_solver.options["maxiter"] = 100
coupled.nonlinear_solver.options["atol"] = 1e-7

The subsystem order (struct before aero) matches the execution order inside OAS's CoupledAS group, ensuring consistent convergence behavior.

Initializing coupling variables

RemoteExplicitComponent initializes all inputs to 1.0 by default. For correct convergence, coupling variables must be zero-initialized after prob.setup():

prob.set_val("coupled.aero.disp", np.zeros((ny, 6)))
prob.set_val("coupled.struct.loads", np.zeros((ny, 6)))

Geometry design variables must also be set to their correct baseline values (e.g., twist_cp from generate_mesh(), thickness_cp).

Large Mesh Examples

Both monolithic and split examples have large-mesh counterparts in examples/oas_aerostruct_large/ and examples/oas_aerostruct_split_large/ using a 21x7 CRM wing mesh. These demonstrate how to configure disciplines with custom mesh options:

MESH_DICT = {
"num_y": 21,
"num_x": 7,
"wing_type": "CRM",
"symmetry": True,
"num_twist_cp": 5,
}

discipline = OasAerostructDiscipline(mesh_dict=MESH_DICT)

Run them the same way:

cd examples/oas_aerostruct_large
python run_analysis.py

Configuring via gRPC Options

All OAS disciplines support runtime configuration through Philote options. Instead of passing constructor arguments, a remote client can send options before setup:

client.send_options({
"mesh_dict": {
"num_y": 21,
"num_x": 7,
"wing_type": "CRM",
"symmetry": True,
},
"surface": {
"E": 73.1e9,
"G": 33.0e9,
"with_viscous": True,
},
})

The mesh_dict option controls wing planform generation. The surface option overrides default material and aerodynamic properties, using the same keys as the OAS surface dictionary.

Troubleshooting

Port already in use The default ports are 50051--50053. If other processes are using them, stop them or change the PORT / GEOM_PORT / AERO_PORT / STRUCT_PORT constants.

Convergence failure If the split example fails to converge, check that coupling variables are zero-initialized and geometry design variables are set to correct baseline values after prob.setup().

Mismatched results between monolithic and split Ensure all three split disciplines use the same mesh_dict as the monolithic discipline. Different mesh sizes produce different variable shapes and will cause connection errors or incorrect results.