Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
bec1163
Add GEMSEO interface, examples and tests
FrancoisGallard Sep 9, 2026
1f2abff
Add GEMSEO tutorials to the Docusaurus site
FrancoisGallard Sep 9, 2026
fda56c0
Minor changes : remove changes on doc config, and update gemseo dep
FrancoisGallard Sep 15, 2026
2debb0c
Add tests to close coverage gaps in philote_mdo.gemseo
FrancoisGallard Sep 15, 2026
a3d3185
Add gemseo in main doc pages
FrancoisGallard Sep 15, 2026
c5d1465
Enable @docusaurus/theme-mermaid so the GEMSEO tutorial flowcharts re…
FrancoisGallard Sep 17, 2026
100bd80
docs: keep the GEMSEO pages out of the 0.8.0 snapshot
chrislupp Sep 18, 2026
105673d
docs: sync package-lock.json with the mermaid theme dependency
chrislupp Sep 18, 2026
6722a20
docs: require philote-examples 0.5.1 for the OpenAeroStruct example
chrislupp Sep 19, 2026
ec9254a
docs: correct the PhiloteDiscipline description in the GEMSEO intro
chrislupp Sep 19, 2026
2764659
docs: list GEMSEO as a test dependency
chrislupp Sep 19, 2026
766ab39
Rename gemseo_discipine to gemseo_discipline
chrislupp Sep 19, 2026
ed64e7f
PhiloteDiscipline: use the server's name, reject unsupported variables
chrislupp Sep 19, 2026
e8c7fb0
Check Sellar MDA outputs against the canonical result
chrislupp Sep 19, 2026
4f88d2d
docs: list GEMSEO among the clients in the quickstart
chrislupp Sep 19, 2026
3472f27
Drop support for Python 3.9
chrislupp Sep 19, 2026
9b479b5
feat(gemseo): support discrete variables in PhiloteDiscipline
FrancoisGallard Sep 19, 2026
6e9dd57
feat(gemseo): serve discrete variables from GEMSEOtoPhiloteDiscipline
FrancoisGallard Sep 19, 2026
dd1cdd8
docs(gemseo): document discrete variable support
FrancoisGallard Sep 19, 2026
56adcf2
test(gemseo): cover the non-differentiable branches of the wrapper
FrancoisGallard Sep 19, 2026
e44e3c8
fix(gemseo): classify a wrapped variable with is_continuous, not is_n…
FrancoisGallard Sep 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12"]
python-version: ["3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@main
Expand All @@ -34,7 +34,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 coverage[toml] grpcio grpcio-tools protoletariat numpy scipy openmdao
pip install flake8 coverage[toml] grpcio grpcio-tools protoletariat numpy scipy openmdao gemseo
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
sudo apt install -y protobuf-compiler
- name: Install Package
Expand Down
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
twice for an implicit output, which is quadratic in the number of dynamic
variables. Both now index the metadata by type and name once. Applying
shapes to 1,000 dynamic variables drops from 922 ms to 120 ms.
- Added a GEMSEO interface (`philote_mdo.gemseo`) allowing GEMSEO
disciplines to be called from Philote-MDO, and Philote-MDO disciplines
(local or remote, e.g. served from OpenMDAO) to be called from GEMSEO.
Discrete variables are carried in both directions. The discrete
variables of a remote Philote discipline are part of the GEMSEO
grammars next to the continuous ones, bound to no type since a discrete
variable may carry any JSON-compatible value. Conversely, a variable
of a wrapped GEMSEO discipline is served as a discrete Philote variable
when its grammar's data converter does not report it as continuous,
which includes the integer-valued ones, since a continuous Philote
variable is an array of doubles. Discrete variables are excluded from
the Jacobian, including when it is requested in full with
`compute_all_jacobians=True`.
- Added examples and tutorials demonstrating interoperability between
GEMSEO, OpenMDAO and OpenAeroStruct through Philote-MDO.

### Bug Fixes

Expand Down Expand Up @@ -121,9 +136,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
removes redundant work inside `compute_totals` on every gradient call.
Results were already correct, since the totals are indexed by the
`(of, wrt)` pair rather than by position (#80).
- Fixed the compilation of the proto files on Windows.

### Documentation & Infrastructure

- Dropped support for Python 3.9, which reached end of life in October
2025. `requires-python` is now `>=3.10`, and CI no longer tests 3.9.
GEMSEO 6.3 requires Python 3.10, so the 3.9 job was the only one
testing an older GEMSEO (6.2); the latest `grpcio`, `protobuf` and
OpenMDAO also require 3.10 or later.
- Updated the documentation site's dependencies to clear known npm
advisories, taking the audit from 42 findings (2 critical, 26 high) to 17
(all high). `npm audit fix` resolved the `webpack-dev-server`, `sockjs`,
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Philote-Python is the reference Python implementation of the Philote-MDO standar
## Build System

### Requirements
- Python 3.9 - 3.12
- Python 3.10 - 3.12
- `numpy`, `scipy`, `grpcio`, `protobuf` (installed automatically)
- `openmdao` (only for the OpenMDAO bindings and integration tests)

Expand Down Expand Up @@ -51,7 +51,7 @@ docs/ # Docusaurus documentation site (see docs/README.md)
## CI/CD

### Tests workflow (`.github/workflows/tests.yaml`)
Runs `pytest` against Python 3.9 / 3.10 / 3.11 / 3.12 on every push to `main`, `develop`, `release/*`, `support/*`, and on PRs into `main` / `develop`.
Runs `pytest` against Python 3.10 / 3.11 / 3.12 on every push to `main`, `develop`, `release/*`, `support/*`, and on PRs into `main` / `develop`.

### Documentation workflow (`.github/workflows/documentation.yaml`)
Builds the Docusaurus site under `docs/` and deploys it to GitHub Pages whenever `docs/**` changes on `develop`. Uses the GitHub Pages artifact pipeline (`actions/configure-pages` + `upload-pages-artifact` + `deploy-pages`).
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ installed automatically during the installation process:
To run the unit and integration tests, you will need:

- openmdao (can be found [here](https://github.com/OpenMDAO/OpenMDAO) or installed via pip)
- gemseo (can be found [here](https://gitlab.com/gemseo/dev/gemseo) or installed via pip)

## Installation

Expand Down
211 changes: 211 additions & 0 deletions docs/docs/gemseo/gemseo-openaerostruct.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
---
sidebar_position: 2
title: "Coupling OpenAeroStruct and GEMSEO"
---

# Coupling OpenAeroStruct and GEMSEO through Philote-MDO

This tutorial builds on [Using GEMSEO with Philote-MDO](./gemseo.md) and
walks through
[`examples/openaerostruct_to_gemseo.py`](https://github.com/MDO-Standards/Philote-Python/blob/main/examples/openaerostruct_to_gemseo.py),
a more realistic case: instead of a toy analytic function, the remote
discipline is an [OpenAeroStruct](https://github.com/mdolab/OpenAeroStruct)
aerostructural analysis -- a coupled VLM aerodynamic model and a
finite-element structural model, solved together with an OpenMDAO
nonlinear solver -- and it is optimized with a gradient-based algorithm.

This example additionally requires OpenMDAO, OpenAeroStruct and the
[`philote-examples`](https://pypi.org/project/philote-examples/) package,
version 0.5.1 or later
(`pip install openaerostruct "philote-examples>=0.5.1"`), which provides
the `OasAerostructDiscipline` used below. Earlier versions of
`philote-examples` do not declare the partial derivatives this tutorial
relies on.

## Why this matters: hiding an OpenMDAO MDA behind a Philote-MDO discipline

OpenAeroStruct is written for [OpenMDAO](https://openmdao.org): the
`philote-examples` package provides `OasAerostructDiscipline`, which
builds an OpenMDAO `Group` combining `AerostructGeometry` and
`AerostructPoint` for a wing, resolves the aero-structural coupling
internally with `NonlinearBlockGS`, and wraps the whole `om.Problem` as a
**single Philote-MDO explicit discipline**.

This is the key interoperability point: from the Philote-MDO (and
therefore GEMSEO) side, this coupled, iterative OpenMDAO analysis is
completely invisible. `PhiloteDiscipline` only ever sees a stateless
function `inputs -> outputs` plus a Jacobian -- exactly like the
`Paraboloid` discipline of the [previous tutorial](./gemseo.md). GEMSEO
does not need OpenMDAO, OpenAeroStruct, or any of their dependencies
installed: it only talks gRPC to a server that happens to run them.

```mermaid
flowchart LR
subgraph GEMSEO_process["GEMSEO process"]
PD["PhiloteDiscipline"]
SC["MDOScenario<br/>(SLSQP)"]
SC --> PD
end
subgraph Server_process["Server process"]
OAS["OasAerostructDiscipline<br/>(Philote explicit discipline)"]
subgraph OM["OpenMDAO Problem"]
VLM["VLM aerodynamics"]
FEM["FEM structure"]
VLM <-->|"NonlinearBlockGS"| FEM
end
OAS --> OM
end
PD <-- "gRPC: alpha -> CL, CD, ...<br/>+ Jacobian" --> OAS
```

## A gradient-based scenario is limited to what the server differentiates

`OasAerostructDiscipline` only declares two partial derivatives on the
server side (in its `_build_discipline` method):

```python
self.declare_subproblem_partial("CD", "alpha")
self.declare_subproblem_partial("CL", "alpha")
```

Only declared partials are computed and sent to the client: a
gradient-based GEMSEO scenario can therefore only use `alpha` as a design
variable, and `CL`/`CD` as objective/constraint candidates -- using any
other input as a design variable, or any other output as an objective or
constraint, would make GEMSEO request a Jacobian entry the server never
computes.

This shapes the scenario: **trim the wing** by finding the angle of
attack `alpha` that **minimizes drag** `CD` while holding the **lift
coefficient** `CL` at a target value, a classic aerostructural design
problem.

## Walkthrough

### Fixing the non-design inputs

The Philote-MDO protocol only transfers variable names, shapes and units
over gRPC -- not GEMSEO's default values. Since `alpha` is the only
design variable, every other input of the discipline (flight conditions
and mission parameters) must be given a value explicitly, or GEMSEO would
have no default to execute the discipline with. This is done once, by
updating the connected discipline's `default_input_data`:

```python
FIXED_INPUTS = {
"v": array([248.136]),
"Mach_number": array([0.84]),
"re": array([1e6]),
"rho": array([0.38]),
"CT": array([grav_constant * 17.0e-6]),
"R": array([11.165e6]),
"W0": array([0.4 * 3e5]),
"speed_of_sound": array([295.4]),
"load_factor": array([1.0]),
"empty_cg": np.zeros(3),
}


def create_discipline(server: grpc.Server) -> PhiloteDiscipline:
discipline = pmdo.ExplicitServer(discipline=OasAerostructDiscipline())
discipline.attach_to_server(server)
oas_discipline = PhiloteDiscipline(channel=grpc.insecure_channel(f"{HOST}:{PORT}"))
oas_discipline.default_input_data.update(FIXED_INPUTS)
return oas_discipline
```

:::note
This is not specific to GEMSEO: any Philote-MDO client that does not set
a value for a non-design input runs into the same issue. The
[Sellar example](https://github.com/MDO-Standards/Philote-Python/blob/main/examples/sellar_gemseo_to_openmdao.py),
which drives remote *GEMSEO* disciplines from *OpenMDAO*, sets its fixed
parameters explicitly with `prob.set_val(...)` for the exact same reason.
:::

### Serving the discipline

As in the previous tutorial, serving the discipline only requires
starting a gRPC server and attaching an `ExplicitServer` wrapping it:

```python
def create_server() -> grpc.Server:
server = grpc.server(futures.ThreadPoolExecutor(max_workers=1))
server.add_insecure_port(f"[::]:{PORT}")
server.start()
return server
```

### Building and running the scenario

The design space has a single variable, `alpha` (in degrees); the
scenario minimizes `CD` under the equality constraint `CL = 0.5`, using
the gradient-based `SLSQP` algorithm -- made possible because the server
provides the `CL`/`CD` derivatives with respect to `alpha` analytically:

```python
if __name__ == "__main__":
server = create_server()

try:
oas_disc = create_discipline(server)

design_space = create_design_space()
# alpha is in degrees, as declared by OasAerostructDiscipline.
design_space.add_variable(
"alpha", lower_bound=-10.0, upper_bound=15.0, value=5.0
)

scenario = create_scenario(
[oas_disc],
objective_name="CD",
design_space=design_space,
formulation_settings_model=DisciplinaryOpt_Settings(),
)
# Trim the wing: CL = 0.5
scenario.add_constraint("CL", constraint_type="eq", value=0.5)
scenario.execute(algo_settings_model=SLSQP_Settings(max_iter=20))

out = oas_disc.local_data
print(
"Trimmed solution:",
{name: out[name] for name in ("alpha", "CL", "CD", "failure")},
)
execute_post(scenario, OptHistoryView_Settings(save=True, show=False))

finally:
# Stop the server explicitly:
# its worker threads would otherwise keep the process alive.
server.stop(0)
```

Running the script converges in a handful of iterations and prints:

```text
Trimmed solution: {
'alpha': array([4.07490213]),
'CL': array([0.5]),
'CD': array([0.03553854]),
'failure': array([-0.91249284]),
}
```

`CL` reaches the target `0.5` exactly (the equality constraint fully
determines `alpha` here, since there is a single design variable), and
`failure` stays negative, meaning the structure remains within its
allowable stress margin at the trimmed condition.

## The full script

See the actual, always up-to-date source at
[`examples/openaerostruct_to_gemseo.py`](https://github.com/MDO-Standards/Philote-Python/blob/main/examples/openaerostruct_to_gemseo.py)
in the repository.

## Where to go next

[`examples/sellar_gemseo_to_openmdao.py`](https://github.com/MDO-Standards/Philote-Python/blob/main/examples/sellar_gemseo_to_openmdao.py)
shows the mirror image of this tutorial: instead of GEMSEO calling an
OpenMDAO/OpenAeroStruct discipline, GEMSEO's own Sellar disciplines are
served over gRPC with `GEMSEOtoPhiloteDiscipline` and optimized from an
OpenMDAO model with SLSQP -- the same coupling pattern used here (an
internal Gauss-Seidel solver resolving an algebraic loop behind the
scenes), but with the two frameworks swapped.
Loading
Loading