From 9d72d79f38e4bf7bb91a7af4c76fc5141e4ceaad Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Thu, 22 Dec 2022 15:19:18 -0800 Subject: [PATCH 1/5] add OS/BLAS pip-based test matrix as GitHub workflow --- .github/scripts/set-pip-test-matrix.py | 28 ++++ .github/workflows/os-blas-test-matrix.yml | 176 ++++++++++++++++++++++ 2 files changed, 204 insertions(+) create mode 100644 .github/scripts/set-pip-test-matrix.py create mode 100644 .github/workflows/os-blas-test-matrix.yml diff --git a/.github/scripts/set-pip-test-matrix.py b/.github/scripts/set-pip-test-matrix.py new file mode 100644 index 000000000..ed18239d0 --- /dev/null +++ b/.github/scripts/set-pip-test-matrix.py @@ -0,0 +1,28 @@ +""" set-pip-test-matrix.py + +Create test matrix for pip wheels +""" +import json +from pathlib import Path + +system_opt_blas_libs = {'ubuntu': ['OpenBLAS'], + 'macos' : ['OpenBLAS', 'Apple']} + +wheel_jobs = [] +for wkey in Path("slycot-wheels").iterdir(): + wos, wpy, wbl = wkey.name.split("-") + wheel_jobs.append({'packagekey': wkey.name, + 'os': wos, + 'python': wpy, + 'blas_lib': wbl, + }) + if wbl == "Generic": + for bl in system_opt_blas_libs[wos]: + wheel_jobs.append({ 'packagekey': wkey.name, + 'os': wos, + 'python': wpy, + 'blas_lib': bl, + }) + +matrix = { 'include': wheel_jobs } +print(json.dumps(matrix)) \ No newline at end of file diff --git a/.github/workflows/os-blas-test-matrix.yml b/.github/workflows/os-blas-test-matrix.yml new file mode 100644 index 000000000..ff5b37744 --- /dev/null +++ b/.github/workflows/os-blas-test-matrix.yml @@ -0,0 +1,176 @@ +name: OS/BLAS test matrix + +on: push + +jobs: + build-pip: + name: Build pip Py${{ matrix.python }}, ${{ matrix.os }}, ${{ matrix.bla_vendor}} BLA_VENDOR + runs-on: ${{ matrix.os }}-latest + strategy: + fail-fast: false + matrix: + os: + - 'ubuntu' + - 'macos' + python: + - '3.8' + - '3.11' + bla_vendor: [ 'unset' ] + include: + - os: 'ubuntu' + python: '3.11' + bla_vendor: 'Generic' + - os: 'ubuntu' + python: '3.11' + bla_vendor: 'OpenBLAS' + - os: 'macos' + python: '3.11' + bla_vendor: 'Apple' + - os: 'macos' + python: '3.11' + bla_vendor: 'Generic' + - os: 'macos' + python: '3.11' + bla_vendor: 'OpenBLAS' + + steps: + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python }} + - name: Checkout Slycot + uses: actions/checkout@v3 + with: + repository: python-control/Slycot + fetch-depth: 0 + submodules: 'recursive' + - name: Setup Ubuntu + if: matrix.os == 'ubuntu' + run: | + sudo apt-get -y update + sudo apt-get -y install gfortran cmake --fix-missing + case ${{ matrix.bla_vendor }} in + unset | Generic ) sudo apt-get -y install libblas-dev liblapack-dev ;; + OpenBLAS ) sudo apt-get -y install libopenblas-dev ;; + *) + echo "bla_vendor option ${{ matrix.bla_vendor }} not supported" + exit 1 ;; + esac + - name: Setup macOS + if: matrix.os == 'macos' + run: | + case ${{ matrix.bla_vendor }} in + unset | Generic | Apple ) ;; # Found in system + OpenBLAS ) + brew install openblas + echo "BLAS_ROOT=/usr/local/opt/openblas/" >> $GITHUB_ENV + echo "LAPACK_ROOT=/usr/local/opt/openblas/" >> $GITHUB_ENV + ;; + *) + echo "bla_vendor option ${{ matrix.bla_vendor }} not supported" + exit 1 ;; + esac + echo "FC=gfortran-11" >> $GITHUB_ENV + - name: Build wheel + env: + BLA_VENDOR: ${{ matrix.bla_vendor }} + CMAKE_GENERATOR: Unix Makefiles + run: | + if [[ $BLA_VENDOR = unset ]]; then unset BLA_VENDOR; fi + python -m pip install --upgrade pip + pip wheel -v -w . . + wheeldir=slycot-wheels/${{ matrix.os }}-${{ matrix.python }}-${{ matrix.bla_vendor }} + mkdir -p ${wheeldir} + cp ./slycot*.whl ${wheeldir}/ + - name: Save wheel + uses: actions/upload-artifact@v3 + with: + name: slycot-wheels + path: slycot-wheels + + + create-wheel-test-matrix: + name: Create wheel test matrix + runs-on: ubuntu-latest + needs: build-pip + if: always() # run tests for all successful builds, even if others failed + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - name: Checkout python-control + uses: actions/checkout@v3 + - name: Download wheels (if any) + uses: actions/download-artifact@v3 + with: + name: slycot-wheels + path: slycot-wheels + - id: set-matrix + run: echo "matrix=$(python3 .github/scripts/set-pip-test-matrix.py)" >> $GITHUB_OUTPUT + + + test-wheel: + name: Test wheel ${{ matrix.packagekey }}, ${{matrix.blas_lib}} BLAS lib ${{ matrix.failok }} + needs: create-wheel-test-matrix + runs-on: ${{ matrix.os }}-latest + continue-on-error: ${{ matrix.failok == 'FAILOK' }} + + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.create-wheel-test-matrix.outputs.matrix) }} + + steps: + - name: Checkout Slycot + uses: actions/checkout@v3 + with: + repository: 'python-control/Slycot' + path: slycot-src + - name: Checkout python-control + uses: actions/checkout@v3 + with: + repository: 'python-control/python-control' + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python }} + - name: Setup Ubuntu + if: matrix.os == 'ubuntu' + run: | + set -xe + sudo apt-get -y update + case ${{ matrix.blas_lib }} in + Generic ) sudo apt-get -y install libblas3 liblapack3 ;; + unset | OpenBLAS ) sudo apt-get -y install libopenblas-base ;; + *) + echo "BLAS ${{ matrix.blas_lib }} not supported for wheels on Ubuntu" + exit 1 ;; + esac + update-alternatives --display libblas.so.3-x86_64-linux-gnu + update-alternatives --display liblapack.so.3-x86_64-linux-gnu + - name: Setup macOS + if: matrix.os == 'macos' + run: | + set -xe + brew install coreutils + case ${{ matrix.blas_lib }} in + unset | Generic | Apple ) ;; # system provided (Uses Apple Accelerate Framework) + OpenBLAS ) + brew install openblas + echo "DYLIB_LIBRARY_PATH=/usr/local/opt/openblas/lib" >> $GITHUB_ENV + ;; + *) + echo "BLAS option ${{ matrix.blas_lib }} not supported for wheels on MacOS" + exit 1 ;; + esac + - name: Download wheels + uses: actions/download-artifact@v3 + with: + name: slycot-wheels + path: slycot-wheels + - name: Install Wheel + run: | + python -m pip install --upgrade pip + pip install matplotlib scipy pytest pytest-cov pytest-timeout coverage coveralls + pip install slycot-wheels/${{ matrix.packagekey }}/slycot*.whl + pip show slycot + - name: Test with pytest + run: pytest -v control/tests From 120120253a398c30a1cb0c28d590be6f6c7594e6 Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Thu, 22 Dec 2022 22:12:53 -0800 Subject: [PATCH 2/5] add OS/BLAS conda-based test matrix as GitHub workflow --- .github/conda-env/build-env.yml | 4 + .github/conda-env/test-env.yml | 1 + .github/scripts/set-conda-test-matrix.py | 34 ++++++ .github/workflows/os-blas-test-matrix.yml | 139 ++++++++++++++++++++++ 4 files changed, 178 insertions(+) create mode 100644 .github/conda-env/build-env.yml create mode 100644 .github/scripts/set-conda-test-matrix.py diff --git a/.github/conda-env/build-env.yml b/.github/conda-env/build-env.yml new file mode 100644 index 000000000..f75973640 --- /dev/null +++ b/.github/conda-env/build-env.yml @@ -0,0 +1,4 @@ +name: build-env +dependencies: + - boa + - numpy !=1.23.0 diff --git a/.github/conda-env/test-env.yml b/.github/conda-env/test-env.yml index a4944f768..1c28589a4 100644 --- a/.github/conda-env/test-env.yml +++ b/.github/conda-env/test-env.yml @@ -1,5 +1,6 @@ name: test-env dependencies: + - conda-build # for conda index - pip - coverage - coveralls diff --git a/.github/scripts/set-conda-test-matrix.py b/.github/scripts/set-conda-test-matrix.py new file mode 100644 index 000000000..954480cb0 --- /dev/null +++ b/.github/scripts/set-conda-test-matrix.py @@ -0,0 +1,34 @@ +""" set-conda-test-matrix.py + +Create test matrix for conda packages +""" +import json, re +from pathlib import Path + +osmap = {'linux': 'ubuntu', + 'osx': 'macos', + 'win': 'windows', + } + +blas_implementations = ['unset', 'Generic', 'OpenBLAS', 'Intel10_64lp'] + +combinations = {'ubuntu': blas_implementations, + 'macos': blas_implementations, + 'windows': ['unset', 'Intel10_64lp'], + } + +conda_jobs = [] +for conda_pkg_file in Path("slycot-conda-pkgs").glob("*/*.tar.bz2"): + cos = osmap[conda_pkg_file.parent.name.split("-")[0]] + m = re.search(r'py(\d)(\d+)_', conda_pkg_file.name) + pymajor, pyminor = int(m[1]), int(m[2]) + cpy = f'{pymajor}.{pyminor}' + for cbl in combinations[cos]: + cjob = {'packagekey': f'{cos}-{cpy}', + 'os': cos, + 'python': cpy, + 'blas_lib': cbl} + conda_jobs.append(cjob) + +matrix = { 'include': conda_jobs } +print(json.dumps(matrix)) diff --git a/.github/workflows/os-blas-test-matrix.yml b/.github/workflows/os-blas-test-matrix.yml index ff5b37744..2a08da31e 100644 --- a/.github/workflows/os-blas-test-matrix.yml +++ b/.github/workflows/os-blas-test-matrix.yml @@ -89,6 +89,57 @@ jobs: path: slycot-wheels + build-conda: + name: Build conda Py${{ matrix.python }}, ${{ matrix.os }} + runs-on: ${{ matrix.os }}-latest + strategy: + fail-fast: false + matrix: + os: + - 'ubuntu' + - 'macos' + - 'windows' + python: + - '3.9' + - '3.11' + + steps: + - name: Checkout Slycot + uses: actions/checkout@v3 + with: + repository: python-control/Slycot + fetch-depth: 0 + submodules: 'recursive' + - name: Setup Conda + uses: conda-incubator/setup-miniconda@v2 + with: + python-version: ${{ matrix.python }} + activate-environment: build-env + environment-file: .github/conda-env/build-env.yml + miniforge-version: latest + miniforge-variant: Mambaforge + channel-priority: strict + auto-update-conda: false + auto-activate-base: false + - name: Conda build + shell: bash -l {0} + run: | + set -e + numpyversion=$(python -c 'import numpy; print(numpy.version.version)') + conda mambabuild --python "${{ matrix.python }}" --numpy $numpyversion conda-recipe + # preserve directory structure for custom conda channel + find "${CONDA_PREFIX}/conda-bld" -maxdepth 2 -name 'slycot*.tar.bz2' | while read -r conda_pkg; do + conda_platform=$(basename $(dirname "${conda_pkg}")) + mkdir -p "slycot-conda-pkgs/${conda_platform}" + cp "${conda_pkg}" "slycot-conda-pkgs/${conda_platform}/" + done + - name: Save to local conda pkg channel + uses: actions/upload-artifact@v3 + with: + name: slycot-conda-pkgs + path: slycot-conda-pkgs + + create-wheel-test-matrix: name: Create wheel test matrix runs-on: ubuntu-latest @@ -108,6 +159,25 @@ jobs: run: echo "matrix=$(python3 .github/scripts/set-pip-test-matrix.py)" >> $GITHUB_OUTPUT + create-conda-test-matrix: + name: Create conda test matrix + runs-on: ubuntu-latest + needs: build-conda + if: always() # run tests for all successful builds, even if others failed + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - name: Checkout python-control + uses: actions/checkout@v3 + - name: Download conda packages + uses: actions/download-artifact@v3 + with: + name: slycot-conda-pkgs + path: slycot-conda-pkgs + - id: set-matrix + run: echo "matrix=$(python3 .github/scripts/set-conda-test-matrix.py)" >> $GITHUB_OUTPUT + + test-wheel: name: Test wheel ${{ matrix.packagekey }}, ${{matrix.blas_lib}} BLAS lib ${{ matrix.failok }} needs: create-wheel-test-matrix @@ -174,3 +244,72 @@ jobs: pip show slycot - name: Test with pytest run: pytest -v control/tests + + + test-conda: + name: Test conda ${{ matrix.packagekey }}, ${{matrix.blas_lib}} BLAS lib ${{ matrix.failok }} + needs: create-conda-test-matrix + runs-on: ${{ matrix.os }}-latest + continue-on-error: ${{ matrix.failok == 'FAILOK' }} + + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.create-conda-test-matrix.outputs.matrix) }} + + defaults: + run: + shell: bash -l {0} + + steps: + - name: Checkout Slycot + uses: actions/checkout@v3 + with: + repository: 'python-control/Slycot' + path: slycot-src + - name: Checkout python-control + uses: actions/checkout@v3 + - name: Setup macOS + if: matrix.os == 'macos' + run: brew install coreutils + - name: Setup Conda + uses: conda-incubator/setup-miniconda@v2 + with: + python-version: ${{ matrix.python }} + miniforge-version: latest + miniforge-variant: Mambaforge + activate-environment: test-env + environment-file: .github/conda-env/test-env.yml + channel-priority: strict + auto-activate-base: false + - name: Download conda packages + uses: actions/download-artifact@v3 + with: + name: slycot-conda-pkgs + path: slycot-conda-pkgs + - name: Install Conda package + run: | + set -e + case ${{ matrix.blas_lib }} in + unset ) # the conda-forge default (os dependent) + mamba install libblas libcblas liblapack + ;; + Generic ) + mamba install 'libblas=*=*netlib' 'libcblas=*=*netlib' 'liblapack=*=*netlib' + echo "libblas * *netlib" >> $CONDA_PREFIX/conda-meta/pinned + ;; + OpenBLAS ) + mamba install 'libblas=*=*openblas' openblas + echo "libblas * *openblas" >> $CONDA_PREFIX/conda-meta/pinned + ;; + Intel10_64lp ) + mamba install 'libblas=*=*mkl' mkl + echo "libblas * *mkl" >> $CONDA_PREFIX/conda-meta/pinned + ;; + esac + conda index --no-progress ./slycot-conda-pkgs + mamba install -c ./slycot-conda-pkgs slycot + conda list + - name: Test with pytest + run: JOBNAME=$JOBNAME pytest control/tests + env: + JOBNAME: ${{ matrix.packagekey }} ${{ matrix.blas_lib }} From f477c1336e983f2eafe5d79a4b9360b8d01d2464 Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Fri, 23 Dec 2022 20:00:38 -0800 Subject: [PATCH 3/5] triger on pull_request --- .github/workflows/os-blas-test-matrix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/os-blas-test-matrix.yml b/.github/workflows/os-blas-test-matrix.yml index 2a08da31e..fdd59763e 100644 --- a/.github/workflows/os-blas-test-matrix.yml +++ b/.github/workflows/os-blas-test-matrix.yml @@ -1,6 +1,6 @@ name: OS/BLAS test matrix -on: push +on: pull_request jobs: build-pip: From b8c4e5c9f961a17bab76d2395998a1f21882a61b Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Fri, 23 Dec 2022 20:55:37 -0800 Subject: [PATCH 4/5] add flatsys xfails for platform=ubunto, BLAS=Generic, numpy=1.24.0 --- control/tests/flatsys_test.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/control/tests/flatsys_test.py b/control/tests/flatsys_test.py index 5e3fe5d7c..4d4c8cf6b 100644 --- a/control/tests/flatsys_test.py +++ b/control/tests/flatsys_test.py @@ -13,6 +13,8 @@ import scipy as sp import re import warnings +import os +import platform import control as ct import control.flatsys as fs @@ -201,9 +203,26 @@ def test_kinematic_car_ocp( minimize_kwargs={'method': method}, ) xd, ud = traj_ocp.eval(timepts) + if not traj_ocp.success: - # If unsuccessful, make sure the error is just about precision - assert re.match(".*precision loss.*", traj_ocp.message) is not None + # Known failure cases + if re.match(".*precision loss.*", traj_ocp.message): + pytest.xfail("precision loss in some configurations") + + elif re.match("Iteration limit.*", traj_ocp.message) and \ + re.match("ubuntu-3.* Generic", os.getenv('JOBNAME')) and \ + np.__version__ == '1.24.0': + pytest.xfail("gh820: iteration limit exceeded") + + else: + # Dump out information to allow creation of an exception + print("Platform: ", platform.platform()) + print("Python: ", platform.python_version()) + np.show_config() + print("JOBNAME: ", os.getenv('JOBNAME')) + + pytest.fail( + "unknown failure; view output to identify configuration") # Make sure the constraints are satisfied if input_constraints: From 4d6a6bf7b53b8c4b074738a54f7696e5669f9acd Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Sat, 24 Dec 2022 07:05:56 -0800 Subject: [PATCH 5/5] change trigger to workflow_dispatch or PR against relevant files --- .github/workflows/os-blas-test-matrix.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/os-blas-test-matrix.yml b/.github/workflows/os-blas-test-matrix.yml index fdd59763e..fede19025 100644 --- a/.github/workflows/os-blas-test-matrix.yml +++ b/.github/workflows/os-blas-test-matrix.yml @@ -1,7 +1,15 @@ name: OS/BLAS test matrix -on: pull_request - +on: + workflow_dispatch: + pull_request: + paths: + - .github/workflows/os-blas-test-matrix.yml + - .github/scripts/set-conda-test-matrix.py + - .github/scripts/set-conda-pip-matrix.py + - .github/conda-env/build-env.yml + - .github/conda-env/test-env.yml + jobs: build-pip: name: Build pip Py${{ matrix.python }}, ${{ matrix.os }}, ${{ matrix.bla_vendor}} BLA_VENDOR