Test Latest OpenMDAO Development Version #1815
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Run Tests | |
# Matrix Options | |
# DOCS: 2-Build and publish, 1-build only, 0-no docs | |
name: Dymos Tests | |
on: | |
# Trigger on push, pull request | |
push: | |
branches: [ master ] | |
pull_request: | |
branches: [ master, develop ] | |
# Allow running the workflow manually from the Actions tab | |
# All jobs are excluded by default, desired jobs must be selected | |
workflow_dispatch: | |
inputs: | |
run_name: | |
type: string | |
description: 'Name of workflow run as it will appear under Actions tab:' | |
required: false | |
default: "" | |
baseline: | |
type: boolean | |
description: "Include 'baseline' in test matrix" | |
required: false | |
default: false | |
no_pyoptsparse: | |
type: boolean | |
description: "Include 'no_pyoptsparse' in test matrix" | |
required: false | |
default: false | |
numpy2_no_pyoptsparse: | |
type: boolean | |
description: "Include 'numpy2_no_pyoptsparse' in test matrix" | |
required: false | |
default: false | |
no_snopt: | |
type: boolean | |
description: "Include 'no_snopt' in test matrix" | |
required: false | |
default: false | |
no_mpi: | |
type: boolean | |
description: "Include 'no_mpi' Baseline in test matrix" | |
required: false | |
default: false | |
latest: | |
type: boolean | |
description: "Include 'latest' in test matrix" | |
required: false | |
default: false | |
python313: | |
type: boolean | |
description: "Include 'python313' in test matrix" | |
required: false | |
default: false | |
oldest: | |
type: boolean | |
description: "Include 'oldest' in test matrix" | |
required: false | |
default: false | |
run-name: ${{ inputs.run_name }} | |
jobs: | |
test_ubuntu: | |
runs-on: ubuntu-latest | |
timeout-minutes: 90 | |
strategy: | |
fail-fast: false | |
matrix: | |
include: | |
# baseline versions | |
- NAME: baseline | |
PY: '3.12' | |
NUMPY: 1.26 | |
SCIPY: 1.13 | |
PETSc: 3.21.0 | |
PYOPTSPARSE: 'v2.11.0' | |
SNOPT: 7.7 | |
OPENMDAO: 'latest' | |
PEP517: true | |
OPTIONAL: '[all]' | |
JAX: '0.4.28' | |
EXCLUDE: ${{ github.event_name == 'workflow_dispatch' && ! inputs.baseline }} | |
# baseline versions except no pyoptsparse or SNOPT | |
- NAME: no_pyoptsparse | |
PY: '3.12' | |
NUMPY: 1.26 | |
SCIPY: 1.13 | |
PETSc: 3.21.0 | |
OPENMDAO: 'latest' | |
OPTIONAL: '[test]' | |
EXCLUDE: ${{ github.event_name == 'workflow_dispatch' && ! inputs.no_pyoptsparse }} | |
# numpy 2.x but no PETSc, pyoptsparse or SNOPT | |
- NAME: numpy2_no_pyoptsparse | |
PY: '3.12' | |
NUMPY: 2 | |
SCIPY: 1.14 | |
OPENMDAO: 'latest' | |
OPTIONAL: '[all]' | |
EXCLUDE: ${{ github.event_name == 'workflow_dispatch' && ! inputs.numpy2_no_pyoptsparse }} | |
# baseline versions except with pyoptsparse but no SNOPT | |
- NAME: no_snopt | |
PY: '3.12' | |
NUMPY: 1.26 | |
SCIPY: 1.13 | |
PETSc: 3.21.0 | |
PYOPTSPARSE: 'v2.11.0' | |
OPENMDAO: 'latest' | |
OPTIONAL: '[all]' | |
JAX: '0.4.28' | |
EXCLUDE: ${{ github.event_name == 'workflow_dispatch' && ! inputs.no_snopt }} | |
# baseline versions except no MPI/PETSc | |
- NAME: no_mpi | |
PY: '3.12' | |
NUMPY: 1.26 | |
SCIPY: 1.13 | |
PYOPTSPARSE: 'v2.11.0' | |
OPENMDAO: 'latest' | |
OPTIONAL: '[test]' | |
EXCLUDE: ${{ github.event_name == 'workflow_dispatch' && ! inputs.no_mpi }} | |
# try latest versions | |
# sticking with Python 3.12 here, 3.13 requires NumPy 2.1 which does not work yet with pyoptsparse | |
- NAME: latest | |
PY: '3.12' | |
NUMPY: 1 | |
SCIPY: 1 | |
PETSc: 3.21.0 | |
PYOPTSPARSE: 'latest' | |
SNOPT: 7.7 | |
OPENMDAO: 'dev' | |
OPTIONAL: '[all]' | |
JAX: 'latest' | |
EXCLUDE: ${{ github.event_name == 'workflow_dispatch' && ! inputs.latest }} | |
# Python 3.13 (requires NumPy 2.1 which does not work yet with pyoptsparse) | |
- NAME: python313 | |
PY: '3.13' | |
NUMPY: 2 | |
SCIPY: 1 | |
PETSc: 3 | |
# PYOPTSPARSE: 'latest' | |
SNOPT: 7.7 | |
OPENMDAO: 'dev' | |
OPTIONAL: '[test]' | |
JAX: 'latest' | |
EXCLUDE: ${{ github.event_name == 'workflow_dispatch' && ! inputs.python313 }} | |
# oldest supported versions | |
- NAME: oldest | |
PY: 3.9 | |
NUMPY: 1.22 | |
SCIPY: 1.7 | |
OPENMPI: '4.0' | |
MPI4PY: '3.0' | |
PETSc: 3.13 | |
PYOPTSPARSE: 'v2.9.0' | |
SNOPT: 7.2 | |
OPENMDAO: 3.28.0 | |
MATPLOTLIB: 3.5 | |
OPTIONAL: '[test]' | |
EXCLUDE: ${{ github.event_name == 'workflow_dispatch' && ! inputs.oldest }} | |
steps: | |
- name: Display run details | |
run: | | |
echo "=============================================================" | |
echo "Run #${GITHUB_RUN_NUMBER}" | |
echo "Run ID: ${GITHUB_RUN_ID}" | |
echo "Testing: ${GITHUB_REPOSITORY}" | |
echo "Triggered by: ${GITHUB_EVENT_NAME}" | |
echo "Initiated by: ${GITHUB_ACTOR}" | |
echo "=============================================================" | |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then | |
if [[ "${{ matrix.EXCLUDE }}" == "false" ]]; then | |
echo "Triggered by 'workflow_dispatch', '${{ matrix.NAME }}' build was selected and will run." | |
else | |
echo "Triggered by 'workflow_dispatch', '${{ matrix.NAME }}' build not selected and will not run." | |
fi | |
fi | |
- name: Create SSH key | |
if: ${{ ! matrix.EXCLUDE }} | |
shell: bash | |
env: | |
SSH_PRIVATE_KEY: ${{secrets.SSH_PRIVATE_KEY}} | |
SSH_KNOWN_HOSTS: ${{secrets.SSH_KNOWN_HOSTS}} | |
run: | | |
mkdir -p ~/.ssh/ | |
echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa | |
sudo chmod 600 ~/.ssh/id_rsa | |
echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts | |
- name: Checkout code | |
if: ${{ ! matrix.EXCLUDE }} | |
uses: actions/checkout@v4 | |
- name: Setup conda | |
if: ${{ ! matrix.EXCLUDE }} | |
uses: conda-incubator/setup-miniconda@v3 | |
with: | |
python-version: ${{ matrix.PY }} | |
channels: conda-forge | |
conda-remove-defaults: true | |
- name: Install Numpy/Scipy | |
if: ${{ ! matrix.EXCLUDE }} | |
shell: bash -l {0} | |
run: | | |
echo "=============================================================" | |
echo "Install Numpy/Scipy" | |
echo "=============================================================" | |
conda install numpy=${{ matrix.NUMPY }} scipy=${{ matrix.SCIPY }} -q -y | |
- name: Install jax | |
if: ${{ ! matrix.EXCLUDE && matrix.JAX }} | |
shell: bash -l {0} | |
run: | | |
echo "=============================================================" | |
echo "Install jax" | |
echo "=============================================================" | |
if [[ "${{ matrix.JAX }}" == "latest" ]]; then | |
python -m pip install jaxlib jax | |
else | |
python -m pip install jaxlib==${{ matrix.JAX }} jax==${{ matrix.JAX }} | |
fi | |
- name: Install PETSc | |
if: ${{ ! matrix.EXCLUDE && matrix.PETSc }} | |
shell: bash -l {0} | |
run: | | |
echo "=============================================================" | |
echo "Install PETSc" | |
echo "=============================================================" | |
if [[ "${{ matrix.OPENMPI }}" && "${{ matrix.MPI4PY }}" ]]; then | |
conda install openmpi=${{ matrix.OPENMPI }} mpi4py=${{ matrix.MPI4PY }} petsc4py=${{ matrix.PETSc }} -q -y | |
elif [[ "${{ matrix.MPI4PY }}" ]]; then | |
conda install mpich mpi4py=${{ matrix.MPI4PY }} petsc4py=${{ matrix.PETSc }} -q -y | |
else | |
conda install mpich mpi4py petsc4py=${{ matrix.PETSc }} -q -y | |
fi | |
export OMPI_MCA_rmaps_base_oversubscribe=1 | |
export OMPI_MCA_btl=^openib | |
echo "OMPI_MCA_rmaps_base_oversubscribe=1" >> $GITHUB_ENV | |
echo "OMPI_MCA_btl=^openib" >> $GITHUB_ENV | |
echo "-----------------------" | |
echo "Quick test of mpi4py:" | |
mpirun -n 2 python -c "from mpi4py import MPI; print(f'Rank: {MPI.COMM_WORLD.rank}')" | |
echo "-----------------------" | |
echo "Quick test of petsc4py:" | |
mpirun -n 2 python -c "import numpy; from mpi4py import MPI; comm = MPI.COMM_WORLD; import petsc4py; petsc4py.init(); x = petsc4py.PETSc.Vec().createWithArray(numpy.ones(5)*comm.rank, comm=comm); print(x.getArray())" | |
echo "-----------------------" | |
- name: Install pyOptSparse | |
id: build_pyoptsparse | |
if: ${{ ! matrix.EXCLUDE && matrix.PYOPTSPARSE }} | |
shell: bash -l {0} | |
run: | | |
echo "=============================================================" | |
echo "Install pyoptsparse" | |
echo "=============================================================" | |
if [[ "${{ matrix.PYOPTSPARSE }}" == "conda-forge" ]]; then | |
if [[ "${{ matrix.SNOPT }}" ]]; then | |
echo "SNOPT ${{ matrix.SNOPT }} was requested but is not available on conda-forge" | |
fi | |
conda install -c conda-forge pyoptsparse | |
else | |
pip install git+https://github.com/OpenMDAO/build_pyoptsparse | |
if [[ "${{ matrix.PYOPTSPARSE }}" == "latest" ]]; then | |
LATEST_URL=`curl -fsSLI -o /dev/null -w %{url_effective} https://github.com/mdolab/pyoptsparse/releases/latest` | |
LATEST_VER=`echo $LATEST_URL | awk '{split($0,a,"/tag/"); print a[2]}'` | |
BRANCH="-b $LATEST_VER" | |
else | |
BRANCH="-b ${{ matrix.PYOPTSPARSE }}" | |
fi | |
if [[ "${{ matrix.PAROPT }}" ]]; then | |
PAROPT="-a" | |
fi | |
if [[ "${{ matrix.SNOPT }}" == "7.7" && "${{ secrets.SNOPT_LOCATION_77 }}" ]]; then | |
echo " > Secure copying SNOPT 7.7 over SSH" | |
mkdir SNOPT | |
scp -qr ${{ secrets.SNOPT_LOCATION_77 }} SNOPT | |
SNOPT="-s SNOPT/src" | |
elif [[ "${{ matrix.SNOPT }}" == "7.2" && "${{ secrets.SNOPT_LOCATION_72 }}" ]]; then | |
echo " > Secure copying SNOPT 7.2 over SSH" | |
mkdir SNOPT | |
scp -qr ${{ secrets.SNOPT_LOCATION_72 }} SNOPT | |
SNOPT="-s SNOPT/source" | |
elif [[ "${{ matrix.SNOPT }}" ]]; then | |
echo "SNOPT version ${{ matrix.SNOPT }} was requested but source is not available" | |
fi | |
build_pyoptsparse $BRANCH $PAROPT $SNOPT | |
fi | |
- name: Install OpenMDAO | |
if: ${{ ! matrix.EXCLUDE && matrix.OPENMDAO }} | |
shell: bash -l {0} | |
run: | | |
echo "=============================================================" | |
echo "Install OpenMDAO" | |
echo "=============================================================" | |
if [[ "${{ matrix.OPENMDAO }}" == "dev" ]]; then | |
pip install git+https://github.com/OpenMDAO/OpenMDAO | |
elif [[ "${{ matrix.OPENMDAO }}" == "latest" ]]; then | |
echo "The latest version OpenMDAO will be installed from pypi per the Dymos dependency" | |
else | |
pip install openmdao==${{ matrix.OPENMDAO }} | |
fi | |
- name: Install Matplotlib | |
if: ${{ ! matrix.EXCLUDE && matrix.MATPLOTLIB }} | |
shell: bash -l {0} | |
run: | | |
echo "=============================================================" | |
echo "Install requested version of Matpltlib" | |
echo "=============================================================" | |
python -m pip install matplotlib==${{ matrix.MATPLOTLIB }} | |
- name: Install Dymos | |
if: ${{ ! matrix.EXCLUDE }} | |
shell: bash -l {0} | |
run: | | |
echo "=============================================================" | |
echo "Install Dymos" | |
echo "=============================================================" | |
if [[ "${{ matrix.PEP517 }}" == "true" ]]; then | |
pip wheel --no-deps --use-pep517 . | |
WHEEL=`find dymos-*.whl` | |
echo "-----------------------------------------------------------" | |
echo "Installing from wheel: $WHEEL" | |
echo "-----------------------------------------------------------" | |
python -m pip install $WHEEL${{ matrix.OPTIONAL }} | |
else | |
python -m pip install .${{ matrix.OPTIONAL }} | |
fi | |
- name: Install optional dependencies | |
if: ${{ ! matrix.EXCLUDE && matrix.OPTIONAL == '[all]' }} | |
run: | | |
echo "=============================================================" | |
echo "Install additional packages for testing/coverage" | |
echo "=============================================================" | |
echo "Pre-install playwright dependencies to avoid 'Playwright Host validation warning'" | |
pip install playwright | |
playwright install --with-deps | |
- name: Display environment info | |
id: env_info | |
if: ${{ ! matrix.EXCLUDE }} | |
shell: bash -l {0} | |
run: | | |
conda info | |
conda list | |
conda env export --name test --file ${{ matrix.NAME }}_environment.yml | |
echo "=============================================================" | |
echo "Check installed versions of Python, Numpy and Scipy" | |
echo "=============================================================" | |
python -c "ver='${{ matrix.PY }}'; import sys; assert str(sys.version).startswith(ver), \ | |
f'Python version {sys.version} is not the requested version ({ver})'" | |
python -c "ver='${{ matrix.NUMPY }}'; import numpy; assert str(numpy.__version__).startswith(ver), \ | |
f'Numpy version {numpy.__version__} is not the requested version ({ver})'" | |
python -c "ver='${{ matrix.SCIPY }}'; import scipy; assert str(scipy.__version__).startswith(ver), \ | |
f'Scipy version {scipy.__version__} is not the requested version ({ver})'" | |
- name: Display dependency tree | |
if: failure() && steps.env_info.outcome == 'failure' | |
run: | | |
pip install pipdeptree | |
pipdeptree | |
- name: Upload environment artifact | |
if: ${{ ! matrix.EXCLUDE }} | |
uses: actions/upload-artifact@v4 | |
with: | |
name: ${{ matrix.NAME }}_environment | |
path: ${{ matrix.NAME }}_environment.yml | |
retention-days: 5 | |
- name: Check NumPy 2.0 Compatibility | |
if: ${{ ! matrix.EXCLUDE }} | |
run: | | |
echo "=============================================================" | |
echo "Check Dymos code for NumPy 2.0 compatibility" | |
echo "See: https://numpy.org/devdocs/numpy_2_0_migration_guide.html" | |
echo "=============================================================" | |
python -m pip install ruff | |
ruff check . --select NPY201 | |
- name: Perform linting with Ruff | |
if: ${{ ! matrix.EXCLUDE && matrix.NAME == 'baseline' }} | |
run: | | |
echo "=============================================================" | |
echo "Lint Dymos code per settings in pyproject.toml" | |
echo "=============================================================" | |
python -m pip install ruff | |
ruff check . --config pyproject.toml | |
- name: Run tests | |
if: ${{ ! matrix.EXCLUDE }} | |
env: | |
DYMOS_CHECK_PARTIALS: True | |
shell: bash -l {0} | |
run: | | |
echo "=============================================================" | |
echo "Run Tests" | |
echo "Environment:" | |
echo " DYMOS_CHECK_PARTIALS: $DYMOS_CHECK_PARTIALS" | |
echo "=============================================================" | |
testflo -n 1 docs/dymos_book/test --pre_announce | |
testflo -n 1 joss/test --pre_announce | |
testflo -b benchmark --pre_announce | |
cd $HOME | |
if [[ "${{ matrix.NAME }}" != "latest" ]]; then | |
testflo dymos -n 4 --pre_announce --show_skipped --durations 20 --coverage --coverpkg dymos | |
else | |
testflo dymos -n 4 --pre_announce --show_skipped --durations 20 | |
fi | |
- name: Submit coverage | |
if: github.event_name != 'workflow_dispatch' | |
continue-on-error: true | |
shell: bash -l {0} | |
env: | |
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
COVERALLS_SERVICE_NAME: "github" | |
COVERALLS_PARALLEL: True | |
run: | | |
echo "=============================================================" | |
echo "Submit coverage" | |
echo "=============================================================" | |
cp $HOME/.coverage . | |
pip install coveralls | |
SITE_DIR=`python -c 'import site; print(site.getsitepackages()[-1])'` | |
coveralls --basedir $SITE_DIR | |
coveralls: | |
name: Finish coveralls | |
if: github.event_name != 'workflow_dispatch' | |
continue-on-error: true | |
needs: test_ubuntu | |
runs-on: ubuntu-latest | |
steps: | |
- uses: coverallsapp/github-action@master | |
with: | |
github-token: ${{ secrets.GITHUB_TOKEN }} | |
parallel-finished: true |