Skip to main content
Version: 0.4.0

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.