diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1a0741f49..9a9e2d8e2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,11 +6,11 @@ on: branches: [ master, development ] concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true jobs: - build-source: + source-build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -24,15 +24,12 @@ jobs: run: python -m pip install --upgrade -r scripts/requirements.txt - name: Fabricate COCO run: python scripts/fabricate - - name: Configure unit test + - name: Run core unit tests working-directory: ${{github.workspace}}/code-experiments/test/unit-test/ - run: cmake -B build . - - name: Build unit test - working-directory: ${{github.workspace}}/code-experiments/test/unit-test/ - run: cmake --build build - - name: Run unit test - working-directory: ${{github.workspace}}/code-experiments/test/unit-test/ - run: ctest --test-dir build + run: | + cmake -B build . + cmake --build build + ctest --test-dir build - name: Build cocoex Python package run: python -m build -s -o dist code-experiments/build/python - name: Build cocopp Python package @@ -62,7 +59,7 @@ jobs: with: name: dist-rust path: dist/cocoex-rust-*.zip - - name: Archive cocoex Python source package + - name: Archive cocoex and cocopp Python source package uses: actions/upload-artifact@v3 with: name: dist-python-sdist @@ -70,44 +67,124 @@ jobs: dist/cocoex*.tar.gz dist/cocopp*.tar.gz - build-python-wheels: - needs: build-source + python-wheels-build-cocopp: + needs: source-build + runs-on: ubuntu-latest + defaults: + run: + working-directory: ${{github.workspace}} + steps: + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Download Python source package + uses: actions/download-artifact@v3 + with: + name: dist-python-sdist + - name: Build cocopp wheel + shell: bash + run: python -m pip wheel -w dist/ cocopp-*.tar.gz + - name: Archive Python wheels + uses: actions/upload-artifact@v3 + with: + name: dist-python-wheels + path: dist/cocopp*.whl + + python-wheels-build-cocoex: + needs: source-build + # This strategy is taken straight from the numpy wheels.yaml CI job + # + # Since we depend on numpy, there's no reason to try and build a binary cocoex wheel + # on platform that don't have a compiled numpy available. strategy: + # Ensure that a wheel builder finishes even if another fails + fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - version: ["3.8", "3.9", "3.10", "3.11"] - runs-on: ${{matrix.os}} + buildplat: + - [ubuntu-20.04, manylinux_x86_64] + - [ubuntu-20.04, musllinux_x86_64] + - [macos-12, macosx_x86_64] + - [windows-2019, win_amd64] + - [windows-2019, win32] + python: ["cp39", "cp310", "cp311", "cp312", "pp39"] + exclude: + # Don't build PyPy 32-bit windows + - buildplat: [windows-2019, win32] + python: "pp39" + - buildplat: [ ubuntu-20.04, musllinux_x86_64 ] + python: "pp39" + runs-on: ${{ matrix.buildplat[0] }} defaults: run: working-directory: ${{github.workspace}} steps: - - uses: actions/setup-python@v4 + - name: Setup MSVC (32-bit) + if: ${{ matrix.buildplat[1] == 'win32' }} + uses: bus1/cabuild/action/msdevshell@e22aba57d6e74891d059d66501b6b5aed8123c4d # v1 with: - python-version: ${{matrix.version}} + architecture: 'x86' + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: "3.10" - name: Download Python source package uses: actions/download-artifact@v3 with: name: dist-python-sdist - - name: Setup Python - run: python -m pip install --upgrade pip wheel + - name: Install cibuildwheel + run: python -m pip install cibuildwheel==2.16.2 - name: Build cocoex wheel shell: bash - run: python -m pip wheel -w dist/ cocoex-*.tar.gz - - name: Build cocopp wheel - shell: bash - run: python -m pip wheel -w dist/ cocopp-*.tar.gz - - name: Archive Python package + run: python -m cibuildwheel --output-dir dist/ cocoex-*.tar.gz + env: + CIBW_PRERELEASE_PYTHONS: True + CIBW_BUILD: ${{ matrix.python }}-${{ matrix.buildplat[1] }} + - name: Archive Python wheels uses: actions/upload-artifact@v3 with: name: dist-python-wheels - path: dist/*.whl + path: dist/cocoex*.whl - test-python-wheels: - needs: build-python-wheels + python-wheels-test: + needs: python-wheels-build-cocoex strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] - version: ["3.8", "3.9", "3.10", "3.11"] + version: ["3.9", "3.10", "3.11", "3.12"] + runs-on: ${{matrix.os}} + defaults: + run: + working-directory: ${{github.workspace}} + steps: + - uses: actions/download-artifact@v3 + with: + name: dist-python-sdist + path: dist + - uses: actions/download-artifact@v3 + with: + name: dist-python-wheels + path: wheels + - uses: actions/setup-python@v4 + with: + python-version: ${{matrix.version}} + - name: Setup Python + run: python -m pip install --upgrade pip wheel + - name: Install cocoex + run: python -m pip install --find-links wheels/ cocoex + - name: Run cocoex test + shell: bash + run: | + tar xf dist/cocoex-*.tar.gz --strip-components=1 + python -m pip install pytest pytest-cov + pytest --cov=cocoex test/ + + python-wheels-example: + needs: [python-wheels-build-cocoex, python-wheels-build-cocopp] + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + version: ["3.9", "3.10", "3.11", "3.12"] runs-on: ${{matrix.os}} defaults: run: @@ -124,14 +201,61 @@ jobs: - name: Setup Python run: python -m pip install --upgrade pip wheel - name: Install cocoex and cocopp - run: pip install --find-links wheels cocoex cocopp + run: python -m pip install --find-links wheels/ cocoex cocopp - name: Install scipy for example experiment - run: pip install scipy + run: python -m pip install scipy - name: Run example experiment run: python code-experiments/build/python/example/example_experiment2.py + + python-lint: + needs: source-build + runs-on: "ubuntu-latest" + steps: + - uses: actions/download-artifact@v3 + with: + name: dist-python-sdist + path: dist + - uses: actions/setup-python@v4 + with: + python-version: "3.11" + - name: Setup Python + run: python -m pip install --upgrade pip wheel + - name: Lint with Ruff + shell: bash + continue-on-error: true + run: | + pip install ruff + tar xf dist/cocoex-*.tar.gz --strip-components=1 + ruff --output-format=github . - test-c: - needs: build-source + cocopp-example: + needs: python-wheels-build-cocopp + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + version: ["3.9", "3.10", "3.11", "3.12"] + runs-on: ${{matrix.os}} + defaults: + run: + working-directory: ${{github.workspace}} + steps: + - uses: actions/download-artifact@v3 + with: + name: dist-python-wheels + path: wheels + - uses: actions/setup-python@v4 + with: + python-version: ${{matrix.version}} + - name: Setup Python + run: python -m pip install --upgrade pip wheel + - name: Run BBOB postprocessing example + shell: bash + run: | + python -m pip install wheels/cocopp*.whl + python -m cocopp -v AMALGAM\! BIPOP-CMA-ES\! + + c-test: + needs: source-build strategy: matrix: # FIXME: Currently fails on windows because of path issues. @@ -159,14 +283,20 @@ jobs: if: failure() uses: actions/upload-artifact@v3 with: - name: test-c-${{matrix.os}} + name: c-test-${{matrix.os}} path: | **/build/ - test-java: - needs: build-source - # FIXME: Should also test on windows and macos - runs-on: ubuntu-latest + java-example: + needs: source-build + strategy: + matrix: + # FIXME: Currently fails on windows because of path issues. + # + # Debug and add `windows-latest` back to list at some point. + os: [ubuntu-latest, macos-latest] + java: [11, 17, 21] + runs-on: ${{matrix.os}} defaults: run: working-directory: ${{github.workspace}} @@ -175,6 +305,11 @@ jobs: uses: actions/download-artifact@v3 with: name: dist-java + - name: Setup java + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: ${{ matrix.java }} - name: Unpack run: unzip cocoex-java-*.zip - name: Build @@ -186,11 +321,11 @@ jobs: - name: Archive Java example experiment results uses: actions/upload-artifact@v3 with: - name: exdata-java + name: java-example-${{matrix.os}} path: cocoex-java/exdata/* - test-octave: - needs: build-source + octave-example: + needs: source-build # FIXME: Should also test on windows and macos runs-on: ubuntu-latest defaults: @@ -214,13 +349,16 @@ jobs: - name: Archive Octave example experiment results uses: actions/upload-artifact@v3 with: - name: exdata-octave + name: octave-example path: cocoex-octave/exdata/* - test-rust: - needs: build-source - # FIXME: Should also test on windows and macos - runs-on: ubuntu-latest + rust-test: + if: false # Disabled since we don't have any tests yes + needs: source-build + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{matrix.os}} defaults: run: working-directory: ${{github.workspace}} @@ -237,12 +375,32 @@ jobs: - name: Test working-directory: ${{github.workspace}}/cocoex-rust/ run: cargo test -r - - name: Experiment + + rust-example: + needs: source-build + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{matrix.os}} + defaults: + run: + working-directory: ${{github.workspace}} + steps: + - name: Download Rust source package + uses: actions/download-artifact@v3 + with: + name: dist-rust + - name: Unpack + run: unzip cocoex-rust-*.zip + - name: Build + working-directory: ${{github.workspace}}/cocoex-rust/ + run: cargo build -r + - name: Example Experiment working-directory: ${{github.workspace}}/cocoex-rust/ - run: ./target/release/examples/example-experiment + run: cargo run --example example-experiment - name: Archive Rust example experiment results uses: actions/upload-artifact@v3 with: - name: exdata-rust + name: rust-example-${{matrix.os}} path: cocoex-rust/exdata/* diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 28b4ad308..2fe04c06a 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -17,7 +17,7 @@ jobs: fetch-depth: 0 - uses: actions/setup-python@v4 with: - python-version: "3.7" + python-version: "3.8" cache: "pip" - name: Setup Python run: python -m pip install --upgrade -r scripts/requirements.txt @@ -40,7 +40,7 @@ jobs: fetch-depth: 0 - uses: actions/setup-python@v4 with: - python-version: "3.7" + python-version: "3.8" cache: "pip" - name: Setup Python run: python -m pip install --upgrade -r scripts/requirements.txt @@ -65,7 +65,7 @@ jobs: fetch-depth: 0 - uses: actions/setup-python@v4 with: - python-version: "3.7" + python-version: "3.8" cache: "pip" - name: Setup Python run: python -m pip install --upgrade -r scripts/requirements.txt diff --git a/.gitignore b/.gitignore index bfb5ae1de..bb395257a 100644 --- a/.gitignore +++ b/.gitignore @@ -29,13 +29,14 @@ code-experiments/build/python/build/ code-experiments/build/python/dist/ code-experiments/build/python/src/cocoex/_version.py code-experiments/build/python/src/cocoex/interface.c +code-experiments/build/python/src/cocoex/function.c code-experiments/build/rust/Cargo.lock code-experiments/build/rust/Cargo.toml code-experiments/build/rust/coco-sys/Cargo.toml code-experiments/build/rust/coco-sys/vendor/ code-experiments/build/rust/target/ code-experiments/examples/*/coco.[ch] -code-experiments/src/coco_version.h +code-experiments/src/coco_version.c code-experiments/test/**/coco.[ch] code-experiments/test/unit-test/build/ code-experiments/test/unit-test/exdata/ diff --git a/README.md b/README.md index fe4f58948..50da489a5 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,6 @@ Languages currently available to connect a solver to the benchmarks are - `Python` - `Rust` -Code for others might be available in branched code. Contributions to link further languages (including a better example in `C++`) are more than welcome. @@ -41,62 +40,46 @@ For more general information: # Requirements -1. For a machine running experiments - - A `C` compiler, such as gcc - - Python >=3.7 with `setuptools` installed - - optional: `git` -2. For a machine displaying data by running the post-processing - - Python 3 with `numpy`, `scipy`, `matplotlib`, and `six` installed. - We recommend installing the [Anaconda Python library](https://www.continuum.io/downloads) -For Ubuntu 16.04+, all the requirements can be installed using the following command: +The requirements vary depending on the lanuage used to run the experiments -``` -apt-get install build-essential python-dev python-numpy python-matplotlib \ - python-scipy python-six python-setuptools -``` +## Running experiments + +Depending on the language you use to implement your algorithm, the required dependencies differ. -For macOS, the `C` compiler comes with installing the Xcode command line tools like +* **C**: We provide cross-platform build files for both [CMake](https://cmake.org/) and [meson](https://mesonbuild.com/). + Additionally you will need to bring a C compiler. +* **Java**: You need a C compiler and any Java Development Kit (JDK), such that `javac` and `javah` are accessible (i.e. in the system path). Again we provide a [CMake](https://cmake.org) based build but it should be relatively straight forward to build the interface with any other build tool. +* **MATLAB**: You need at least MATLAB 2008, for details, see [here](./code-experiments/build/matlab/README.md) +* **Python**: We support Python 3.8 and newer. You can install the latest `cocoex` package from [PyPI](https://pypi.org/project/cocoex/) using pip by running `pip install cocoex`. + We have prebuilt binaries for Windows, Linux and MacOS. If there are no binaries for your platform, you will need a C compiler to install the package from PyPI. +* **Octave**: Octave 4.0.0 or later. On operating systems other than Windows, earlier versions might work. + Under Linux the package `liboctave-dev` might be necessary. +* **Rust**: We provide the `coco-rs` crate on [crates.io](https://crates.io/crates/coco-rs). Just run `cargo add coco-rs@0.7.0` to add it to your Rust based experiment. + +### Additional hints + +#### MacOS + +For macOS, the C compiler comes with installing the Xcode command line tools like ``` -xcode-select install +xcode-select --install ``` -### Windows Specifics +#### Windows Under Windows, two alternative compile toolchains can be used: -1. [Cygwin](https://www.cygwin.com/) which comes with gcc and make, available in 32- and 64-bit versions. -2. MinGW's gcc (http://www.mingw.org/ for 32-bit or https://mingw-w64.org for 64-bit machines). Make sure to update the Windows path to MinGW's make.exe and rename/link the gcc.exe to cc.exe. +1. [Cygwin](https://www.cygwin.com/) which comes with gcc and make, available in 32- and 64-bit versions. +1. MinGW's gcc (http://www.mingw.org/ for 32-bit or https://mingw-w64.org for 64-bit machines). Make sure to update the Windows path to MinGW's make.exe and rename/link the gcc.exe to cc.exe. -For using `git` under Windows (optional), we recommend installing [TortoiseGit](https://tortoisegit.org/). +## Analysing results -### Programming Language Specifics -_Additional_ requirements for running an algorithm in a specific language. +To analyse and a compare the results, we provide a separate Python package named [`cocopp`](https://pypi.org/project/cocopp/) that can be installed from PyPI. +The package will automatically install all required dependencies. -* **C**: `make`, such as GNU make (when using [GNU make for Windows](http://gnuwin32.sourceforge.net/packages/make.htm), make sure that your ``CC`` environment variable is set to `gcc` by potentially typing `set CC=gcc` if you see an error). -* **Java**: `gcc` and any Java Development Kit (JDK), such that `javac` and `javah` are accessible - (i.e. in the system path). -* **Rust**: For details, take a look at the [Rust Readme](./code-experiments/build/rust/README.md) -* **MATLAB**: at least MATLAB 2008, for details, see [here](./code-experiments/build/matlab/README.md) -* **Python on Windows with MinGW**: Python 2.7 and the Microsoft compiler package for Python 2.7 - containing VC9, available [here](https://www.microsoft.com/en-us/download/details.aspx?id=44266). - These are necessary to build the C extensions for the Python `cocoex` module for Windows. - The package contains 32-bit and 64-bit compilers and the Windows SDK headers. -* **Python on Linux**: `python-dev` must be installed to compile/install the `cocoex` module. -* **Octave**: Octave 4.0.0 or later. On operating systems other than Windows, earlier versions might work. - Under Linux the package `liboctave-dev` might be necessary. - -### Guaranties (None) -We tested the framework on Mac OSX, Ubuntu linux, Fedora linux, and Windows (XP, -7, 10) in various combinations of 32-bit and 64-bit compilers, python versions -etc. Naturally, we cannot guarantee that the framework runs on any combination -of operating system and software installed. In case you experience some incompatibilies, -check out the [_Known Issues / Trouble Shooting_ Section](#Known-Issues) below. -Otherwise we will be happy if you can document them in detail on the -[issue tracker](https://github.com/numbbo/coco/issues). - -Getting Started +# Getting Started --------------- 0. Check out the [_Requirements_](#Requirements) above. @@ -108,6 +91,7 @@ Getting Started As long as no experiments are meant to be run, the next points 2.-6. can be skipped and continue with points 7. and 8. below. +1. If you want to run 1. **Download** the COCO framework code from github, - either by clicking the [Download ZIP button](https://github.com/numbbo/coco/archive/master.zip) @@ -165,7 +149,7 @@ Getting Started respectively). Output is automatically generated in the specified data `result_folder`. By now, more suites might be available, see below. -6. **Postprocess** the data from the results folder by +6. **Postprocess** the data from the results folder by typing ```sh @@ -335,215 +319,7 @@ our issue tracker at https://github.com/numbbo/coco/issues. * howtos contains a few text files with generic howtos. -Known Issues / Trouble-Shooting -------------------------------- -### Java -#### `javah` call fails -If you see something like this when running `python do.py run-java` or `build-java` -under Linux -``` -COPY code-experiments/src/coco.h -> code-experiments/build/java/coco.h -WRITE code-experiments/build/java/REVISION -WRITE code-experiments/build/java/VERSION -RUN javac CocoJNI.java in code-experiments/build/java -RUN javah CocoJNI in code-experiments/build/java -Traceback (most recent call last): - File "do.py", line 590, in - main(sys.argv[1:]) - File "do.py", line 563, in main - elif cmd == 'build-java': build_java() - File "do.py", line 437, in build_java - env = os.environ, universal_newlines = True) - File "/..../code-experiments/tools/cocoutils.py", line 34, in check_output - raise error -subprocess.CalledProcessError: Command '['locate', 'jni.h']' returned non-zero exit status 1 -``` -it means `javah` is either not installed (see above) or cannot be found in the system -path, see [this](http://stackoverflow.com/questions/13526701/javah-missing-after-jdk-install-linux) -and possibly [this](https://github.com/numbbo/coco/issues/416) for a solution. - -### Matlab - -#### Path to matlab -If you see something like this when running `python do.py build-matlab` -``` -AML ['code-experiments/src/coco_generics.c', 'code-experiments/src/coco_random.c', 'code-experiments/src/coco_suite.c', 'code-experiments/src/coco_suites.c', 'code-experiments/src/coco_observer.c', 'code-experiments/src/coco_runtime_c.c'] -> code-experiments/build/matlab/coco.c -COPY code-experiments/src/coco.h -> code-experiments/build/matlab/coco.h -COPY code-experiments/src/best_values_hyp.txt -> code-experiments/build/matlab/best_values_hyp.txt -WRITE code-experiments/build/matlab/REVISION -WRITE code-experiments/build/matlab/VERSION -RUN matlab -nodisplay -nosplash -r setup, exit in code-experiments/build/matlab -Traceback (most recent call last): - File "do.py", line 447, in - main(sys.argv[1:]) - File "do.py", line 429, in main - elif cmd == 'build-matlab': build_matlab() - File "do.py", line 278, in build_matlab - run('code-experiments/build/matlab', ['matlab', '-nodisplay', '-nosplash', '-r', 'setup, exit']) - File "/Users/auger/workviasvn/newcoco/numbbo/code-experiments/tools/cocoutils.py", line 68, in run - universal_newlines=True) - File "//anaconda/lib/python2.7/subprocess.py", line 566, in check_output - process = Popen(stdout=PIPE, *popenargs, **kwargs) - File "//anaconda/lib/python2.7/subprocess.py", line 710, in __init__ - errread, errwrite) - File "//anaconda/lib/python2.7/subprocess.py", line 1335, in _execute_child - raise child_exception -OSError: [Errno 2] No such file or directory -``` -it might be because your system does not know the `matlab` command. To fix this, -you should edit the file `/etc/paths` and add the path to the `matlab` bin file -(Linux/Mac) or add the path to the folder where the `matlab.exe` lies to your -Windows path. For instance, the `etc/paths` should look like something like this -``` -/usr/local/bin -/usr/bin -/bin -/usr/sbin -/sbin -/Applications/MATLAB_R2012a.app/bin/ -``` - -#### SMS-EMOA example does not compile under Mac -With the more complex SMS-EMOA example, the problem is related to the compilation -of the external C++ hypervolume calculation in `hv.cpp`. - -A fix for this issue consists in adding to the files `hv.cpp` and `paretofront.c` -``` -#define char16_t UINT16_T -``` -just before the line: -``` -#include "mex.h" -``` - -#### Access to mex files denied -If it happens that you get some `Access is denied` errors during -`python do.py build-matlab` or `python do.py run-matlab` like this one -``` -C:\Users\dimo\Desktop\numbbo-brockho>python do.py run-matlab -Traceback (most recent call last): - File "do.py", line 649, in - main(sys.argv[1:]) - File "do.py", line 630, in main - elif cmd == 'run-matlab': run_matlab() - File "do.py", line 312, in run_matlab - os.remove( filename ) -WindowsError: [Error 5] Access is denied: 'code-experiments/build/matlab\\cocoEv -aluateFunction.mexw32' -``` -a reason can be that a previously opened Matlab window still has some -file handles open. Simply close all Matlab windows (and all running Matlab -processes if there is any) before to run the `do.py` command again. - - -### Octave - -#### `octave-dev` under Linux -When running -``` - python do.py run-octave -``` -or -``` - python do.py build-octave -``` -and seeing something like -``` - [...] - compiling cocoCall.c...error: mkoctfile: please install the Debian package "liboctave-dev" to get the mkoctfile command -``` -then, unsurprisingly, installing `liboctave-dev` like -``` - sudo apt-get install liboctave-dev -``` -should do the job. - - -### Python - -#### `setuptools` is not installed -If you see something like this -``` -$ python do.py run-python # or build-python -[...] -PYTHON setup.py install --user in code-experiments/build/python -ERROR: return value=1 -Traceback (most recent call last): - File "setup.py", line 8, in - import setuptools -ImportError: No module named setuptools - -Traceback (most recent call last): - File "do.py", line 562, in - main(sys.argv[1:]) - File "do.py", line 539, in main - elif cmd == 'build-python': build_python() - File "do.py", line 203, in build_python - python('code-experiments/build/python', ['setup.py', 'install', '--user']) - File "/vol2/twagner/numbbo/code-experiments/tools/cocoutils.py", line 92, in python - universal_newlines=True) - File "/usr/local/lib/python2.7/subprocess.py", line 575, in check_output - raise CalledProcessError(retcode, cmd, output=output) -subprocess.CalledProcessError: Command '['/usr/local/bin/python', 'setup.py', 'install', '--user']' returned non-zero exit status 1 -``` -then `setuptools` needs to be installed: -``` - pip install setuptools -``` -or `easy_install setuptools` should do the job. - -#### Compilation During Install of `cocoex` Fails (under Linux) -If you see something like this: -``` -$ python do.py run-python # or build-python -[...] -cython/interface.c -o build/temp.linux-i686-2.6/cython/interface.o -cython/interface.c:4:20: error: Python.h: file not found -cython/interface.c:6:6: error: #error Python headers needed to compile C extensions, please install development version of Python. -error: command 'gcc' failed with exit status 1 -``` -or -``` -$ python do.py run-python # or build-python -[...] -cython/interface.c -o build/temp.linux-x86_64-2.7/cython/interface.o -cython/interface.c:4:20: fatal error: Python.h: No such file or directory -#include "Python.h" -^ -compilation terminated. -error: command 'x86_64-linux-gnu-gcc' failed with exit status 1 -``` -Under Linux -``` - sudo apt-get install python-dev -``` -should do the trick. - -#### Module Update/Install Does Not Propagate -We have observed a case where the update of the `cocoex` Python module seemed to have no -effect. In this case it has been successful to remove all previously installed versions, -see [here](https://github.com/numbbo/coco/issues/586) for a few more details. - - -#### Installing `cocoex` after migrating macOS to the ARM chipset (M1 or M2) -Reinstall the Xcode command line tools with -``` -xcode-select install -``` -and uninstall previous versions of `cocoex` -``` -pip uninstall cocoex -``` -until the message -``` -WARNING: Skipping cocoex as it is not installed. -``` -appears. Then -``` -python do.py run-python -``` -in the coco home folder should do the job. - +# Known Issues / Trouble-Shooting ### Post-Processing diff --git a/coco.png b/coco.png index 1e5ef2d9e..41b6d59d0 100644 Binary files a/coco.png and b/coco.png differ diff --git a/code-experiments/build/python/MANIFEST.in b/code-experiments/build/python/MANIFEST.in index 103f87e69..2b445f590 100644 --- a/code-experiments/build/python/MANIFEST.in +++ b/code-experiments/build/python/MANIFEST.in @@ -1,3 +1,8 @@ -exclude src/cocoex/interface.c +graft test include src/cocoex/coco.h -include test/bbob2009*.txt +# Don't include Cython generated files. +exclude src/cocoex/interface.c +exclude src/cocoex/function.c +# Build artifacts not meant for source distribution +global-exclude *.pyc +global-exclude __pycache__ diff --git a/code-experiments/build/python/pyproject.toml b/code-experiments/build/python/pyproject.toml index e83d11bcb..d0d6d5f2b 100644 --- a/code-experiments/build/python/pyproject.toml +++ b/code-experiments/build/python/pyproject.toml @@ -1,7 +1,7 @@ [build-system] requires = [ - "setuptools>=61.0", - "cython ~= 0.29.33", + "setuptools >= 67.0", + "cython ~= 3.0", "wheel", "numpy" ] @@ -20,17 +20,17 @@ authors = [ ] description = 'Benchmarking framework for all types of black-box optimization algorithms.' readme = "README.md" -requires-python = ">=3.7" +requires-python = ">=3.8" license = {text = "BSD-3-Clause"} classifiers = [ "Development Status :: 4 - Beta", "License :: OSI Approved :: BSD License", "Programming Language :: Python", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation :: CPython", ] dependencies = [ @@ -42,7 +42,11 @@ dynamic = ["version"] version = {attr = "cocoex._version.__version__"} [tool.setuptools.packages.find] -where = ["src"] # list of folders that contain the packages (["."] by default) +where = ["src"] + +[tool.pytest] +log_cli = true +addopts = "--doctest-modules" [project.urls] Homepage = "https://github.com/numbbo/coco" diff --git a/code-experiments/build/python/setup.py b/code-experiments/build/python/setup.py index 8e00fb6cc..42ac73a7b 100644 --- a/code-experiments/build/python/setup.py +++ b/code-experiments/build/python/setup.py @@ -4,6 +4,11 @@ extensions = [] extensions.append(Extension(name="cocoex.interface", sources=["src/cocoex/coco.c", "src/cocoex/interface.pyx"], + define_macros=[("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION")], + include_dirs=[np.get_include()])) +extensions.append(Extension(name="cocoex.function", + sources=["src/cocoex/coco.c", "src/cocoex/function.pyx"], + define_macros=[("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION")], include_dirs=[np.get_include()])) setup(ext_modules=extensions) diff --git a/code-experiments/build/python/src/cocoex/__init__.py b/code-experiments/build/python/src/cocoex/__init__.py index 0fcb3ab72..4fc8ff72f 100644 --- a/code-experiments/build/python/src/cocoex/__init__.py +++ b/code-experiments/build/python/src/cocoex/__init__.py @@ -15,13 +15,14 @@ A more complete example use case can be found in the `example_experiment.py` file. """ -from . import interface as _interface -from . import solvers -from . import utilities -from . import exceptions -from .interface import Suite as _Suite +from . import solvers # noqa: F401 +from . import utilities # noqa: F401 +from . import exceptions # noqa: F401 from .interface import Observer as _Observer +from .interface import Problem as _Problem +from .interface import Suite as _Suite from .interface import known_suite_names +from .interface import log_level # noqa: F401 from ._version import __version__ # noqa: F401 @@ -413,7 +414,7 @@ def result_folder(self): return super().result_folder # this definition is copy-edited from interface, solely to pass docstrings to pydoctor -class Problem(_interface.Problem): +class Problem(_Problem): """`Problem` instances are usually generated using class `Suite`. The main feature of a problem instance is that it is callable, returning the @@ -606,14 +607,3 @@ def info(self): See also: ``repr(self)`` """ return str(self) - -def log_level(level=None): - """``log_level(level=None)`` return current log level and - set new log level if ``level is not None and level``. - - :param level: must be 'error' or 'warning' or 'info' or 'debug', listed - with increasing verbosity, or '' which doesn't change anything. - - """ - return _interface.log_level(level) - diff --git a/code-experiments/build/python/src/cocoex/function.pyx b/code-experiments/build/python/src/cocoex/function.pyx new file mode 100644 index 000000000..6c4f735d3 --- /dev/null +++ b/code-experiments/build/python/src/cocoex/function.pyx @@ -0,0 +1,147 @@ +# -*- mode: cython -*- +#cython: language_level=3, boundscheck=False, c_string_type=str, c_string_encoding=ascii + +import numpy as np +cimport numpy as np + +from .exceptions import NoSuchProblemException, NoSuchSuiteException + +np.import_array() + + +cdef extern from 'coco.h': + ctypedef struct coco_problem_t: + pass + + char *coco_problem_get_id(coco_problem_t *p) + void coco_evaluate_function(coco_problem_t *p, double *x, double *y) + void coco_problem_free(coco_problem_t *p) + +# IMPORTANT: These functions are *not* declared public in coco.h so we have to +# explicitly declare it as an external function. Otherwise Cython will *not* +# add a declaration to the generated source files. +cdef extern coco_problem_t *coco_get_bbob_problem(size_t function, size_t + dimension, size_t instance) +cdef extern double coco_problem_get_best_value(coco_problem_t *p) + + +cdef class BenchmarkFunction: + """A bare benchmark function from one of the available suites. + + Examples + -------- + + >>> import numpy as np + + Create a 13 dimensional sphere function + + >>> fn = BenchmarkFunction("bbob", 1, 13, 1) + >>> fn + BenchmarkFunction('bbob', 1, 13, 1) + + We can also get a short mnemonic name for the function + + >>> str(fn) + 'bbob_f001_i01_d13' + + And of course evaluate it + + >>> x = np.zeros(fn.dimension) + >>> fn(x) + 124.61122368000001 + """ + + cdef coco_problem_t *_problem + cdef readonly char* suite + cdef readonly char* id + cdef readonly int function + cdef readonly int dimension + cdef readonly int instance + + def __init__(self, suite: str, function: int, dimension: int, instance: int): + """ + Create a bare benchmark function from one of the COCO suites. + + Parameters + ---------- + suite + Name of benchmark suite ("bbob" only currently) + + function + ID of function from suite + + dimension + Dimension of instantiated function + + instance + Instance ID of instantiated function + + + Raises + ------ + NoSuchSuiteException + If the `suite` is not known or not yet supported + + NoSuchProblemException + If no problem with the given `function`, `dimension` and `instance` exists in + the given `suite`. + """ + self.suite = suite + self.function = function + self.dimension = dimension + self.instance = instance + self._problem = NULL + if suite == "bbob": + self._problem = coco_get_bbob_problem(function, dimension, instance) + if self._problem == NULL: + # FIXME: Possibly extend Exception to include dimension and instance? + raise NoSuchProblemException(suite, function) + else: + raise NoSuchSuiteException(suite) + + self.id = coco_problem_get_id(self._problem) + + def __del__(self): + if self._problem != NULL: + coco_problem_free(self._problem) + + def best_value(self): + """Return the best (lowest) possible function value""" + return coco_problem_get_best_value(self._problem) + + def __str__(self): + return self.id + + def __repr__(self): + return f"BenchmarkFunction('{self.suite}', {self.function}, {self.dimension}, {self.instance})" + + def __call__(self, x): + cdef double[::1] xi + cdef double[:, ::1] X + cdef Py_ssize_t N, D, i + cdef double[::1] y_view + cdef double y + if isinstance(x, list): + x = np.array(x, dtype=float) + if x.ndim == 1: + # Evaluate a single parameter + xi = np.array(x, dtype=float) + coco_evaluate_function(self._problem, &xi[0], &y) + return y + elif x.ndim == 2: + # Evaluate several parameters at once + X = x + N = X.shape[0] + D = X.shape[1] + Y = np.zeros(N, dtype=np.float64) + y_view = Y + for i in range(N): + xi = X[i, :] + coco_evaluate_function(self._problem, &xi[0], &y) + y_view[i] = y + return Y + else: + return None + + +__all__ = ["BenchmarkFunction"] diff --git a/code-experiments/build/python/src/cocoex/utilities.py b/code-experiments/build/python/src/cocoex/utilities.py index 204124a49..ff0039450 100644 --- a/code-experiments/build/python/src/cocoex/utilities.py +++ b/code-experiments/build/python/src/cocoex/utilities.py @@ -369,12 +369,15 @@ def __init__(self): self.evals_dimension = 0 self.evals_by_dimension = {} self.runs_function = 0 + def print(self, problem, end="", **kwargs): print(self(problem), end=end, **kwargs) _sys.stdout.flush() + def add_evals(self, evals, runs): self.evals_dimension += evals self.runs_function += runs + def dimension_done(self): self.evals_by_dimension[self.d_current] = (_time.time() - self.t0_dimension) / self.evals_dimension s = '\n %d-D done in %.1e seconds/evaluation' % (self.d_current, self.evals_by_dimension[self.d_current]) @@ -382,10 +385,12 @@ def dimension_done(self): self.evals_dimension = 0 self.t0_dimension = _time.time() return s + def function_done(self): s = "(%d)" % self.runs_function + (2 - int(np.log10(self.runs_function))) * ' ' self.runs_function = 0 return s + def __call__(self, problem): """uses `problem.id` and `problem.dimension` to decide what to print. """ @@ -409,6 +414,7 @@ def __call__(self, problem): self.f_current = f # print_flush(res) return res + def print_timings(self): print(" dimension seconds/evaluations") print(" -----------------------------") @@ -416,12 +422,13 @@ def print_timings(self): print(" %3d %.1e " % (dim, self.evals_by_dimension[dim])) print(" -----------------------------") + @staticmethod def short_time_stap(): - l = _time.asctime().split() - d = l[0] - d = l[1] + l[2] - h, m, s = l[3].split(':') + t = _time.asctime().split() + d = t[0] + d = t[1] + t[2] + h, m, s = t[3].split(':') return d + ' ' + h + 'h' + m + ':' + s diff --git a/code-experiments/build/python/test/test_function.py b/code-experiments/build/python/test/test_function.py new file mode 100644 index 000000000..39c32ca4f --- /dev/null +++ b/code-experiments/build/python/test/test_function.py @@ -0,0 +1,34 @@ +import pytest +import numpy as np + +from cocoex.function import BenchmarkFunction +from cocoex.exceptions import NoSuchSuiteException + + +def test_bad_suite(): + with pytest.raises(NoSuchSuiteException): + BenchmarkFunction("bad_suite", 1, 1, 1) + + +def test_bbob(): + for fid in range(1, 25): + for dimension in [2, 3, 4, 5, 7, 11, 13, 17, 18, 19, 20, 31, 40]: + x0 = np.zeros(dimension) + for instance in [0, 1, 10, 100, 1000]: + fn = BenchmarkFunction("bbob", fid, dimension, instance) + assert str(fn) == f"bbob_f{fid:03d}_i{instance:02d}_d{dimension:02d}" + assert fn(x0) >= fn.best_value() + + +def test_list(): + fn = BenchmarkFunction("bbob", 1, 4, 1) + assert fn([1, 2, 3, 4]) >= fn([1.0, 2.0, 3.0, 4.0]) + + +def test_multiple_parameters(): + for n in [1, 2, 10, 100, 200]: + fn = BenchmarkFunction("bbob", 1, 4, 1) + X = np.random.uniform(-5, 5, size=(n, 4)) + y = fn(X) + assert len(y) == n + assert np.all(y >= fn.best_value()) diff --git a/code-experiments/build/python/test/test_regression.py b/code-experiments/build/python/test/test_regression.py index 49b38be9a..73bafdbc6 100644 --- a/code-experiments/build/python/test/test_regression.py +++ b/code-experiments/build/python/test/test_regression.py @@ -1,9 +1,12 @@ +import pytest import cocoex +from cocoex.exceptions import NoSuchSuiteException + def test_crash_no_dimension(): """C code crashes when no dimensions remain after filtering. See #2181""" - s = cocoex.Suite("bbob", "", "dimensions:4") - assert len(s) == 0 + with pytest.raises(NoSuchSuiteException): + cocoex.Suite("bbob", "", "dimensions:4") diff --git a/code-experiments/build/rust/src/lib.rs b/code-experiments/build/rust/src/lib.rs index 6d383af16..71e2b503f 100644 --- a/code-experiments/build/rust/src/lib.rs +++ b/code-experiments/build/rust/src/lib.rs @@ -21,7 +21,7 @@ pub use observer::Observer; /// COCO’s version. pub fn version() -> &'static str { - unsafe { CStr::from_ptr(coco_sys::coco_version.as_ptr()) } + unsafe { CStr::from_ptr(coco_sys::coco_version) } .to_str() .unwrap() } diff --git a/code-experiments/src/coco.h b/code-experiments/src/coco.h index d903bd3df..7fb7470ec 100644 --- a/code-experiments/src/coco.h +++ b/code-experiments/src/coco.h @@ -92,7 +92,15 @@ typedef unsigned __int64 uint64_t; extern "C" { #endif -#include "coco_version.h" +/** +* @brief COCO's version. +* +* The version number is dervied from the latest tag in the +* repository plus the number of commits after the tag. +*/ +/**@{*/ +extern const char *coco_version; +/**@}*/ /***********************************************************************************************************/ /** diff --git a/code-experiments/src/coco_platform.h b/code-experiments/src/coco_platform.h index 8471ef936..897234510 100644 --- a/code-experiments/src/coco_platform.h +++ b/code-experiments/src/coco_platform.h @@ -22,7 +22,7 @@ static const char *coco_path_separator = "\\"; #define COCO_PATH_MAX MAX_PATH #define HAVE_GFA 1 -#elif defined(__gnu_linux__) +#elif defined(__gnu_linux__) || defined(__linux__) #include #include #include diff --git a/code-experiments/src/suite_bbob.c b/code-experiments/src/suite_bbob.c index 35aec8892..7479de859 100644 --- a/code-experiments/src/suite_bbob.c +++ b/code-experiments/src/suite_bbob.c @@ -95,7 +95,7 @@ static const char *suite_bbob_get_instances_by_year(const int year) { * * Useful for other suites as well (see for example suite_biobj.c). */ -static coco_problem_t *coco_get_bbob_problem(const size_t function, +coco_problem_t *coco_get_bbob_problem(const size_t function, const size_t dimension, const size_t instance) { coco_problem_t *problem = NULL; diff --git a/code-postprocessing/MANIFEST.in b/code-postprocessing/MANIFEST.in index 879e9f626..57387a1d4 100644 --- a/code-postprocessing/MANIFEST.in +++ b/code-postprocessing/MANIFEST.in @@ -1,3 +1,4 @@ include cocopp/js/* include cocopp/refalgs/*.tar.gz include cocopp/bbob-*infos.txt +include cocopp/*.pickle.gz diff --git a/code-postprocessing/cocopp/archiving.py b/code-postprocessing/cocopp/archiving.py index 76c74a5ee..9398231fa 100644 --- a/code-postprocessing/cocopp/archiving.py +++ b/code-postprocessing/cocopp/archiving.py @@ -88,7 +88,14 @@ "https://numbbo.github.io/data-archive/data-archive", # new location ] coco_url = coco_urls[-1] # may be reassigned if it doesn't work out -cocopp_home = os.path.abspath(os.path.expanduser(os.path.join("~", ".cocopp"))) + +# cocopp needs a directory where it can cache downloaded datasets. +# +# We use `platformdirs` to find the users cache directory in a platform independent way +# and create a subdirectory within for cocopp. + +import platformdirs +cocopp_home = platformdirs.user_cache_dir("cocopp", ensure_exists=True) default_archive_location = os.path.join(cocopp_home, 'data-archives') default_definition_filename = 'coco_archive_definition.txt' cocopp_home_archives = default_archive_location diff --git a/code-postprocessing/pyproject.toml b/code-postprocessing/pyproject.toml index 04b0cd789..639b18f74 100644 --- a/code-postprocessing/pyproject.toml +++ b/code-postprocessing/pyproject.toml @@ -34,6 +34,8 @@ classifiers = [ dependencies = [ "matplotlib >=3.5.0", "numpy >=1.21.0", + "setuptools >=61.0", # For pkg_resources + "platformdirs >=3.8.1", ] dynamic = ["version"] diff --git a/scripts/fabricate b/scripts/fabricate index c6952ece6..b968582ff 100755 --- a/scripts/fabricate +++ b/scripts/fabricate @@ -29,7 +29,8 @@ RUST_DIR = BUILD_DIR / "rust" CORE_FILES = ['code-experiments/src/coco_random.c', 'code-experiments/src/coco_suite.c', 'code-experiments/src/coco_observer.c', - 'code-experiments/src/coco_archive.c' + 'code-experiments/src/coco_archive.c', + 'code-experiments/src/coco_version.c' ] @@ -186,26 +187,15 @@ def write_version_to_python(filename, version): fd.write(f'__version__ = version = "{version}"\n') -def write_version_to_c_header(filename, version, +def write_version_to_c_file(filename, version, variable="coco_version", guard="COCO_VERSION"): info("version", f"{version} -> {filename}") with open(ROOT_DIR / filename, "wt") as fd: - fd.write("/* file generated by do.py\n") + fd.write("/* file generated by fabricate.py\n") fd.write(" * don't change, don't track in version control!\n") fd.write(" */\n\n") - fd.write(f"#ifndef {guard}_H\n") - fd.write(f"#define {guard}_H\n") - fd.write("/**\n") - fd.write("* @brief COCO's version.\n") - fd.write("*\n") - fd.write("* The version number is dervied from the latest tag in the\n") - fd.write("* repository plus the number of commits after the tag.\n") - fd.write("*/\n") - fd.write("/**@{*/\n") - fd.write(f'static const char { variable }[{ len(version)+1 }] = "{ version }";\n') - fd.write("/**@}*/\n") - fd.write("#endif\n") + fd.write(f'const char *{ variable } = "{ version }";\n') ################################################################################ ## MAIN @@ -251,7 +241,7 @@ OCTAVE_DISTFILE = DIST_DIR / f"cocoex-octave-{COCO_VERSION}.zip" JAVA_DISTFILE = DIST_DIR / f"cocoex-java-{COCO_VERSION}.zip" RUST_DISTFILE = DIST_DIR / f"cocoex-rust-{COCO_VERSION}.zip" -write_version_to_c_header("code-experiments/src/coco_version.h", COCO_VERSION) +write_version_to_c_file("code-experiments/src/coco_version.c", COCO_VERSION) if not args.skip_c: write_version_file("code-experiments/build/c/VERSION", COCO_VERSION)