Installation

cdadt is developed and tested in a conda environment named cdadt_env, on Windows, with Python 3.11. Several of the choices below are not stylistic; each one is explained, because an environment that is nearly right produces a stack that imports and then aborts the interpreter in the middle of a Newton solve.

The environment

conda create -y -n cdadt_env -c conda-forge python=3.11 "numpy<2" scipy matplotlib pyyaml \
    openmdao pyoptsparse ipopt cyipopt pytest pytest-cov sphinx sphinx_rtd_theme ruff black
conda install -y -n cdadt_env -c conda-forge "libblas=*=*openblas"
conda activate cdadt_env

Then install OpenConcept and cdadt, in that order:

pip install -e /path/to/openconcept --no-deps
pip install -e ".[dev,docs]"

That is the whole of what the suite needs. Two optional extras add the aerodynamics cdadt owns; see Supplying your own aerodynamics for both, and note that they take opposite flags for reasons given there:

pip install "jax<0.5" "jaxlib<0.5"
pip install -e /path/to/openavl --no-deps        # --no-deps: it declares numpy>=2.4
pip install -e /path/to/OpenAeroStruct           # with deps: all its floors are satisfied

Why each of those flags is there

numpy<2

OpenConcept 1.2.6 declares an upper bound on NumPy. Installing NumPy 2 produces a stack that imports and then fails inside components that use APIs removed in 2.0.

--no-deps on OpenConcept

Without it, pip reads OpenConcept’s own requirements and acts on them, downgrading the conda-forge NumPy, SciPy and OpenMDAO that conda just resolved. The result is a mixed conda/pip stack that is difficult to diagnose because nothing about it looks wrong. cdadt deliberately does not list OpenConcept in its own dependencies for the same reason.

The OpenBLAS pin

Not optional on Windows. With an MKL-backed BLAS, NumPy aborts the interpreter with 0xc06d007f inside numpy.linalg.solve – which OpenConcept calls at import time, so the failure happens before any cdadt code runs. Forcing the OpenBLAS build of libblas avoids it.

pyoptsparse, ipopt and cyipopt

Optional for sizing, effectively required for optimization. The reason is specific: the black box is a Newton-solved implicit system, and an optimizer exploring a wide design space will eventually propose a design it cannot converge. OpenMDAO’s pyOptSparse driver catches that, reports the point as failed, and lets the optimizer shorten its step. SciPy’s SLSQP driver re-raises, so one unconvergeable trial design ends the run. SLSQP is supported and is the code’s default because it is always available; the shipped optimization case uses IPOPT.

Reproducing the exact environment

environment.yml at the repository root pins the environment these results were produced in:

conda env create -f environment.yml
conda activate cdadt_env
pip install -e /path/to/openconcept --no-deps
pip install -e ".[dev,docs]"

It is not a raw ``conda env export``, and it must not be regenerated as one. A plain export is broken in two ways that only surface when someone builds from it, and both were found by building a fresh environment from this file and running the whole suite in it:

It records the editable installs as PyPI pins. conda env export writes cdadt==0.2.0 and openconcept==1.2.6 into the pip section, because that is what the metadata of an editable install looks like. Building from that would try to fetch cdadt from PyPI, where it does not exist, and would fetch a different OpenConcept than the local clone. Both are therefore omitted, and installed with pip install -e afterwards.

``–no-builds`` strips the BLAS variant selector. The libblas, libcblas and liblapack entries carry an explicit =*openblas build selector. Without it conda resolves the MKL-backed build, and the environment then dies exactly as described above – 0xc06d007f inside numpy.linalg.solve, during OpenConcept’s import, before any cdadt code runs. The selector is load-bearing, and an export would silently remove it.

Which OpenConcept

cdadt drives whichever OpenConcept is importable; it is loaded by name from the case file. The numbers in this documentation were produced against the clone described in Validation, which records the commit and the one respect in which it differs from OpenConcept’s published main. That matters for reproducibility and is stated there rather than assumed.

Checking the installation

pytest -q -m "not slow"     # the fast loop
pytest -q                   # everything, including the live reference comparison
cdadt inspect cases/b738.yaml

The full suite runs the shipped sizing case, OpenConcept’s own example, and the shipped optimization study. If it passes, the installation is not merely importable but produces the documented numbers.

Note that a broken BLAS does not fail gracefully. It aborts the interpreter, so pytest terminates mid-collection with a Windows fatal exception rather than reporting a failed test. If that happens, check the BLAS variant before anything else:

conda list -n cdadt_env "^(libblas|libopenblas|mkl)$"

libblas must show an *openblas build.