diff --git a/.github/workflows/dockercentos.yml b/.github/workflows/dockercentos.yml index 2494213d5..80eac9bd5 100644 --- a/.github/workflows/dockercentos.yml +++ b/.github/workflows/dockercentos.yml @@ -1,13 +1,28 @@ # This files generates Python wheels for Linux on: push: - branches: - - codac1 - - codac2 - - codac2_codac4matlab + branches: ['**'] tags-ignore: ['**'] # Ignore all tag pushes pull_request: +permissions: + contents: write + +concurrency: + # Keep only the newest run of this workflow for a given branch or pull + # request: a push that supersedes another leaves the older run computing a + # result nobody will read, while its jobs hold runners the newest run is + # waiting for. + # + # The three release branches are excluded. They are where the jobs upload + # their packages to a GitHub release (see the "github.ref_name==" conditions + # further down), and cancelling such a run halfway would leave the release + # with only part of its assets. The same expression is used in every + # workflow of this directory, including the ones that publish nothing, so + # that the rule stays a single thing to know. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref_name != 'codac1' && github.ref_name != 'codac2' && github.ref_name != 'codac2_codac4matlab' }} + jobs: dockercentos: runs-on: ${{ matrix.cfg.os }} diff --git a/.github/workflows/dockermatrix.yml b/.github/workflows/dockermatrix.yml index 5045d0245..1ab715bae 100644 --- a/.github/workflows/dockermatrix.yml +++ b/.github/workflows/dockermatrix.yml @@ -1,17 +1,31 @@ # This file checks that the lib runs on ARM on: push: - branches: - - codac1 - - codac2 - # The following is implied by above selection... - #branches-ignore: - #- codac2_codac4matlab + branches-ignore: + - codac2_codac4matlab tags-ignore: ['**'] # Ignore all tag pushes pull_request: branches-ignore: - codac2_codac4matlab +permissions: + contents: write + +concurrency: + # Keep only the newest run of this workflow for a given branch or pull + # request: a push that supersedes another leaves the older run computing a + # result nobody will read, while its jobs hold runners the newest run is + # waiting for. + # + # The three release branches are excluded. They are where the jobs upload + # their packages to a GitHub release (see the "github.ref_name==" conditions + # further down), and cancelling such a run halfway would leave the release + # with only part of its assets. The same expression is used in every + # workflow of this directory, including the ones that publish nothing, so + # that the rule stays a single thing to know. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref_name != 'codac1' && github.ref_name != 'codac2' && github.ref_name != 'codac2_codac4matlab' }} + jobs: dockermatrix: runs-on: ${{ matrix.cfg.os }} @@ -72,25 +86,14 @@ jobs: if [ \"${{ matrix.cfg.deb }}\" = \"true\" ]; then \ sudo sh -c 'echo \"deb [trusted=yes] https://webperso.ensta.fr/packages/\$(if [ -z \"\$(. /etc/os-release && echo \$UBUNTU_CODENAME)\" ]; then echo debian/\$(. /etc/os-release && echo \$VERSION_CODENAME); else echo ubuntu/\$(. /etc/os-release && echo \$UBUNTU_CODENAME); fi) ./\" > /etc/apt/sources.list.d/ensta-bretagne.list' && \ #sudo apt-get -q update ; sudo apt-get -y install libeigen3-dev catch2 dpkg-dev || true && \\ - sudo apt-get -q update ; sudo apt-get -y install catch2 dpkg-dev || true && \ - wget https://github.com/lebarsfa/ibex-lib/releases/download/ibex-2.8.9.20260819/libibex-dev-2.8.9.20260819-0${{ matrix.cfg.runtime }}0_\$(dpkg --print-architecture).deb --no-check-certificate -nv && \ - sudo dpkg -i libibex-dev-2.8.9.20260819-0${{ matrix.cfg.runtime }}0_\$(dpkg --print-architecture).deb && \ - rm -Rf libibex-dev-2.8.9.20260819-0${{ matrix.cfg.runtime }}0_\$(dpkg --print-architecture).deb ; \ - else \ - wget https://github.com/lebarsfa/ibex-lib/releases/download/ibex-2.8.9.20260819/ibex_${{ matrix.cfg.arch }}_${{ matrix.cfg.runtime }}.zip --no-check-certificate -nv && \ - unzip -q ibex_${{ matrix.cfg.arch }}_${{ matrix.cfg.runtime }}.zip && \ - rm -Rf ibex_${{ matrix.cfg.arch }}_${{ matrix.cfg.runtime }}.zip && \ - sudo cp -Rf ibex/* /usr/ ; \ + sudo apt-get -q update ; sudo apt-get -y install catch2 dpkg-dev || true ; \ fi && \ mkdir build ; cd build && \ - cmake -E env CXXFLAGS="${{ matrix.cfg.cmake_flags }}" CFLAGS="${{ matrix.cfg.cmake_flags }}" cmake ${{ matrix.cfg.cmake_params }} -D BUILD_TESTS=ON -D CMAKE_INSTALL_PREFIX="../codac" .. && \ + cmake -E env CXXFLAGS="${{ matrix.cfg.cmake_flags }}" CFLAGS="${{ matrix.cfg.cmake_flags }}" cmake ${{ matrix.cfg.cmake_params }} -D BUILD_TESTS=ON -D TEST_EXAMPLES=ON -D CMAKE_INSTALL_PREFIX="../codac" .. && \ cmake --build . -j 4 --config Release --target install && \ cd .. && \ zip -q -r codac_${{ matrix.cfg.arch }}_${{ matrix.cfg.runtime }}.zip codac && \ mkdir -p codac_standalone/example ; cd codac_standalone && \ - if [ \"${{ matrix.cfg.deb }}\" = \"true\" ]; then mkdir -p ibex/include ; mkdir -p ibex/lib ; mkdir -p ibex/share ; mkdir -p ibex/bin ; cp -Rf /usr/include/ibex* ibex/include/ ; cp -Rf /usr/lib/*ibex* ibex/lib/ ; cp -Rf /usr/share/*ibex* ibex/share/ ; cp -Rf /usr/share/pkgconfig ibex/share/ ; cp -Rf /usr/bin/ibex* ibex/bin/ ; \ - else cp -Rf ../ibex . ; \ - fi && \ cp -Rf ../codac . ; cp -Rf ../tests/test_codac/* ./example/ ; cd .. ; zip -q -r codac_standalone_${{ matrix.cfg.arch }}_${{ matrix.cfg.runtime }}.zip codac_standalone && \ cd codac_standalone/example && \ cmake ${{ matrix.cfg.cmake_params }} . && \ diff --git a/.github/workflows/macdebug.yml b/.github/workflows/macdebug.yml new file mode 100644 index 000000000..d7dbc2142 --- /dev/null +++ b/.github/workflows/macdebug.yml @@ -0,0 +1,251 @@ +# Debug builds on macOS, under AddressSanitizer and UndefinedBehaviorSanitizer. +# +# The macOS counterpart of unixdebug.yml, and the same reasoning: every other +# workflow builds macOS in Release, so nothing was ever run there under a +# sanitizer. It also runs the examples, which -D TEST_EXAMPLES=ON registers as +# ctest integration tests (see examples/CMakeLists.txt). +# +# As on Linux, GitHub offers only two instruction sets here, so the third +# configuration is a compiler rather than a third architecture: AppleClang next +# to GCC, on arm64. +# +# Two things make macOS harder than Linux, and shape the matrix below. +# +# GCC on macOS links against libstdc++ while AppleClang links against libc++, +# so a library built with one of them cannot be linked by the other -- the two +# standard libraries are not ABI-compatible. GAOL is no obstacle: codac builds +# it with the compiler of the job (see scripts/CMakeModules/codac_gaol.cmake). +# Catch2 has to be treated the same way, which the Toolchain step below does. +# +# The Python bindings are left out of every job here, sanitized macOS being a +# configuration the test suite cannot currently run them in: the block of +# tests/CMakeLists.txt that hands the sanitizer runtime to the interpreter is +# Linux-only by construction, and says so -- LD_PRELOAD has no equivalent there, +# macOS needing DYLD_INSERT_LIBRARIES and a differently named runtime. Without +# that preload the extension module fails to load on the first unresolved +# __asan_* symbol. The Python half of the suite therefore runs under a sanitizer +# in unixdebug.yml, where the mechanism exists, and macOS covers the C++ half: +# the library, its unit tests and the C++ examples. Turning WITH_PYTHON off also +# removes the need for doxygen here, which doc/CMakeLists.txt only requires for +# the bindings. +on: + push: + branches: ['**'] + tags-ignore: ['**'] # Ignore all tag pushes + pull_request: + +concurrency: + # Keep only the newest run of this workflow for a given branch or pull + # request: a push that supersedes another leaves the older run computing a + # result nobody will read, while its jobs hold runners the newest run is + # waiting for. + # + # The three release branches are excluded. They are where the jobs upload + # their packages to a GitHub release (see the "github.ref_name==" conditions + # further down), and cancelling such a run halfway would leave the release + # with only part of its assets. The same expression is used in every + # workflow of this directory, including the ones that publish nothing, so + # that the rule stays a single thing to know. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref_name != 'codac1' && github.ref_name != 'codac2' && github.ref_name != 'codac2_codac4matlab' }} + +jobs: + macdebug: + runs-on: ${{ matrix.cfg.os }} + # A sanitized Debug build is roughly an order of magnitude slower to run + # than the Release builds of the other workflows, and the examples are run + # on top of the test suite. Three hours leaves room for that while still + # turning a hang into a red job rather than into six hours of runner time. + timeout-minutes: 180 + defaults: + run: + shell: bash + strategy: + fail-fast: false + matrix: + cfg: + - { os: macos-26 , arch: arm64 , runtime: tahoe , compiler: gcc , with_python: 'OFF', desc: 'macOS Tahoe GCC arm64 Debug ASan+UBSan' } + - { os: macos-15-intel, arch: x86_64, runtime: sequoia, compiler: gcc , with_python: 'OFF', desc: 'macOS Sequoia GCC x86_64 Debug ASan+UBSan' } + - { os: macos-26 , arch: arm64 , runtime: tahoe , compiler: appleclang, with_python: 'OFF', desc: 'macOS Tahoe AppleClang arm64 Debug ASan+UBSan' } + - { os: macos-26 , arch: arm64 , runtime: tahoe , compiler: llvm , with_python: 'OFF', desc: 'macOS Tahoe LLVM Clang arm64 Debug ASan+UBSan' } + name: ${{ matrix.cfg.desc }} + steps: + - uses: actions/checkout@v7 + with: + submodules: true + fetch-depth: 0 + clean: false + + - run: echo "VERBOSE=1" >> $GITHUB_ENV + + # Homebrew's "gcc" formula is the newest GCC it packages, and the binaries + # it installs carry the major version in their name (g++-15 and so on), the + # plain g++ of macOS being a symlink to AppleClang. The version is therefore + # resolved here rather than pinned, and written to the environment for the + # later steps. + # Catch2 is deliberately not installed from Homebrew: the bottle is built + # with AppleClang against libc++, so the GCC jobs cannot link it -- the + # standard-library split described at the top of this file, showing up as + # undefined std::__1:: symbols at link time. Leaving it out lets + # tests/CMakeLists.txt fetch Catch2 and build it with the compiler of the + # job, which is correct for every entry of this matrix. + - name: Toolchain + run: | + case "${{ matrix.cfg.compiler }}" in + gcc) + brew install gcc + GCC_MAJOR=$(brew list --versions gcc | awk '{print $2}' | cut -d. -f1) + echo "CC=gcc-${GCC_MAJOR}" >> "$GITHUB_ENV" + echo "CXX=g++-${GCC_MAJOR}" >> "$GITHUB_ENV" + ;; + llvm) + # Upstream Clang, which is a different compiler from the AppleClang + # of the entry above: its own release cycle, its own diagnostics and + # its own sanitizer runtimes. Homebrew keeps it out of the way of the + # system toolchain, so it has to be named by its prefix, and it needs + # to be pointed at its own libc++ -- the formula says as much -- or + # it compiles against headers newer than the library it links. + brew install llvm + LLVM_PREFIX=$(brew --prefix llvm) + echo "CC=$LLVM_PREFIX/bin/clang" >> "$GITHUB_ENV" + echo "CXX=$LLVM_PREFIX/bin/clang++" >> "$GITHUB_ENV" + echo "LDFLAGS=-L$LLVM_PREFIX/lib/c++ -Wl,-rpath,$LLVM_PREFIX/lib/c++" >> "$GITHUB_ENV" + ;; + *) + echo "CC=clang" >> "$GITHUB_ENV" + echo "CXX=clang++" >> "$GITHUB_ENV" + ;; + esac + + - name: Compiler version + run: $CXX --version + + - name: Configure and build + run: | + mkdir build ; cd build + cmake \ + -D CMAKE_BUILD_TYPE=Debug \ + -D CMAKE_CXX_FLAGS="-fPIC" \ + -D CMAKE_C_FLAGS="-fPIC" \ + -D CMAKE_INSTALL_PREFIX="../codac" \ + -D BUILD_TESTS=ON \ + -D TEST_EXAMPLES=ON \ + -D WITH_PYTHON=${{ matrix.cfg.with_python }} \ + -D PYBIND11_FINDPYTHON=OFF \ + -D WITH_CAPD=OFF \ + .. 2>&1 | tee configure.log + cmake --build . -j 4 + + # The point of this workflow is the sanitizers, and the top-level + # CMakeLists.txt deliberately falls back to an unsanitized Debug build when + # it cannot find their runtime rather than failing to link. That fallback is + # the right default for someone building codac by hand, and exactly the + # wrong outcome here: the job would come out green having checked nothing it + # was written for. GCC's Darwin sanitizer support is the reason this is not + # theoretical. + - name: Check that the sanitizers really are enabled + run: | + if grep -q "will not be sanitized" build/configure.log ; then + echo "This job exists to run the suite under ASan and UBSan, and cmake reported:" + grep "will not be sanitized" build/configure.log + exit 1 + fi + echo "Sanitizers enabled." + + # The unit tests and the examples are the same ctest suite: examples are + # registered as tests by examples/CMakeLists.txt, so this one command runs + # both, and a sanitizer report in either fails the job. + - name: Unit tests and examples + run: | + cd build + ctest -V --output-on-failure 2>&1 | tee ctest.log + + # ctest is run verbose so that everything the sanitizers print reaches the + # log. ASan and UBSan do not agree on what a diagnostic costs: a leak or a + # buffer overflow aborts the process and fails the test, but a UBSan runtime + # error only prints and lets the run continue, so a test can pass having + # reported dozens of undefined behaviours. Without -V that output is thrown + # away for every test that passes -- which is precisely the output worth + # reading here. + - name: Sanitizer diagnostics + if: always() + run: | + log=build/ctest.log + [ -f "$log" ] || { echo "No ctest output to scan." ; exit 0 ; } + # A digest, because the verbose log of the whole suite is far too long + # to scan by eye. The step reports rather than judges: it never fails + # the job, the tests themselves decide that. + n=$(grep -cE "runtime error:|ERROR: AddressSanitizer|ERROR: LeakSanitizer|SUMMARY: (Address|Undefined|Leak)Sanitizer" "$log" || true) + echo "Sanitizer diagnostics found: $n" + if [ "$n" -gt 0 ]; then + echo "--- distinct messages, most frequent first ---" + # awk rather than head: this shell runs with pipefail, and head + # closing the pipe early makes sort die on SIGPIPE, which failed the + # step -- the one thing it was written never to do. + grep -hoE "runtime error: .*|ERROR: (Address|Leak)Sanitizer: [a-z-]+" "$log" \ + | sed -E "s/0x[0-9a-f]+/0xADDR/g" | sort | uniq -c | sort -rn | awk 'NR<=40' + echo "--- first occurrences in context ---" + grep -nE "runtime error:|ERROR: (Address|Leak)Sanitizer" "$log" | awk 'NR<=20' + fi + + # The macOS counterpart of the packaging job of unixdebug.yml, and the same + # reasoning: the two ways of consuming an installed Codac -- find_package(CODAC) + # through codac-config.cmake and the export set beside it, and pkg-config + # through codac.pc -- are written by hand in different places of + # src/CMakeLists.txt, had drifted apart in both directions, and nothing in CI + # built anything against an installed Codac at all. See + # tests/packaging/check_consumer_parity.sh. + # + # A job of its own, not a step of the matrix above, because the library there + # is Debug under AddressSanitizer and an ordinary consumer cannot link such an + # archive. Only AppleClang appears here: the parity check reads what the + # install tree says about itself, which does not depend on the compiler. + packaging: + runs-on: ${{ matrix.cfg.os }} + timeout-minutes: 60 + defaults: + run: + shell: bash + strategy: + fail-fast: false + matrix: + cfg: + # Both instruction sets GitHub offers for macOS. + - { os: macos-26 , arch: arm64 , runtime: tahoe , desc: 'macOS Tahoe arm64 packaging (find_package vs pkg-config)' } + - { os: macos-15-intel, arch: x86_64, runtime: sequoia , desc: 'macOS Sequoia x86_64 packaging (find_package vs pkg-config)' } + name: ${{ matrix.cfg.desc }} + steps: + - uses: actions/checkout@v7 + with: + submodules: true + fetch-depth: 0 + clean: false + + # pkgconf is what provides pkg-config on Homebrew. It is already on the + # images, so this only guards against an image that drops it; the parity + # check below is run with --require-pkgconfig and fails loudly if it is + # missing rather than checking half of what it was added for. + - name: Toolchain and dependencies + run: | + brew install pkgconf || true + pkg-config --version + clang++ --version + + - name: Build and install Codac + run: | + mkdir build ; cd build + cmake \ + -D CMAKE_BUILD_TYPE=Release \ + -D CMAKE_INSTALL_PREFIX="$PWD/../codac_install" \ + -D BUILD_TESTS=OFF \ + -D WITH_PYTHON=OFF \ + -D WITH_CAPD=OFF \ + .. + cmake --build . -j 4 --target install + + - name: find_package and pkg-config must agree + run: | + bash tests/packaging/check_consumer_parity.sh \ + --prefix "$PWD/codac_install" \ + --build-dir "$PWD/build_packaging_check" \ + --require-pkgconfig diff --git a/.github/workflows/macosmatrix.yml b/.github/workflows/macosmatrix.yml index 20ba35698..d607f26d6 100644 --- a/.github/workflows/macosmatrix.yml +++ b/.github/workflows/macosmatrix.yml @@ -1,13 +1,28 @@ # This file generates Python wheels for macOS on: push: - branches: - - codac1 - - codac2 - - codac2_codac4matlab + branches: ['**'] tags-ignore: ['**'] # Ignore all tag pushes pull_request: +permissions: + contents: write + +concurrency: + # Keep only the newest run of this workflow for a given branch or pull + # request: a push that supersedes another leaves the older run computing a + # result nobody will read, while its jobs hold runners the newest run is + # waiting for. + # + # The three release branches are excluded. They are where the jobs upload + # their packages to a GitHub release (see the "github.ref_name==" conditions + # further down), and cancelling such a run halfway would leave the release + # with only part of its assets. The same expression is used in every + # workflow of this directory, including the ones that publish nothing, so + # that the rule stays a single thing to know. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref_name != 'codac1' && github.ref_name != 'codac2' && github.ref_name != 'codac2_codac4matlab' }} + jobs: macosmatrix: runs-on: ${{ matrix.cfg.os }} @@ -21,17 +36,17 @@ jobs: fail-fast: false matrix: cfg: - - { os: macos-14 , shell: bash, arch: arm64 , runtime: sonoma , cmake_flags: '-fPIC', trgt: '11.0' , cpcfg: '-macosx_11_0_arm64' , py_v_maj: 3, py_v_min: 14, desc: 'macOS Sonoma Python 3.14 arm64' } - - { os: macos-14 , shell: bash, arch: arm64 , runtime: sonoma , cmake_flags: '-fPIC', trgt: '11.0' , cpcfg: '-macosx_11_0_arm64' , py_v_maj: 3, py_v_min: 13, desc: 'macOS Sonoma Python 3.13 arm64' } - - { os: macos-14 , shell: bash, arch: arm64 , runtime: sonoma , cmake_flags: '-fPIC', trgt: '11.0' , cpcfg: '-macosx_11_0_arm64' , py_v_maj: 3, py_v_min: 12, desc: 'macOS Sonoma Python 3.12 arm64' } - - { os: macos-14 , shell: bash, arch: arm64 , runtime: sonoma , cmake_flags: '-fPIC', trgt: '11.0' , cpcfg: '-macosx_11_0_arm64' , py_v_maj: 3, py_v_min: 11, desc: 'macOS Sonoma Python 3.11 arm64' } - - { os: macos-15-intel , shell: bash, arch: x86_64 , runtime: sequoia , cmake_flags: '-fPIC', trgt: '10.16', cpcfg: '-macosx_10_16_x86_64', py_v_maj: 3, py_v_min: 14, desc: 'macOS Sequoia Python 3.14 x86_64' } - - { os: macos-15-intel , shell: bash, arch: x86_64 , runtime: sequoia , cmake_flags: '-fPIC', trgt: '10.16', cpcfg: '-macosx_10_16_x86_64', py_v_maj: 3, py_v_min: 13, desc: 'macOS Sequoia Python 3.13 x86_64' } - - { os: macos-15-intel , shell: bash, arch: x86_64 , runtime: sequoia , cmake_flags: '-fPIC', trgt: '10.16', cpcfg: '-macosx_10_16_x86_64', py_v_maj: 3, py_v_min: 12, desc: 'macOS Sequoia Python 3.12 x86_64' } - - { os: macos-15-intel , shell: bash, arch: x86_64 , runtime: sequoia , cmake_flags: '-fPIC', trgt: '10.16', cpcfg: '-macosx_10_16_x86_64', py_v_maj: 3, py_v_min: 11, desc: 'macOS Sequoia Python 3.11 x86_64' } - - { os: macos-15-intel , shell: bash, arch: x86_64 , runtime: sequoia , cmake_flags: '-fPIC', trgt: '10.16', cpcfg: '-macosx_10_16_x86_64', py_v_maj: 3, py_v_min: 10, desc: 'macOS Sequoia Python 3.10 x86_64' } - - { os: macos-15-intel , shell: bash, arch: x86_64 , runtime: sequoia , cmake_flags: '-fPIC', trgt: '10.16', cpcfg: '-macosx_10_16_x86_64', py_v_maj: 3, py_v_min: 9 , desc: 'macOS Sequoia Python 3.9 x86_64' } - - { os: macos-15-intel , shell: bash, arch: x86_64 , runtime: sequoia , cmake_flags: '-fPIC', trgt: '10.16', cpcfg: '-macosx_10_16_x86_64', py_v_maj: 3, py_v_min: 8 , desc: 'macOS Sequoia Python 3.8 x86_64' } + - { os: macos-14 , shell: bash, arch: arm64 , runtime: sonoma , trgt: '11.0' , cpcfg: '-macosx_11_0_arm64' , py_v_maj: 3, py_v_min: 14, desc: 'macOS Sonoma Python 3.14 arm64' } + - { os: macos-14 , shell: bash, arch: arm64 , runtime: sonoma , trgt: '11.0' , cpcfg: '-macosx_11_0_arm64' , py_v_maj: 3, py_v_min: 13, desc: 'macOS Sonoma Python 3.13 arm64' } + - { os: macos-14 , shell: bash, arch: arm64 , runtime: sonoma , trgt: '11.0' , cpcfg: '-macosx_11_0_arm64' , py_v_maj: 3, py_v_min: 12, desc: 'macOS Sonoma Python 3.12 arm64' } + - { os: macos-14 , shell: bash, arch: arm64 , runtime: sonoma , trgt: '11.0' , cpcfg: '-macosx_11_0_arm64' , py_v_maj: 3, py_v_min: 11, desc: 'macOS Sonoma Python 3.11 arm64' } + - { os: macos-15-intel , shell: bash, arch: x86_64 , runtime: sequoia , trgt: '10.16', cpcfg: '-macosx_10_16_x86_64', py_v_maj: 3, py_v_min: 14, desc: 'macOS Sequoia Python 3.14 x86_64' } + - { os: macos-15-intel , shell: bash, arch: x86_64 , runtime: sequoia , trgt: '10.16', cpcfg: '-macosx_10_16_x86_64', py_v_maj: 3, py_v_min: 13, desc: 'macOS Sequoia Python 3.13 x86_64' } + - { os: macos-15-intel , shell: bash, arch: x86_64 , runtime: sequoia , trgt: '10.16', cpcfg: '-macosx_10_16_x86_64', py_v_maj: 3, py_v_min: 12, desc: 'macOS Sequoia Python 3.12 x86_64' } + - { os: macos-15-intel , shell: bash, arch: x86_64 , runtime: sequoia , trgt: '10.16', cpcfg: '-macosx_10_16_x86_64', py_v_maj: 3, py_v_min: 11, desc: 'macOS Sequoia Python 3.11 x86_64' } + - { os: macos-15-intel , shell: bash, arch: x86_64 , runtime: sequoia , trgt: '10.16', cpcfg: '-macosx_10_16_x86_64', py_v_maj: 3, py_v_min: 10, desc: 'macOS Sequoia Python 3.10 x86_64' } + - { os: macos-15-intel , shell: bash, arch: x86_64 , runtime: sequoia , trgt: '10.16', cpcfg: '-macosx_10_16_x86_64', py_v_maj: 3, py_v_min: 9 , desc: 'macOS Sequoia Python 3.9 x86_64' } + - { os: macos-15-intel , shell: bash, arch: x86_64 , runtime: sequoia , trgt: '10.16', cpcfg: '-macosx_10_16_x86_64', py_v_maj: 3, py_v_min: 8 , desc: 'macOS Sequoia Python 3.8 x86_64' } name: ${{ matrix.cfg.desc }} steps: - uses: actions/checkout@v7 @@ -42,7 +57,10 @@ jobs: - uses: actions/setup-python@v7 with: python-version: ${{ matrix.cfg.py_v_maj }}.${{ matrix.cfg.py_v_min }} - architecture: x64 + # arm64 on the Apple Silicon entries: the interpreter has to run + # natively to load the arm64-only extension, which "x64" only achieved + # as long as the build it installed happened to be universal2. + architecture: ${{ matrix.cfg.arch == 'arm64' && 'arm64' || 'x64' }} if: matrix.cfg.py_v_maj!='' - run: | python -c "import sys; print(sys.version)" @@ -52,19 +70,53 @@ jobs: shell: bash #- run: brew install eigen # if: runner.os=='macOS' - - run: brew install catch2 # Issues with binary packages when cross-compiling... - if: (runner.os=='macOS')&&(matrix.cfg.cross!=true) - - run: brew install graphviz ; brew install --formula doxygen ; python -m pip install --upgrade pip ; pip install --upgrade wheel setuptools sphinx breathe sphinx_rtd_theme sphinx-tabs sphinx-issues sphinx-reredirects furo sphinx-math-dollar sphinx_togglebutton sympy - if: runner.os=='macOS' + # Catch2 is not taken from Homebrew: its package is built for the macOS of + # the runner, newer than the MACOSX_DEPLOYMENT_TARGET of these jobs, and the + # linker warned "was built for newer 'macOS' version (14.0) than being + # linked (10.16)" for each of its objects, in every test (some 76000 lines + # per run). tests/CMakeLists.txt then builds Catch2 with the deployment + # target and the architecture of the job. + # Doxygen 1.18.0, the version Homebrew currently installs, segfaults on the + # Intel runners while parsing this project's headers, and does so before + # writing doc/api/xml/index.xml, so cmake cannot even configure -- + # doc/CMakeLists.txt tolerates a crash that still produced usable XML, but + # there is nothing to salvage here. Those jobs therefore take doxygen from + # the project's own release, which is the only place an older version is + # available in binary form: Homebrew never offers anything but the newest. + # + # The arm64 runners keep Homebrew's doxygen. They survive 1.18.0, and the + # arm build of the official release cannot run there anyway: it is linked + # against macOS 15 while these runners are macOS 14, so it aborts at load + # time on a missing libc++ symbol. Two doxygen versions across the matrix is + # not ideal -- the names of the docstring macros are derived from the text + # doxygen produces, so a wording change between versions renames them and + # the bindings that spell them out stop compiling, which is exactly what the + # manylinux images hit (see scripts/doxygen/doxygen2docstring.py). Keeping + # the two versions close, and normalizing that text rather than trusting it, + # is what guards against it. - run: | - wget https://github.com/lebarsfa/ibex-lib/releases/download/ibex-2.8.9.20260819/ibex_${{ matrix.cfg.arch }}_${{ matrix.cfg.runtime }}.zip --no-check-certificate -nv - unzip -q ibex_${{ matrix.cfg.arch }}_${{ matrix.cfg.runtime }}.zip - rm -Rf ibex_${{ matrix.cfg.arch }}_${{ matrix.cfg.runtime }}.zip - sudo cp -Rf ibex/* /usr/local/ - shell: bash + brew install graphviz + if [ "${{ matrix.cfg.arch }}" = "arm64" ]; then + brew install --formula doxygen + doxygen --version + else + DOXYGEN_VERSION=1.17.0 + curl -fsSL -o doxygen.zip "https://github.com/doxygen/doxygen/releases/download/Release_${DOXYGEN_VERSION//./_}/doxygen-${DOXYGEN_VERSION}-mac-intel.zip" + unzip -q doxygen.zip -d "$HOME/doxygen" + rm -f doxygen.zip + chmod +x "$HOME/doxygen/doxygen-${DOXYGEN_VERSION}/doxygen" + # $GITHUB_PATH only reaches the later steps, so the check below has to + # name the binary; it runs here rather than there so that a download + # that cannot execute fails at once, next to what produced it. + "$HOME/doxygen/doxygen-${DOXYGEN_VERSION}/doxygen" --version + echo "$HOME/doxygen/doxygen-${DOXYGEN_VERSION}" >> "$GITHUB_PATH" + fi + python -m pip install --upgrade pip + pip install --upgrade wheel setuptools build sphinx breathe sphinx_rtd_theme sphinx-tabs sphinx-issues sphinx-reredirects furo sphinx-math-dollar sphinx_togglebutton sympy + if: runner.os=='macOS' - run: | mkdir build ; cd build - cmake -E env CXXFLAGS="${{ matrix.cfg.cmake_flags }}" CFLAGS="${{ matrix.cfg.cmake_flags }}" cmake ${{ matrix.cfg.cmake_params }} -D CMAKE_SYSTEM_NAME=Darwin -D CMAKE_OSX_ARCHITECTURES=${{ matrix.cfg.arch }} -D CMAKE_INSTALL_PREFIX="../codac" -D BUILD_TESTS=ON -D WITH_CAPD=OFF -D WITH_PYTHON=ON -D PYBIND11_FINDPYTHON=OFF .. + cmake -E env CXXFLAGS="${{ matrix.cfg.cmake_flags }}" CFLAGS="${{ matrix.cfg.cmake_flags }}" cmake ${{ matrix.cfg.cmake_params }} -D CMAKE_SYSTEM_NAME=Darwin -D CMAKE_OSX_ARCHITECTURES=${{ matrix.cfg.arch }} -D CMAKE_INSTALL_PREFIX="../codac" -D BUILD_TESTS=ON -D TEST_EXAMPLES=ON -D WITH_CAPD=OFF -D WITH_PYTHON=ON -D PYBIND11_FINDPYTHON=OFF .. cmake --build . -j 4 --config Release --target install cmake --build . --config Release --target pip_package ; cp `ls *.whl` ../`ls *.whl | sed "s/py3-none-any/cp${{ matrix.cfg.py_v_maj }}${{ matrix.cfg.py_v_min }}-cp${{ matrix.cfg.py_v_maj }}${{ matrix.cfg.py_v_min }}${{ matrix.cfg.cpcfg }}/"` cd .. @@ -82,7 +134,20 @@ jobs: python -c "import sys; print(sys.version)" ; python examples/02_centered_form/main.py pip install numpy sympy --prefer-binary python -m unittest discover codac.tests - cd build && ctest -C Release -V --output-on-failure + # Only the Python half of the suite is run here. These jobs exist to + # build and check a wheel per Python version, and the C++ tests do not + # depend on that version: running them again on every entry repeated + # the same 87 tests 18 times on Windows and 11 times on macOS. They are + # still compiled, so a C++ regression still breaks this workflow, and + # they are still executed in Release on the very same systems by + # unixmatrix.yml -- Visual Studio 2022 on x86/x64/arm64, macOS Sonoma + # arm64 and Sequoia x86_64. + # The same "_py" filter picks up the Python examples, which + # -D TEST_EXAMPLES=ON registers as codac2_examples_*_py. Unlike the C++ + # ones they do run on the interpreter of each entry, so they belong + # here; the C++ examples are only compiled, and run by unixmatrix.yml + # along with the C++ tests. + cd build && ctest -C Release -R "_py" -V --output-on-failure cd .. shell: bash if: (matrix.cfg.cross!=true)&&(github.ref_name!='codac2_codac4matlab')&&(github.event.pull_request.base.ref!='codac2_codac4matlab') diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index afb301494..b79fc5abb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,6 +8,21 @@ on: branches-ignore: - codac2_codac4matlab +concurrency: + # Keep only the newest run of this workflow for a given branch or pull + # request: a push that supersedes another leaves the older run computing a + # result nobody will read, while its jobs hold runners the newest run is + # waiting for. + # + # The three release branches are excluded. They are where the jobs upload + # their packages to a GitHub release (see the "github.ref_name==" conditions + # further down), and cancelling such a run halfway would leave the release + # with only part of its assets. The same expression is used in every + # workflow of this directory, including the ones that publish nothing, so + # that the rule stays a single thing to know. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref_name != 'codac1' && github.ref_name != 'codac2' && github.ref_name != 'codac2_codac4matlab' }} + jobs: tests: runs-on: ${{ matrix.cfg.os }} @@ -45,7 +60,7 @@ jobs: sudo sh -c 'echo "deb [trusted=yes] https://webperso.ensta.fr/packages/$(if [ -z "$(. /etc/os-release && echo $UBUNTU_CODENAME)" ]; then echo debian/$(. /etc/os-release && echo $VERSION_CODENAME); else echo ubuntu/$(. /etc/os-release && echo $UBUNTU_CODENAME); fi) ./" > /etc/apt/sources.list.d/ensta-bretagne.list' sudo apt update - sudo apt-get -y install flex bison catch2 pybind11-dev # libeigen3-dev + sudo apt-get -y install catch2 pybind11-dev # libeigen3-dev # For documentation pip install sphinx breathe sphinx-issues sphinx-tabs sphinx_rtd_theme sympy @@ -71,16 +86,12 @@ jobs: ls cd $ORIGIN_DIR - # IBEX - bash scripts/dependencies/install_ibex.sh - # CAPD # cancelled on 2023/05/09: bash scripts/dependencies/install_capd.sh if [ "${{ matrix.cfg.with_capd }}" = "ON" ]; then git clone -b master https://github.com/CAPDGroup/CAPD.git ; cd CAPD ; git checkout 380b117 ; mkdir build ; cd build ; cmake .. ; cmake -E env CXXFLAGS="-fPIC" CFLAGS="-fPIC" sudo cmake --build . -j 4 --config Release --target install ; cd ../.. ; fi # Environment variables export CMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH:$HOME/codac/build_install - export CMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH:$HOME/ibex-lib/build_install export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib #py_version=$(python -c "import sys; print(sys.version[:3])") # default python version @@ -94,7 +105,7 @@ jobs: cd build # Building lib + tests - cmake -DCMAKE_INSTALL_PREFIX=$HOME/codac/build_install -DCMAKE_PREFIX_PATH=$HOME/ibex-lib/build_install -DCMAKE_CXX_FLAGS="-fPIC" -DCMAKE_C_FLAGS="-fPIC" -DWITH_CAPD=${{ matrix.cfg.with_capd }} -DWITH_PYTHON=ON -DPYBIND11_FINDPYTHON=OFF -DBUILD_TESTS=ON -DTEST_EXAMPLES=ON .. + cmake -DCMAKE_INSTALL_PREFIX=$HOME/codac/build_install -DWITH_CAPD=${{ matrix.cfg.with_capd }} -DWITH_PYTHON=ON -DPYBIND11_FINDPYTHON=OFF -DBUILD_TESTS=ON -DTEST_EXAMPLES=ON .. make -j 4 #make doc # todo make install @@ -121,7 +132,7 @@ jobs: cd ../examples cd 01_batman/ - mkdir build ; cd build ; cmake -DCMAKE_PREFIX_PATH="$HOME/ibex-lib/build_install;$HOME/codac/build_install" -DCMAKE_BUILD_TYPE=Debug .. ; make ; ./codac_example + mkdir build ; cd build ; cmake -DCMAKE_PREFIX_PATH="$HOME/codac/build_install" -DCMAKE_BUILD_TYPE=Debug .. ; make ; ./codac_example cd ../../02_centered_form/ - mkdir build ; cd build ; cmake -DCMAKE_PREFIX_PATH="$HOME/ibex-lib/build_install;$HOME/codac/build_install" -DCMAKE_BUILD_TYPE=Debug .. ; make ; ./codac_example \ No newline at end of file + mkdir build ; cd build ; cmake -DCMAKE_PREFIX_PATH="$HOME/codac/build_install" -DCMAKE_BUILD_TYPE=Debug .. ; make ; ./codac_example diff --git a/.github/workflows/unixdebug.yml b/.github/workflows/unixdebug.yml new file mode 100644 index 000000000..82215de99 --- /dev/null +++ b/.github/workflows/unixdebug.yml @@ -0,0 +1,291 @@ +# Debug builds on Linux, under AddressSanitizer and UndefinedBehaviorSanitizer, +# plus one coverage run. +# +# The rest of this directory answers "does it build and do the tests pass?" on a +# wide spread of systems, all of it in Release. Nothing there ever runs codac +# under a sanitizer on Linux or macOS: windebugmatrix.yml is the only Debug +# workflow and its only sanitized entries are the MSVC ones (the MinGW ones are +# unsanitized for want of a runtime in that toolchain). The whole GCC/Clang +# sanitizer path of the top-level CMakeLists.txt -- the configuration this +# project is developed in -- was therefore never exercised by CI, so a +# use-after-free or a signed overflow could only ever be caught on a developer's +# machine. This workflow closes that gap, and macdebug.yml does the same for +# macOS. +# +# It also runs the examples, as every workflow of this directory that runs the +# test suite now does: -D TEST_EXAMPLES=ON builds every example against the +# in-tree library and runs it as an integration test (see +# examples/CMakeLists.txt). Under a sanitizer they are worth as much as the unit +# tests: they exercise long chains of the library the way a user writes them. +# +# GitHub only offers x86_64 and arm64 Linux runners, so the third distinct +# configuration below is a compiler rather than a third instruction set: Clang +# and GCC disagree often enough about undefined behaviour, and their sanitizer +# runtimes are different implementations, that running both is worth more than a +# third architecture reached through emulation -- where ASan does not work +# anyway, its shadow memory needing an address space QEMU cannot provide. +on: + push: + branches: ['**'] + tags-ignore: ['**'] # Ignore all tag pushes + pull_request: + +concurrency: + # Keep only the newest run of this workflow for a given branch or pull + # request: a push that supersedes another leaves the older run computing a + # result nobody will read, while its jobs hold runners the newest run is + # waiting for. + # + # The three release branches are excluded. They are where the jobs upload + # their packages to a GitHub release (see the "github.ref_name==" conditions + # further down), and cancelling such a run halfway would leave the release + # with only part of its assets. The same expression is used in every + # workflow of this directory, including the ones that publish nothing, so + # that the rule stays a single thing to know. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref_name != 'codac1' && github.ref_name != 'codac2' && github.ref_name != 'codac2_codac4matlab' }} + +jobs: + unixdebug: + runs-on: ${{ matrix.cfg.os }} + # A sanitized Debug build is roughly an order of magnitude slower to run + # than the Release builds of the other workflows, and the examples are run + # on top of the test suite. Measured on this matrix: 37 min on GCC arm64, + # 58 min on GCC x86_64, and more than three hours under Clang, whose + # instrumentation is markedly more expensive here -- the first run of this + # workflow spent its whole budget inside the test step and was cut there. + # Six hours is GitHub's own ceiling; it is set to it because a shorter one + # kills a job that is progressing, while ctest already bounds each + # individual test to 1500 s, so a genuinely hung test cannot reach this. + timeout-minutes: 360 + defaults: + run: + shell: bash + strategy: + fail-fast: false + matrix: + cfg: + # The distribution's own compilers are used rather than a pinned + # version from a PPA: on the newest Ubuntu they are already the + # newest GCC and Clang, and every job prints what it actually got, + # so an upgrade of the image shows up in the log instead of + # silently doing nothing. Adding ubuntu-toolchain-r would also put + # a Launchpad round-trip on the critical path of every job, which + # has already taken this repository's CI down with a 504. + - { os: ubuntu-26.04 , arch: x86_64, runtime: resolute, cc: gcc , cxx: g++ , packages: 'g++' , build_type: Debug , with_python: 'ON' , coverage: false, desc: 'Ubuntu 26.04 GCC x86_64 Debug ASan+UBSan' } + - { os: ubuntu-26.04-arm, arch: arm64 , runtime: resolute, cc: gcc , cxx: g++ , packages: 'g++' , build_type: Debug , with_python: 'ON' , coverage: false, desc: 'Ubuntu 26.04 GCC arm64 Debug ASan+UBSan' } + - { os: ubuntu-26.04 , arch: x86_64, runtime: resolute, cc: clang , cxx: clang++ , packages: 'clang', build_type: Debug , with_python: 'ON' , coverage: false, test_timeout: 300, desc: 'Ubuntu 26.04 Clang x86_64 Debug ASan+UBSan' } + - { os: ubuntu-26.04-arm, arch: arm64 , runtime: resolute, cc: clang , cxx: clang++ , packages: 'clang', build_type: Debug , with_python: 'ON' , coverage: false, test_timeout: 300, desc: 'Ubuntu 26.04 Clang arm64 Debug ASan+UBSan' } + - { os: ubuntu-26.04 , arch: x86_64, runtime: resolute, cc: gcc , cxx: g++ , packages: 'g++' , build_type: Release, with_python: 'ON' , coverage: true , desc: 'Ubuntu 26.04 GCC x86_64 Release coverage' } + name: ${{ matrix.cfg.desc }} + steps: + - uses: actions/checkout@v7 + with: + submodules: true + fetch-depth: 0 + clean: false + + - run: echo "VERBOSE=1" >> $GITHUB_ENV + + # setup-python rather than the interpreter of the image: the bindings need + # the development headers, and this is how tests.yml already provides them. + - uses: actions/setup-python@v7 + with: + python-version: '3.13' + + # doxygen and graphviz are what the docstrings of the bindings are generated + # from. pybind11 is deliberately not installed from the distribution: codac + # asks for 3.0.1 and fetches that version itself when it is not found, which + # is what every other workflow of this directory ends up using, whereas the + # package of the newest Ubuntu got picked up first and left codac-sympy + # linking a pybind11::pybind11 target that its CMake config does not define. + - name: Toolchain and dependencies + run: | + sudo apt-get -q update + sudo apt-get -y install ${{ matrix.cfg.packages }} cmake catch2 doxygen graphviz dpkg-dev || true + ${{ matrix.cfg.cxx }} --version + + - name: Python tooling + run: | + python -m pip install --upgrade pip wheel setuptools numpy sympy + if [ "${{ matrix.cfg.coverage }}" = "true" ]; then + python -m pip install --upgrade gcovr + fi + + # The instrumentation is requested through -D WITH_COVERAGE=ON rather than + # by passing --coverage by hand, so that this job and a developer running + # `make coverage` measure the same build with the same tool: the top-level + # CMakeLists.txt is the single place where the flags, the gcov executable + # matching the compiler and the gcovr filters are decided. The build type + # stays Release, so the coverage job compiles exactly what the Release jobs + # of the other workflows compile, instrumentation aside. + - name: Configure and build + env: + CC: ${{ matrix.cfg.cc }} + CXX: ${{ matrix.cfg.cxx }} + run: | + mkdir build ; cd build + cmake \ + -D WITH_COVERAGE=${{ matrix.cfg.coverage && 'ON' || 'OFF' }} \ + -D CMAKE_BUILD_TYPE=${{ matrix.cfg.build_type }} \ + -D CMAKE_INSTALL_PREFIX="../codac" \ + -D BUILD_TESTS=ON \ + -D TEST_EXAMPLES=ON \ + -D WITH_PYTHON=${{ matrix.cfg.with_python }} \ + -D PYBIND11_FINDPYTHON=OFF \ + -D WITH_CAPD=OFF \ + .. 2>&1 | tee configure.log + cmake --build . -j 4 + + # The point of the Debug jobs is the sanitizers, and the top-level + # CMakeLists.txt deliberately falls back to an unsanitized Debug build when + # it cannot find their runtime rather than failing to link. That fallback is + # the right default for someone building codac by hand, and exactly the + # wrong outcome here: the job would come out green having checked nothing it + # was written for. + - name: Check that the sanitizers really are enabled + if: matrix.cfg.build_type == 'Debug' + run: | + if grep -q "will not be sanitized" build/configure.log ; then + echo "This job exists to run the suite under ASan and UBSan, and cmake reported:" + grep "will not be sanitized" build/configure.log + exit 1 + fi + echo "Sanitizers enabled." + + # The unit tests and the examples are the same ctest suite: examples are + # registered as tests by examples/CMakeLists.txt, so this one command runs + # both, and a sanitizer report in either fails the job. + - name: Unit tests and examples + run: | + cd build + # ctest's own default here is 1500 s per test, which is far above what + # any test of this suite needs even instrumented -- the slowest of them + # takes under three minutes on a developer machine under ASan. Where a + # job sets test_timeout, that bound is tightened so that a test which + # stops progressing is reported as the one at fault, with its output, + # instead of the job being killed hours later with nothing to read. + TIMEOUT_FLAG="" + if [ -n "${{ matrix.cfg.test_timeout }}" ]; then + TIMEOUT_FLAG="--timeout ${{ matrix.cfg.test_timeout }}" + fi + # -V on the Debug jobs only: the coverage job runs the same suite + # without instrumentation, and its verbose output would carry nothing + # the others do not already show. + VERBOSE_FLAG="" + if [ "${{ matrix.cfg.build_type }}" = "Debug" ]; then VERBOSE_FLAG="-V" ; fi + ctest $VERBOSE_FLAG --output-on-failure $TIMEOUT_FLAG 2>&1 | tee ctest.log + + # ctest is run verbose so that everything the sanitizers print reaches the + # log. ASan and UBSan do not agree on what a diagnostic costs: a leak or a + # buffer overflow aborts the process and fails the test, but a UBSan runtime + # error only prints and lets the run continue, so a test can pass having + # reported dozens of undefined behaviours. Without -V that output is thrown + # away for every test that passes -- which is precisely the output worth + # reading here. + - name: Sanitizer diagnostics + if: always() + run: | + log=build/ctest.log + [ -f "$log" ] || { echo "No ctest output to scan." ; exit 0 ; } + # A digest, because the verbose log of the whole suite is far too long + # to scan by eye. The step reports rather than judges: it never fails + # the job, the tests themselves decide that. + n=$(grep -cE "runtime error:|ERROR: AddressSanitizer|ERROR: LeakSanitizer|SUMMARY: (Address|Undefined|Leak)Sanitizer" "$log" || true) + echo "Sanitizer diagnostics found: $n" + if [ "$n" -gt 0 ]; then + echo "--- distinct messages, most frequent first ---" + # awk rather than head: this shell runs with pipefail, and head + # closing the pipe early makes sort die on SIGPIPE, which failed the + # step -- the one thing it was written never to do. + grep -hoE "runtime error: .*|ERROR: (Address|Leak)Sanitizer: [a-z-]+" "$log" \ + | sed -E "s/0x[0-9a-f]+/0xADDR/g" | sort | uniq -c | sort -rn | awk 'NR<=40' + echo "--- first occurrences in context ---" + grep -nE "runtime error:|ERROR: (Address|Leak)Sanitizer" "$log" | awk 'NR<=20' + fi + + # coverage-report, not coverage: the suite has already been run by the step + # above, and this target only turns the counters it left behind into a + # report. The gcovr invocation lives in the top-level CMakeLists.txt, where + # `make coverage` reaches the very same one. + - name: Coverage report + if: matrix.cfg.coverage + run: | + cd build + gcovr --version + cmake --build . --target coverage-report + + - uses: actions/upload-artifact@v7 + if: matrix.cfg.coverage + with: + name: coverage-report + path: | + build/coverage.* + retention-days: 14 + + # The two ways of consuming an installed Codac, compared against each other: + # find_package(CODAC), which reads codac-config.cmake and the export set beside + # it, and pkg-config, which reads codac.pc. Both are written by hand, in + # different places of src/CMakeLists.txt, and had drifted apart in both + # directions -- see tests/packaging/check_consumer_parity.sh for what each was + # missing. Nothing caught it, because no workflow built anything against an + # installed Codac at all: the matrix above sets CMAKE_INSTALL_PREFIX and never + # installs. + # + # It is a job of its own rather than a step of the matrix above because the + # library there is built Debug under AddressSanitizer, and an ordinary consumer + # cannot link such an archive -- it would fail on the first unresolved __asan_* + # symbol, in both halves, and prove nothing about either. This one builds + # Release, with neither the tests nor the bindings, which is also what makes it + # short next to its neighbours. + packaging: + runs-on: ${{ matrix.cfg.os }} + timeout-minutes: 60 + defaults: + run: + shell: bash + strategy: + fail-fast: false + matrix: + cfg: + # Both instruction sets GitHub offers for Linux. The compiler is + # not varied: what is under test is what the install tree says + # about itself, not code generation. + - { os: ubuntu-26.04 , runtime: resolute, desc: 'Ubuntu 26.04 x86_64 packaging (find_package vs pkg-config)' } + - { os: ubuntu-26.04-arm, runtime: resolute, desc: 'Ubuntu 26.04 arm64 packaging (find_package vs pkg-config)' } + name: ${{ matrix.cfg.desc }} + steps: + - uses: actions/checkout@v7 + with: + submodules: true + fetch-depth: 0 + clean: false + + - name: Toolchain and dependencies + run: | + sudo apt-get -q update + sudo apt-get -y install g++ cmake pkg-config dpkg-dev || true + pkg-config --version + + - name: Build and install Codac + run: | + mkdir build ; cd build + cmake \ + -D CMAKE_BUILD_TYPE=Release \ + -D CMAKE_INSTALL_PREFIX="$PWD/../codac_install" \ + -D BUILD_TESTS=OFF \ + -D WITH_PYTHON=OFF \ + -D WITH_CAPD=OFF \ + .. + cmake --build . -j 4 --target install + + # --require-pkgconfig: on this platform pkg-config is expected to work, so + # a codac.pc that cannot be resolved -- a Requires: naming a module no .pc + # answers for, say -- has to fail the job rather than quietly reduce it to + # its find_package half. + - name: find_package and pkg-config must agree + run: | + bash tests/packaging/check_consumer_parity.sh \ + --prefix "$PWD/codac_install" \ + --build-dir "$PWD/build_packaging_check" \ + --require-pkgconfig diff --git a/.github/workflows/unixmatrix.yml b/.github/workflows/unixmatrix.yml index d35535996..f56d62a0f 100644 --- a/.github/workflows/unixmatrix.yml +++ b/.github/workflows/unixmatrix.yml @@ -1,10 +1,28 @@ -# This file generates .deb (Unix) and .nupkg (Windows) packages (and zip for having Codac and IBEX binaries for several Visual Studio versions) +# This file generates .deb (Unix) and .nupkg (Windows) packages (and zip for having Codac binaries for several Visual Studio versions) on: push: branches: ['**'] tags-ignore: ['**'] # Ignore all tag pushes pull_request: +permissions: + contents: write + +concurrency: + # Keep only the newest run of this workflow for a given branch or pull + # request: a push that supersedes another leaves the older run computing a + # result nobody will read, while its jobs hold runners the newest run is + # waiting for. + # + # The three release branches are excluded. They are where the jobs upload + # their packages to a GitHub release (see the "github.ref_name==" conditions + # further down), and cancelling such a run halfway would leave the release + # with only part of its assets. The same expression is used in every + # workflow of this directory, including the ones that publish nothing, so + # that the rule stays a single thing to know. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref_name != 'codac1' && github.ref_name != 'codac2' && github.ref_name != 'codac2_codac4matlab' }} + jobs: deploy: runs-on: ubuntu-latest @@ -43,14 +61,6 @@ jobs: #- { os: windows-2022 , shell: cmd , arch: x86 , bitness: 32, runtime: vc14 , cmake_params: '-G "Visual Studio 17" -T v140 -A Win32,version=10.0.19041.0 -D CMAKE_SYSTEM_VERSION=10.0.14393.0', cmake_flags: ' /MP4 /wd4267 /wd4244 /wd4305 /wd4996', test_config: 'Release/', choco_flags: '--x86', desc: 'Windows Visual Studio 2015 x86' } - { os: windows-2022 , shell: cmd , arch: x64 , bitness: 64, runtime: mingw15 , cmake_params: '-G "MinGW Makefiles"', cmake_flags: '-fPIC', desc: 'Windows MinGW 15.2.0 x64' } - { os: windows-2022 , shell: cmd , arch: x86 , bitness: 32, runtime: mingw15 , cmake_params: '-G "MinGW Makefiles"', cmake_flags: '-fPIC', choco_flags: '--x86', desc: 'Windows MinGW 15.2.0 x86' } - - { os: windows-2022 , shell: cmd , arch: x64 , bitness: 64, runtime: mingw14 , cmake_params: '-G "MinGW Makefiles"', cmake_flags: '-fPIC', desc: 'Windows MinGW 14.2.0 x64' } - - { os: windows-2022 , shell: cmd , arch: x86 , bitness: 32, runtime: mingw14 , cmake_params: '-G "MinGW Makefiles"', cmake_flags: '-fPIC', choco_flags: '--x86', desc: 'Windows MinGW 14.2.0 x86' } - - { os: windows-2022 , shell: cmd , arch: x64 , bitness: 64, runtime: mingw13 , cmake_params: '-G "MinGW Makefiles"', cmake_flags: '-fPIC', desc: 'Windows MinGW 13.2.0 x64' } - - { os: windows-2022 , shell: cmd , arch: x86 , bitness: 32, runtime: mingw13 , cmake_params: '-G "MinGW Makefiles"', cmake_flags: '-fPIC', choco_flags: '--x86', desc: 'Windows MinGW 13.2.0 x86' } - - { os: windows-2022 , shell: cmd , arch: x64 , bitness: 64, runtime: mingw12 , cmake_params: '-G "MinGW Makefiles"', cmake_flags: '-fPIC', desc: 'Windows MinGW 12.2.0 x64' } - - { os: windows-2022 , shell: cmd , arch: x86 , bitness: 32, runtime: mingw12 , cmake_params: '-G "MinGW Makefiles"', cmake_flags: '-fPIC', choco_flags: '--x86', desc: 'Windows MinGW 12.2.0 x86' } - - { os: windows-2022 , shell: cmd , arch: x64 , bitness: 64, runtime: mingw11 , cmake_params: '-G "MinGW Makefiles"', cmake_flags: '-fPIC', desc: 'Windows MinGW 11.2.0 x64' } - - { os: windows-2022 , shell: cmd , arch: x86 , bitness: 32, runtime: mingw11 , cmake_params: '-G "MinGW Makefiles"', cmake_flags: '-fPIC', choco_flags: '--x86', desc: 'Windows MinGW 11.2.0 x86' } # Problems related to C++20 for MinGW 8 and earlier? #- { os: windows-2022 , shell: cmd , arch: x64 , bitness: 64, runtime: mingw8 , cmake_params: '-G "MinGW Makefiles"', cmake_flags: '-fPIC', desc: 'Windows MinGW 8.1.0 x64' } #- { os: windows-2022 , shell: cmd , arch: x86 , bitness: 32, runtime: mingw8 , cmake_params: '-G "MinGW Makefiles"', cmake_flags: '-fPIC', choco_flags: '--x86', desc: 'Windows MinGW 8.1.0 x86' } @@ -117,26 +127,6 @@ jobs: %SystemDrive%\cmake_extra_tools\pathman /as C:\ProgramData\chocolatey\lib\mingw\tools\install\mingw${{ matrix.cfg.bitness }}\bin & cd. & rem Non-zero exit code...? echo export BASHMINGWPATH=/c/ProgramData/chocolatey/lib/mingw/tools/install/mingw${{ matrix.cfg.bitness }}/bin>>%USERPROFILE%\.bashrc if: (matrix.cfg.runtime=='mingw8') - - run: | - choco install -y -r --no-progress mingw --version=11.2.0.07112021 --force ${{ matrix.cfg.choco_flags }} - %SystemDrive%\cmake_extra_tools\pathman /as C:\ProgramData\chocolatey\lib\mingw\tools\install\mingw${{ matrix.cfg.bitness }}\bin & cd. & rem Non-zero exit code...? - echo export BASHMINGWPATH=/c/ProgramData/chocolatey/lib/mingw/tools/install/mingw${{ matrix.cfg.bitness }}/bin>>%USERPROFILE%\.bashrc - if: (matrix.cfg.runtime=='mingw11') - - run: | - choco install -y -r --no-progress mingw --version=12.2.0.03042023 --force ${{ matrix.cfg.choco_flags }} - %SystemDrive%\cmake_extra_tools\pathman /as C:\ProgramData\chocolatey\lib\mingw\tools\install\mingw${{ matrix.cfg.bitness }}\bin & cd. & rem Non-zero exit code...? - echo export BASHMINGWPATH=/c/ProgramData/chocolatey/lib/mingw/tools/install/mingw${{ matrix.cfg.bitness }}/bin>>%USERPROFILE%\.bashrc - if: (matrix.cfg.runtime=='mingw12') - - run: | - choco install -y -r --no-progress mingw --version=13.2.0 --force ${{ matrix.cfg.choco_flags }} - %SystemDrive%\cmake_extra_tools\pathman /as C:\ProgramData\mingw64\mingw${{ matrix.cfg.bitness }}\bin & cd. & rem Non-zero exit code...? - echo export BASHMINGWPATH=/c/ProgramData/mingw64/mingw${{ matrix.cfg.bitness }}/bin>>%USERPROFILE%\.bashrc - if: (matrix.cfg.runtime=='mingw13') - - run: | - choco install -y -r --no-progress mingw --version=14.2.0 --force ${{ matrix.cfg.choco_flags }} - %SystemDrive%\cmake_extra_tools\pathman /as C:\ProgramData\mingw64\mingw${{ matrix.cfg.bitness }}\bin & cd. & rem Non-zero exit code...? - echo export BASHMINGWPATH=/c/ProgramData/mingw64/mingw${{ matrix.cfg.bitness }}/bin>>%USERPROFILE%\.bashrc - if: (matrix.cfg.runtime=='mingw14') - run: | choco install -y -r --no-progress mingw --version=15.2.0 --force ${{ matrix.cfg.choco_flags }} %SystemDrive%\cmake_extra_tools\pathman /as C:\ProgramData\mingw64\mingw${{ matrix.cfg.bitness }}\bin & cd. & rem Non-zero exit code...? @@ -165,38 +155,21 @@ jobs: # choco install -y -r --no-progress cmake --force # echo export BASHCMAKEPATH="/c/Program Files/CMake/bin">>%USERPROFILE%\.bashrc # if: (matrix.cfg.runtime=='vc18')&&(matrix.cfg.os!='windows-11-arm') - - run: | - rem choco install -y -r --no-progress eigen --version=3.4.0.20240224 ${{ matrix.cfg.choco_flags }} - wget https://github.com/lebarsfa/ibex-lib/releases/download/ibex-2.8.9.20260819/ibex.2.8.9.20260819.nupkg --no-check-certificate -nv - choco install -y -r --no-progress --ignore-dependencies -s . ibex --version=2.8.9.20260819 ${{ matrix.cfg.choco_flags }} --params "'/url:https://github.com/lebarsfa/ibex-lib/releases/download/ibex-2.8.9.20260819/ibex_${{ matrix.cfg.arch }}_${{ matrix.cfg.runtime }}.zip'" - del /f /q ibex.2.8.9.20260819.nupkg - if: runner.os=='Windows' + #- run: choco install -y -r --no-progress eigen --version=3.4.0.20240224 ${{ matrix.cfg.choco_flags }} + # if: runner.os=='Windows' - run: | sudo sh -c 'echo "deb [trusted=yes] https://webperso.ensta.fr/packages/$(if [ -z "$(. /etc/os-release && echo $UBUNTU_CODENAME)" ]; then echo debian/$(. /etc/os-release && echo $VERSION_CODENAME); else echo ubuntu/$(. /etc/os-release && echo $UBUNTU_CODENAME); fi) ./" > /etc/apt/sources.list.d/ensta-bretagne.list' - # Replace this line by the next ones to test a specific binary package of IBEX. - #sudo apt-get -q update ; sudo apt-get -y install libibex-dev catch2 dpkg-dev || true # libeigen3-dev sudo apt-get -q update ; sudo apt-get -y install catch2 dpkg-dev || true # libeigen3-dev - wget https://github.com/lebarsfa/ibex-lib/releases/download/ibex-2.8.9.20260819/libibex-dev-2.8.9.20260819-0${{ matrix.cfg.runtime }}0_$(dpkg --print-architecture).deb --no-check-certificate -nv - sudo dpkg -i libibex-dev-2.8.9.20260819-0${{ matrix.cfg.runtime }}0_$(dpkg --print-architecture).deb - rm -Rf libibex-dev-2.8.9.20260819-0${{ matrix.cfg.runtime }}0_$(dpkg --print-architecture).deb shell: bash if: matrix.cfg.deb==true #- run: brew install eigen # if: runner.os=='macOS' - run: brew install catch2 # Issues with binary packages when cross-compiling... if: (runner.os=='macOS')&&(matrix.cfg.cross!=true) - - run: | - wget https://github.com/lebarsfa/ibex-lib/releases/download/ibex-2.8.9.20260819/ibex_${{ matrix.cfg.arch }}_${{ matrix.cfg.runtime }}.zip --no-check-certificate -nv - unzip -q ibex_${{ matrix.cfg.arch }}_${{ matrix.cfg.runtime }}.zip - rm -Rf ibex_${{ matrix.cfg.arch }}_${{ matrix.cfg.runtime }}.zip - sudo cp -Rf ibex/* /usr/local/ - if: runner.os=='macOS' -# - run: git clone --depth 1 -b master https://github.com/lebarsfa/ibex-lib.git ; cd ibex-lib ; mkdir build ; cd build ; cmake -E env CXXFLAGS="${{ matrix.cfg.cmake_flags }}" CFLAGS="${{ matrix.cfg.cmake_flags }}" cmake ${{ matrix.cfg.cmake_params }} -D CMAKE_INSTALL_PREFIX="../../ibex" .. ; cmake --build . --config Release --target install ; cd ../.. -# shell: bash - run: | if [ ${{ runner.os }} = Windows ]; then source ~/refreshenv.bashrc ; refreshenv ; export PATH=$BASHMINGWPATH:$BASHCMAKEPATH:$PATH ; fi mkdir build ; cd build - cmake -E env CXXFLAGS="${{ matrix.cfg.cmake_flags }}" CFLAGS="${{ matrix.cfg.cmake_flags }}" cmake ${{ matrix.cfg.cmake_params }} -D BUILD_TESTS=ON -D CMAKE_INSTALL_PREFIX="../codac" .. + cmake -E env CXXFLAGS="${{ matrix.cfg.cmake_flags }}" CFLAGS="${{ matrix.cfg.cmake_flags }}" cmake ${{ matrix.cfg.cmake_params }} -D BUILD_TESTS=ON -D TEST_EXAMPLES=ON -D CMAKE_INSTALL_PREFIX="../codac" .. cmake --build . -j 4 --config Release --target install cd .. sed_param=s/PATH_SUFFIXES\ /PATHS\ \$\{CMAKE_CURRENT_LIST_FILE\}\\/..\\/..\\/..\\/..\\/\ PATH_SUFFIXES\ / @@ -207,10 +180,6 @@ jobs: if [ ${{ runner.os }} = Windows ]; then source ~/refreshenv.bashrc ; refreshenv ; export PATH=$BASHMINGWPATH:$BASHCMAKEPATH:$PATH ; fi mkdir -p codac_standalone/example ; cd codac_standalone #wget https://community.chocolatey.org/api/v2/package/eigen/3.4.0.20240224 --no-check-certificate -nv ; unzip -q 3.4.0.20240224 -d eigen ; rm -Rf 3.4.0.20240224 eigen/*.xml eigen/*.nuspec eigen/_* eigen/package eigen/tools - if [ ${{ runner.os }} = Windows ]; then cp -Rf /C/ProgramData/chocolatey/lib/ibex . ; rm -Rf ibex/tools ibex/ibex.* - elif [ ${{ matrix.cfg.deb }} = true ]; then mkdir -p ibex/include ; mkdir -p ibex/lib ; mkdir -p ibex/share ; mkdir -p ibex/bin ; cp -Rf /usr/include/ibex* ibex/include/ ; cp -Rf /usr/lib/*ibex* ibex/lib/ ; cp -Rf /usr/share/*ibex* ibex/share/ ; cp -Rf /usr/share/pkgconfig ibex/share/ ; cp -Rf /usr/bin/ibex* ibex/bin/ - else cp -Rf ../ibex . - fi cp -Rf ../codac . ; cp -Rf ../tests/test_codac/* ./example/ ; cd .. ; zip -q -r codac_standalone_${{ matrix.cfg.arch }}_${{ matrix.cfg.runtime }}.zip codac_standalone shell: bash - run: | @@ -240,7 +209,7 @@ jobs: cd ../../.. checksum -f=codac.$PACKAGE_VERSION.nupkg -t=sha256 choco install -y -r --no-progress --ignore-dependencies -s . codac --pre --params "'/url:./codac_${{ matrix.cfg.arch }}_${{ matrix.cfg.runtime }}.zip'" - if [ ${{ matrix.cfg.runtime }} != mingw13 ] || [ ${{ matrix.cfg.arch }} != x64 ]; then rm -Rf codac.$PACKAGE_VERSION.nupkg ; fi # To avoid upload conflicts of the same file... + if [ ${{ matrix.cfg.runtime }} != mingw15 ] || [ ${{ matrix.cfg.arch }} != x64 ]; then rm -Rf codac.$PACKAGE_VERSION.nupkg ; fi # To avoid upload conflicts of the same file... checksum -f=codac_${{ matrix.cfg.arch }}_${{ matrix.cfg.runtime }}.zip -t=sha256 shell: bash if: runner.os=='Windows' diff --git a/.github/workflows/vcmatrix.yml b/.github/workflows/vcmatrix.yml index 27ac4fd0c..2a146e13e 100644 --- a/.github/workflows/vcmatrix.yml +++ b/.github/workflows/vcmatrix.yml @@ -1,13 +1,28 @@ # This file generates Python wheels for Windows on: push: - branches: - - codac1 - - codac2 - - codac2_codac4matlab + branches: ['**'] tags-ignore: ['**'] # Ignore all tag pushes pull_request: +permissions: + contents: write + +concurrency: + # Keep only the newest run of this workflow for a given branch or pull + # request: a push that supersedes another leaves the older run computing a + # result nobody will read, while its jobs hold runners the newest run is + # waiting for. + # + # The three release branches are excluded. They are where the jobs upload + # their packages to a GitHub release (see the "github.ref_name==" conditions + # further down), and cancelling such a run halfway would leave the release + # with only part of its assets. The same expression is used in every + # workflow of this directory, including the ones that publish nothing, so + # that the rule stays a single thing to know. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref_name != 'codac1' && github.ref_name != 'codac2' && github.ref_name != 'codac2_codac4matlab' }} + jobs: vcmatrix: runs-on: ${{ matrix.cfg.os }} @@ -60,17 +75,45 @@ jobs: if: runner.os=='Windows' #- run: choco install -y -r --no-progress eigen --version=3.4.0.20240224 ${{ matrix.cfg.choco_flags }} # if: runner.os=='Windows' - - run: choco install -y -r --no-progress graphviz doxygen.install & python -m pip install --upgrade pip & pip install --upgrade wheel setuptools sphinx breathe sphinx-issues sphinx-tabs sphinx_rtd_theme sphinx-reredirects furo sphinx-math-dollar sphinx_togglebutton sympy + - run: choco install -y -r --no-progress graphviz + if: runner.os=='Windows' + - name: Install Doxygen (up to 5 attempts, non fatal) + shell: bash + run: | + export PATH="/c/ProgramData/chocolatey/bin:$PATH" + ok=0 + for i in 1 2 3 4 5; do + echo "=== Doxygen install attempt $i/5 ===" + choco install -y -r --no-progress doxygen.install + rc=$? + if [ $rc -eq 0 ] || [ $rc -eq 3010 ]; then + if doxygen --version; then ok=1; break; fi + echo "choco reported success but doxygen is not callable." + fi + echo "Attempt $i failed (exit code $rc), retrying in 20 s..." + sleep 20 + done + if [ $ok -eq 1 ]; then + echo "WITH_PYTHON=ON" >> $GITHUB_ENV + else + echo "::warning::Doxygen could not be installed after 5 attempts - building without documentation and without Python bindings." + echo "WITH_PYTHON=OFF" >> $GITHUB_ENV + fi + if: runner.os=='Windows' + - run: python -m pip install --upgrade pip && pip install --upgrade wheel setuptools build sphinx breathe sphinx-issues sphinx-tabs sphinx_rtd_theme sphinx-reredirects furo sphinx-math-dollar sphinx_togglebutton sympy + shell: bash if: runner.os=='Windows' - run: | - wget https://github.com/lebarsfa/ibex-lib/releases/download/ibex-2.8.9.20260819/ibex.2.8.9.20260819.nupkg --no-check-certificate -nv - choco install -y -r --no-progress --ignore-dependencies -s . ibex --version=2.8.9.20260819 ${{ matrix.cfg.choco_flags }} --params "'/url:https://github.com/lebarsfa/ibex-lib/releases/download/ibex-2.8.9.20260819/ibex_${{ matrix.cfg.arch }}_${{ matrix.cfg.runtime }}.zip'" - del /f /q ibex.2.8.9.20260819.nupkg - - run: | + WITH_PYTHON=${WITH_PYTHON:-ON} + echo "Configuring with WITH_PYTHON=$WITH_PYTHON" mkdir build ; cd build - cmake -E env CXXFLAGS=" /MP4 /wd4267 /wd4244 /wd4305 /wd4996" CFLAGS=" /MP4 /wd4267 /wd4244 /wd4305 /wd4996" cmake ${{ matrix.cfg.cmake_params }} -D CMAKE_INSTALL_PREFIX="../codac" -D BUILD_TESTS=ON -D WITH_CAPD=OFF -D WITH_PYTHON=ON -D PYBIND11_FINDPYTHON=OFF .. + cmake -E env CXXFLAGS=" /MP4 /wd4267 /wd4244 /wd4305 /wd4996" CFLAGS=" /MP4 /wd4267 /wd4244 /wd4305 /wd4996" cmake ${{ matrix.cfg.cmake_params }} -D CMAKE_INSTALL_PREFIX="../codac" -D BUILD_TESTS=ON -D TEST_EXAMPLES=ON -D WITH_CAPD=OFF -D WITH_PYTHON=$WITH_PYTHON -D PYBIND11_FINDPYTHON=OFF .. cmake --build . -j 4 --config Release --target install - cmake --build . --config Release --target pip_package ; cp `ls *.whl` ../`ls *.whl | sed "s/py3-none-any/cp${{ matrix.cfg.py_v_maj }}${{ matrix.cfg.py_v_min }}-cp${{ matrix.cfg.py_v_maj }}${{ matrix.cfg.py_v_min }}${{ matrix.cfg.cpcfg }}/"` + if [ "$WITH_PYTHON" = "ON" ]; then + cmake --build . --config Release --target pip_package ; cp `ls *.whl` ../`ls *.whl | sed "s/py3-none-any/cp${{ matrix.cfg.py_v_maj }}${{ matrix.cfg.py_v_min }}-cp${{ matrix.cfg.py_v_maj }}${{ matrix.cfg.py_v_min }}${{ matrix.cfg.cpcfg }}/"` + else + echo "Skipping pip_package target (no Doxygen docstrings available)." + fi cd .. shell: bash - uses: xresloader/upload-to-github-release@v1 @@ -80,13 +123,30 @@ jobs: file: "*.whl" overwrite: true tag_name: autotagname-${{ github.sha }} - if: (github.event_name!='pull_request')&&((github.ref_name=='codac1')||(github.ref_name=='codac2')||(github.ref_name=='codac2_codac4matlab')) + if: (env.WITH_PYTHON!='OFF')&&(github.event_name!='pull_request')&&((github.ref_name=='codac1')||(github.ref_name=='codac2')||(github.ref_name=='codac2_codac4matlab')) - run: | pip install --no-deps --no-index *.whl python -c "import sys; print(sys.version)" ; python examples/02_centered_form/main.py pip install numpy sympy --prefer-binary python -m unittest discover codac.tests - cd build && ctest -C Release -V --output-on-failure + shell: bash + if: (env.WITH_PYTHON!='OFF')&&(github.ref_name!='codac2_codac4matlab')&&(github.event.pull_request.base.ref!='codac2_codac4matlab') + - run: | + # Only the Python half of the suite is run here. These jobs exist to + # build and check a wheel per Python version, and the C++ tests do not + # depend on that version: running them again on every entry repeated + # the same 87 tests 18 times on Windows and 11 times on macOS. They are + # still compiled, so a C++ regression still breaks this workflow, and + # they are still executed in Release on the very same systems by + # unixmatrix.yml -- Visual Studio 2022 on x86/x64/arm64, macOS Sonoma + # arm64 and Sequoia x86_64. + # The same "_py" filter picks up the Python examples, which + # -D TEST_EXAMPLES=ON registers as codac2_examples_*_py. Unlike the C++ + # ones they do run on the interpreter of each entry, so they belong + # here; the C++ examples are only compiled, and run by unixmatrix.yml + # along with the C++ tests. + cd build && ctest -C Release -R "_py" -V --output-on-failure cd .. shell: bash - if: (github.ref_name!='codac2_codac4matlab')&&(github.event.pull_request.base.ref!='codac2_codac4matlab') + if: (env.WITH_PYTHON!='OFF')&&(github.ref_name!='codac2_codac4matlab')&&(github.event.pull_request.base.ref!='codac2_codac4matlab') + diff --git a/.github/workflows/windebugmatrix.yml b/.github/workflows/windebugmatrix.yml new file mode 100644 index 000000000..c9bd09c54 --- /dev/null +++ b/.github/workflows/windebugmatrix.yml @@ -0,0 +1,254 @@ +# Debug builds and unit tests on Windows, with the latest MinGW and MSVC +# toolchains, on x64, x86 and (MSVC only) arm64, on both Windows 2022 (MSVC +# v143 / Visual Studio 2022) and the very latest Windows images (MSVC v145 / +# Visual Studio 2026, on windows-2025-vs2026 and windows-11-vs2026-arm). +# Unlike vcmatrix.yml/unixmatrix.yml (which build in Release mode and +# package the result), this workflow only compiles and runs the test suite +# in Debug configuration, so that the sanitizers enabled for Debug builds +# (see the "if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR +# CMAKE_CONFIGURATION_TYPES)" block in the top-level CMakeLists.txt) +# actually get exercised on Windows. There is no MinGW entry beyond +# windows-2022/x64/x86, and no MinGW arm64 entry at all: no such +# configuration exists anywhere else in this repo's workflows (only +# vc17/vc18 have an arm64 variant, and only vc17 is built on windows-2022), +# so MinGW stays on windows-2022 here too. +# +# gaol, the interval library codac is built upon, is built by CMake along +# with codac (see scripts/CMakeModules/codac_gaol.cmake), and always in +# Release, whatever the configuration of codac. Under MSVC this means +# codac's own Debug build is forced onto the Release CRT (/MD instead of +# the default /MDd) so it stays link-compatible with that gaol.lib; see the +# CMAKE_MSVC_RUNTIME_LIBRARY override in the top-level CMakeLists.txt for +# the details of what that trades away (debug-CRT heap/iterator checking) +# versus what it keeps (unoptimized code, debug symbols, ASan). +# +# The MinGW entries simply pin the newest MinGW version (same as +# unixmatrix.yml's own mingw15 entries): unlike MSVC, the choco `mingw` +# package does not ship libasan.a/libubsan.a at all here, for either x86 or +# x64, in any of the versions it offers (checked via `g++ +# -print-file-name=libasan*`/`libubsan*` across mingw15 down to mingw11) -- +# there is no version/architecture combination worth hunting for. The +# top-level CMakeLists.txt detects this (the same capability probe used for +# the MSVC ASan runtime DLL below) and simply leaves Debug builds +# unsanitized on a toolchain that lacks the runtime, rather than failing at +# link time with "ld.exe: cannot find -lasan/-lubsan"; actual ASan coverage +# on Windows comes from the MSVC entries in this matrix instead. +on: + push: + branches-ignore: + - codac2_codac4matlab + tags-ignore: ['**'] # Ignore all tag pushes + pull_request: + branches-ignore: + - codac2_codac4matlab + +concurrency: + # Keep only the newest run of this workflow for a given branch or pull + # request: a push that supersedes another leaves the older run computing a + # result nobody will read, while its jobs hold runners the newest run is + # waiting for. + # + # The three release branches are excluded. They are where the jobs upload + # their packages to a GitHub release (see the "github.ref_name==" conditions + # further down), and cancelling such a run halfway would leave the release + # with only part of its assets. The same expression is used in every + # workflow of this directory, including the ones that publish nothing, so + # that the rule stays a single thing to know. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref_name != 'codac1' && github.ref_name != 'codac2' && github.ref_name != 'codac2_codac4matlab' }} + +jobs: + windebugmatrix: + runs-on: ${{ matrix.cfg.os }} + # A test binary that cannot load a DLL it needs does not always fail fast: + # on the Visual Studio 2026 images every test of the suite instead sat + # until ctest's own 1500 s timeout, so the job kept going for the six hours + # GitHub allows before killing it, twice per run. Two hours is well above + # the forty minutes or so a healthy job takes here -- twenty for the build + # and the unit tests, and about as much again for the examples under ASan + # on MSVC -- and turns such a hang into a quick red job rather than a day of + # runner time. + timeout-minutes: 120 + defaults: + run: + shell: ${{ matrix.cfg.shell }} + strategy: + fail-fast: false + matrix: + cfg: + - { os: windows-2022, shell: cmd, arch: x64 , bitness: 64, runtime: mingw15, cmake_params: '-G "MinGW Makefiles" -D CMAKE_BUILD_TYPE=Debug', cmake_flags: '-fPIC', desc: 'Windows MinGW x64 Debug (unsanitized)' } + - { os: windows-2022, shell: cmd, arch: x86 , bitness: 32, runtime: mingw15, cmake_params: '-G "MinGW Makefiles" -D CMAKE_BUILD_TYPE=Debug', cmake_flags: '-fPIC', choco_flags: '--x86', desc: 'Windows MinGW x86 Debug (unsanitized)' } + - { os: windows-2022, shell: cmd, arch: x64 , bitness: 64, runtime: vc17 , cmake_params: '-G "Visual Studio 17" -T v143 -A x64' , cmake_flags: ' /MP4 /wd4267 /wd4244 /wd4305 /wd4996', desc: 'Windows Visual Studio 2022 x64 Debug' } + - { os: windows-2022, shell: cmd, arch: x86 , bitness: 32, runtime: vc17 , cmake_params: '-G "Visual Studio 17" -T v143 -A Win32', cmake_flags: ' /MP4 /wd4267 /wd4244 /wd4305 /wd4996', choco_flags: '--x86', desc: 'Windows Visual Studio 2022 x86 Debug' } + - { os: windows-11-arm, shell: cmd, arch: arm64, bitness: 64, runtime: vc17 , cmake_params: '-G "Visual Studio 17" -T v143 -A arm64', cmake_flags: ' /MP4 /wd4267 /wd4244 /wd4305 /wd4996', desc: 'Windows Visual Studio 2022 arm64 Debug' } + - { os: windows-2025-vs2026 , shell: cmd, arch: x64 , bitness: 64, runtime: vc18, cmake_params: '-G "Visual Studio 18" -T v145 -A x64' , cmake_flags: ' /MP4 /wd4267 /wd4244 /wd4305 /wd4996', desc: 'Windows Visual Studio 2026 x64 Debug' } + - { os: windows-2025-vs2026 , shell: cmd, arch: x86 , bitness: 32, runtime: vc18, cmake_params: '-G "Visual Studio 18" -T v145 -A Win32', cmake_flags: ' /MP4 /wd4267 /wd4244 /wd4305 /wd4996', choco_flags: '--x86', desc: 'Windows Visual Studio 2026 x86 Debug' } + - { os: windows-11-vs2026-arm, shell: cmd, arch: arm64, bitness: 64, runtime: vc18, cmake_params: '-G "Visual Studio 18" -T v145 -A arm64', cmake_flags: ' /MP4 /wd4267 /wd4244 /wd4305 /wd4996', desc: 'Windows Visual Studio 2026 arm64 Debug' } + name: ${{ matrix.cfg.desc }} + steps: + - uses: actions/checkout@v7 + with: + submodules: true + fetch-depth: 0 + clean: false + - run: echo "VERBOSE=1" >> $GITHUB_ENV + shell: bash + - run: | + choco install -y -r --no-progress wget + wget https://lebarsfa.github.io/cache/cmake_extra_tools.zip --no-check-certificate -nv + 7z x cmake_extra_tools.zip -o"%SystemDrive%" -y + del /f /q cmake_extra_tools.zip + wget https://gist.github.com/lebarsfa/237841f9e5dad55ef192713b3b1b2f16/raw/04d77ced3457346c55f183ca12a10dbcb850e6d5/refreshenv.bashrc --no-check-certificate -nv + move /y refreshenv.bashrc %USERPROFILE% + # MinGW is pinned to the single newest version choco offers (15.2.0): + # none of its versions ship libasan/libubsan for this package, for + # either x86 or x64 (see the header comment above), so there is no + # ASan/UBSan-capable candidate left to probe for -- the top-level + # CMakeLists.txt detects the missing runtime itself and builds + # unsanitized instead. BASHMINGWPATH is written to $GITHUB_ENV so every + # later step (regardless of shell) picks it up automatically, without + # needing choco's own refreshenv.bashrc dance. + - run: | + choco install -y -r --no-progress mingw --version=15.2.0 --force ${{ matrix.cfg.choco_flags }} + echo "BASHMINGWPATH=/c/ProgramData/mingw64/mingw${{ matrix.cfg.bitness }}/bin" >> "$GITHUB_ENV" + if: startsWith(matrix.cfg.runtime, 'mingw') + shell: bash + - run: | + if [ -n "${BASHMINGWPATH:-}" ]; then export PATH="$BASHMINGWPATH:$PATH" ; fi + mkdir build ; cd build + cmake -E env CXXFLAGS="${{ matrix.cfg.cmake_flags }}" CFLAGS="${{ matrix.cfg.cmake_flags }}" cmake ${{ matrix.cfg.cmake_params }} -D CMAKE_INSTALL_PREFIX="../codac" -D BUILD_TESTS=ON -D TEST_EXAMPLES=ON .. + cmake --build . -j 4 --config Debug --target install + cd .. + shell: bash + - run: | + if [ -n "${BASHMINGWPATH:-}" ]; then export PATH="$BASHMINGWPATH:$PATH" ; fi + cd build && ctest -C Debug -V --output-on-failure + cd .. + shell: bash + # A test killed by a signal loses everything it had written to its + # block-buffered standard output, so ctest reports the signal and nothing + # else. That is all this job has ever shown for the tests that crash on it, + # every one of them a user of Eigen's matrix product (arithmetic_mul and + # arithmetic_div crash where arithmetic_add and arithmetic_sub pass), and a + # bare signal says nothing about where. Re-running the first few failures + # under gdb prints the faulting frame, which is the one thing missing to + # diagnose them. The step reports rather than judges: it only runs after a + # failure, and never turns a green job red on its own. + - name: Backtrace of the failed tests + if: failure() + continue-on-error: true + run: | + if [ -n "${BASHMINGWPATH:-}" ]; then export PATH="$BASHMINGWPATH:$PATH" ; fi + + if ! command -v gdb > /dev/null 2>&1 ; then + echo "No gdb in this toolchain, skipping the backtraces." + exit 0 + fi + + failed=build/Testing/Temporary/LastTestsFailed.log + if [ ! -f "$failed" ] ; then + echo "ctest recorded no failed test, nothing to trace." + exit 0 + fi + + cd build + # LastTestsFailed.log holds one ":" per line; the C++ + # tests are the "_cpp" ones and their executable is the target + # itself, next to the tests directory of this single-configuration + # build -- or in the examples directory, for the examples that + # -D TEST_EXAMPLES=ON registers the same way. Three backtraces are + # plenty to identify a common frame, and keep this step short. + sed 's/^[0-9]*://' "../$failed" | grep '_cpp$' | head -3 | while read -r name ; do + exe="tests/${name%_cpp}.exe" + [ -f "$exe" ] || exe="examples/${name%_cpp}.exe" + [ -f "$exe" ] || continue + echo "===================== $exe =====================" + gdb -batch -ex "set confirm off" -ex run -ex "bt 40" --args "./$exe" 2>&1 | tail -60 + done + shell: bash + + # The Windows counterpart of the packaging jobs of unixdebug.yml and + # macdebug.yml: the two ways of consuming an installed Codac -- + # find_package(CODAC) through codac-config.cmake and the export set beside it, + # and pkg-config through codac.pc -- are written by hand in different places of + # src/CMakeLists.txt, had drifted apart in both directions, and nothing in CI + # built anything against an installed Codac at all. See + # tests/packaging/check_consumer_parity.sh. + # + # How much of the check runs depends on the toolchain, and the matrix says so + # per entry rather than leaving it to be discovered: + # + # - MinGW gets both halves. pkg-config answers in -I/-L/-l, which is what a + # GCC driver takes, and the Makefile generator records the command lines the + # comparison reads back. + # - MSVC gets the find_package half alone. cl.exe takes none of those flags, + # and the Visual Studio generators write neither compile_commands.json nor + # link.txt. That half is still worth its runner time: it is the only place + # anything is built against an installed Codac with MSVC at all, and it is + # what covers arm64, for which this matrix has no MinGW entry. + # + # Release, without the tests, which is what keeps these jobs short next to + # their neighbours -- and, unlike the Debug matrix above, what an ordinary + # consumer can actually link against on the MSVC entries, whose archives are + # built with /MDd and under ASan. + packaging: + runs-on: ${{ matrix.cfg.os }} + timeout-minutes: 90 + defaults: + run: + shell: bash + strategy: + fail-fast: false + matrix: + cfg: + - { os: windows-2022 , arch: x64 , bitness: 64, runtime: mingw15, generator: 'MinGW Makefiles' , cmake_arch: '', mingw: true , require_pkgconfig: true , desc: 'Windows MinGW x64 packaging (find_package vs pkg-config)' } + - { os: windows-2022 , arch: x86 , bitness: 32, runtime: mingw15, generator: 'MinGW Makefiles' , cmake_arch: '', mingw: true , require_pkgconfig: true , choco_flags: '--x86', desc: 'Windows MinGW x86 packaging (find_package vs pkg-config)' } + - { os: windows-11-arm, arch: arm64, bitness: 64, runtime: vc17 , generator: 'Visual Studio 17', cmake_arch: 'arm64', mingw: false, require_pkgconfig: false, desc: 'Windows Visual Studio 2022 arm64 packaging (find_package only)' } + name: ${{ matrix.cfg.desc }} + steps: + - uses: actions/checkout@v7 + with: + submodules: true + fetch-depth: 0 + clean: false + + - name: Tools + run: | + choco install -y -r --no-progress wget + if [ "${{ matrix.cfg.mingw }}" = "true" ]; then + choco install -y -r --no-progress mingw --version=15.2.0 --force ${{ matrix.cfg.choco_flags }} + echo "BASHMINGWPATH=/c/ProgramData/mingw64/mingw${{ matrix.cfg.bitness }}/bin" >> "$GITHUB_ENV" + # pkgconfiglite is what puts pkg-config.exe on PATH; the image has no + # pkg-config of its own. + choco install -y -r --no-progress pkgconfiglite + fi + + - name: Build and install Codac + run: | + if [ -n "${BASHMINGWPATH:-}" ]; then export PATH="$BASHMINGWPATH:$PATH" ; fi + mkdir build ; cd build + ARCH_ARG="" + if [ -n "${{ matrix.cfg.cmake_arch }}" ]; then ARCH_ARG="-A ${{ matrix.cfg.cmake_arch }}" ; fi + cmake -G "${{ matrix.cfg.generator }}" $ARCH_ARG \ + -D CMAKE_BUILD_TYPE=Release \ + -D CMAKE_INSTALL_PREFIX="$PWD/../codac_install" \ + -D BUILD_TESTS=OFF \ + -D WITH_PYTHON=OFF \ + -D WITH_CAPD=OFF \ + .. + cmake --build . -j 4 --config Release --target install + + - name: find_package and pkg-config must agree + run: | + if [ -n "${BASHMINGWPATH:-}" ]; then export PATH="$BASHMINGWPATH:$PATH" ; fi + args=(--prefix "$PWD/codac_install" --build-dir "$PWD/build_packaging_check") + if [ "${{ matrix.cfg.require_pkgconfig }}" = "true" ]; then + args+=(--require-pkgconfig) + else + # The Visual Studio generators record no command line to compare, so + # the script is told to build the consumer the way this job builds + # Codac and runs the find_package half alone. + args+=(--generator "${{ matrix.cfg.generator }}") + args+=(--cmake-arg -A --cmake-arg "${{ matrix.cfg.cmake_arch }}") + fi + bash tests/packaging/check_consumer_parity.sh "${args[@]}" diff --git a/.gitignore b/.gitignore index 64523ced0..d4f0b655a 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ .cproject .settings .project +.pydevproject # waf generated stuff .waf* @@ -56,6 +57,7 @@ doc/doxygen/xml/* build/ build_*/ build-*/ +CMakePresets.json # Latex files *.aux diff --git a/CMakeLists.txt b/CMakeLists.txt index f65251466..ab0a2e351 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,35 @@ #set(CMAKE_C_COMPILER "gcc-7") #set(CMAKE_CXX_COMPILER "/usr/bin/g++-7") - project(codac VERSION ${VERSION} LANGUAGES CXX) + # CMP0091 (CMake >=3.15) makes the CMAKE_MSVC_RUNTIME_LIBRARY variable set + # below actually control MSVC's runtime library selection, instead of + # CMake's older behavior of baking a hardcoded /MD.../MDd into + # CMAKE__FLAGS_. It must be set to NEW before project() -- + # afterwards is too late -- for that variable to have any effect at all. + if(POLICY CMP0091) + cmake_policy(SET CMP0091 NEW) + endif() + + # CMP0156 (CMake >=3.29) lets CMake name a static library once on a link + # line for the linkers that do not need it repeated -- Apple's, MSVC's, LLD. + # With the OLD behavior, every test and example linking codac-core and the + # modules depending on it named libcodac-core.a twice, and the Apple linker + # warned "ignoring duplicate libraries" for each of them. + if(POLICY CMP0156) + cmake_policy(SET CMP0156 NEW) + endif() + + # C as well as C++: the codac sources are all C++, but mathlib, which GAOL + # depends on, is C, and codac_gaol_build() builds it when no GAOL is installed + # (see scripts/CMakeModules/codac_gaol.cmake). Enabled here, the C compiler + # and its flags are settled -- and cached -- by the first configuration, so + # that what is handed to that build is the same at every later one. Enabled + # further down by a dependency instead, as happened when only CXX was listed, + # the compiler went from the name it was given to its full path between the + # first configuration and the second, the build of GAOL took that for a + # change and was configured again, and everything in codac that includes an + # interval was recompiled. + project(codac VERSION ${VERSION} LANGUAGES C CXX) if(NOT VERSION_ID) set(PROJECT_VERSION_FULL ${PROJECT_VERSION}) @@ -84,6 +112,230 @@ add_compile_options(-Wall -Wextra -Wpedantic) endif() + if(MSVC) + # The gaol interval library is only ever linked as a Release binary: + # codac_gaol_build() (scripts/CMakeModules/codac_gaol.cmake) builds it in + # Release whatever the configuration of codac, and a gaol found on the + # system comes prebuilt that way too. gaol.lib is thus compiled with the + # Release CRT (/MD). MSVC's linker refuses + # to mix object files built against different CRT variants in the same + # binary -- linking that Release-CRT gaol.lib together with codac's own + # object files, which without this override would default to the Debug + # CRT (/MDd) in a Debug build, fails with error LNK2038 ("mismatch + # detected for 'RuntimeLibrary'/'_ITERATOR_DEBUG_LEVEL'") followed by + # fatal error LNK1319. + # + # Forcing every configuration onto the Release CRT keeps codac's own + # Debug build link-compatible with that Release gaol.lib without + # having to build gaol in Debug as well. Release + # itself already defaulted to /MD, so in practice this only changes + # what the Debug configuration uses. Debug builds still get no + # optimization (/Od), full debug symbols (/Zi) and AddressSanitizer + # (see CODAC_MSVC_ASAN_RUNTIME_DLL below) -- what is traded away is + # only the *separate* debug-CRT extras that /MDd would otherwise add on + # top of that (its own heap debugging and iterator-debug-level checks). + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL") + endif() + + if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_CONFIGURATION_TYPES) + # CMAKE_BUILD_TYPE only has meaning for single-configuration generators + # (Makefiles, Ninja): multi-configuration generators (Visual Studio, + # Xcode, Ninja Multi-Config -- notably what this project's own CI + # drives MSVC through, via `-G "Visual Studio 17/18"` in + # .github/workflows/*matrix.yml) instead select the actual + # configuration later, at build time (cmake --build . --config + # Debug), not at configure time; CMAKE_BUILD_TYPE stays whatever it + # was defaulted to above ("Release") regardless of what configuration + # is later built. So this outer if() only decides whether it is worth + # *probing* for Debug-only capabilities at all -- true either when a + # single-config build was explicitly configured as Debug, or when a + # multi-config generator defers that choice (CMAKE_CONFIGURATION_TYPES + # is then non-empty) and so might still end up building a Debug + # configuration later. The actual compile/link options below are + # separately wrapped in a $ generator expression, which + # -- unlike CMAKE_BUILD_TYPE -- CMake evaluates correctly against + # whichever configuration is ultimately built, for both single- and + # multi-config generators alike. + if(MSVC) + # cl.exe silently ignores GCC/Clang-style "-f..."/"-g"/"-O0" flags + # with a D9002 warning rather than failing the build, so without this + # branch a Debug build under MSVC would quietly end up *unsanitized* + # instead of erroring -- a behavior gap invisible until someone + # actually diffs test coverage between platforms. + # + # MSVC's AddressSanitizer (/fsanitize=address) has shipped since + # Visual Studio 16.9; there is no UndefinedBehaviorSanitizer on this + # toolchain, so Debug builds here get ASan coverage only, not the + # ASan+UBSan combination GCC/Clang get below. Its runtime is a DLL + # shipped next to cl.exe, rather than a LD_PRELOAD-style shared + # object that the dynamic loader pulls in transparently: *every* + # executable and DLL built with /fsanitize=address needs that + # runtime DLL to be discoverable at load time (next to the binary, + # or on PATH) -- per Microsoft's own documentation, this holds even + # for /MT static-CRT builds, unlike every other CRT component. + # See tests/CMakeLists.txt and python/src/core/CMakeLists.txt for + # how CODAC_MSVC_ASAN_RUNTIME_DLL, set below, is used to make that + # DLL discoverable for the test suite and for the Python bindings. + # + # Source: https://learn.microsoft.com/en-us/cpp/sanitizers/asan-runtime + # + # Rather than enable /fsanitize=address unconditionally, this whole + # feature is opt-in on whether the matching runtime DLL can actually + # be located next to this cl.exe: if it can't (toolset too old, + # target architecture unsupported by MSVC ASan, or a packaging + # layout this check doesn't recognize), Debug builds simply stay + # unsanitized rather than either failing configuration or compiling + # binaries that are guaranteed to fail to load at run time with a + # missing-DLL error. + # + # MSVC ASan runtime binaries are named using Clang's architecture + # conventions (i386/x86_64/aarch64), not MSVC's own (X86/x64/ARM64) -- + # this is documented by Microsoft itself, not a codac convention. + # CMAKE_CXX_COMPILER_ARCHITECTURE_ID (a builtin CMake variable, MSVC + # only) reports the *target* architecture regardless of host, which + # is what a flag like /fsanitize=address needs to match; MSVC + # AddressSanitizer only supports x86, x64 and ARM64 (ARM64 as of + # VS 2022 17.9, as a preview) -- any other value (32-bit ARM, IA64, + # ARM64EC...) is simply left unmapped below, so the search for a + # runtime DLL naturally comes up empty and the feature is skipped, + # exactly as intended. + set(_msvc_asan_dll_arch "") + if(CMAKE_CXX_COMPILER_ARCHITECTURE_ID STREQUAL "x64") + set(_msvc_asan_dll_arch "x86_64") + elseif(CMAKE_CXX_COMPILER_ARCHITECTURE_ID STREQUAL "X86") + set(_msvc_asan_dll_arch "i386") + elseif(CMAKE_CXX_COMPILER_ARCHITECTURE_ID STREQUAL "ARM64") + set(_msvc_asan_dll_arch "aarch64") + endif() + + set(CODAC_MSVC_ASAN_RUNTIME_DLL "") + if(_msvc_asan_dll_arch) + get_filename_component(_msvc_bin_dir "${CMAKE_CXX_COMPILER}" DIRECTORY) + # Visual Studio 2022 17.7 Preview 3 unified every CRT configuration + # (/MT, /MTd, /MD, /MDd) onto a single clang_rt.asan_dynamic-{arch}.dll. + # Older toolsets instead shipped a separate debug-CRT DLL + # (clang_rt.asan_dbg_dynamic-{arch}.dll) for /MDd specifically. + # CMAKE_MSVC_RUNTIME_LIBRARY is forced to the Release CRT (/MD) for + # every configuration above (Debug included), so in practice only + # the plain, non-debug DLL is ever needed here now; the /MDd-only + # name is kept as a fallback purely in case that override above is + # ever relaxed back to the CMake default. Rather than pin an exact + # MSVC_VERSION cutoff for the 17.7-era switch -- a compiler-rt + # packaging detail, not something reported by any compiler flag -- + # just look for whichever of the two names actually exists next to + # this cl.exe, newest first. + foreach(_dll_name + "clang_rt.asan_dynamic-${_msvc_asan_dll_arch}.dll" + "clang_rt.asan_dbg_dynamic-${_msvc_asan_dll_arch}.dll" + ) + if(EXISTS "${_msvc_bin_dir}/${_dll_name}") + set(CODAC_MSVC_ASAN_RUNTIME_DLL "${_msvc_bin_dir}/${_dll_name}") + break() + endif() + endforeach() + endif() + + if(CODAC_MSVC_ASAN_RUNTIME_DLL) + add_compile_options($<$:/fsanitize=address>) + # Incremental linking cannot be combined with ASan: the linker drops + # /INCREMENTAL as soon as an input module carries ASan metadata, and + # says so once per link (warning LNK4300, 870 times across the five + # MSVC Debug jobs of .github/workflows/windebugmatrix.yml, VS 2022 and + # VS 2026 alike). Asking for it off removes the cause rather than the + # message, and leaves LNK4300 free to report any other option ASan + # would silently disable. One item per generator expression, for the + # DIRECTORY-scope reason explained below. + add_link_options($<$:/INCREMENTAL:NO>) + # Enabling ASan on MSVC also switches on the MSVC STL's own + # "container annotation" instrumentation for std::string, + # std::vector and (recent toolsets) std::optional: it poisons + # their reserved-but-unused capacity so ASan can catch + # out-of-bounds access within it, and every translation unit + # linked together records, via #pragma detect_mismatch, whether it + # was compiled with that instrumentation on or off. gaol.lib + # (a Release build without ASan -- see the + # CMAKE_MSVC_RUNTIME_LIBRARY comment above for why it is not + # built in Debug here) was compiled without ASan, so its + # object files carry the instrumentation-off value; without this, + # codac's own ASan-instrumented Debug object files would carry the + # opposite value, and MSVC's linker would reject the mix with a + # second error LNK2038 ("mismatch detected for 'annotate_string' / + # 'annotate_vector' / 'annotate_optional'"), on top of the CRT one + # already addressed above. _DISABLE_STRING_ANNOTATION and + # _DISABLE_VECTOR_ANNOTATION are Microsoft's own documented way to + # turn this instrumentation back off so codac's object files match + # gaol.lib's; _DISABLE_OPTIONAL_ANNOTATION is added defensively for + # the same std::optional check (undocumented as of this writing, + # but following Microsoft's existing naming convention for the + # other two) -- worst case it is simply an unused macro if that + # name turns out to be wrong. Kept as three separate + # add_compile_definitions() calls, one flag per $ + # generator expression, for the same reason as the single-flag + # add_compile_options() calls further below: this is a + # DIRECTORY-scope property, which does not evaluate correctly when + # one such expression packs more than one ';'-separated item. + add_compile_definitions($<$:_DISABLE_STRING_ANNOTATION>) + add_compile_definitions($<$:_DISABLE_VECTOR_ANNOTATION>) + add_compile_definitions($<$:_DISABLE_OPTIONAL_ANNOTATION>) + message(STATUS "${COLOR_BLUE}MSVC AddressSanitizer will be enabled for Debug-configuration builds (runtime: ${CODAC_MSVC_ASAN_RUNTIME_DLL}).${COLOR_RESET}") + else() + message(STATUS "${COLOR_RED}No MSVC AddressSanitizer runtime found next to ${CMAKE_CXX_COMPILER} for architecture '${CMAKE_CXX_COMPILER_ARCHITECTURE_ID}': Debug-configuration builds will not be sanitized.${COLOR_RESET}") + endif() + + # Cached so python/src/core/CMakeLists.txt and tests/CMakeLists.txt + # can reuse this result (empty string if not found) without + # repeating the detection above. + set(CODAC_MSVC_ASAN_RUNTIME_DLL ${CODAC_MSVC_ASAN_RUNTIME_DLL} CACHE INTERNAL "Path to the MSVC ASan runtime DLL, empty if unavailable") + else() + # add_compile_options() (a DIRECTORY-scope property, unlike + # target_compile_options()) does not evaluate correctly when a single + # $ generator expression's content packs more than one + # flag separated by ';' -- reproduced locally with both Ninja and + # Unix Makefiles (CMake 3.16 and 4.4.3 alike): the embedded ';' gets + # split before the genexpr is fully evaluated, corrupting it into + # literal, unrecognized compiler arguments (e.g. the literal text + # "$<1:-Wreturn-type" as one broken token). target_compile_options() + # is not affected, but this is DIRECTORY-scope by design (it must + # apply project-wide, not to one target), so instead each flag gets + # its own single-flag generator expression below. + add_compile_options($<$:-Wreturn-type>) + add_compile_options($<$:-fno-omit-frame-pointer>) + add_compile_options($<$:-g>) + add_compile_options($<$:-O0>) + + # -fsanitize=address,undefined is only added once we have actually + # confirmed that this compiler can *link* a sanitized binary, rather + # than unconditionally, the way this project used to. On Linux/macOS + # GCC/Clang, libasan/libubsan are essentially always installed + # alongside the compiler and this check is a formality; the case it + # actually guards against is MinGW-w64: the choco `mingw` package used + # by .github/workflows/windebugmatrix.yml does not ship libasan.a/ + # libubsan.a at all for some GCC versions/architectures, and neither + # x86 (i686-w64-mingw32) nor x86_64 (x86_64-w64-mingw32) is reliably + # covered across the versions that package offers -- there is no + # version/architecture combination worth hardcoding an exception list + # for. Probing capability directly, exactly like the MSVC ASan + # runtime-DLL search above, means + # a Debug configuration simply comes out unsanitized on a toolchain + # that lacks the runtime, instead of configuring successfully and then + # failing later at link time with "cannot find -lasan"/"-lubsan" on + # every single target. + include(CheckCXXSourceCompiles) + set(CMAKE_REQUIRED_FLAGS "-fsanitize=address,undefined") + check_cxx_source_compiles("int main() { return 0; }" CODAC_COMPILER_SUPPORTS_ASAN_UBSAN) + unset(CMAKE_REQUIRED_FLAGS) + + if(CODAC_COMPILER_SUPPORTS_ASAN_UBSAN) + add_compile_options($<$:-fsanitize=address>) + add_compile_options($<$:-fsanitize=undefined>) + add_link_options($<$:-fsanitize=address,undefined>) + message(STATUS "${COLOR_BLUE}ASan/UBSan will be enabled for Debug-configuration builds.${COLOR_RESET}") + else() + message(STATUS "${COLOR_RED}No ASan/UBSan runtime found for ${CMAKE_CXX_COMPILER}: Debug-configuration builds will not be sanitized.${COLOR_RESET}") + endif() + endif() + endif() + # Temporary attempts to fix errors similar to: # _ number of sections exceeded object file format limit. # _ out of memory allocating XXX bytes. @@ -114,16 +366,122 @@ #if(NOT CMAKE_CXX_STANDARD) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) + set(CMAKE_CXX_EXTENSIONS OFF) #endif() ################################################################################ -# Looking for IBEX +# Looking for GAOL ################################################################################ - find_package(IBEX REQUIRED) - ibex_init_common() # IBEX should have installed this function - message(STATUS "Found IBEX version ${IBEX_VERSION}") + # GAOL is the interval arithmetic library codac2::Interval is built upon, and + # mathlib (libultim) the library GAOL computes its elementary functions with. + # Both used to come from IBEX, which bundles them, and they were all Codac + # needed IBEX for: they are now found, or built, by Codac itself. What comes + # from IBEX in doing so, and who wrote it, is said at the top of + # scripts/CMakeModules/codac_gaol.cmake and of FindGAOL.cmake next to it. + include(codac_gaol) + + # GAOL, found or built, as the imported target Codac::gaol: its include + # directories, its libraries, and the flags floating-point rounding depends + # on, which ibex_init_common() used to put on the command line -- without + # -frounding-math and its companions, the compiler is free to constant-fold in + # the wrong rounding mode, and an interval is no longer a bound. They are + # GAOL's to say: codac_gaol_find() takes them from the CMake package of GAOL + # or from its gaol.pc, and determines them itself only for a GAOL found by its + # files alone. The Codac libraries link Codac::gaol PUBLIC, so that every + # target linking them is compiled with those flags -- the tests, the examples + # and the Python bindings included. This comes before Codac's own flags are + # added to CMAKE_CXX_FLAGS below: a GAOL built by codac_gaol_build() is handed + # CMAKE_CXX_FLAGS as it is given to this build, and chooses its flags itself. + option(ENABLE_FIND_PACKAGE_GAOL "ENABLE_FIND_PACKAGE_GAOL" ON) + codac_gaol_find() + + # Consumers need those flags as much as this build does. What Codac::gaol + # gives is kept as lists, handed over through CODAC_CXX_FLAGS in + # codac-config.cmake, and through the Cflags and Libs lines of codac.pc, which + # cannot link a target (see src/CMakeLists.txt). + codac_gaol_usage(CODAC_GAOL_INCLUDE_DIRS CODAC_INTERVAL_CXX_FLAGS CODAC_GAOL_LINK_ITEMS) + message(STATUS "Interval arithmetic flags: ${CODAC_INTERVAL_CXX_FLAGS}") + message(STATUS "GAOL include directories: ${CODAC_GAOL_INCLUDE_DIRS}") + message(STATUS "GAOL libraries: ${CODAC_GAOL_LINK_ITEMS}") + + # The flags of ibex_init_common() that are not those of interval arithmetic, + # where ibex_init_common() put them, in CMAKE_CXX_FLAGS, so that every target + # is compiled with them; handed over to consumers with the others. + codac_gaol_portability_flags(CODAC_PORTABILITY_CXX_FLAGS) + string(REPLACE ";" " " _codac_portability_cxx_flags "${CODAC_PORTABILITY_CXX_FLAGS}") + string(APPEND CMAKE_CXX_FLAGS " ${_codac_portability_cxx_flags}") + + +################################################################################ +# Treating dependencies' headers as system headers +################################################################################ + + # This project compiles with /W4 or -Wall -Wextra -Wpedantic, and a dependency + # pulled in with FetchContent inherits that level: its headers are ordinary + # include directories, not system ones. Eigen and Catch2 alone accounted for + # about 8300 MSVC warnings per job (C4459, C5054, C4308, C4324) and the 557 + # -Wc2y-extensions Clang reports for Catch2's __COUNTER__, drowning the + # handful of warnings that actually belong to Codac. + # + # FetchContent_Declare(... SYSTEM) does exactly this, but only since CMake + # 3.25, while this project accepts 3.14. Setting the property by hand is + # equivalent and has worked for far longer: the compiler then receives + # -isystem (or /external:I, which CMake already emits for the Visual Studio + # generator alongside /external:W0). + function(codac_mark_target_system tgt) + if(NOT TARGET ${tgt}) + return() + endif() + # A property cannot be set on an ALIAS, so resolve it to the target it names + # (Eigen exports Eigen3::Eigen as an alias of eigen). + get_target_property(_aliased ${tgt} ALIASED_TARGET) + if(_aliased) + set(tgt ${_aliased}) + endif() + get_target_property(_inc ${tgt} INTERFACE_INCLUDE_DIRECTORIES) + if(_inc) + set_target_properties(${tgt} PROPERTIES + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${_inc}") + endif() + endfunction() + + + # Records the directories in which a module keeps its public headers, in + # their canonical location under src/. Every consumer (the tests, the + # examples and the Python bindings) lists CODAC_SOURCE_INCLUDE_DIRS first, + # so that a header is opened at src/... rather than through a copy in the + # build tree. Both compile identically, but the path used is the path gcov + # records: reached both ways, a header ends up as two unrelated entries in + # the coverage report, each showing only what its own callers exercised, + # and is reported well below its real coverage. + # + # The variable is a cache entry because it is filled in by src/*/CMakeLists.txt + # and read from tests/, examples/ and python/, which are separate directory + # scopes; src/CMakeLists.txt empties it before the modules append to it, so + # that a reconfigure does not append to what the previous one left behind. + function(codac_publish_include_dirs) + set(CODAC_SOURCE_INCLUDE_DIRS ${CODAC_SOURCE_INCLUDE_DIRS} ${ARGN} + CACHE INTERNAL "Directories holding the public headers of the codac modules") + endfunction() + + + # The companion of the above, for the umbrella headers CMake generates rather + # than the ones kept in the source tree: codac-core.h, codac-graphics.h and + # the rest. Each module writes its own into its own binary directory, so + # ${CMAKE_BINARY_DIR}/src -- where the tests used to look, and the only place + # they looked -- holds none of them. Only the CAPD snippet of the manual + # includes them (every other test reaches for a codac2_*.h directly), which + # is why the omission surfaced as nothing but the CAPD jobs failing on + # "codac-core.h: No such file or directory". + # + # It is a cache entry, and emptied by src/CMakeLists.txt before the modules + # fill it, for the same reasons as CODAC_SOURCE_INCLUDE_DIRS above. + function(codac_publish_generated_include_dirs) + set(CODAC_GENERATED_INCLUDE_DIRS ${CODAC_GENERATED_INCLUDE_DIRS} ${ARGN} + CACHE INTERNAL "Directories holding the umbrella headers generated for the codac modules") + endfunction() ################################################################################ @@ -141,6 +499,9 @@ FetchContent_Declare(Eigen3 URL http://github.com/codac-team/eigen/archive/refs/heads/eigen-5.0.zip) #FetchContent_Declare(Eigen3 URL ${CMAKE_CURRENT_SOURCE_DIR}/3rd/eigen-a0dc3994bd5d4e804dab58decb78c56a5af516ba.zip) # If needed to be self-contained... FetchContent_MakeAvailable(Eigen3) + # Only needed for the FetchContent path: a target coming from + # find_package() is IMPORTED, and CMake already treats those as system. + codac_mark_target_system(Eigen3::Eigen) endif() # Adds Eigen3::Eigen @@ -148,6 +509,12 @@ # Looking for Threads ################################################################################ + # The same preference the generated codac-config.cmake asks of consumers, so + # that both sides of a link end up with the same answer: without it this + # build could settle on -lpthread while a consumer, which does set it, got + # -pthread, and codac.pc -- written from what is found here -- would then + # describe something other than what find_package(CODAC) hands out. + set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) ################################################################################ @@ -160,17 +527,179 @@ find_package(CAPD REQUIRED) endif() +################################################################################ +# Code coverage +################################################################################ + + # WITH_COVERAGE instruments the build and adds two targets: `coverage`, which + # runs the whole ctest suite and turns the counters gcov leaves behind into a + # report, and `coverage-report`, which only does the second half. That is what + # the "Ubuntu 26.04 GCC x86_64 Release coverage" job of + # .github/workflows/unixdebug.yml now calls, so that a developer measuring + # coverage by hand and the job reporting it use the same flags, the same gcov + # and the same filters, and cannot drift apart. Deciding all of that here + # rather than leaving it to the caller is also what spares everyone the traps + # below. + # + # Coverage answers "what do the tests reach", so it is worth turning + # BUILD_TESTS and TEST_EXAMPLES on with it -- the examples are integration + # tests and reach code the unit tests do not. + # + # This block has to come before the add_subdirectory() calls below: + # add_compile_options() is a directory property, and only reaches the targets + # created after it. Declared any later, it configures cleanly, builds + # cleanly, and leaves no .gcno file behind at all -- an empty report rather + # than an error. + option(WITH_COVERAGE "Instrument the build for code coverage (GCC/Clang)" OFF) + + if(WITH_COVERAGE) + + if(MSVC) + message(FATAL_ERROR "WITH_COVERAGE relies on gcov, which MSVC does not provide.") + endif() + + add_compile_options(--coverage) + add_link_options(--coverage) + + # pybind11 adds -flto to the modules it builds, and GCC cannot combine that + # with gcov instrumentation: the link then fails on an undefined vtable, + # the vtable being emitted nowhere once both are on. pybind11 leaves LTO + # alone as soon as CMAKE_INTERPROCEDURAL_OPTIMIZATION is set, which is what + # this does -- silently, because a coverage build that does not link is of + # no use to anyone. + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION OFF) + + # gcov itself is toolchain-specific: the notes and counters Clang writes are + # read by `llvm-cov gcov`, not by GCC's gcov, and pointing gcovr at the + # wrong one yields an empty report rather than an error. The one shipped + # next to the compiler in use is therefore named explicitly. + set(CODAC_GCOV_ARG "") + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + # Debian and its derivatives ship the tools of each LLVM release under a + # versioned name in /usr/bin and unversioned in the release's own prefix, + # so both spellings and both places are tried, the version being taken + # from the compiler that will produce the notes. + get_filename_component(_codac_cxx_dir "${CMAKE_CXX_COMPILER}" DIRECTORY) + string(REGEX MATCH "^[0-9]+" _codac_llvm_major "${CMAKE_CXX_COMPILER_VERSION}") + find_program(LLVM_COV_EXECUTABLE + NAMES llvm-cov llvm-cov-${_codac_llvm_major} + HINTS ${_codac_cxx_dir} + /usr/lib/llvm-${_codac_llvm_major}/bin) + if(LLVM_COV_EXECUTABLE) + set(CODAC_GCOV_ARG --gcov-executable "${LLVM_COV_EXECUTABLE} gcov") + else() + message(STATUS "${COLOR_RED}llvm-cov was not found next to ${CMAKE_CXX_COMPILER}: the coverage report would come out empty.${COLOR_RESET}") + endif() + endif() + + find_program(GCOVR_EXECUTABLE gcovr) + + if(NOT GCOVR_EXECUTABLE) + + message(STATUS "${COLOR_RED}gcovr was not found: the build is instrumented, but the `coverage` target is unavailable (pip install gcovr).${COLOR_RESET}") + + else() + + # A note on paths, since it decides what the report is worth: gcov + # records the path each translation unit actually opened a header + # through, so a header reached both directly and through a copy in + # the build tree comes out as two unrelated entries, each showing + # only what its own callers exercised. Every consumer (tests, + # examples, Python bindings) therefore lists CODAC_SOURCE_INCLUDE_DIRS + # first -- see codac_publish_include_dirs() above. Without that, a + # header was reported well below its real coverage, and every header + # of graphics/, unsupported/ and extensions/ was measured on a + # build-tree copy that is not the file anyone edits. + # + # --gcov-exclude-directories keeps gcovr from even descending into the + # dependencies FetchContent puts in the build tree, which --exclude alone + # would only drop from the finished report. It matters beyond noise: + # Catch2 compiles with -ffile-prefix-map, so the paths recorded in its + # notes do not resolve from anywhere and gcov fails outright on them; and + # gcov reports negative hit counts on heavily inlined code, Eigen being + # where that first shows, which gcovr treats as fatal unless told + # otherwise. The pattern has no leading ".*/" on purpose: gcovr matches it + # against a path relative to the working directory, which here starts with + # "_deps/" and so has nothing for that slash to match. + set(CODAC_GCOVR_COMMAND + ${GCOVR_EXECUTABLE} + --root ${CMAKE_SOURCE_DIR} + ${CODAC_GCOV_ARG} + --gcov-exclude-directories ".*_deps.*" + --gcov-ignore-parse-errors=negative_hits.warn_once_per_file + --exclude ".*/tests/.*" + --exclude ".*/examples/.*" + --exclude ".*/python/.*" + --print-summary + --html-details ${CMAKE_BINARY_DIR}/coverage.html + --xml ${CMAKE_BINARY_DIR}/coverage.xml + # Where to look for the counters. Without it gcovr falls back on + # --root, i.e. the source tree, and walks every build directory + # sitting under it: a second configuration kept next to this one + # (a Debug tree, an older coverage tree) then has its own .gcda + # read into this report, mixing counters produced by a different + # build of different sources. "." rather than an absolute path + # because the working directory below is already the build tree, + # and because --gcov-exclude-directories above matches against + # paths relative to it. + .) + + # Two targets rather than one, because the counters gcov leaves behind + # accumulate across runs and are read independently of what produced + # them: `coverage` is the whole thing, `coverage-report` re-reads whatever + # the last ctest invocation happened to touch. The second is what to use + # after running part of the suite by hand -- ctest -R something -- which + # is the usual way of asking what one test reaches. + # + # VERBATIM on both, because the arguments above are regular expressions: + # without it CMake hands them to the shell unquoted, which expands + # ".*/tests/.*" as a file pattern -- gcovr then receives "./tests/.." and + # refuses it. + add_custom_target(coverage + COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure + COMMAND ${CODAC_GCOVR_COMMAND} + COMMAND ${CMAKE_COMMAND} -E echo + "Coverage report: ${CMAKE_BINARY_DIR}/coverage.html (and coverage.xml, Cobertura)" + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMENT "Running the tests and measuring what they reach" + VERBATIM) + + add_custom_target(coverage-report + COMMAND ${CODAC_GCOVR_COMMAND} + COMMAND ${CMAKE_COMMAND} -E echo + "Coverage report: ${CMAKE_BINARY_DIR}/coverage.html (and coverage.xml, Cobertura)" + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMENT "Turning the counters left by the last test run into a report" + VERBATIM) + + message(STATUS "${COLOR_BLUE}Coverage instrumentation enabled; run `make coverage` to produce the report.${COLOR_RESET}") + + endif() + + endif() + + ################################################################################ # Compile sources ################################################################################ + # Python binding: + option(WITH_PYTHON "Build Python binding" OFF) + if(WITH_PYTHON) + # The Python modules are shared libraries that the static libraries of src/ + # are linked into, so these have to be position independent code as well. + # CMAKE_POSITION_INDEPENDENT_CODE only initializes the + # POSITION_INDEPENDENT_CODE property of the targets created after it is set: + # it has to be set before add_subdirectory(src). Set after it, as it used to + # be, it reached the Python modules only, which are position independent + # anyway, and -fPIC had to be given by hand in CMAKE_CXX_FLAGS. + set(CMAKE_POSITION_INDEPENDENT_CODE ON) + endif() + add_subdirectory(src) # C++ sources add_subdirectory(doc) # documentation (Doxygen + Sphinx manual) - # Python binding: - option(WITH_PYTHON "Build Python binding" OFF) if(WITH_PYTHON) - set(CMAKE_POSITION_INDEPENDENT_CODE TRUE) add_subdirectory(python) endif() @@ -188,9 +717,30 @@ endif() if(BUILD_TESTS) include(CTest) + # `check` is `test` plus two things: it prints the output of what failed, + # and it rebuilds the suite before running it. CMake's own `test` target + # does neither -- it runs ctest on whatever happens to be in the build tree. + # + # There is no DEPENDS here on purpose. In add_custom_target(), DEPENDS names + # *files*, not targets, and this target used to carry "DEPENDS + # ${PROJECT_NAME}", which is neither: no file and no target is called + # "codac", the libraries being codac-core, codac-graphics and so on. make + # therefore stopped on "No rule to make target 'codac'" after rebuilding + # everything and never reached ctest, so `make check` could not run the + # suite at all. What must exist before the suite runs is attached with + # add_dependencies() instead: every test executable, by + # tests/CMakeLists.txt; every example, by examples/CMakeLists.txt; and the + # Python extension modules just below, which the Python half of the suite + # imports. add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure $(ARGS) - DEPENDS ${PROJECT_NAME} COMMENT "Running the tests") + COMMENT "Running the tests") + + foreach(_codac_py_module _core _graphics _unsupported) + if(TARGET ${_codac_py_module}) + add_dependencies(check ${_codac_py_module}) + endif() + endforeach() add_subdirectory(tests) endif() @@ -203,7 +753,15 @@ # Archives and packages ################################################################################ - set(CPACK_GENERATOR "TGZ" "ZIP" "DEB") + set(CPACK_GENERATOR "TGZ" "ZIP") + # The DEB generator shells out to dpkg-deb, which only exists on + # Debian-based systems -- adding it unconditionally would make `cpack`/ + # `make package` fail on Windows, macOS, and non-Debian Linux, even + # though nothing else in the build depends on it. + find_program(DPKG_DEB_EXECUTABLE dpkg-deb) + if(DPKG_DEB_EXECUTABLE) + list(APPEND CPACK_GENERATOR "DEB") + endif() string(TOLOWER "${CMAKE_PROJECT_NAME}" CPACK_PACKAGE_NAME) set(CPACK_PACKAGE_VENDOR "Codac Team") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY ${CODAC_DESCRIPTION}) diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 1747bfb62..8665c7846 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -34,10 +34,19 @@ message(STATUS "Building API Documentation (mandatory for Python binding or for building the manual)") - set(SPHINX_EXTRA_API ${CMAKE_CURRENT_BINARY_DIR}/manual/extra_html/api) - file(MAKE_DIRECTORY ${SPHINX_EXTRA_API}) - - execute_process(COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_INPUT} OUTPUT_QUIET) + execute_process( + COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_INPUT} + OUTPUT_QUIET + ERROR_VARIABLE DOXYGEN_ERROR + RESULT_VARIABLE DOXYGEN_RESULT + ) + if(NOT DOXYGEN_RESULT EQUAL 0) + if(DOXYGEN_RESULT STREQUAL "Segmentation fault" AND EXISTS "${CMAKE_BINARY_DIR}/doc/api/xml/index.xml") + message(WARNING "Doxygen exited with '${DOXYGEN_RESULT}' but generated XML output. Continuing so Python docstrings can still be generated.") + else() + message(FATAL_ERROR "Doxygen failed to generate the API documentation (exit code ${DOXYGEN_RESULT}), which is required for the Python docstrings. Doxygen errors: ${DOXYGEN_ERROR}") + endif() + endif() add_custom_target(api COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_INPUT} OUTPUT_QUIET) endif() @@ -58,17 +67,28 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/manual/conf.py.in ${CMAKE_CURRENT_BINARY_DIR}/manual/conf.py) set(SPHINX_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/manual) - set(SPHINX_BUILD ${CMAKE_CURRENT_BINARY_DIR}/manual) + # The HTML output (outdir) is kept distinct from, and nested under, the confdir + # (${CMAKE_CURRENT_BINARY_DIR}/manual, where conf.py/_static live): otherwise + # Sphinx warns that html_static_path sits inside outdir. + set(SPHINX_BUILD ${CMAKE_CURRENT_BINARY_DIR}/manual/html) # Copying _static files of Sphinx to build directories - foreach(static_file ${CMAKE_CURRENT_SOURCE_DIR}/manual/_static/) - file(COPY ${static_file} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/manual/_static/) - endforeach() + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/manual/_static") + execute_process( + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_CURRENT_SOURCE_DIR}/manual/_static" + "${CMAKE_CURRENT_BINARY_DIR}/manual/_static" + ) + endif() # Copying tmp files - foreach(static_file ${CMAKE_CURRENT_SOURCE_DIR}/manual/tmp/) - file(COPY ${static_file} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/manual/tmp/) - endforeach() + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/manual/tmp") + execute_process( + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_CURRENT_SOURCE_DIR}/manual/tmp" + "${CMAKE_CURRENT_BINARY_DIR}/manual/tmp" + ) + endif() # todo: the SPHINX_EXECUTABLE is already set by FindSphinx.cmake: # check that it works without the following overload for Win and Linux: @@ -91,15 +111,17 @@ TARGET manual POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_BINARY_DIR}/doc/api/html - ${SPHINX_EXTRA_API} - COMMENT "Copying generated Doxygen HTML to Sphinx extra_html/api" + ${SPHINX_BUILD}/api + COMMENT "Copying generated Doxygen HTML into the manual output (outdir)/api" ) - + + # The Doxygen API HTML is already part of ${SPHINX_BUILD}/api (copied there + # by the POST_BUILD step above), so installing ${SPHINX_BUILD}/ below is + # sufficient -- a separate install of ${CMAKE_CURRENT_BINARY_DIR}/api would + # duplicate it under a different, unreferenced layout (api/html/*.html + # alongside the Doxyfile, instead of api/*.html). install(DIRECTORY ${SPHINX_BUILD}/ DESTINATION share/codac_website OPTIONAL) - install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/api - DESTINATION share/codac_website - OPTIONAL) endif() \ No newline at end of file diff --git a/doc/api/Doxyfile.in b/doc/api/Doxyfile.in index c4c4ed42f..8e621a420 100644 --- a/doc/api/Doxyfile.in +++ b/doc/api/Doxyfile.in @@ -1,7 +1,7 @@ -# Doxyfile 1.11.0 +# Doxyfile 1.17.0 # This file describes the settings to be used by the documentation system -# doxygen (www.doxygen.org) for a project. +# Doxygen (www.doxygen.org) for a project. # # All text after a double hash (##) is considered a comment and is placed in # front of the TAG it is preceding. @@ -15,10 +15,10 @@ # # Note: # -# Use doxygen to compare the used configuration file with the template +# Use Doxygen to compare the used configuration file with the template # configuration file: # doxygen -x [configFile] -# Use doxygen to compare the used configuration file with the template +# Use Doxygen to compare the used configuration file with the template # configuration file without replacing the environment variables or CMake type # replacement variables: # doxygen -x_noenv [configFile] @@ -51,7 +51,7 @@ PROJECT_NAME = ${CMAKE_PROJECT_NAME} PROJECT_NUMBER = ${CMAKE_PROJECT_VERSION} # Using the PROJECT_BRIEF tag one can provide an optional one line description -# for a project that appears at the top of each page and should give viewer a +# for a project that appears at the top of each page and should give viewers a # quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = @@ -71,17 +71,17 @@ PROJECT_ICON = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is -# entered, it will be relative to the location where doxygen was started. If +# entered, it will be relative to the location where Doxygen was started. If # left blank the current directory will be used. OUTPUT_DIRECTORY = ${CMAKE_BINARY_DIR}/doc/api/ -# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096 +# If the CREATE_SUBDIRS tag is set to YES then Doxygen will create up to 4096 # sub-directories (in 2 levels) under the output directory of each output format # and will distribute the generated files over these directories. Enabling this -# option can be useful when feeding doxygen a huge amount of source files, where -# putting all generated files in the same directory would otherwise causes -# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# option can be useful when feeding Doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise cause +# performance problems for the file system. Adjust CREATE_SUBDIRS_LEVEL to # control the number of sub-directories. # The default value is: NO. @@ -98,7 +98,7 @@ CREATE_SUBDIRS = NO CREATE_SUBDIRS_LEVEL = 8 -# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# If the ALLOW_UNICODE_NAMES tag is set to YES, Doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode # U+3044. @@ -107,7 +107,7 @@ CREATE_SUBDIRS_LEVEL = 8 ALLOW_UNICODE_NAMES = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all -# documentation generated by doxygen is written. Doxygen will use this +# documentation generated by Doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, # Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English @@ -121,14 +121,14 @@ ALLOW_UNICODE_NAMES = NO OUTPUT_LANGUAGE = English -# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# If the BRIEF_MEMBER_DESC tag is set to YES, Doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. # The default value is: YES. BRIEF_MEMBER_DESC = YES -# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# If the REPEAT_BRIEF tag is set to YES, Doxygen will prepend the brief # description of a member or function before the detailed description # # Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the @@ -159,13 +159,13 @@ ABBREVIATE_BRIEF = "The $name class" \ the # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# doxygen will generate a detailed section even if there is only a brief +# Doxygen will generate a detailed section even if there is only a brief # description. # The default value is: NO. ALWAYS_DETAILED_SEC = NO -# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# If the INLINE_INHERITED_MEMB tag is set to YES, Doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. @@ -173,7 +173,7 @@ ALWAYS_DETAILED_SEC = NO INLINE_INHERITED_MEMB = NO -# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# If the FULL_PATH_NAMES tag is set to YES, Doxygen will prepend the full path # before files name in the file list and in the header files. If set to NO the # shortest path that makes the file name unique will be used # The default value is: YES. @@ -183,11 +183,11 @@ FULL_PATH_NAMES = YES # The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. # Stripping is only done if one of the specified strings matches the left-hand # part of the path. The tag can be used to show relative paths in the file list. -# If left blank the directory from which doxygen is run is used as the path to +# If left blank the directory from which Doxygen is run is used as the path to # strip. # # Note that you can specify absolute paths here, but also relative paths, which -# will be relative from the directory where doxygen is started. +# will be relative from the directory where Doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. STRIP_FROM_PATH = @@ -201,41 +201,42 @@ STRIP_FROM_PATH = STRIP_FROM_INC_PATH = -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but -# less readable) file names. This can be useful is your file systems doesn't +# If the SHORT_NAMES tag is set to YES, Doxygen will generate much shorter (but +# less readable) file names. This can be useful if your file system doesn't # support long names like on DOS, Mac, or CD-ROM. # The default value is: NO. SHORT_NAMES = NO -# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the -# first line (until the first dot) of a Javadoc-style comment as the brief -# description. If set to NO, the Javadoc-style will behave just like regular Qt- -# style comments (thus requiring an explicit @brief command for a brief -# description.) +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen will interpret the +# first line (until the first dot, question mark or exclamation mark) of a +# Javadoc-style comment as the brief description. If set to NO, the Javadoc- +# style will behave just like regular Qt-style comments (thus requiring an +# explicit @brief command for a brief description.) # The default value is: NO. JAVADOC_AUTOBRIEF = NO -# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# If the JAVADOC_BANNER tag is set to YES then Doxygen will interpret a line # such as # /*************** # as being the beginning of a Javadoc-style comment "banner". If set to NO, the # Javadoc-style will behave just like regular comments and it will not be -# interpreted by doxygen. +# interpreted by Doxygen. # The default value is: NO. JAVADOC_BANNER = NO -# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first -# line (until the first dot) of a Qt-style comment as the brief description. If -# set to NO, the Qt-style will behave just like regular Qt-style comments (thus -# requiring an explicit \brief command for a brief description.) +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will interpret the first +# line (until the first dot, question mark or exclamation mark) of a Qt-style +# comment as the brief description. If set to NO, the Qt-style will behave just +# like regular Qt-style comments (thus requiring an explicit \brief command for +# a brief description.) # The default value is: NO. QT_AUTOBRIEF = NO -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen treat a # multi-line C++ special comment block (i.e. a block of //! or /// comments) as # a brief description. This used to be the default behavior. The new default is # to treat a multi-line C++ comment block as a detailed description. Set this @@ -247,10 +248,10 @@ QT_AUTOBRIEF = NO MULTILINE_CPP_IS_BRIEF = NO -# By default Python docstrings are displayed as preformatted text and doxygen's +# By default Python docstrings are displayed as preformatted text and Doxygen's # special commands cannot be used. By setting PYTHON_DOCSTRING to NO the -# doxygen's special commands can be used and the contents of the docstring -# documentation blocks is shown as doxygen documentation. +# Doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as Doxygen documentation. # The default value is: YES. PYTHON_DOCSTRING = YES @@ -261,7 +262,7 @@ PYTHON_DOCSTRING = YES INHERIT_DOCS = YES -# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# If the SEPARATE_MEMBER_PAGES tag is set to YES then Doxygen will produce a new # page for each member. If set to NO, the documentation of a member will be part # of the file/class/namespace that contains it. # The default value is: NO. @@ -289,8 +290,8 @@ TAB_SIZE = 4 # with the commands \{ and \} for these it is advised to use the version @{ and # @} or use a double escape (\\{ and \\}) -ALIASES = "license=@par License: " \ - "bugfixes=@par Bug fixes: " +ALIASES = "license=@par License:" \ + "bugfixes=@par Bug fixes:" # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For @@ -332,35 +333,49 @@ OPTIMIZE_OUTPUT_SLICE = NO # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and -# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# language is one of the parsers supported by Doxygen: IDL, Java, JavaScript, # Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, # VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: # FortranFree, unknown formatted Fortran: Fortran. In the later case the parser # tries to guess whether the code is fixed or free formatted code, this is the -# default for Fortran type files). For instance to make doxygen treat .inc files +# default for Fortran type files). For instance to make Doxygen treat .inc files # as Fortran files (default is PHP), and .f files as C (default is Fortran), # use: inc=Fortran f=C. # # Note: For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise -# the files are not read by doxygen. When specifying no_extension you should add +# the files are not read by Doxygen. When specifying no_extension you should add # * to the FILE_PATTERNS. # # Note see also the list of default file extension mappings. EXTENSION_MAPPING = -# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# If the MARKDOWN_SUPPORT tag is enabled then Doxygen pre-processes all comments # according to the Markdown format, which allows for more readable # documentation. See https://daringfireball.net/projects/markdown/ for details. -# The output of markdown processing is further processed by doxygen, so you can -# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# The output of markdown processing is further processed by Doxygen, so you can +# mix Doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. # The default value is: YES. MARKDOWN_SUPPORT = YES +# If the MARKDOWN_STRICT tag is enabled then Doxygen treats text in comments as +# Markdown formatted also in cases where Doxygen's native markup format +# conflicts with that of Markdown. This is only relevant in cases where +# backticks are used. Doxygen's native markup style allows a single quote to end +# a text fragment started with a backtick and then treat it as a piece of quoted +# text, whereas in Markdown such text fragment is treated as verbatim and only +# ends when a second matching backtick is found. Also, Doxygen's native markup +# format requires double quotes to be escaped when they appear in a backtick +# section, whereas this is not needed for Markdown. +# The default value is: YES. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +MARKDOWN_STRICT = YES + # When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up # to that level are automatically included in the table of contents, even if # they do not have an id attribute. @@ -381,17 +396,26 @@ TOC_INCLUDE_HEADINGS = 0 MARKDOWN_ID_STYLE = DOXYGEN -# When enabled doxygen tries to link words that correspond to documented +# When enabled Doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or -# globally by setting AUTOLINK_SUPPORT to NO. +# globally by setting AUTOLINK_SUPPORT to NO. Words listed in the +# AUTOLINK_IGNORE_WORDS tag are excluded from automatic linking. # The default value is: YES. AUTOLINK_SUPPORT = YES +# This tag specifies a list of words that, when matching the start of a word in +# the documentation, will suppress auto links generation, if it is enabled via +# AUTOLINK_SUPPORT. This list does not affect links explicitly created using # +# or the \link or \ref commands. +# This tag requires that the tag AUTOLINK_SUPPORT is set to YES. + +AUTOLINK_IGNORE_WORDS = + # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should set this -# tag to YES in order to let doxygen match functions declarations and +# tag to YES in order to let Doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); # versus func(std::string) {}). This also makes the inheritance and # collaboration diagrams that involve STL classes more complete and accurate. @@ -415,7 +439,7 @@ SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate # getter and setter methods for a property. Setting this option to YES will make -# doxygen to replace the get and set methods by a property in the documentation. +# Doxygen to replace the get and set methods by a property in the documentation. # This will only work if the methods are indeed getting or setting a simple # type. If this is not the case, or you want to show the methods anyway, you # should set this option to NO. @@ -424,7 +448,7 @@ SIP_SUPPORT = NO IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES then doxygen will reuse the documentation of the first +# tag is set to YES then Doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. # The default value is: NO. @@ -482,18 +506,18 @@ TYPEDEF_HIDES_STRUCT = NO # The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This # cache is used to resolve symbols given their name and scope. Since this can be # an expensive process and often the same symbol appears multiple times in the -# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small -# doxygen will become slower. If the cache is too large, memory is wasted. The +# code, Doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# Doxygen will become slower. If the cache is too large, memory is wasted. The # cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range # is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 -# symbols. At the end of a run doxygen will report the cache usage and suggest +# symbols. At the end of a run Doxygen will report the cache usage and suggest # the optimal cache size from a speed point of view. # Minimum value: 0, maximum value: 9, default value: 0. LOOKUP_CACHE_SIZE = 0 -# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use -# during processing. When set to 0 doxygen will based this on the number of +# The NUM_PROC_THREADS specifies the number of threads Doxygen is allowed to use +# during processing. When set to 0 Doxygen will based this on the number of # cores available in the system. You can set it explicitly to a value larger # than 0 to get more control over the balance between CPU load and processing # speed. At this moment only the input processing can be done using multiple @@ -501,7 +525,7 @@ LOOKUP_CACHE_SIZE = 0 # which effectively disables parallel processing. Please report any issues you # encounter. Generating dot graphs in parallel is controlled by the # DOT_NUM_THREADS setting. -# Minimum value: 0, maximum value: 32, default value: 1. +# Minimum value: 0, maximum value: 512, default value: 1. NUM_PROC_THREADS = 1 @@ -517,7 +541,7 @@ TIMESTAMP = NO # Build related configuration options #--------------------------------------------------------------------------- -# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# If the EXTRACT_ALL tag is set to YES, Doxygen will assume all entities in # documentation are documented, even if no documentation was available. Private # class members and static file members will be hidden unless the # EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. @@ -583,7 +607,7 @@ EXTRACT_ANON_NSPACES = NO RESOLVE_UNNAMED_PARAMS = YES -# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation # section is generated. This option has no effect if EXTRACT_ALL is enabled. @@ -591,7 +615,7 @@ RESOLVE_UNNAMED_PARAMS = YES HIDE_UNDOC_MEMBERS = YES -# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If set # to NO, these classes will be included in the various overviews. This option # will also hide undocumented C++ concepts if enabled. This option has no effect @@ -600,14 +624,22 @@ HIDE_UNDOC_MEMBERS = YES HIDE_UNDOC_CLASSES = YES -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# If the HIDE_UNDOC_NAMESPACES tag is set to YES, Doxygen will hide all +# undocumented namespaces that are normally visible in the namespace hierarchy. +# If set to NO, these namespaces will be included in the various overviews. This +# option has no effect if EXTRACT_ALL is enabled. +# The default value is: YES. + +HIDE_UNDOC_NAMESPACES = YES + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all friend # declarations. If set to NO, these declarations will be included in the # documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = YES -# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. If set to NO, these # blocks will be appended to the function's detailed documentation block. # The default value is: NO. @@ -621,7 +653,7 @@ HIDE_IN_BODY_DOCS = YES INTERNAL_DOCS = NO -# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# With the correct setting of option CASE_SENSE_NAMES Doxygen will better be # able to match the capabilities of the underlying filesystem. In case the # filesystem is case sensitive (i.e. it supports files in the same directory # whose names only differ in casing), the option must be set to YES to properly @@ -630,7 +662,7 @@ INTERNAL_DOCS = NO # output files written for symbols that only differ in casing, such as for two # classes, one named CLASS and the other named Class, and to also support # references to files without having to specify the exact matching casing. On -# Windows (including Cygwin) and MacOS, users should typically set this option +# Windows (including Cygwin) and macOS, users should typically set this option # to NO, whereas on Linux or other Unix flavors it should typically be set to # YES. # Possible values are: SYSTEM, NO and YES. @@ -638,14 +670,14 @@ INTERNAL_DOCS = NO CASE_SENSE_NAMES = NO -# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# If the HIDE_SCOPE_NAMES tag is set to NO then Doxygen will show members with # their full class and namespace scopes in the documentation. If set to YES, the # scope will be hidden. # The default value is: NO. HIDE_SCOPE_NAMES = NO -# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then Doxygen will # append additional text to a page's title, such as Class Reference. If set to # YES the compound reference will be hidden. # The default value is: NO. @@ -658,7 +690,7 @@ HIDE_COMPOUND_REFERENCE= NO SHOW_HEADERFILE = YES -# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# If the SHOW_INCLUDE_FILES tag is set to YES then Doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. @@ -671,7 +703,7 @@ SHOW_INCLUDE_FILES = YES SHOW_GROUPED_MEMB_INC = NO -# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen will list include # files with double quotes in the documentation rather than with sharp brackets. # The default value is: NO. @@ -683,14 +715,14 @@ FORCE_LOCAL_INCLUDES = NO INLINE_INFO = YES -# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# If the SORT_MEMBER_DOCS tag is set to YES then Doxygen will sort the # (detailed) documentation of file and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. # The default value is: YES. SORT_MEMBER_DOCS = NO -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# If the SORT_BRIEF_DOCS tag is set to YES then Doxygen will sort the brief # descriptions of file, namespace and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. Note that # this will also influence the order of the classes in the class list. @@ -698,7 +730,7 @@ SORT_MEMBER_DOCS = NO SORT_BRIEF_DOCS = NO -# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then Doxygen will sort the # (brief and detailed) documentation of class members so that constructors and # destructors are listed first. If set to NO the constructors will appear in the # respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. @@ -710,7 +742,7 @@ SORT_BRIEF_DOCS = NO SORT_MEMBERS_CTORS_1ST = NO -# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# If the SORT_GROUP_NAMES tag is set to YES then Doxygen will sort the hierarchy # of group names into alphabetical order. If set to NO the group names will # appear in their defined order. # The default value is: NO. @@ -727,11 +759,11 @@ SORT_GROUP_NAMES = NO SORT_BY_SCOPE_NAME = NO -# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# If the STRICT_PROTO_MATCHING option is enabled and Doxygen fails to do proper # type resolution of all parameters of a function it will reject a match between # the prototype and the implementation of a member function even if there is # only one candidate or it is obvious which candidate to choose by doing a -# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# simple string match. By disabling STRICT_PROTO_MATCHING Doxygen will still # accept a match between prototype and implementation in such cases. # The default value is: NO. @@ -762,6 +794,27 @@ GENERATE_BUGLIST = YES GENERATE_DEPRECATEDLIST= YES +# The GENERATE_REQUIREMENTS tag can be used to enable (YES) or disable (NO) the +# requirements page. When enabled, this page is automatically created when at +# least one comment block with a \requirement command appears in the input. +# The default value is: YES. + +GENERATE_REQUIREMENTS = YES + +# The REQ_TRACEABILITY_INFO tag controls if traceability information is shown on +# the requirements page (only relevant when using \requirement comment blocks). +# The setting NO will disable the traceability information altogether. The +# setting UNSATISFIED_ONLY will show a list of requirements that are missing a +# satisfies relation (through the command: \satisfies). Similarly the setting +# UNVERIFIED_ONLY will show a list of requirements that are missing a verifies +# relation (through the command: \verifies). Setting the tag to YES (the +# default) will show both lists if applicable. +# Possible values are: YES, NO, UNSATISFIED_ONLY and UNVERIFIED_ONLY. +# The default value is: YES. +# This tag requires that the tag GENERATE_REQUIREMENTS is set to YES. + +REQ_TRACEABILITY_INFO = YES + # The ENABLED_SECTIONS tag can be used to enable conditional documentation # sections, marked by \if ... \endif and \cond # ... \endcond blocks. @@ -801,25 +854,25 @@ SHOW_FILES = YES SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that -# doxygen should invoke to get the current version for each file (typically from +# Doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command command input-file, where command is the value of the # FILE_VERSION_FILTER tag, and input-file is the name of an input file provided -# by doxygen. Whatever the program writes to standard output is used as the file +# by Doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed -# by doxygen. The layout file controls the global structure of the generated +# by Doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file -# that represents doxygen's defaults, run doxygen with the -l option. You can +# that represents Doxygen's defaults, run Doxygen with the -l option. You can # optionally specify a file name after the option, if omitted DoxygenLayout.xml # will be used as the name of the layout file. See also section "Changing the # layout of pages" for information. # -# Note that if you run doxygen from a directory containing a file called -# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# Note that if you run Doxygen from a directory containing a file called +# DoxygenLayout.xml, Doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. LAYOUT_FILE = @@ -834,19 +887,35 @@ LAYOUT_FILE = CITE_BIB_FILES = +# The EXTERNAL_TOOL_PATH tag can be used to extend the search path (PATH +# environment variable) so that external tools such as latex and gs can be +# found. +# Note: Directories specified with EXTERNAL_TOOL_PATH are added in front of the +# path already specified by the PATH variable, and are added in the order +# specified. +# Note: This option is particularly useful for macOS version 14 (Sonoma) and +# higher, when running Doxygen from Doxywizard, because in this case any user- +# defined changes to the PATH are ignored. A typical example on macOS is to set +# EXTERNAL_TOOL_PATH = /Library/TeX/texbin /usr/local/bin +# together with the standard path, the full search path used by doxygen when +# launching external tools will then become +# PATH=/Library/TeX/texbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin + +EXTERNAL_TOOL_PATH = + #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated to -# standard output by doxygen. If QUIET is set to YES this implies that the +# standard output by Doxygen. If QUIET is set to YES this implies that the # messages are off. # The default value is: NO. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are -# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# generated to standard error (stderr) by Doxygen. If WARNINGS is set to YES # this implies that the warnings are on. # # Tip: Turn warnings on while writing the documentation. @@ -854,14 +923,14 @@ QUIET = NO WARNINGS = YES -# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# If the WARN_IF_UNDOCUMENTED tag is set to YES then Doxygen will generate # warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: YES. WARN_IF_UNDOCUMENTED = YES -# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# If the WARN_IF_DOC_ERROR tag is set to YES, Doxygen will generate warnings for # potential errors in the documentation, such as documenting some parameters in # a documented function twice, or documenting parameters that don't exist or # using markup commands wrongly. @@ -869,8 +938,8 @@ WARN_IF_UNDOCUMENTED = YES WARN_IF_DOC_ERROR = YES -# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete -# function parameter documentation. If set to NO, doxygen will accept that some +# If WARN_IF_INCOMPLETE_DOC is set to YES, Doxygen will warn about incomplete +# function parameter documentation. If set to NO, Doxygen will accept that some # parameters have no documentation without warning. # The default value is: YES. @@ -878,7 +947,7 @@ WARN_IF_INCOMPLETE_DOC = YES # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return -# value. If set to NO, doxygen will only warn about wrong parameter +# value. If set to NO, Doxygen will only warn about wrong parameter # documentation, but not about the absence of documentation. If EXTRACT_ALL is # set to YES then this flag will automatically be disabled. See also # WARN_IF_INCOMPLETE_DOC @@ -886,20 +955,28 @@ WARN_IF_INCOMPLETE_DOC = YES WARN_NO_PARAMDOC = NO -# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about -# undocumented enumeration values. If set to NO, doxygen will accept +# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, Doxygen will warn about +# undocumented enumeration values. If set to NO, Doxygen will accept # undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: NO. WARN_IF_UNDOC_ENUM_VAL = NO -# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# If WARN_LAYOUT_FILE option is set to YES, Doxygen will warn about issues found +# while parsing the user defined layout file, such as missing or wrong elements. +# See also LAYOUT_FILE for details. If set to NO, problems with the layout file +# will be suppressed. +# The default value is: YES. + +WARN_LAYOUT_FILE = YES + +# If the WARN_AS_ERROR tag is set to YES then Doxygen will immediately stop when # a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS -# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but -# at the end of the doxygen process doxygen will return with a non-zero status. -# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves -# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not +# then Doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the Doxygen process Doxygen will return with a non-zero status. +# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then Doxygen behaves +# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined Doxygen will not # write the warning messages in between other messages but write them at the end # of a run, in case a WARN_LOGFILE is defined the warning messages will be # besides being in the defined file also be shown at the end of a run, unless @@ -910,7 +987,7 @@ WARN_IF_UNDOC_ENUM_VAL = NO WARN_AS_ERROR = NO -# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# The WARN_FORMAT tag determines the format of the warning messages that Doxygen # can produce. The string should contain the $file, $line, and $text tags, which # will be replaced by the file and line number from which the warning originated # and the warning text. Optionally the format may contain $version, which will @@ -923,7 +1000,7 @@ WARN_FORMAT = "$file:$line: $text" # In the $text part of the WARN_FORMAT command it is possible that a reference # to a more specific place is given. To make it easier to jump to this place -# (outside of doxygen) the user can define a custom "cut" / "paste" string. +# (outside of Doxygen) the user can define a custom "cut" / "paste" string. # Example: # WARN_LINE_FORMAT = "'vi $file +$line'" # See also: WARN_FORMAT @@ -953,7 +1030,7 @@ WARN_LOGFILE = INPUT = ${CMAKE_SOURCE_DIR}/src # This tag can be used to specify the character encoding of the source files -# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# that Doxygen parses. Internally Doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv # documentation (see: # https://www.gnu.org/software/libiconv/) for the list of possible encodings. @@ -963,10 +1040,10 @@ INPUT = ${CMAKE_SOURCE_DIR}/src INPUT_ENCODING = UTF-8 # This tag can be used to specify the character encoding of the source files -# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify +# that Doxygen parses. The INPUT_FILE_ENCODING tag can be used to specify # character encoding on a per file pattern basis. Doxygen will compare the file # name with each pattern and apply the encoding instead of the default -# INPUT_ENCODING) if there is a match. The character encodings are a list of the +# INPUT_ENCODING if there is a match. The character encodings are a list of the # form: pattern=encoding (like *.php=ISO-8859-1). # See also: INPUT_ENCODING for further information on supported encodings. @@ -978,16 +1055,16 @@ INPUT_FILE_ENCODING = # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not -# read by doxygen. +# read by Doxygen. # # Note the list of default checked file patterns might differ from the list of # default file extension mappings. # # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm, # *.cpp, *.cppm, *.ccm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, -# *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, -# *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to -# be provided as doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, +# *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be +# provided as Doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, # *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.h @@ -1002,10 +1079,10 @@ RECURSIVE = YES # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. # -# Note that relative paths are relative to the directory from which doxygen is +# Note that relative paths are relative to the directory from which Doxygen is # run. -EXCLUDE = ../src/graphics/3rd/vibes/vibes.h +EXCLUDE = ${CMAKE_SOURCE_DIR}/src/graphics/3rd/vibes/vibes.h # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded @@ -1057,7 +1134,7 @@ EXAMPLE_RECURSIVE = NO IMAGE_PATH = -# The INPUT_FILTER tag can be used to specify a program that doxygen should +# The INPUT_FILTER tag can be used to specify a program that Doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command: # @@ -1072,14 +1149,14 @@ IMAGE_PATH = # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. # -# Note that doxygen will use the data processed and written to standard output +# Note that Doxygen will use the data processed and written to standard output # for further processing, therefore nothing else, like debug statements or used # commands (so in case of a Windows batch file always use @echo OFF), should be # written to standard output. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not -# properly processed by doxygen. +# properly processed by Doxygen. INPUT_FILTER = @@ -1092,7 +1169,7 @@ INPUT_FILTER = # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not -# properly processed by doxygen. +# properly processed by Doxygen. FILTER_PATTERNS = @@ -1114,10 +1191,19 @@ FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub -# and want to reuse the introduction page also for the doxygen output. +# and want to reuse the introduction page also for the Doxygen output. USE_MDFILE_AS_MAINPAGE = +# If the IMPLICIT_DIR_DOCS tag is set to YES, any README.md file found in sub- +# directories of the project's root, is used as the documentation for that sub- +# directory, except when the README.md starts with a \dir, \page or \mainpage +# command. If set to NO, the README.md file needs to start with an explicit \dir +# command in order to be used as directory documentation. +# The default value is: YES. + +IMPLICIT_DIR_DOCS = YES + # The Fortran standard specifies that for fixed formatted Fortran code all # characters from position 72 are to be considered as comment. A common # extension is to allow longer lines before the automatic comment starts. The @@ -1147,7 +1233,7 @@ SOURCE_BROWSER = NO INLINE_SOURCES = YES -# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct Doxygen to hide any # special comment blocks from generated source code fragments. Normal C, C++ and # Fortran comments will always remain visible. # The default value is: YES. @@ -1185,7 +1271,7 @@ REFERENCES_LINK_SOURCE = YES SOURCE_TOOLTIPS = YES # If the USE_HTAGS tag is set to YES then the references to source code will -# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# point to the HTML generated by the htags(1) tool instead of Doxygen built-in # source browser. The htags tool is part of GNU's global source tagging system # (see https://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. @@ -1199,14 +1285,14 @@ SOURCE_TOOLTIPS = YES # Doxygen will invoke htags (and that will in turn invoke gtags), so these # tools must be available from the command line (i.e. in the search path). # -# The result: instead of the source browser generated by doxygen, the links to +# The result: instead of the source browser generated by Doxygen, the links to # source code will now point to the output of htags. # The default value is: NO. # This tag requires that the tag SOURCE_BROWSER is set to YES. USE_HTAGS = NO -# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# If the VERBATIM_HEADERS tag is set the YES then Doxygen will generate a # verbatim copy of the header file for each class for which an include is # specified. Set to NO to disable this. # See also: Section \class. @@ -1214,46 +1300,6 @@ USE_HTAGS = NO VERBATIM_HEADERS = YES -# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the -# clang parser (see: -# http://clang.llvm.org/) for more accurate parsing at the cost of reduced -# performance. This can be particularly helpful with template rich C++ code for -# which doxygen's built-in parser lacks the necessary type information. -# Note: The availability of this option depends on whether or not doxygen was -# generated with the -Duse_libclang=ON option for CMake. -# The default value is: NO. - -#CLANG_ASSISTED_PARSING = NO - -# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS -# tag is set to YES then doxygen will add the directory of each input to the -# include path. -# The default value is: YES. -# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. - -#CLANG_ADD_INC_PATHS = YES - -# If clang assisted parsing is enabled you can provide the compiler with command -# line options that you would normally use when invoking the compiler. Note that -# the include paths will already be set by doxygen for the files and directories -# specified with INPUT and INCLUDE_PATH. -# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. - -#CLANG_OPTIONS = - -# If clang assisted parsing is enabled you can provide the clang parser with the -# path to the directory containing a file called compile_commands.json. This -# file is the compilation database (see: -# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the -# options used when the source files were built. This is equivalent to -# specifying the -p option to a clang tool, such as clang-check. These options -# will then be passed to the parser. Any options specified with CLANG_OPTIONS -# will be added as well. -# Note: The availability of this option depends on whether or not doxygen was -# generated with the -Duse_libclang=ON option for CMake. - -#CLANG_DATABASE_PATH = - #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- @@ -1278,7 +1324,7 @@ IGNORE_PREFIX = # Configuration options related to the HTML output #--------------------------------------------------------------------------- -# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# If the GENERATE_HTML tag is set to YES, Doxygen will generate HTML output # The default value is: YES. GENERATE_HTML = YES @@ -1299,40 +1345,40 @@ HTML_OUTPUT = html HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a user-defined HTML header file for -# each generated HTML page. If the tag is left blank doxygen will generate a +# each generated HTML page. If the tag is left blank Doxygen will generate a # standard header. # # To get valid HTML the header file that includes any scripts and style sheets -# that doxygen needs, which is dependent on the configuration options used (e.g. +# that Doxygen needs, which is dependent on the configuration options used (e.g. # the setting GENERATE_TREEVIEW). It is highly recommended to start with a # default header using # doxygen -w html new_header.html new_footer.html new_stylesheet.css # YourConfigFile # and then modify the file new_header.html. See also section "Doxygen usage" -# for information on how to generate the default header that doxygen normally +# for information on how to generate the default header that Doxygen normally # uses. # Note: The header is subject to change so you typically have to regenerate the -# default header when upgrading to a newer version of doxygen. For a description +# default header when upgrading to a newer version of Doxygen. For a description # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each -# generated HTML page. If the tag is left blank doxygen will generate a standard +# generated HTML page. If the tag is left blank Doxygen will generate a standard # footer. See HTML_HEADER for more information on how to generate a default # footer and what special commands can be used inside the footer. See also # section "Doxygen usage" for information on how to generate the default footer -# that doxygen normally uses. +# that Doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of -# the HTML output. If left blank doxygen will generate a default style sheet. +# the HTML output. If left blank Doxygen will generate a default style sheet. # See also section "Doxygen usage" for information on how to generate the style -# sheet that doxygen normally uses. +# sheet that Doxygen normally uses. # Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as # it is more robust and this tag (HTML_STYLESHEET) will in the future become # obsolete. @@ -1342,7 +1388,7 @@ HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # cascading style sheets that are included after the standard style sheets -# created by doxygen. Using this option one can overrule certain style aspects. +# created by Doxygen. Using this option one can overrule certain style aspects. # This is preferred over using HTML_STYLESHEET since it does not replace the # standard style sheet and is therefore more robust against future updates. # Doxygen will copy the style sheet files to the output directory. @@ -1437,7 +1483,7 @@ HTML_DYNAMIC_SECTIONS = NO HTML_CODE_FOLDING = YES -# If the HTML_COPY_CLIPBOARD tag is set to YES then doxygen will show an icon in +# If the HTML_COPY_CLIPBOARD tag is set to YES then Doxygen will show an icon in # the top right corner of code and text fragments that allows the user to copy # its content to the clipboard. Note this only works if supported by the browser # and the web page is served via a secure context (see: @@ -1450,7 +1496,7 @@ HTML_COPY_CLIPBOARD = YES # Doxygen stores a couple of settings persistently in the browser (via e.g. # cookies). By default these settings apply to all HTML pages generated by -# doxygen across all projects. The HTML_PROJECT_COOKIE tag can be used to store +# Doxygen across all projects. The HTML_PROJECT_COOKIE tag can be used to store # the settings under a project specific key, such that the user preferences will # be stored separately. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1474,7 +1520,7 @@ HTML_INDEX_NUM_ENTRIES = 100 # generated that can be used as input for Apple's Xcode 3 integrated development # environment (see: # https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To -# create a documentation set, doxygen will generate a Makefile in the HTML +# create a documentation set, Doxygen will generate a Makefile in the HTML # output directory. Running make will produce the docset in that directory and # running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at @@ -1522,18 +1568,18 @@ DOCSET_PUBLISHER_ID = org.doxygen.Publisher DOCSET_PUBLISHER_NAME = Publisher -# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# If the GENERATE_HTMLHELP tag is set to YES then Doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop # on Windows. In the beginning of 2021 Microsoft took the original page, with -# a.o. the download links, offline the HTML help workshop was already many years -# in maintenance mode). You can download the HTML help workshop from the web -# archives at Installation executable (see: -# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo -# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). +# a.o. the download links, offline (the HTML help workshop was already many +# years in maintenance mode). You can download the HTML help workshop from the +# web archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/https://download.microsoft.com/downl +# oad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). # # The HTML Help Workshop contains a compiler that can convert all HTML output -# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# generated by Doxygen into a single compiled HTML file (.chm). Compiled HTML # files are now used as the Windows 98 help format, and will replace the old # Windows help format (.hlp) on all Windows platforms in the future. Compressed # HTML files also contain an index, a table of contents, and you can search for @@ -1553,7 +1599,7 @@ CHM_FILE = # The HHC_LOCATION tag can be used to specify the location (absolute path # including file name) of the HTML help compiler (hhc.exe). If non-empty, -# doxygen will try to run the HTML help compiler on the generated index.hhp. +# Doxygen will try to run the HTML help compiler on the generated index.hhp. # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. @@ -1655,7 +1701,7 @@ QHP_CUST_FILTER_ATTRS = QHP_SECT_FILTER_ATTRS = # The QHG_LOCATION tag can be used to specify the location (absolute path -# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# including file name) of Qt's qhelpgenerator. If non-empty Doxygen will try to # run qhelpgenerator on the generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1700,29 +1746,38 @@ DISABLE_INDEX = NO # (i.e. any modern browser). Windows users are probably better off using the # HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can # further fine tune the look of the index (see "Fine-tuning the output"). As an -# example, the default style sheet generated by doxygen has an example that +# example, the default style sheet generated by Doxygen has an example that # shows how to put an image at the root of the tree instead of the PROJECT_NAME. -# Since the tree basically has the same information as the tab index, you could -# consider setting DISABLE_INDEX to YES when enabling this option. -# The default value is: NO. +# Since the tree basically has more details information than the tab index, you +# could consider setting DISABLE_INDEX to YES when enabling this option. +# The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = NO -# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the -# FULL_SIDEBAR option determines if the side bar is limited to only the treeview -# area (value NO) or if it should extend to the full height of the window (value -# YES). Setting this to YES gives a layout similar to -# https://docs.readthedocs.io with more room for contents, but less room for the -# project logo, title, and description. If either GENERATE_TREEVIEW or -# DISABLE_INDEX is set to NO, this option has no effect. +# When GENERATE_TREEVIEW is set to YES, the PAGE_OUTLINE_PANEL option determines +# if an additional navigation panel is shown at the right hand side of the +# screen, displaying an outline of the contents of the main page, similar to +# e.g. https://developer.android.com/reference If GENERATE_TREEVIEW is set to +# NO, this option has no effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +PAGE_OUTLINE_PANEL = YES + +# When GENERATE_TREEVIEW is set to YES, the FULL_SIDEBAR option determines if +# the side bar is limited to only the treeview area (value NO) or if it should +# extend to the full height of the window (value YES). Setting this to YES gives +# a layout similar to e.g. https://docs.readthedocs.io with more room for +# contents, but less room for the project logo, title, and description. If +# GENERATE_TREEVIEW is set to NO, this option has no effect. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. FULL_SIDEBAR = NO # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that -# doxygen will group on one line in the generated HTML documentation. +# Doxygen will group on one line in the generated HTML documentation. # # Note that a value of 0 will completely suppress the enum values from appearing # in the overview section. @@ -1731,6 +1786,12 @@ FULL_SIDEBAR = NO ENUM_VALUES_PER_LINE = 4 +# When the SHOW_ENUM_VALUES tag is set doxygen will show the specified +# enumeration values besides the enumeration mnemonics. +# The default value is: NO. + +SHOW_ENUM_VALUES = NO + # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used # to set the initial width (in pixels) of the frame in which the tree is shown. # Minimum value: 0, maximum value: 1500, default value: 250. @@ -1738,21 +1799,21 @@ ENUM_VALUES_PER_LINE = 4 TREEVIEW_WIDTH = 250 -# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# If the EXT_LINKS_IN_WINDOW option is set to YES, Doxygen will open links to # external symbols imported via tag files in a separate window. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. EXT_LINKS_IN_WINDOW = NO -# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email +# If the OBFUSCATE_EMAILS tag is set to YES, Doxygen will obfuscate email # addresses. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. OBFUSCATE_EMAILS = YES -# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# If the HTML_FORMULA_FORMAT option is set to svg, Doxygen will use the pdf2svg # tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see # https://inkscape.org) to generate formulas as SVG images instead of PNGs for # the HTML output. These images will generally look nicer at scaled resolutions. @@ -1765,7 +1826,7 @@ HTML_FORMULA_FORMAT = png # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful -# doxygen run you need to manually remove any form_*.png images from the HTML +# Doxygen run you need to manually remove any form_*.png images from the HTML # output directory to force them to be regenerated. # Minimum value: 8, maximum value: 50, default value: 10. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1794,7 +1855,7 @@ USE_MATHJAX = YES # regards to the different settings, so it is possible that also other MathJax # settings have to be changed when switching between the different MathJax # versions. -# Possible values are: MathJax_2 and MathJax_3. +# Possible values are: MathJax_2, MathJax_3 and MathJax_4. # The default value is: MathJax_2. # This tag requires that the tag USE_MATHJAX is set to YES. @@ -1803,9 +1864,10 @@ MATHJAX_VERSION = MathJax_2 # When MathJax is enabled you can set the default output format to be used for # the MathJax output. For more details about the output format see MathJax # version 2 (see: -# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# https://docs.mathjax.org/en/v2.7/output.html), MathJax version 3 (see: +# https://docs.mathjax.org/en/v3.2/output/index.html) and MathJax version 4 # (see: -# http://docs.mathjax.org/en/latest/web/components/output.html). +# https://docs.mathjax.org/en/v4.0/output/index.htm). # Possible values are: HTML-CSS (which is slower, but has the best # compatibility. This is the name for Mathjax version 2, for MathJax version 3 # this will be translated into chtml), NativeMML (i.e. MathML. Only supported @@ -1818,47 +1880,61 @@ MATHJAX_VERSION = MathJax_2 MATHJAX_FORMAT = HTML-CSS # When MathJax is enabled you need to specify the location relative to the HTML -# output directory using the MATHJAX_RELPATH option. The destination directory -# should contain the MathJax.js script. For instance, if the mathjax directory -# is located at the same level as the HTML output directory, then -# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax -# Content Delivery Network so you can quickly see the result without installing -# MathJax. However, it is strongly recommended to install a local copy of -# MathJax from https://www.mathjax.org before deployment. The default value is: +# output directory using the MATHJAX_RELPATH option. For Mathjax version 2 the +# destination directory should contain the MathJax.js script. For instance, if +# the mathjax directory is located at the same level as the HTML output +# directory, then MATHJAX_RELPATH should be ../mathjax. For Mathjax versions 3 +# and 4 the destination directory should contain the tex-.js script +# (where is either chtml or svg). The default value points to the +# MathJax Content Delivery Network so you can quickly see the result without +# installing MathJax. However, it is strongly recommended to install a local +# copy of MathJax from https://www.mathjax.org before deployment. The default +# value is: # - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 # - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 +# - in case of MathJax version 4: https://cdn.jsdelivr.net/npm/mathjax@4 # This tag requires that the tag USE_MATHJAX is set to YES. -#MATHJAX_RELPATH = ../mathjax +MATHJAX_RELPATH = # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example -# for MathJax version 2 (see -# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): +# for MathJax version 2 (see https://docs.mathjax.org/en/v2.7/tex.html): # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # For example for MathJax version 3 (see -# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# https://docs.mathjax.org/en/v3.2/input/tex/extensions/): # MATHJAX_EXTENSIONS = ams +# For example for MathJax version 4 (see +# https://docs.mathjax.org/en/v4.0/input/tex/extensions/): +# MATHJAX_EXTENSIONS = units +# Note that for Mathjax version 4 quite a few extensions are already +# automatically loaded. To disable a package in Mathjax version 4 one can use +# the package name prepended with a minus sign (- like MATHJAX_EXTENSIONS += +# -textmacros) # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = TeX/AMSmath \ TeX/AMSsymbols -# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces -# of code that will be used on startup of the MathJax code. See the MathJax site -# (see: -# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an -# example see the documentation. +# The MATHJAX_CODEFILE tag can be used to specify a file with JavaScript pieces +# of code that will be used on startup of the MathJax code. See the Mathjax site +# for more details: +# - MathJax version 2 (see: +# https://docs.mathjax.org/en/v2.7/) +# - MathJax version 3 (see: +# https://docs.mathjax.org/en/v3.2/) +# - MathJax version 4 (see: +# https://docs.mathjax.org/en/v4.0/) For an example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. -#MATHJAX_CODEFILE = +MATHJAX_CODEFILE = -# When the SEARCHENGINE tag is enabled doxygen will generate a search box for -# the HTML output. The underlying search engine uses javascript and DHTML and +# When the SEARCHENGINE tag is enabled Doxygen will generate a search box for +# the HTML output. The underlying search engine uses JavaScript and DHTML and # should work on any modern browser. Note that when using HTML help # (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) # there is already a search function so this one should typically be disabled. -# For large projects the javascript based search engine can be slow, then +# For large projects the JavaScript based search engine can be slow, then # enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to # search using the keyboard; to jump to the search box use + S # (what the is depends on the OS and browser, but it is typically @@ -1877,7 +1953,7 @@ SEARCHENGINE = YES # When the SERVER_BASED_SEARCH tag is enabled the search engine will be # implemented using a web server instead of a web client using JavaScript. There # are two flavors of web server based searching depending on the EXTERNAL_SEARCH -# setting. When disabled, doxygen will generate a PHP script for searching and +# setting. When disabled, Doxygen will generate a PHP script for searching and # an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing # and searching needs to be provided by external tools. See the section # "External Indexing and Searching" for details. @@ -1886,7 +1962,7 @@ SEARCHENGINE = YES SERVER_BASED_SEARCH = NO -# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP +# When EXTERNAL_SEARCH tag is enabled Doxygen will no longer generate the PHP # script for searching. Instead the search results are written to an XML file # which needs to be processed by an external indexer. Doxygen will invoke an # external search engine pointed to by the SEARCHENGINE_URL option to obtain the @@ -1931,7 +2007,7 @@ SEARCHDATA_FILE = searchdata.xml EXTERNAL_SEARCH_ID = -# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen +# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through Doxygen # projects other than the one defined by this configuration file, but that are # all added to the same external search index. Each project needs to have a # unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of @@ -1945,7 +2021,7 @@ EXTRA_SEARCH_MAPPINGS = # Configuration options related to the LaTeX output #--------------------------------------------------------------------------- -# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output. +# If the GENERATE_LATEX tag is set to YES, Doxygen will generate LaTeX output. # The default value is: YES. GENERATE_LATEX = NO @@ -1990,7 +2066,7 @@ MAKEINDEX_CMD_NAME = makeindex LATEX_MAKEINDEX_CMD = makeindex -# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX +# If the COMPACT_LATEX tag is set to YES, Doxygen generates more compact LaTeX # documents. This may be useful for small projects and may help to save some # trees in general. # The default value is: NO. @@ -2021,15 +2097,15 @@ EXTRA_PACKAGES = # The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for # the generated LaTeX document. The header should contain everything until the -# first chapter. If it is left blank doxygen will generate a standard header. It +# first chapter. If it is left blank Doxygen will generate a standard header. It # is highly recommended to start with a default header using # doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty # and then modify the file new_header.tex. See also section "Doxygen usage" for -# information on how to generate the default header that doxygen normally uses. +# information on how to generate the default header that Doxygen normally uses. # # Note: Only use a user-defined header if you know what you are doing! # Note: The header is subject to change so you typically have to regenerate the -# default header when upgrading to a newer version of doxygen. The following +# default header when upgrading to a newer version of Doxygen. The following # commands have a special meaning inside the header (and footer): For a # description of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -2038,10 +2114,10 @@ LATEX_HEADER = # The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for # the generated LaTeX document. The footer should contain everything after the -# last chapter. If it is left blank doxygen will generate a standard footer. See +# last chapter. If it is left blank Doxygen will generate a standard footer. See # LATEX_HEADER for more information on how to generate a default footer and what # special commands can be used inside the footer. See also section "Doxygen -# usage" for information on how to generate the default footer that doxygen +# usage" for information on how to generate the default footer that Doxygen # normally uses. Note: Only use a user-defined footer if you know what you are # doing! # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -2050,7 +2126,7 @@ LATEX_FOOTER = # The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined # LaTeX style sheets that are included after the standard style sheets created -# by doxygen. Using this option one can overrule certain style aspects. Doxygen +# by Doxygen. Using this option one can overrule certain style aspects. Doxygen # will copy the style sheet files to the output directory. # Note: The order of the extra style sheet files is of importance (e.g. the last # style sheet in the list overrules the setting of the previous ones in the @@ -2076,7 +2152,7 @@ LATEX_EXTRA_FILES = PDF_HYPERLINKS = YES -# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as +# If the USE_PDFLATEX tag is set to YES, Doxygen will use the engine as # specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX # files. Set this option to YES, to get a higher quality PDF documentation. # @@ -2101,7 +2177,7 @@ USE_PDFLATEX = YES LATEX_BATCHMODE = NO -# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the +# If the LATEX_HIDE_INDICES tag is set to YES then Doxygen will not include the # index chapters (such as File Index, Compound Index, etc.) in the output. # The default value is: NO. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -2111,7 +2187,7 @@ LATEX_HIDE_INDICES = NO # The LATEX_BIB_STYLE tag can be used to specify the style to use for the # bibliography, e.g. plainnat, or ieeetr. See # https://en.wikipedia.org/wiki/BibTeX and \cite for more info. -# The default value is: plain. +# The default value is: plainnat. # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_BIB_STYLE = plain @@ -2128,7 +2204,7 @@ LATEX_EMOJI_DIRECTORY = # Configuration options related to the RTF output #--------------------------------------------------------------------------- -# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The +# If the GENERATE_RTF tag is set to YES, Doxygen will generate RTF output. The # RTF output is optimized for Word 97 and may not look too pretty with other RTF # readers/editors. # The default value is: NO. @@ -2143,7 +2219,7 @@ GENERATE_RTF = NO RTF_OUTPUT = rtf -# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF +# If the COMPACT_RTF tag is set to YES, Doxygen generates more compact RTF # documents. This may be useful for small projects and may help to save some # trees in general. # The default value is: NO. @@ -2163,18 +2239,18 @@ COMPACT_RTF = NO RTF_HYPERLINKS = NO -# Load stylesheet definitions from file. Syntax is similar to doxygen's +# Load stylesheet definitions from file. Syntax is similar to Doxygen's # configuration file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. # # See also section "Doxygen usage" for information on how to generate the -# default style sheet that doxygen normally uses. +# default style sheet that Doxygen normally uses. # This tag requires that the tag GENERATE_RTF is set to YES. RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an RTF document. Syntax is -# similar to doxygen's configuration file. A template extensions file can be +# similar to Doxygen's configuration file. A template extensions file can be # generated using doxygen -e rtf extensionFile. # This tag requires that the tag GENERATE_RTF is set to YES. @@ -2192,7 +2268,7 @@ RTF_EXTRA_FILES = # Configuration options related to the man page output #--------------------------------------------------------------------------- -# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for +# If the GENERATE_MAN tag is set to YES, Doxygen will generate man pages for # classes and files. # The default value is: NO. @@ -2223,7 +2299,7 @@ MAN_EXTENSION = .3 MAN_SUBDIR = -# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, then it # will generate one additional man file for each entity documented in the real # man page(s). These additional files only source the real man page, but without # them the man command would be unable to find the correct page. @@ -2236,7 +2312,7 @@ MAN_LINKS = NO # Configuration options related to the XML output #--------------------------------------------------------------------------- -# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that +# If the GENERATE_XML tag is set to YES, Doxygen will generate an XML file that # captures the structure of the code including all documentation. # The default value is: NO. @@ -2250,7 +2326,7 @@ GENERATE_XML = YES XML_OUTPUT = xml -# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program +# If the XML_PROGRAMLISTING tag is set to YES, Doxygen will dump the program # listings (including syntax highlighting and cross-referencing information) to # the XML output. Note that enabling this will significantly increase the size # of the XML output. @@ -2259,7 +2335,7 @@ XML_OUTPUT = xml XML_PROGRAMLISTING = YES -# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include +# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, Doxygen will include # namespace members in file scope as well, matching the HTML output. # The default value is: NO. # This tag requires that the tag GENERATE_XML is set to YES. @@ -2270,7 +2346,7 @@ XML_NS_MEMB_FILE_SCOPE = NO # Configuration options related to the DOCBOOK output #--------------------------------------------------------------------------- -# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files +# If the GENERATE_DOCBOOK tag is set to YES, Doxygen will generate Docbook files # that can be used to generate PDF. # The default value is: NO. @@ -2288,7 +2364,7 @@ DOCBOOK_OUTPUT = docbook # Configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- -# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an +# If the GENERATE_AUTOGEN_DEF tag is set to YES, Doxygen will generate an # AutoGen Definitions (see https://autogen.sourceforge.net/) file that captures # the structure of the code including all documentation. Note that this feature # is still experimental and incomplete at the moment. @@ -2300,8 +2376,8 @@ GENERATE_AUTOGEN_DEF = NO # Configuration options related to Sqlite3 output #--------------------------------------------------------------------------- -# If the GENERATE_SQLITE3 tag is set to YES doxygen will generate a Sqlite3 -# database with symbols found by doxygen stored in tables. +# If the GENERATE_SQLITE3 tag is set to YES Doxygen will generate a Sqlite3 +# database with symbols found by Doxygen stored in tables. # The default value is: NO. GENERATE_SQLITE3 = NO @@ -2315,7 +2391,7 @@ GENERATE_SQLITE3 = NO SQLITE3_OUTPUT = sqlite3 # The SQLITE3_RECREATE_DB tag is set to YES, the existing doxygen_sqlite3.db -# database file will be recreated with each doxygen run. If set to NO, doxygen +# database file will be recreated with each Doxygen run. If set to NO, Doxygen # will warn if a database file is already found and not modify it. # The default value is: YES. # This tag requires that the tag GENERATE_SQLITE3 is set to YES. @@ -2326,7 +2402,7 @@ SQLITE3_RECREATE_DB = YES # Configuration options related to the Perl module output #--------------------------------------------------------------------------- -# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module +# If the GENERATE_PERLMOD tag is set to YES, Doxygen will generate a Perl module # file that captures the structure of the code including all documentation. # # Note that this feature is still experimental and incomplete at the moment. @@ -2334,7 +2410,7 @@ SQLITE3_RECREATE_DB = YES GENERATE_PERLMOD = NO -# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary +# If the PERLMOD_LATEX tag is set to YES, Doxygen will generate the necessary # Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI # output from the Perl module output. # The default value is: NO. @@ -2364,20 +2440,20 @@ PERLMOD_MAKEVAR_PREFIX = # Configuration options related to the preprocessor #--------------------------------------------------------------------------- -# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all +# If the ENABLE_PREPROCESSING tag is set to YES, Doxygen will evaluate all # C-preprocessor directives found in the sources and include files. # The default value is: YES. ENABLE_PREPROCESSING = YES -# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names +# If the MACRO_EXPANSION tag is set to YES, Doxygen will expand all macro names # in the source code. If set to NO, only conditional compilation will be # performed. Macro expansion can be done in a controlled way by setting # EXPAND_ONLY_PREDEF to YES. # The default value is: NO. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -MACRO_EXPANSION = NO +MACRO_EXPANSION = YES # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then # the macro expansion is limited to the macros specified with the PREDEFINED and @@ -2385,7 +2461,7 @@ MACRO_EXPANSION = NO # The default value is: NO. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -EXPAND_ONLY_PREDEF = NO +EXPAND_ONLY_PREDEF = YES # If the SEARCH_INCLUDES tag is set to YES, the include files in the # INCLUDE_PATH will be searched if a #include is found. @@ -2427,9 +2503,26 @@ PREDEFINED = # definition found in the source code. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -EXPAND_AS_DEFINED = - -# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will +# Codac: the tube and trajectory operations declare their functions with a macro +# standing in for the body (see codac2_SlicedTube_operations.h and +# codac2_SampledTraj_operations.h). Without expansion Doxygen documents the macro +# call instead of the function, and the Python bindings then reference docstring +# identifiers that no longer exist. Keep this list in sync with those headers. + +EXPAND_AS_DEFINED = macro_unary_tube \ + macro_binary_tube_tube \ + macro_binary_real_tube \ + macro_binary_tube_real \ + macro_member_binary_tube_tube \ + macro_member_binary_tube_real \ + macro_unary_traj \ + macro_binary_traj_traj \ + macro_binary_real_traj \ + macro_binary_traj_real \ + macro_member_binary_traj_traj \ + macro_member_binary_traj_real + +# If the SKIP_FUNCTION_MACROS tag is set to YES then Doxygen's preprocessor will # remove all references to function-like macros that are alone on a line, have # an all uppercase name, and do not end with a semicolon. Such function macros # are typically used for boiler-plate code, and will confuse the parser if not @@ -2453,12 +2546,12 @@ SKIP_FUNCTION_MACROS = YES # section "Linking to external documentation" for more information about the use # of tag files. # Note: Each tag file must have a unique name (where the name does NOT include -# the path). If a tag file is not located in the directory in which doxygen is +# the path). If a tag file is not located in the directory in which Doxygen is # run, you must also specify the path to the tagfile here. TAGFILES = -# When a file name is specified after GENERATE_TAGFILE, doxygen will create a +# When a file name is specified after GENERATE_TAGFILE, Doxygen will create a # tag file that is based on the input files it reads. See section "Linking to # external documentation" for more information about the usage of tag files. @@ -2495,7 +2588,7 @@ EXTERNAL_PAGES = YES HIDE_UNDOC_RELATIONS = YES -# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# If you set the HAVE_DOT tag to YES then Doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz (see: # https://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent # Bell Labs. The other options in this section have no effect if this option is @@ -2504,19 +2597,32 @@ HIDE_UNDOC_RELATIONS = YES HAVE_DOT = NO -# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed -# to run in parallel. When set to 0 doxygen will base this on the number of +# The DOT_NUM_THREADS specifies the number of dot invocations Doxygen is allowed +# to run in parallel. When set to 0 Doxygen will base this on the number of # processors available in the system. You can set it explicitly to a value # larger than 0 to get control over the balance between CPU load and processing # speed. -# Minimum value: 0, maximum value: 32, default value: 0. +# Minimum value: 0, maximum value: 512, default value: 0. # This tag requires that the tag HAVE_DOT is set to YES. DOT_NUM_THREADS = 0 +# The DOT_BATCH_SIZE specifies the number of dot graphs Doxygen is allowed to +# compile in a single invocation of dot. When set to 1 Doxygen will invoke dot +# for each graph separately, which can cause significant process creation +# overhead especially on systems with many CPU cores. Together with +# DOT_NUM_THREADS this setting can be used to optimise the dot processing speed +# for a particular system. Doxygen will try to give each thread a balanced batch +# of work. If the total number of graphs to process exceeds DOT_NUM_THREADS * +# DOT_BATCH_SIZE then additional batches will be created for dot to process. +# Minimum value: 1, maximum value: 1000, default value: 50. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_BATCH_SIZE = 50 + # DOT_COMMON_ATTR is common attributes for nodes, edges and labels of # subgraphs. When you want a differently looking font in the dot files that -# doxygen generates you can specify fontname, fontcolor and fontsize attributes. +# Doxygen generates you can specify fontname, fontcolor and fontsize attributes. # For details please see Node, # Edge and Graph Attributes specification You need to make sure dot is able # to find the font, which can be done by putting it in a standard location or by @@ -2550,7 +2656,7 @@ DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4" DOT_FONTPATH = -# If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then doxygen will +# If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then Doxygen will # generate a graph for each documented class showing the direct and indirect # inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and # HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case @@ -2567,7 +2673,7 @@ DOT_FONTPATH = CLASS_GRAPH = YES -# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a +# If the COLLABORATION_GRAPH tag is set to YES then Doxygen will generate a # graph for each documented class showing the direct and indirect implementation # dependencies (inheritance, containment, and class references variables) of the # class with other documented classes. Explicit enabling a collaboration graph, @@ -2579,7 +2685,7 @@ CLASS_GRAPH = YES COLLABORATION_GRAPH = YES -# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for +# If the GROUP_GRAPHS tag is set to YES then Doxygen will generate a graph for # groups, showing the direct groups dependencies. Explicit enabling a group # dependency graph, when GROUP_GRAPHS is set to NO, can be accomplished by means # of the command \groupgraph. Disabling a directory graph can be accomplished by @@ -2590,7 +2696,7 @@ COLLABORATION_GRAPH = YES GROUP_GRAPHS = YES -# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and +# If the UML_LOOK tag is set to YES, Doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. # The default value is: NO. @@ -2611,10 +2717,19 @@ UML_LOOK = NO UML_LIMIT_NUM_FIELDS = 10 -# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and +# If the UML_LOOK tag is enabled, field labels are shown along the edge between +# two class nodes. If there are many fields and many nodes the graph may become +# too cluttered. The UML_MAX_EDGE_LABELS threshold limits the number of items to +# make the size more manageable. Set this to 0 for no limit. +# Minimum value: 0, maximum value: 100, default value: 10. +# This tag requires that the tag UML_LOOK is set to YES. + +UML_MAX_EDGE_LABELS = 10 + +# If the DOT_UML_DETAILS tag is set to NO, Doxygen will show attributes and # methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS -# tag is set to YES, doxygen will add type and arguments for attributes and -# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen +# tag is set to YES, Doxygen will add type and arguments for attributes and +# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, Doxygen # will not generate fields with class member information in the UML graphs. The # class diagrams will look similar to the default class diagrams but using UML # notation for the relationships. @@ -2642,7 +2757,7 @@ DOT_WRAP_THRESHOLD = 17 TEMPLATE_RELATIONS = NO # If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to -# YES then doxygen will generate a graph for each documented file showing the +# YES then Doxygen will generate a graph for each documented file showing the # direct and indirect include dependencies of the file with other documented # files. Explicit enabling an include graph, when INCLUDE_GRAPH is is set to NO, # can be accomplished by means of the command \includegraph. Disabling an @@ -2653,7 +2768,7 @@ TEMPLATE_RELATIONS = NO INCLUDE_GRAPH = YES # If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are -# set to YES then doxygen will generate a graph for each documented file showing +# set to YES then Doxygen will generate a graph for each documented file showing # the direct and indirect include dependencies of the file with other documented # files. Explicit enabling an included by graph, when INCLUDED_BY_GRAPH is set # to NO, can be accomplished by means of the command \includedbygraph. Disabling @@ -2664,7 +2779,7 @@ INCLUDE_GRAPH = YES INCLUDED_BY_GRAPH = YES -# If the CALL_GRAPH tag is set to YES then doxygen will generate a call +# If the CALL_GRAPH tag is set to YES then Doxygen will generate a call # dependency graph for every global function or class method. # # Note that enabling this option will significantly increase the time of a run. @@ -2676,7 +2791,7 @@ INCLUDED_BY_GRAPH = YES CALL_GRAPH = NO -# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller +# If the CALLER_GRAPH tag is set to YES then Doxygen will generate a caller # dependency graph for every global function or class method. # # Note that enabling this option will significantly increase the time of a run. @@ -2688,14 +2803,14 @@ CALL_GRAPH = NO CALLER_GRAPH = NO -# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical +# If the GRAPHICAL_HIERARCHY tag is set to YES then Doxygen will graphical # hierarchy of all classes instead of a textual one. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. GRAPHICAL_HIERARCHY = YES -# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the +# If the DIRECTORY_GRAPH tag is set to YES then Doxygen will show the # dependencies a directory has on other directories in a graphical way. The # dependency relations are determined by the #include relations between the # files in the directories. Explicit enabling a directory graph, when @@ -2718,24 +2833,29 @@ DIR_GRAPH_MAX_DEPTH = 1 # generated by dot. For an explanation of the image formats see the section # output formats in the documentation of the dot tool (Graphviz (see: # https://www.graphviz.org/)). -# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order -# to make the SVG files visible in IE 9+ (other browsers do not have this -# requirement). +# +# Note the formats svg:cairo and svg:cairo:cairo cannot be used in combination +# with INTERACTIVE_SVG (the INTERACTIVE_SVG will be set to NO). # Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo, -# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and -# png:gdiplus:gdiplus. +# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus, +# png:gdiplus:gdiplus, svg:cairo, svg:cairo:cairo, svg:svg, svg:svg:core, +# gif:cairo, gif:cairo:gd, gif:cairo:gdiplus, gif:gdiplus, gif:gdiplus:gdiplus, +# gif:gd, gif:gd:gd, jpg:cairo, jpg:cairo:gd, jpg:cairo:gdiplus, jpg:gd, +# jpg:gd:gd, jpg:gdiplus and jpg:gdiplus:gdiplus. # The default value is: png. # This tag requires that the tag HAVE_DOT is set to YES. DOT_IMAGE_FORMAT = png -# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to -# enable generation of interactive SVG images that allow zooming and panning. +# If DOT_IMAGE_FORMAT is set to svg or svg:svg or svg:svg:core, then this option +# can be set to YES to enable generation of interactive SVG images that allow +# zooming and panning. # # Note that this requires a modern browser other than Internet Explorer. Tested # and working are Firefox, Chrome, Safari, and Opera. -# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make -# the SVG files visible. Older versions of IE do not have SVG support. +# +# Note This option will be automatically disabled when DOT_IMAGE_FORMAT is set +# to svg:cairo or svg:cairo:cairo. # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2754,7 +2874,7 @@ DOT_PATH = DOTFILE_DIRS = -# You can include diagrams made with dia in doxygen documentation. Doxygen will +# You can include diagrams made with dia in Doxygen documentation. Doxygen will # then run dia to produce the diagram and insert it in the documentation. The # DIA_PATH tag allows you to specify the directory where the dia binary resides. # If left empty dia is assumed to be found in the default search path. @@ -2767,7 +2887,7 @@ DIA_PATH = DIAFILE_DIRS = -# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the +# When using PlantUML, the PLANTUML_JAR_PATH tag should be used to specify the # path where java can find the plantuml.jar file or to the filename of jar file # to be used. If left blank, it is assumed PlantUML is not used or called during # a preprocessing step. Doxygen will generate a warning when it encounters a @@ -2775,19 +2895,77 @@ DIAFILE_DIRS = PLANTUML_JAR_PATH = -# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a -# configuration file for plantuml. +# When using PlantUML, the PLANTUML_CFG_FILE tag can be used to specify a +# configuration file for PlantUML. PLANTUML_CFG_FILE = -# When using plantuml, the specified paths are searched for files specified by -# the !include statement in a plantuml block. +# When using PlantUML, the specified paths are searched for files specified by +# the !include statement in a PlantUML block. PLANTUML_INCLUDE_PATH = +# The PLANTUMLFILE_DIRS tag can be used to specify one or more directories that +# contain PlantUml files that are included in the documentation (see the +# \plantumlfile command). + +PLANTUMLFILE_DIRS = + +# When using Mermaid diagrams with CLI rendering, the MERMAID_PATH tag should be +# used to specify the directory where the mmdc (Mermaid CLI) executable can be +# found. If left blank, CLI-based rendering is disabled. For HTML output, +# client-side rendering via JavaScript is used by default and does not require +# mmdc. For LaTeX/PDF output, mmdc is required to pre-generate images. Doxygen +# will generate a warning when CLI rendering is needed but mmdc is not +# available. + +MERMAID_PATH = + +# When using Mermaid diagrams, the MERMAID_CONFIG_FILE tag can be used to +# specify a JSON configuration file for the Mermaid CLI tool (mmdc). This file +# can contain theme settings and other Mermaid configuration options. + +MERMAID_CONFIG_FILE = + +# The MERMAID_RENDER_MODE tag selects how Mermaid diagrams are rendered. +# Possible values are: AUTO (use client-side rendering for HTML and mmdc for +# LaTeX/PDF and other formats. If MERMAID_PATH is not set, non-HTML diagrams +# will produce a warning), CLI (use the mmdc tool to pre-generate images +# (requires Node.js and mermaid-js/mermaid-cli). Works for all output formats) +# and CLIENT_SIDE (embed mermaid.js in HTML output for client-side rendering. +# Does not require mmdc but only works for HTML output). +# The default value is: AUTO. + +MERMAID_RENDER_MODE = AUTO + +# The MERMAID_JS_URL tag specifies the URL to load mermaid.js from when using +# client-side rendering (MERMAID_RENDER_MODE is CLIENT_SIDE or AUTO). The +# default points to the latest Mermaid v11 release on the jsDelivr CDN. +# +# The default CDN URL requires internet access when viewing the generated +# documentation. For offline use, download mermaid.esm.min.mjs and set this to a +# relative path, or use MERMAID_RENDER_MODE=CLI to pre-generate images instead. +# Examples: +# - Latest v11 (default): +# 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs' +# - Pinned version: +# 'https://cdn.jsdelivr.net/npm/mermaid@11.3.0/dist/mermaid.esm.min.mjs' +# - Local copy: './mermaid.esm.min.mjs' (user must place file in HTML output +# directory) +# The default value is: +# https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs. + +MERMAID_JS_URL = https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs + +# The MERMAIDFILE_DIRS tag can be used to specify one or more directories that +# contain Mermaid files that are included in the documentation (see the +# \mermaidfile command). + +MERMAIDFILE_DIRS = + # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes # that will be shown in the graph. If the number of nodes in a graph becomes -# larger than this value, doxygen will truncate the graph, which is visualized +# larger than this value, Doxygen will truncate the graph, which is visualized # by representing a node as a red box. Note that if the number of direct # children of the root node in a graph is already larger than # DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that @@ -2809,26 +2987,17 @@ DOT_GRAPH_MAX_NODES = 100 MAX_DOT_GRAPH_DEPTH = 0 -# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output -# files in one run (i.e. multiple -o and -T options on the command line). This -# makes dot run faster, but since only newer versions of dot (>1.8.10) support -# this, this feature is disabled by default. -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_MULTI_TARGETS = NO - -# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page +# If the GENERATE_LEGEND tag is set to YES Doxygen will generate a legend page # explaining the meaning of the various boxes and arrows in the dot generated # graphs. -# Note: This tag requires that UML_LOOK isn't set, i.e. the doxygen internal +# Note: This tag requires that UML_LOOK isn't set, i.e. the Doxygen internal # graphical representation for inheritance and collaboration diagrams is used. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. GENERATE_LEGEND = YES -# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate +# If the DOT_CLEANUP tag is set to YES, Doxygen will remove the intermediate # files that are used to generate the various graphs. # # Note: This setting is not only used for dot files but also for msc temporary @@ -2837,11 +3006,11 @@ GENERATE_LEGEND = YES DOT_CLEANUP = YES -# You can define message sequence charts within doxygen comments using the \msc -# command. If the MSCGEN_TOOL tag is left empty (the default), then doxygen will +# You can define message sequence charts within Doxygen comments using the \msc +# command. If the MSCGEN_TOOL tag is left empty (the default), then Doxygen will # use a built-in version of mscgen tool to produce the charts. Alternatively, # the MSCGEN_TOOL tag can also specify the name an external tool. For instance, -# specifying prog as the value, doxygen will call the tool as prog -T +# specifying prog as the value, Doxygen will call the tool as prog -T # -o . The external tool should support # output file formats "png", "eps", "svg", and "ismap". diff --git a/doc/manual/conf.py.in b/doc/manual/conf.py.in index 8c92a6019..b91a492a5 100644 --- a/doc/manual/conf.py.in +++ b/doc/manual/conf.py.in @@ -108,4 +108,9 @@ breathe_projects = { togglebutton_hint = "Reveal the solution." togglebutton_hint_hide = "Hide the solution." -html_extra_path = ['extra_html'] \ No newline at end of file +# The generated Doxygen API HTML is copied directly into outdir/api by a +# POST_BUILD step in doc/CMakeLists.txt (after sphinx-build runs), rather +# than staged through html_extra_path: since outdir is a distinct directory +# from confdir, there is no ordering constraint forcing it to exist beforehand, +# so a direct copy avoids maintaining (and duplicating on disk) an +# intermediate staging copy that only html_extra_path would ever read. diff --git a/doc/manual/development/api_redirect.rst b/doc/manual/development/api_redirect.rst index 4effec051..aa27dc980 100644 --- a/doc/manual/development/api_redirect.rst +++ b/doc/manual/development/api_redirect.rst @@ -5,4 +5,4 @@ C++ API .. raw:: html - + diff --git a/doc/manual/development/info_dev.rst b/doc/manual/development/info_dev.rst index e99bf5a24..18bccd9c9 100644 --- a/doc/manual/development/info_dev.rst +++ b/doc/manual/development/info_dev.rst @@ -12,8 +12,28 @@ To build this manual using Sphinx, follow these steps: .. code-block:: bash - sudo apt-get install sphinx-common graphviz - pip install sphinx sphinx_rtd_theme furo sphinx-math-dollar sphinx_tabs breathe sphinx_togglebutton + sudo apt-get install graphviz + pip install sphinx breathe furo sphinx-rtd-theme sphinx-math-dollar sphinx-tabs sphinx-togglebutton + + That list is where the ``extensions`` declared in ``doc/manual/conf.py.in`` + come from -- ``breathe``, ``sphinx_math_dollar``, ``sphinx_tabs``, + ``sphinx_togglebutton`` and ``sphinx_rtd_theme``, the sixth one, + ``sphinx.ext.mathjax``, shipping with Sphinx itself -- plus ``furo``, which is + the theme the manual is actually rendered with (``html_theme``). Adding an + extension to ``conf.py.in`` means adding its package here as well. Sphinx + itself comes from ``pip`` rather than from the distribution's + ``sphinx-common`` package, so that Sphinx and its extensions are installed by + the same tool and cannot end up in two incompatible versions. + + Building the manual asks for a more recent Python than building the binding + does. The current Sphinx requires **Python 3.12 or newer**, and the current + ``sphinx-math-dollar``, ``sphinx-tabs`` and ``breathe`` require 3.11, 3.10 and + 3.9 respectively. Nothing fails on an older interpreter, and that is precisely + what makes it worth knowing: ``pip`` quietly falls back to whatever release + still supports it, so on Python 3.8 the command line above installs Sphinx + 7.1.2 and ``furo`` 2024.8.6 rather than the current ones. When the manual + renders differently on two machines, comparing ``pip list`` is the first thing + to do. 2. **Building the manual**: After configuring the CMake project, the manual can be build with: @@ -40,17 +60,17 @@ If you simply want to use the latest Codac release in Python, you can download t .. code-block:: bash - sudo apt-get install -y g++ gcc cmake git flex bison + sudo apt-get install -y g++ gcc cmake git - a supported version of Python (>=3.8). - - a recent `Doxygen `_ version (for instance, release 1.16.1 or newest). On Linux systems, latest releases are not available as Debian packages, so we advise to install Doxygen from the sources: + - `Doxygen `_. **1.17.0 is the recommended version, and 1.16.1 the minimum.** Avoid 1.18.0: it segfaults while parsing Codac's headers on macOS with an Intel processor, which is why ``.github/workflows/macosmatrix.yml`` pins 1.17.0 on those runners. On Linux systems, recent releases are not available as Debian packages, so we advise to install Doxygen from the sources: .. code-block:: bash cd $HOME git clone https://github.com/doxygen/doxygen cd doxygen - git checkout Release_1_16_1 # 1.16.1, or any newer release + git checkout Release_1_17_0 # 1.16.1 is the oldest that works; 1.18.0 is known to break on MacOS intel mkdir build ; cd build cmake -DCMAKE_INSTALL_PREFIX=$HOME/doxygen/build_install .. make ; make install @@ -59,25 +79,10 @@ If you simply want to use the latest Codac release in Python, you can download t Doxygen software extracts C++ documentation from header files into XML format. We then convert this data into docstring format before embedding it into the binding binaries. In this way, the writing of the documentation is centralized in a single location in the C++ header files. + This is also why the Doxygen version is worth pinning rather than taking whatever is at hand. The names of the generated docstring macros are derived from the text Doxygen produces, so a wording change between two releases renames them, and the binding sources that spell those names out then stop compiling. ``scripts/doxygen/doxygen2docstring.py`` normalizes that text rather than trusting it, which absorbs small differences; staying within the recommended range is what covers the rest. -2. **Configure IBEX prior to compiling Codac**: - - We recall that IBEX sources can be obtained with: - - .. code-block:: bash - - git clone https://github.com/lebarsfa/ibex-lib.git $HOME/ibex-lib - cd $HOME/ibex-lib - You will need to compile both IBEX and Codac using the ``-fPIC`` options. This can be done with the following CMake configuration: - - .. code-block:: bash - - mkdir build ; cd build - cmake -DCMAKE_CXX_FLAGS="-fPIC" -DCMAKE_C_FLAGS="-fPIC" -DCMAKE_INSTALL_PREFIX=$HOME/ibex-lib/build_install -DCMAKE_BUILD_TYPE=Release .. - make ; make install - -3. **Compile Codac with Python binding**: +2. **Compile Codac with Python binding**: We recall that Codac sources can be obtained with: @@ -86,40 +91,35 @@ If you simply want to use the latest Codac release in Python, you can download t git clone https://github.com/codac-team/codac $HOME/codac cd $HOME/codac - .. admonition:: Using Codac v2 simultaneously with Codac v1 - In case you want to use the two versions of Codac in the same Python script, you will have to compile the binaries of Codac v2 under a different name in order to avoid ``import`` conflicts. Things are already prepared in the branch ``codac2_renamed``, you can therefore: - - .. code-block:: bash - - git checkout codac2_renamed - - Note that you will then have to ``import codac2`` instead of ``import codac`` in your Python scripts. - - In addition to the ``-fPIC`` options, you will have to configure ``WITH_PYTHON=ON`` and ``PYBIND11_FINDPYTHON=OFF``. Note that CMake will automatically get the `pybind11 `_ files required for the binding. Also, you will have to configure ``BUILD_TESTS=ON`` if you want to run the unit tests. + You will need to configure ``WITH_PYTHON=ON`` and ``PYBIND11_FINDPYTHON=OFF``. Codac is then compiled as position independent code (``-fPIC``) on its own, as is a GAOL built by CMake along with Codac; a GAOL installed on your system has to have been compiled that way as well. Note that CMake will automatically get the `pybind11 `_ files required for the binding. Also, you will have to configure ``BUILD_TESTS=ON`` if you want to run the unit tests. .. code-block:: bash mkdir build ; cd build - cmake -DCMAKE_CXX_FLAGS="-fPIC" -DCMAKE_C_FLAGS="-fPIC" -DWITH_PYTHON=ON -DPYBIND11_FINDPYTHON=OFF -DBUILD_TESTS=ON -DCMAKE_INSTALL_PREFIX=$HOME/codac/build_install -DCMAKE_PREFIX_PATH="$HOME/ibex-lib/build_install;$HOME/doxygen/build_install" -DCMAKE_BUILD_TYPE=Release .. + cmake -DWITH_PYTHON=ON -DPYBIND11_FINDPYTHON=OFF -DBUILD_TESTS=ON -DCMAKE_INSTALL_PREFIX=$HOME/codac/build_install -DCMAKE_PREFIX_PATH="$HOME/doxygen/build_install" -DCMAKE_BUILD_TYPE=Release .. make ; make install -4. **Configure your Python environment**: +3. **Configure your Python environment**: Finally, you need to configure your system so that Python can find access to your Codac binding binaries: .. code-block:: bash cd $HOME/codac/build/python/python_package - python setup.py develop --user + pip install --user -e . + + (Earlier versions of this page used ``python setup.py develop --user``. That + command has been removed from setuptools; ``pip install -e`` is its + replacement and does the same thing.) - And update your configuration file (for instance, ``$HOME/.bashrc`` on Linux systems) with: + Alternatively, or in addition, update your configuration file (for instance, ``$HOME/.bashrc`` on Linux systems) with: .. code-block:: bash export PYTHONPATH="${PYTHONPATH}:$HOME/codac/build/python/python_package/" -5. **Verify the installation** (optional): +4. **Verify the installation** (optional): To ensure that the installation has worked properly, the unit tests of the library can be run: @@ -127,7 +127,7 @@ If you simply want to use the latest Codac release in Python, you can download t python -m unittest discover codac.tests -6. **Try an example** (optional): +5. **Try an example** (optional): You may want to try Codac in Python by running one of the proposed examples. After the installation, you can run the following commands: @@ -140,6 +140,258 @@ If you simply want to use the latest Codac release in Python, you can download t You should obtain a graphical output corresponding to a set inversion. +.. _sec-dev-info-tests: + +Running the tests +----------------- + +The test suite is built when the project is configured with ``-D BUILD_TESTS=ON`` +and run through `CTest `_ +from the build directory: + +.. code-block:: bash + + cd $HOME/codac/build + ctest --output-on-failure + +Two ``make`` targets wrap it, and they are not interchangeable: + +.. code-block:: bash + + make test # what CMake provides: runs ctest on the build tree as it stands + make check # rebuilds the suite first, and prints the output of what failed + +``make check`` is the one to use after editing a source file. ``make test`` runs +whatever executables are already there, so it happily reports a pass on the +previous build of a test you have just changed. Both accept ``ARGS`` to forward +options to ctest, with the Makefile generator: + +.. code-block:: bash + + make check ARGS="-R IntervalVector" + +Every entry of the suite exists in up to two flavours, one per language: a C++ +test, and -- when the build has ``WITH_PYTHON=ON`` and a Python file of the same +name sits next to the C++ one -- its Python counterpart. The CTest name of each +is built from the path of the source under ``tests/``, with the slashes turned +into underscores and ``_cpp`` or ``_py`` appended, so +``tests/core/domains/interval/codac2_tests_IntervalVector.cpp`` becomes +``codac2_tests_core_domains_interval_IntervalVector_cpp``. ``-R`` takes a +regular expression matched anywhere in that name, which is what makes the long +names painless: + +.. code-block:: bash + + ctest -N # list the tests without running them + ctest -R IntervalVector # both flavours of one test + ctest -R "_py" # the whole Python half of the suite + ctest -R "_cpp" -j 8 # the C++ half, eight at a time + ctest -V -R IntervalVector_cpp # verbose: everything the test prints + +``ctest -V`` matters more than it looks, for the reason given in the next +section: a sanitizer can report a problem without failing the test, and without +``-V`` CTest discards the output of every test that passes. + +.. _sec-dev-info-adding-a-test: + +Adding a test +------------- + +1. Write the test as ``tests//codac2_tests_.cpp``, using + `Catch2 `_ (version 3), next to the + existing tests of the same part of the library. + +2. **Register it**, by adding its path -- relative to ``tests/``, without the + ``.cpp`` extension -- to the ``SRC_TESTS`` list of ``tests/CMakeLists.txt``: + + .. code-block:: cmake + + list(APPEND SRC_TESTS + + # ---------------------------------------------------------------- + # Domains + # ---------------------------------------------------------------- + + core/domains/interval/codac2_tests_IntervalVector + core/domains/interval/codac2_tests_MyNewTest # <-- the new one + ... + + This step is not optional, and nothing will remind you of it: a test file that + is not listed there is never compiled and never run. The suite stays green + while the new test does nothing at all, which is the one failure mode worth + guarding against by hand. + +3. Optionally, write the Python counterpart as + ``tests//codac2_tests_.py`` -- same name, same directory. It needs + no registration: it is picked up automatically and added as + ``codac2_tests__py`` whenever the build has ``WITH_PYTHON=ON``. + +4. Re-run CMake, and check that what you expect is actually there: + + .. code-block:: bash + + cmake .. && ctest -N -R MyNewTest + +.. _sec-dev-info-debug: + +Debug builds: AddressSanitizer and UndefinedBehaviorSanitizer +------------------------------------------------------------- + +Configuring with ``-D CMAKE_BUILD_TYPE=Debug`` does more than turn the +optimizations off: the top-level ``CMakeLists.txt`` also compiles and links +everything with ``-fsanitize=address,undefined``. A Debug build therefore reports +out-of-bounds accesses, use-after-free, memory leaks and undefined behaviour as it +runs, and running the test suite in that configuration is the cheapest way to find +them. + +.. code-block:: bash + + mkdir build_debug ; cd build_debug + cmake -D CMAKE_BUILD_TYPE=Debug -D BUILD_TESTS=ON -D TEST_EXAMPLES=ON .. + make + ctest -V --output-on-failure + +Two things are worth knowing about it. + +- The sanitizers are enabled only if the compiler in use actually ships their + runtime. CMake probes for it and says which way it went, at configure time: + either ``ASan/UBSan will be enabled for Debug-configuration builds.`` or ``No + ASan/UBSan runtime found ...: Debug-configuration builds will not be + sanitized.`` Falling back to an unsanitized Debug build is deliberate -- it + beats a configuration that fails to link on every target -- but it does mean + that line is worth reading before concluding that a build found nothing. + +- ASan and UBSan do not agree on what a diagnostic costs. A buffer overflow or a + leak aborts the process and fails the test; a UBSan ``runtime error:`` only + prints and lets the run continue. A test can therefore pass having reported + dozens of undefined behaviours, and ``ctest -V`` is what makes them visible. + +This is the configuration ``.github/workflows/unixdebug.yml`` and +``.github/workflows/macdebug.yml`` run on every push, across four Linux and four +macOS jobs (GCC and Clang, x86_64 and arm64). + +.. _sec-dev-info-examples: + +Building and running the examples +--------------------------------- + +``-D TEST_EXAMPLES=ON``, together with ``BUILD_TESTS=ON``, compiles every example +of ``examples/`` against the library being built and registers each one as a CTest +integration test: + +.. code-block:: bash + + cmake -D BUILD_TESTS=ON -D TEST_EXAMPLES=ON -D WITH_PYTHON=ON .. + make + ctest -R codac2_examples + +The names follow the same rule as the tests, from the path under ``examples/``: +``examples/03_sivia/main.cpp`` becomes ``codac2_examples_03_sivia_main_cpp``, and +its Python counterpart ``..._py``. Examples that need an optional dependency -- +CAPD, or SymPy -- are registered only when it is available. + +They are worth running for more than the fact that they still produce a result. +The examples are what a user compiles against the *installed* headers, and only +the public headers are made visible to them here, so an example that stops +compiling in this build is an example a user could no longer build either. + +.. _sec-dev-info-coverage: + +Measuring the code coverage +--------------------------- + +Locally: ``make coverage`` +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Configuring with ``-D WITH_COVERAGE=ON`` instruments the build and adds two +targets. It needs `gcovr `_ (``pip install gcovr``) and a +GCC or Clang toolchain -- MSVC provides no ``gcov`` and the option refuses to +configure there. + +.. code-block:: bash + + mkdir build_coverage ; cd build_coverage + cmake -D CMAKE_BUILD_TYPE=Release \ + -D WITH_COVERAGE=ON \ + -D BUILD_TESTS=ON -D TEST_EXAMPLES=ON -D WITH_PYTHON=ON .. + make + make coverage + +``make coverage`` runs the whole CTest suite and turns the counters ``gcov`` +leaves behind into a report: a summary printed on the console, ``coverage.html`` +to open in a browser (with one page per source file next to it), and +``coverage.xml`` in the Cobertura format that most editors and code-review tools +read. + +``make coverage-report`` is the second half on its own. It re-reads whatever the +last ``ctest`` invocation happened to touch, without running anything, which is +what to use after running part of the suite by hand: + +.. code-block:: bash + + ctest -R codac2_tests_core_domains_interval + make coverage-report + +Turning ``BUILD_TESTS`` and ``TEST_EXAMPLES`` on is what makes the figure mean +anything -- coverage answers "what do the tests reach", and the examples are +integration tests that reach code the unit tests do not. + +Everything else the measurement needs is decided by the top-level +``CMakeLists.txt`` rather than left to the caller: the instrumentation flags, the +``gcov`` matching the compiler (Clang's counters are read by ``llvm-cov gcov``, +not by GCC's ``gcov``, and pointing ``gcovr`` at the wrong one yields an empty +report rather than an error), the exclusion of the dependencies CMake fetches into +the build tree, and ``CMAKE_INTERPROCEDURAL_OPTIMIZATION=OFF`` -- which is not +optional, pybind11 adding ``-flto`` to the modules it builds and GCC being unable +to combine that with ``gcov`` instrumentation, the link then failing on an +undefined vtable. + +From GitHub Actions +^^^^^^^^^^^^^^^^^^^ + +Every push runs the same measurement in CI, in the job named **Ubuntu 26.04 GCC +x86_64 Release coverage** of ``.github/workflows/unixdebug.yml``. It configures +with ``-D WITH_COVERAGE=ON``, runs the whole suite -- the C++ and Python unit +tests, plus every example -- and calls the very same ``coverage-report`` target, +so what CI measures and what ``make coverage`` measures cannot drift apart. The +summary appears in the job log, and the report is uploaded as an artifact named +``coverage-report``, kept for 14 days. + +1. **Download the report**: with the `GitHub CLI `_, + from a clone of the repository: + + .. code-block:: bash + + # the latest run of the workflow on the current branch + gh run list --workflow unixdebug.yml --limit 1 + gh run download -n coverage-report -D coverage + + The directory then holds ``coverage.xml`` and the ``coverage.*.html`` files, + ``coverage.html`` being the index to open in a browser. + +2. **Read the summary without downloading anything**: the three totals are printed + at the end of the *Coverage report* step of the job, and can be pulled from the + log directly: + + .. code-block:: bash + + gh run view --log | grep -E "^(lines|functions|branches):" + +.. admonition:: Reading the figures + + Two things make the raw percentages narrower than they look. + + A header compiled into several translation units appears once per include + path in gcovr's output -- typically once under ``src/`` and once under + ``build/include/`` -- so summing those entries counts each of its lines twice. + A line reached through either path is covered, and the two entries have to be + merged line by line to get a per-file figure. + + And on heavily templated headers, gcov counts every instantiated line: the + denominators are inflated, and the percentages are best read as orders of + magnitude rather than to the decimal. + + .. admonition:: For admins Get all the *wheels* generated by GitHub Actions (the generated release needs to be explicitely set as latest): diff --git a/doc/manual/index.rst b/doc/manual/index.rst index 29fe3a84a..d8eed352c 100644 --- a/doc/manual/index.rst +++ b/doc/manual/index.rst @@ -344,7 +344,6 @@ User manual * :ref:`sec-extensions` * :ref:`sec-extensions-capd` * :ref:`sec-extensions-sympy` - * Interface with the IBEX library * Frequently Asked Questions @@ -352,7 +351,7 @@ User manual * Related papers * Contributors * How to cite Codac - :ref:`sec-ref-codac-logos` + * :ref:`sec-ref-codac-logos` How-to guides diff --git a/doc/manual/manual/contractors/analytic/index.rst b/doc/manual/manual/contractors/analytic/index.rst index d0e781ade..41657b81f 100644 --- a/doc/manual/manual/contractors/analytic/index.rst +++ b/doc/manual/manual/contractors/analytic/index.rst @@ -1,3 +1,5 @@ +:orphan: + Analytic contractors ==================== diff --git a/doc/manual/manual/contractors/dynamic/ctclohner.rst b/doc/manual/manual/contractors/dynamic/ctclohner.rst index 1999f1ee4..62bf2b9fb 100644 --- a/doc/manual/manual/contractors/dynamic/ctclohner.rst +++ b/doc/manual/manual/contractors/dynamic/ctclohner.rst @@ -144,5 +144,5 @@ Related content .. admonition:: Technical documentation - See the `C++ API documentation of this class <../../../extra_html/api/classcodac2_1_1_ctc_lohner.html>`_. + See the `C++ API documentation of this class <../../../api/classcodac2_1_1_ctc_lohner.html>`_. diff --git a/doc/manual/manual/contractors/dynamic/index.rst b/doc/manual/manual/contractors/dynamic/index.rst new file mode 100644 index 000000000..397c8c488 --- /dev/null +++ b/doc/manual/manual/contractors/dynamic/index.rst @@ -0,0 +1,12 @@ +:orphan: + +Dynamic contractors +=================== + +.. toctree:: + + ctclohner.rst + CtcDeriv + CtcEval + CtcPicard + CtcDiffInclusion diff --git a/doc/manual/manual/contractors/geometric/index.rst b/doc/manual/manual/contractors/geometric/index.rst index 938c40df6..7f4fc3741 100644 --- a/doc/manual/manual/contractors/geometric/index.rst +++ b/doc/manual/manual/contractors/geometric/index.rst @@ -1,3 +1,5 @@ +:orphan: + Geometric contractors ===================== diff --git a/doc/manual/manual/contractors/index.rst b/doc/manual/manual/contractors/index.rst index 9b2537821..b56093c8e 100644 --- a/doc/manual/manual/contractors/index.rst +++ b/doc/manual/manual/contractors/index.rst @@ -1,4 +1,5 @@ .. _sec-ctc: + Contractors, separators ======================= @@ -21,6 +22,7 @@ Contractors, separators .. analytic/index.rst .. geometric/index.rst .. shape/index.rst +.. dynamic/index.rst .. temporal/index.rst diff --git a/doc/manual/manual/contractors/set/index.rst b/doc/manual/manual/contractors/set/index.rst index 908dec14b..8522693c5 100644 --- a/doc/manual/manual/contractors/set/index.rst +++ b/doc/manual/manual/contractors/set/index.rst @@ -1,3 +1,5 @@ +:orphan: + Set contractors =============== diff --git a/doc/manual/manual/contractors/shape/index.rst b/doc/manual/manual/contractors/shape/index.rst index b2e377fdd..a23bba7f9 100644 --- a/doc/manual/manual/contractors/shape/index.rst +++ b/doc/manual/manual/contractors/shape/index.rst @@ -1,3 +1,5 @@ +:orphan: + Shape contractors ================= diff --git a/doc/manual/manual/ellipsoids/Ellipsoid_class.rst b/doc/manual/manual/ellipsoids/Ellipsoid_class.rst index 2f9748d7f..b593f3057 100644 --- a/doc/manual/manual/ellipsoids/Ellipsoid_class.rst +++ b/doc/manual/manual/ellipsoids/Ellipsoid_class.rst @@ -369,7 +369,42 @@ The function stability_analysis can compute a bassin of attraction for discrete .. code-tab:: py - # TODO in the code + # pendulum example + h4 = AnalyticFunction([x], vec(x[0] + 0.5 * x[1], x[1] + 0.5 * (-x[1]-sin(x[0])))) + e13 = Ellipsoid(Vector.zero(2), Matrix.zero(2, 2)) + e13_out = Ellipsoid(Vector.zero(2), Matrix.zero(2, 2)) + alpha_max = 1 + + if stability_analysis(h4, alpha_max, e13, e13_out) == BoolInterval.TRUE: + print('\nStability analysis: the system is stable') + print('Ellipsoidal domain of attraction e13 (red):') + print(e13) + print('Outter enclosure e13_out of the Image of e13 by h4 (green):') + print(e13_out) + else: + print('\nStability analysis: the method is not able to conclude') + + fig6 = Figure2D('Stability analysis - pendulum example', GraphicOutput.VIBES) + fig6.set_axes(axis(0, [-0.1, 0.1]), axis(1, [-0.1, 0.1])) + fig6.set_window_properties([1200, 600], [500, 500]) + fig6.draw_ellipsoid(e13, [Color.red(), Color.red(0.3)]) + fig6.draw_ellipsoid(e13_out, [Color.green(), Color.green(0.3)]) + + """ + Stability analysis: the system is stable + Ellipsoidal domain of attraction e13 (red): + Ellipsoid 2d: + mu=[ 0 ; 0 ] + G= + [[ 0.0530036 , -0.0162022 ] + [ -0.0162022 , 0.0599475 ]] + Outter enclosure e13_out of the Image of e13 by h4 (green): + Ellipsoid 2d: + mu=[ 0 ; 2.4895e-17 ] + G= + [[ 0.0449538 , 0.0137872 ] + [ -0.0346424 , 0.0381184 ]] + """ .. code-tab:: c++ diff --git a/doc/manual/manual/extensions/capd/capd.rst b/doc/manual/manual/extensions/capd/capd.rst index 590fe4c98..20569f5e3 100644 --- a/doc/manual/manual/extensions/capd/capd.rst +++ b/doc/manual/manual/extensions/capd/capd.rst @@ -20,7 +20,7 @@ To install the ``codac-capd`` extension, you need to install the Codac library f .. code-block:: bash - cmake -DCMAKE_INSTALL_PREFIX=$HOME/ibex-lib/build_install -DCMAKE_BUILD_TYPE=Release -DWITH_CAPD=ON .. + cmake -DCMAKE_INSTALL_PREFIX=$HOME/codac/build_install -DCMAKE_BUILD_TYPE=Release -DWITH_CAPD=ON .. We highly recommend to test the installation of the library with the provided tests. To do so, you can use the following command: @@ -61,7 +61,6 @@ Furthermore, you need to link the extension to your project, for instance by upd ${CODAC_LIBRARIES} ${CODAC_CAPD_LIBRARY} # linking to the codac-capd extension capd::capd # linking to CAPD - Ibex::ibex ) You can use the functions ``to_capd`` and ``to_codac`` to convert between CAPD and Codac objects as follows: diff --git a/doc/manual/manual/extensions/capd/src.cpp b/doc/manual/manual/extensions/capd/src.cpp index d1093334b..291b21f68 100644 --- a/doc/manual/manual/extensions/capd/src.cpp +++ b/doc/manual/manual/extensions/capd/src.cpp @@ -78,7 +78,7 @@ int main() // initial set // [codac-capd-5-beg] capd::IVector c(2); - c[0] = -M_PI/2.; + c[0] = -PI/2.; c[1] = 0.; // take some box around c c[0] += capd::Interval(-1,1)*1e-2; @@ -137,7 +137,7 @@ int main() OctaSym id_2d ({1,2}); OctaSym s ({-2,1}); - auto peibos_output_pend = PEIBOS(vectorField_pend, 20., 0.2, psi0_pend, {id_2d,s,s*s,s.invert()}, 0.02, {-M_PI/2.,0.}); + auto peibos_output_pend = PEIBOS(vectorField_pend, 20., 0.2, psi0_pend, {id_2d,s,s*s,s.invert()}, 0.02, {-PI/2.,0.}); auto m_v_par_2d_pend = reach_set(peibos_output_pend); // [peibos-capd-3-end] diff --git a/doc/manual/manual/extensions/sympy/index.rst b/doc/manual/manual/extensions/sympy/index.rst index c1e6b890c..e26be00f4 100644 --- a/doc/manual/manual/extensions/sympy/index.rst +++ b/doc/manual/manual/extensions/sympy/index.rst @@ -54,7 +54,6 @@ In C++ however, you need to link the extension to your project, for instance by ${CODAC_LIBRARIES} ${CODAC_SYMPY_LIBRARY} # linking to the codac-sympy extension pybind11::embed # linking to pybind11 - Ibex::ibex ) Finally, in order to include the features of the extension: diff --git a/doc/manual/manual/functions/analytic/index.rst b/doc/manual/manual/functions/analytic/index.rst index 82496722e..152e0f364 100644 --- a/doc/manual/manual/functions/analytic/index.rst +++ b/doc/manual/manual/functions/analytic/index.rst @@ -1,3 +1,5 @@ +:orphan: + .. _sec-functions-analytic: Analytic inclusion functions @@ -7,5 +9,6 @@ Analytic inclusion functions analytic_functions.rst analytic_operators.rst - Extension to custom expressions - Temporal functions \ No newline at end of file + +.. Extension to custom expressions +.. Temporal functions \ No newline at end of file diff --git a/doc/manual/manual/functions/index.rst b/doc/manual/manual/functions/index.rst index b70ce037d..cef76dfd8 100644 --- a/doc/manual/manual/functions/index.rst +++ b/doc/manual/manual/functions/index.rst @@ -9,4 +9,5 @@ Inclusion functions analytic/analytic_operators.rst parallelepiped/parallelepiped_eval.rst peibos/peibos.rst - .. Set-membership functions \ No newline at end of file + +.. Set-membership functions \ No newline at end of file diff --git a/doc/manual/manual/functions/parallelepiped/src.cpp b/doc/manual/manual/functions/parallelepiped/src.cpp index 983252d3e..6d3bbaff2 100644 --- a/doc/manual/manual/functions/parallelepiped/src.cpp +++ b/doc/manual/manual/functions/parallelepiped/src.cpp @@ -1,10 +1,23 @@ -#include +/** + * Codac tests + * ---------------------------------------------------------------------------- + * \date 2025 + * \author Maël Godard + * \copyright Copyright 2024 Codac Team + * \license GNU Lesser General Public License (LGPL) + */ + +#include +#include +#include +#include +#include +#include using namespace std; using namespace codac2; - -int main() +TEST_CASE("Parallelepiped evaluation - manual") { // 2D case @@ -72,8 +85,8 @@ int main() // [5-end] // [6-beg] - AnalyticTraj f_lb (AnalyticFunction({X_if},1.1*sqr(X_if)),Interval(-2.0,2.0)); - AnalyticTraj f_ub (AnalyticFunction({X_if},1.2*sqr(X_if)),Interval(-2.0,2.0)); + AnalyticTraj f_lb (Interval(-2.0,2.0),AnalyticFunction({X_if},1.1*sqr(X_if))); + AnalyticTraj f_ub (Interval(-2.0,2.0),AnalyticFunction({X_if},1.2*sqr(X_if))); DefaultFigure::plot_trajectory(f_lb.sampled(0.01)); DefaultFigure::plot_trajectory(f_ub.sampled(0.01)); @@ -88,8 +101,8 @@ int main() Interval X0_if(x0_if, x0_if+dx_if); auto p = f_if.parallelepiped_eval(X0_if); DefaultFigure::draw_parallelepiped(p, Color::dark_green()); - + x0_if+=dx_if; } // [7-end] -} \ No newline at end of file +} diff --git a/doc/manual/manual/functions/parallelepiped/src.py b/doc/manual/manual/functions/parallelepiped/src.py index 12bc442d4..66b473959 100644 --- a/doc/manual/manual/functions/parallelepiped/src.py +++ b/doc/manual/manual/functions/parallelepiped/src.py @@ -59,8 +59,8 @@ # [5-end] # [6-beg] -f_lb = AnalyticTraj(AnalyticFunction([X_if],1.1*sqr(X_if)),Interval(-2.0,2.0)) -f_ub = AnalyticTraj(AnalyticFunction([X_if],1.2*sqr(X_if)),Interval(-2.0,2.0)) +f_lb = AnalyticTraj(Interval(-2.0,2.0),AnalyticFunction([X_if],1.1*sqr(X_if))) +f_ub = AnalyticTraj(Interval(-2.0,2.0),AnalyticFunction([X_if],1.2*sqr(X_if))) DefaultFigure.plot_trajectory(f_lb.sampled(0.01)) DefaultFigure.plot_trajectory(f_ub.sampled(0.01)) diff --git a/doc/manual/manual/functions/peibos/src.cpp b/doc/manual/manual/functions/peibos/src.cpp index 118833a8e..ea9fdcf56 100644 --- a/doc/manual/manual/functions/peibos/src.cpp +++ b/doc/manual/manual/functions/peibos/src.cpp @@ -8,6 +8,8 @@ */ #include +#include +#include #include using namespace std; diff --git a/doc/manual/manual/installation/cpp.rst b/doc/manual/manual/installation/cpp.rst index d92dc75b9..b8a0e8546 100644 --- a/doc/manual/manual/installation/cpp.rst +++ b/doc/manual/manual/installation/cpp.rst @@ -39,7 +39,7 @@ Linux Installation .. .. code-block:: bash -.. sudo apt remove libcodac-dev libibex-dev +.. sudo apt remove libcodac-dev .. sudo rm -f /etc/apt/sources.list.d/ensta-bretagne.list .. sudo apt update @@ -74,63 +74,46 @@ Steps sudo apt-get install -y build-essential cmake git -2. **Install the IBEX dependency**: - - Codac still uses some features of the `IBEX library `_ that you have to install first (currently, the only thing Codac uses from IBEX is a wrapper of the `GAOL library `_). The last version of IBEX is maintained on `this unofficial development repository `_: - - .. code-block:: bash + .. admonition:: The Eigen dependency - # Requirements to compile IBEX - sudo apt-get install -y flex bison + | Codac is built on `Eigen `_ (version 3.4 or newer), but you do not have to install it: by default the CMake configuration downloads and builds the version Codac is tested against, so nothing has to be added to the command above. + | To build against an Eigen already installed on your system instead, configure Codac with ``-DENABLE_FIND_PACKAGE_EIGEN3=ON``; CMake then looks for it with ``find_package(Eigen3 3.4.0)``, and ``Eigen3_DIR`` or ``CMAKE_PREFIX_PATH`` can point at a custom installation path. This is what the ``choco install eigen`` of the Windows instructions below provides. Note that the Eigen headers are part of Codac's public interface, so a program using Codac compiles them too: this is why they are installed next to Codac's own headers, and why :ref:`the configuration of a user project ` lists them among its include directories. - # Download IBEX sources from GitHub - git clone -b master https://github.com/lebarsfa/ibex-lib.git $HOME/ibex-lib + .. admonition:: The GAOL dependency - # Configure IBEX before installation - cd $HOME/ibex-lib - mkdir build ; cd build - cmake -DCMAKE_INSTALL_PREFIX=$HOME/ibex-lib/build_install -DCMAKE_BUILD_TYPE=Release .. + | The intervals of Codac are built upon `GAOL `_, the interval arithmetic library written by `Frédéric Goualard `_, which computes its elementary functions with the IBM Accurate Portable Mathematical Library (mathlib). You do not have to install them either: CMake first looks for a GAOL installed on your system and, when it finds none, downloads GAOL from the master branch of `the fork of Jordan Ninin `_ while Codac is configured (brought up to date at each configuration when Git is installed), builds it with its CMake build, which downloads mathlib from `Frédéric Goualard's site `_, and installs both with its CMake installer, in the build directory and along with Codac. The fork adds to GAOL a CMake build, taken from the one of `IBEX `_ (which Codac used to depend on, and no longer does), the changes Codac depends on or which Visual Studio, MinGW and ARM processors need, and tests of the bounds it computes. Its README lists and explains them. + | If you install GAOL yourself, we recommend installing the version of `the fork of Jordan Ninin `_ rather than the original sources. The fork comes with the CMake installer, which builds and installs GAOL and mathlib together (``cmake -S . -B build -DCMAKE_INSTALL_PREFIX=``, ``cmake --build build --config Release``, then ``cmake --install build --config Release``), with the CMake package from which Codac takes the compilation flags and the libraries GAOL needs, while the original sources only have autotools and meson builds. Bugs of GAOL are also fixed there: bounds that did not enclose the exact results (numbers such as ``interval("0.1")`` read with the C runtime of Windows or with musl on 64-bit ARM processors; hyperbolic functions with the libm of glibc 2.31, musl or MinGW-w64; square roots with Visual C++ for 32-bit x86), and powers with a real exponent (``pow([4], 0.5)`` returned ``[1]``, ``pow([-4,-1], [0.5])`` returned ``[-1, 2]``). + | CMake looks for an installed GAOL in three ways, in this order: its CMake package (``gaolConfig.cmake``, which the CMake build of the fork installs), its ``gaol.pc`` through ``pkg-config`` (which the autotools and meson builds of the fork install), then its files (``gaol/gaol.h``, ``MathLib.h`` and the ``gaol`` and ``ultim`` libraries). Codac is compiled with the flags and linked with the libraries the package or ``gaol.pc`` of GAOL gives; only for a GAOL found by its files does Codac determine the flags itself. A ``gaol.pc`` whose flags lack ``-frounding-math``, as the one of the meson build of the original sources, is not used. To use a GAOL installed in a custom location, add its installation prefix to ``CMAKE_PREFIX_PATH``, which the three searches read, or give ``-Dgaol_DIR=/lib/cmake/gaol`` for its CMake package, ``PKG_CONFIG_PATH`` for its ``gaol.pc``, or ``-DGAOL_DIR=`` (and ``-DMATHLIB_DIR=`` for mathlib, if it is installed elsewhere) for its files. To build the GAOL Codac is tested against even where another one is installed, configure Codac with ``-DENABLE_FIND_PACKAGE_GAOL=OFF``. + | On a 32-bit x86 processor, Codac, GAOL and mathlib are compiled with ``-msse2 -mfpmath=sse``, except by Visual Studio, which computes in SSE2 already: computed on the x87 FPU, GAOL's bounds and mathlib's results are only right while its precision stays set to 53 bits, which nothing guarantees. A processor with SSE2 is therefore required there. GAOL is not built with the compilers that do not compute its intervals right, or much too slowly, and its build stops with a message naming the ones to use instead: Clang for 32-bit ARM processors (use GCC), the compilers that say they do not honour the rounding direction, such as Clang 14 for 64-bit ARM processors, and the mingw-w64 runtimes older than version 13 (those of the MinGW-w64 GCC 11 to 14 of Chocolatey, for instance): before version 12, their math library is not accurate enough, and the ``fesetround()`` of version 12 makes the elementary functions of GAOL some 20 times slower. - # Building + installing - make - make install - cd ../.. - - For further CMake options, please refer to the IBEX documentation. - - .. warning:: - - **GAOL prerequisite:** On some platforms, you might need to install manually `MathLib `_ and `GAOL `_ with CMake and `specify where they are `_ in order to build IBEX successfully and have accurate computations. - -3. **Install the Codac library**: +2. **Install the Codac library**: .. code-block:: bash - # The codac directory can be placed in your home, same level as IBEX + # The codac directory can be placed in your home git clone https://github.com/codac-team/codac $HOME/codac # Configure Codac before installation cd $HOME/codac mkdir build ; cd build - cmake -DCMAKE_INSTALL_PREFIX=$HOME/codac/build_install -DCMAKE_PREFIX_PATH=$HOME/ibex-lib/build_install -DCMAKE_BUILD_TYPE=Release .. + cmake -DCMAKE_INSTALL_PREFIX=$HOME/codac/build_install -DCMAKE_BUILD_TYPE=Release .. # Building + installing make make install cd ../.. -4. **Configure your system to find Codac**: +3. **Configure your system to find Codac**: In case Codac and its dependencies have been installed locally on your system, you will have to configure your environment variables. This can be done temporarily with: .. code-block:: bash - export CMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH:$HOME/ibex-lib/build_install export CMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH:$HOME/codac/build_install ... or permanently by updating your ``.bashrc`` file by appending the above commands. -5. **Verify the installation** (optional): +4. **Verify the installation** (optional): To ensure that the installation has worked properly, the unit tests of the library can be run. For this, you have to configure CMake using the ``-DBUILD_TESTS=ON`` option, before compilation. Then, from the ``$HOME/codac/build`` directory: @@ -138,7 +121,7 @@ Steps make test -6. **Try an example** (optional): +5. **Try an example** (optional): You may want to try Codac by running one of the proposed examples. After the installation, you can run the following commands: @@ -157,6 +140,13 @@ Steps You should obtain a graphical output corresponding to a curious set inversion. +6. **Start your own project**: + + ``examples/01_batman/CMakeLists.txt`` is also the shortest possible template + for a project of your own. :ref:`sec-start-cpp-project` goes through it line + by line, says which paths CMake needs and where it looks for them, and covers + the ``pkg-config`` alternative. + Windows Installation -------------------- @@ -169,7 +159,7 @@ Using MinGW .. Check https://community.chocolatey.org/packages/codac. -Install `Chocolatey package manager `_, run `choco install -y ibex cmake make qtcreator` in PowerShell and then download and extract *e.g.* ``codac_standalone_x64_mingw13.zip`` (for MinGW 13) from https://github.com/codac-team/codac/releases/latest, launch Qt Creator and choose Open Project, open ``example\CMakelists.txt``, ensure Desktop is selected and click Configure Project (might be hidden behind notifications at the bottom-right), wait 10 s then click on the big bottom-left green Run button, and finally check that the graphical output appears. +Install `Chocolatey package manager `_, run `choco install -y cmake make qtcreator` in PowerShell and then download and extract *e.g.* ``codac_standalone_x64_mingw15.zip`` (for MinGW 15) from https://github.com/codac-team/codac/releases/latest, launch Qt Creator and choose Open Project, open ``example\CMakelists.txt``, ensure Desktop is selected and click Configure Project (might be hidden behind notifications at the bottom-right), wait 10 s then click on the big bottom-left green Run button, and finally check that the graphical output appears. Note that in order to obtain graphical outputs, you will have to download and run https://github.com/ENSTABretagneRobotics/VIBES/releases/latest/download/VIBes-viewer_x86.exe before running the project. @@ -189,10 +179,10 @@ You will probably need to install these prerequisites (assuming you already inst .. code-block:: bash - choco install cmake git make patch winflexbison + choco install cmake git make choco install eigen -Then, install the desired compiler (*e.g.* ``choco install mingw --version=11.2.0.07112021``). +Then, install the desired compiler (*e.g.* ``choco install mingw --version=15.2.0``; MinGW-w64 older than version 13, which the MinGW-w64 GCC 11 to 14 packages come with, is not supported). Optionally, for Python binding (*e.g.* ``choco install python --version=3.10.4``) and documentation: @@ -259,7 +249,7 @@ Optionally, for Python binding and documentation: wget https://github.com/Homebrew/homebrew-core/raw/d2267b9f2ad247bc9c8273eb755b39566a474a70/Formula/doxygen.rb ; brew reinstall ./doxygen.rb ; brew pin doxygen brew install graphviz python -m pip install --upgrade pip - pip install --upgrade wheel setuptools sphinx sphinx_rtd_theme furo sphinx-math-dollar sphinx_tabs sphinx_togglebutton + pip install --upgrade wheel setuptools sphinx sphinx_rtd_theme furo sphinx-math-dollar sphinx_tabs breathe sphinx_togglebutton Troubleshooting diff --git a/doc/manual/manual/installation/index.rst b/doc/manual/manual/installation/index.rst index fcc2f6326..bfb997099 100644 --- a/doc/manual/manual/installation/index.rst +++ b/doc/manual/manual/installation/index.rst @@ -11,6 +11,7 @@ Codac is available in both C++20 and Python3, as well as MATLAB `through its Pyt python.rst cpp.rst matlab.rst + projects.rst performances.rst | diff --git a/doc/manual/manual/installation/projects.rst b/doc/manual/manual/installation/projects.rst new file mode 100644 index 000000000..24ae3e9c1 --- /dev/null +++ b/doc/manual/manual/installation/projects.rst @@ -0,0 +1,329 @@ +.. _sec-start-cpp-project: + +Using Codac in my own C++ or Python project +=========================================== + +The previous pages explain how to *install* Codac. This one explains how to *use* +the installation you have just made from a project of your own: which files CMake +or ``pkg-config`` look for, which paths have to be given to them, and what a +minimal project looks like in C++ and in Python. + +Everything below assumes an installation prefix, referred to as +``$CODAC_PREFIX``. If you followed :ref:`the installation from sources +` that is the directory you passed to +``-DCMAKE_INSTALL_PREFIX``, typically ``$HOME/codac/build_install``; if you +installed a Debian package or a standalone archive it is ``/usr`` or the +directory you extracted the archive into. Its layout is: + +.. code-block:: text + + $CODAC_PREFIX/ + ├── include/ + │ ├── codac <- the umbrella header, for #include + │ ├── codac-core/ <- one directory per module + │ ├── codac-graphics/ + │ ├── codac-unsupported/ + │ ├── codac-3rd/ <- GAOL and mathlib, when Codac built them + │ └── eigen3/ <- Eigen, part of Codac's public interface + ├── lib/ + │ ├── libcodac-core.a, libcodac-graphics.a, libcodac-unsupported.a + │ └── codac-3rd/ <- libgaol.a, libultim.a and GAOL's CMake package, when Codac built them + └── share/ + ├── codac/cmake/ + │ ├── codac-config.cmake <- what find_package(CODAC) reads + │ └── codac-targets.cmake <- the exported Codac:: targets + └── pkgconfig/ + └── codac.pc <- what pkg-config reads + + +.. _sec-start-cpp-project-cmake: + +A C++ project with CMake +------------------------ + +This is the recommended route, and the one every project under ``examples/`` +takes. Consider a project made of two files: + +.. code-block:: text + + my_project/ + ├── CMakeLists.txt + └── main.cpp + +``main.cpp`` includes the single umbrella header and uses the ``codac2`` +namespace: + +.. code-block:: c++ + + #include + + using namespace codac2; + + int main() + { + VectorVar x(2); + AnalyticFunction f({x}, x[0]*cos(x[0]-x[1])+x[1]); + SepInverse sep(f, {-oo,0}); + DefaultFigure::pave({{-10,10},{-10,10}}, sep, 0.1); + } + +and ``CMakeLists.txt`` is: + +.. code-block:: cmake + + cmake_minimum_required(VERSION 3.14) + project(my_project LANGUAGES CXX) + + # Codac is a C++20 library, so a project using it is compiled as C++20 too. + set(CMAKE_CXX_STANDARD 20) + set(CMAKE_CXX_STANDARD_REQUIRED ON) + + # Where to look for Codac. Leave this out if it is installed in a system + # directory, and prefer passing it on the command line (see below) to + # hard-coding it here. + # set(CMAKE_PREFIX_PATH "$ENV{HOME}/codac/build_install") + + find_package(CODAC REQUIRED) + message(STATUS "Found Codac version ${CODAC_VERSION}") + + add_executable(${PROJECT_NAME} main.cpp) + # CODAC_CXX_FLAGS holds the interval arithmetic flags (-frounding-math and + # the rest). Without them the rounding modes Codac relies on are not + # guaranteed, and the results lose the very property the library is for. + target_compile_options(${PROJECT_NAME} PUBLIC ${CODAC_CXX_FLAGS}) + target_include_directories(${PROJECT_NAME} SYSTEM PUBLIC ${CODAC_INCLUDE_DIRS}) + target_link_libraries(${PROJECT_NAME} PUBLIC ${CODAC_LIBRARIES}) + +Which paths to give to CMake +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``find_package(CODAC REQUIRED)`` looks for ``codac-config.cmake`` under the +prefixes listed in ``CMAKE_PREFIX_PATH``, in ``share/codac/cmake/`` — which is +exactly where Codac installs it. Nothing else is needed: GAOL, the interval +arithmetic library Codac is built upon, is either installed along with Codac, +with its CMake package, which ``codac-config.cmake`` finds there, or found by +``codac-config.cmake`` where Codac found it. There are three ways to give that +prefix: + +.. code-block:: bash + + # 1. On the configuration command line (recommended: nothing in the sources + # then depends on where a given machine happens to keep its libraries) + cmake -DCMAKE_PREFIX_PATH="$HOME/codac/build_install" .. + + # 2. Through the environment, once and for all, e.g. in your .bashrc + export CMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH:$HOME/codac/build_install + + # 3. In the CMakeLists.txt itself, as in the commented line above + # (several prefixes are separated by ";": CMAKE_PREFIX_PATH is a CMake + # list, not a PATH) + +.. note:: + + Projects written for earlier versions of Codac, which depended on IBEX, call + ``ibex_init_common()`` after ``find_package(CODAC)``. That call is no longer + needed, the flags it applied coming with ``CODAC_CXX_FLAGS``, and can be + removed; until it is, ``codac-config.cmake`` provides an ``ibex_init_common()`` + that does nothing, so that such projects still configure. + +.. admonition:: ``CODAC_DIR`` rather than ``CMAKE_PREFIX_PATH`` + + ``-DCODAC_DIR=$CODAC_PREFIX/share/codac/cmake`` points CMake straight at the + configuration file, bypassing the prefix search. It is useful to disambiguate + between two installed versions, but it says nothing about CAPD, which + ``codac-config.cmake`` looks for when that module was built, and which still + has to be found through ``CMAKE_PREFIX_PATH`` or ``CAPD_DIR``. + +What ``find_package(CODAC)`` defines +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - Variable + - Contents + + * - ``CODAC_VERSION`` + - The version of the installation that was found, *e.g.* ``2.1.1``. + + * - ``CODAC_INCLUDE_DIRS`` + - The include directories: the header root (through which ```` is + reached), ``include/eigen3``, and one directory per module. + + * - ``CODAC_LIBRARIES`` + - The libraries to link against. These are *imported targets* + (``Codac::codac-core``, ``Codac::codac-graphics``, + ``Codac::codac-unsupported``, plus ``Codac::gaol``, which brings GAOL, + mathlib and the flags GAOL needs along -- through ``gaol::gaol``, of the + CMake package of GAOL, when GAOL has one -- and ``Threads::Threads``) + rather than library paths, which is what lets CMake work out the link + order for itself. + + * - ``CODAC_CXX_FLAGS`` + - The flags GAOL gives, with which Codac was compiled: those of interval + arithmetic (``-frounding-math`` and the rest), and those of the fused + multiply-add instructions (``-mfma``, and so on). They have to be applied + to your own translation units too: without the former, the rounding modes + Codac relies on are not guaranteed; and Eigen's headers are compiled by + your project, and compiling them under different alignment and + instruction-set assumptions than the installed archives were built with is + an ODR/ABI mismatch. + + * - ``CODAC_CAPD_LIBRARY``, ``CODAC_SYMPY_LIBRARY`` + - Defined only when the corresponding optional module was built. They are + already part of ``CODAC_LIBRARIES``; use them to opt into one module + explicitly. + +Building it +^^^^^^^^^^^ + +.. code-block:: bash + + cd my_project + mkdir build ; cd build + cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="$HOME/codac/build_install" .. + cmake --build . + ./my_project + +On Visual Studio, replace the last two commands with +``cmake --build . --config Release``. If your program produces a graphical +output, remember to launch :ref:`the VIBes viewer ` first. + + +.. _sec-start-cpp-project-pkgconfig: + +A C++ project with pkg-config +----------------------------- + +Codac also installs ``share/pkgconfig/codac.pc``, for projects built with a +plain ``Makefile``, with Autotools, or compiled by hand. Since it is installed in +``share/pkgconfig`` rather than ``lib/pkgconfig``, and since an installation in +your home directory is not on the default search path either, ``pkg-config`` has +to be told where to look: + +.. code-block:: bash + + export PKG_CONFIG_PATH=$CODAC_PREFIX/share/pkgconfig:$PKG_CONFIG_PATH + +Check that it is found, and look at what it answers: + +.. code-block:: bash + + pkg-config --modversion codac # 2.1.1 + pkg-config --cflags codac + pkg-config --libs codac + +Compiling with it +^^^^^^^^^^^^^^^^^ + +.. code-block:: bash + + g++ -std=c++20 -O3 -DNDEBUG $(pkg-config --cflags codac) \ + main.cpp -o my_project $(pkg-config --libs codac) + +The language standard and the optimisation level are deliberately **not** part of +``codac.pc``: they are your project's business, exactly as ``CMAKE_CXX_STANDARD`` +and ``CMAKE_BUILD_TYPE`` are on the CMake side. Everything else is carried by the +file — the include directories (Codac's, Eigen's and GAOL's), the interval +arithmetic and architecture flags, the Codac and GAOL libraries in a working link +order, and the thread library where one is needed. + +The same thing in a ``Makefile``: + +.. code-block:: make + + CXXFLAGS := -std=c++20 -O3 -DNDEBUG $(shell pkg-config --cflags codac) + LDLIBS := $(shell pkg-config --libs codac) + + my_project: main.cpp + $(CXX) $(CXXFLAGS) $< -o $@ $(LDLIBS) + +.. admonition:: ``-frounding-math``, and why it is in ``codac.pc`` + + A CMake consumer gets the interval arithmetic flags through + ``CODAC_CXX_FLAGS``. A ``pkg-config`` consumer has no such variable, so + ``codac.pc`` carries those flags in its ``Cflags:`` line itself. This matters: + compiling Codac's headers without ``-frounding-math`` silently gives up the + guarantee the whole library rests on, and nothing in the build would report it. + Use the flags ``pkg-config`` gives you rather than a subset of them. + +.. warning:: + + ``pkg-config`` speaks ``-I``/``-L``/``-l``, which ``cl.exe`` does not + understand. **On Visual Studio, use the CMake route**; ``codac.pc`` is of no + use there. It works as expected with MinGW. + + +.. _sec-start-py-project: + +A Python project +---------------- + +If Codac was :ref:`installed with pip `, there is nothing to +configure: the package is in your interpreter's ``site-packages`` and a plain +``import`` finds it. A complete script is then: + +.. code-block:: py + + from codac import * + + x = VectorVar(2) + f = AnalyticFunction([x], x[0]*cos(x[0]-x[1])+x[1]) + sep = SepInverse(f, [-oo,0]) + DefaultFigure.pave([[-10,10],[-10,10]], sep, 0.1) + +Pointing Python at a local build +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If instead you :ref:`built the binding yourself `, the +package is left in the build tree, at +``$HOME/codac/build/python/python_package/codac``. Python has to be told about +the directory *containing* the package — that is, ``python_package``, not +``python_package/codac``. Three ways, in decreasing order of preference: + +.. code-block:: bash + + # 1. Register the build tree with your interpreter, once. The installation + # stays editable: rebuilding Codac is enough, there is nothing to reinstall. + cd $HOME/codac/build/python/python_package + pip install --user -e . + + # 2. Through the environment, e.g. in your .bashrc + export PYTHONPATH="${PYTHONPATH}:$HOME/codac/build/python/python_package/" + +The third way is from the script itself, which is useful for a one-off test or +when several builds of Codac coexist on the same machine. The path has to be +added **before** the import: + +.. code-block:: py + + import sys + # The directory that contains the "codac" package, not the package itself: + sys.path.insert(0, "/home/me/codac/build/python/python_package") + + from codac import * + + x = VectorVar(2) + f = AnalyticFunction([x], x[0]*cos(x[0]-x[1])+x[1]) + sep = SepInverse(f, [-oo,0]) + DefaultFigure.pave([[-10,10],[-10,10]], sep, 0.1) + +Checking which Codac you are actually using +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When a pip-installed Codac and a locally built one are both present, this is the +quickest way to see which one won: + +.. code-block:: bash + + python -c "import codac ; print(codac.__version__) ; print(codac.__file__)" + +``__file__`` is the path of the package that was imported, so it tells you +whether you are running the release from PyPI or your own build. + +.. admonition:: MATLAB + + The MATLAB interface is the same Python package under another name, + ``codac4matlab``, so the paths above apply to it as well. See + :ref:`sec-install-matlab`. diff --git a/doc/manual/manual/installation/python.rst b/doc/manual/manual/installation/python.rst index 940281bdf..7cd358053 100644 --- a/doc/manual/manual/installation/python.rst +++ b/doc/manual/manual/installation/python.rst @@ -41,6 +41,8 @@ You can also update and test your Codac Python package: pip install numpy sympy python -m unittest discover codac.tests +Once this works, :ref:`sec-start-py-project` shows what a script of your own looks like, and how to point Python at a locally built Codac rather than at the release from PyPI. + Troubleshooting --------------- diff --git a/doc/manual/manual/intervals/IntervalVector_class.rst b/doc/manual/manual/intervals/IntervalVector_class.rst index f2e160a6e..456824463 100644 --- a/doc/manual/manual/intervals/IntervalVector_class.rst +++ b/doc/manual/manual/intervals/IntervalVector_class.rst @@ -134,6 +134,8 @@ Common predicates include: :end-before: [intervalvector-class-4-end] :dedent: 4 +.. _sec-manual-intervals-operations: + Other operations ---------------- diff --git a/doc/manual/manual/intervals/index.rst b/doc/manual/manual/intervals/index.rst index 368899992..28d4d8de0 100644 --- a/doc/manual/manual/intervals/index.rst +++ b/doc/manual/manual/intervals/index.rst @@ -15,10 +15,11 @@ Codac provides data structures for handling basic interval sets. These structure :hidden: Interval_class.rst - .. Vector_class.rst IntervalVector_class.rst BoolInterval_class.rst +.. Vector_class.rst + .. What is an interval? The Interval class Boolean intervals diff --git a/doc/manual/manual/introduction/index.rst b/doc/manual/manual/introduction/index.rst index fc5cef30f..451a0a619 100644 --- a/doc/manual/manual/introduction/index.rst +++ b/doc/manual/manual/introduction/index.rst @@ -75,6 +75,45 @@ Codac already provides a catalog a contractors that one can use to deal with man Another main distinction lies in the way things are computed: with set-membership methods, estimations are not randomly performed. **Computations are deterministic**: given a set of parameters or inputs, algorithms will always output the same result. +.. _sec-intro-separators: + +Separators +---------- + +A contractor answers one question: *which part of this box can be discarded, because it surely contains no solution?* It says nothing about the part it keeps, which may hold solutions, non-solutions, or both. + +A **separator** answers the two symmetrical questions at once. Applied to a box :math:`[\mathbf{x}]`, a separator :math:`\mathcal{S}` associated with a set :math:`\mathbb{S}` returns a pair of boxes: + +- an **inner** box, obtained by removing from :math:`[\mathbf{x}]` what is certainly *inside* :math:`\mathbb{S}`; +- an **outer** box, obtained by removing from :math:`[\mathbf{x}]` what is certainly *outside* :math:`\mathbb{S}`. + +A separator is therefore the pair made of a contractor for :math:`\mathbb{S}` and a contractor for its complement :math:`\overline{\mathbb{S}}`, and what falls outside both boxes is the part of :math:`[\mathbf{x}]` that has been *proved* to belong to :math:`\mathbb{S}`. This is the essential gain over a contractor alone: besides eliminating, we can now certify. + +In Codac, this is the ``Sep`` interface: its ``separate()`` method takes a box and returns a ``BoxPair``, whose ``inner`` and ``outer`` members are the two boxes described above. Most contractors of the catalog have a separator counterpart, ``CtcInverse`` and ``SepInverse`` for instance, and the two families are listed side by side in :ref:`sec-ctc`. + +The practical consequence is visible on the two examples of the home page. The first one is solved with the contractor ``CtcInverse``: the blue boxes are guaranteed to be solution-free, and nothing is claimed about the rest. The second one is solved with the separator ``SepInverse``: the blue boxes are again guaranteed to have no solution, but *in addition* any vector taken in a green box is a solution of the inequality. + + +.. _sec-intro-pavings: + +Pavings +------- + +Contractors and separators reduce a box, but a single box is rarely a satisfactory description of a solution set: as soon as the set is not box-shaped, its interval enclosure is a coarse over-approximation — the *pessimism* mentioned below. + +The way out is to **bisect**. A box that can no longer be contracted is cut in two, and each half is contracted in turn; the process is repeated until the remaining boxes are smaller than a precision :math:`\epsilon` given by the user. The resulting collection of non-overlapping boxes is called a **paving**, and the algorithm producing it is known as **SIVIA** (*Set Inversion Via Interval Analysis*). + +The boxes of a paving are of three kinds: + +- those proved to contain no solution; +- those proved to contain only solutions — which only a separator, or a direct inclusion test, can establish; +- those still undecided, which are the ones the bisection stops on when they become smaller than :math:`\epsilon`. They form the boundary of the solution set, and their total volume is the price paid for the guarantee. + +In Codac, the paving of a box is obtained with a single call, ``pave(x0, c, eps)``, which accepts a contractor or a separator and returns a ``PavingOut`` or a ``PavingInOut`` accordingly. This is what both examples of the home page do. The function ``sivia(x0, f, y, eps)`` provides the same service directly from a function :math:`\mathbf{f}` and a target domain :math:`[\mathbf{y}]`, without an explicit contractor. + +The choice of :math:`\epsilon` is the usual compromise: the finer it is, the thinner the undecided boundary, and the more boxes to compute. + + Reliable outputs ---------------- diff --git a/doc/manual/manual/references/logos.rst b/doc/manual/manual/references/logos.rst index 419547286..e18ebbea0 100644 --- a/doc/manual/manual/references/logos.rst +++ b/doc/manual/manual/references/logos.rst @@ -1,3 +1,5 @@ +:orphan: + .. _sec-ref-codac-logos: Codac Logos diff --git a/doc/manual/manual/tubes/src.cpp b/doc/manual/manual/tubes/src.cpp index 8e3a932f3..feac64784 100644 --- a/doc/manual/manual/tubes/src.cpp +++ b/doc/manual/manual/tubes/src.cpp @@ -187,9 +187,9 @@ TEST_CASE("SlicedTube class - manual") Interval y(0,0.2); x.invert(y, v_t); - for(const auto& t : v_t) + for(const auto& ti : v_t) { - IntervalVector z = cart_prod(t,y); + IntervalVector z = cart_prod(ti,y); DefaultFigure::draw_box(z, Color::red()); } // [slicedtube-class-5-end] diff --git a/doc/manual/manual/visualization/3d_visualization.rst b/doc/manual/manual/visualization/3d_visualization.rst index 8c2ee2766..3da603a4f 100644 --- a/doc/manual/manual/visualization/3d_visualization.rst +++ b/doc/manual/manual/visualization/3d_visualization.rst @@ -56,6 +56,8 @@ Paving - Subpaving +.. doxygenfunction:: codac2::Figure3D::draw_axes(double, const Vector&) + :project: codac Note that only the stroke color is used in all of the supported drawing functions. diff --git a/doc/manual/manual/visualization/colors.rst b/doc/manual/manual/visualization/colors.rst index aaba29598..589aa72a9 100644 --- a/doc/manual/manual/visualization/colors.rst +++ b/doc/manual/manual/visualization/colors.rst @@ -129,6 +129,7 @@ Additionnal methods are available for any useful purpose: .. _subsec-graphics-colors-style-properties: + StyleProperties --------------- @@ -209,6 +210,7 @@ For more information, see :ref:`subsec-graphics-colors-optional-arguments`. % Red edge, dotted line, line width of 0.1, z-value of 1.5 and on layer1 .. _subsec-graphics-colors-paving-style: + Paving style ------------ @@ -342,6 +344,7 @@ You can also create your own color map : Note that you can add RGB and HSV colors to the same color map. The model of the color map will define the interpolation space. .. _subsec-graphics-colors-style-gradient-properties: + StyleGradientProperties ----------------------- diff --git a/doc/manual/tuto/cp_robotics/src/CMakeLists.txt b/doc/manual/tuto/cp_robotics/src/CMakeLists.txt index 51161a85e..d749449f4 100644 --- a/doc/manual/tuto/cp_robotics/src/CMakeLists.txt +++ b/doc/manual/tuto/cp_robotics/src/CMakeLists.txt @@ -2,22 +2,12 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) -# Adding IBEX - - # In case you installed IBEX in a local directory, you need - # to specify its path with the CMAKE_PREFIX_PATH option. - # set(CMAKE_PREFIX_PATH "~/ibex-lib/build_install") - - find_package(IBEX REQUIRED) - ibex_init_common() # IBEX should have installed this function - message(STATUS "Found IBEX version ${IBEX_VERSION}") - # Adding Codac # In case you installed Codac in a local directory, you need @@ -38,4 +28,4 @@ ) target_compile_options(${PROJECT_NAME} PUBLIC ${CODAC_CXX_FLAGS}) target_include_directories(${PROJECT_NAME} SYSTEM PUBLIC ${CODAC_INCLUDE_DIRS}) - target_link_libraries(${PROJECT_NAME} PUBLIC ${CODAC_LIBRARIES} Ibex::ibex) \ No newline at end of file + target_link_libraries(${PROJECT_NAME} PUBLIC ${CODAC_LIBRARIES}) \ No newline at end of file diff --git a/examples/00_graphics/CMakeLists.txt b/examples/00_graphics/CMakeLists.txt index cf4fae020..f67374ceb 100644 --- a/examples/00_graphics/CMakeLists.txt +++ b/examples/00_graphics/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) @@ -17,10 +17,6 @@ find_package(CODAC REQUIRED) message(STATUS "Found Codac version ${CODAC_VERSION}") -# Initializating Ibex - - ibex_init_common() - # Compilation if(FAST_RELEASE) diff --git a/examples/00_graphics/graphic_animation.cpp b/examples/00_graphics/graphic_animation.cpp index 6f357e68c..309d74a4c 100644 --- a/examples/00_graphics/graphic_animation.cpp +++ b/examples/00_graphics/graphic_animation.cpp @@ -18,7 +18,7 @@ int main() { fig.clear(); fig.draw_point({5.0*cos(theta), 5.0*sin(theta)}, Color::red()); - theta += 2.0 * M_PI / (double) steps; + theta += 2.0 * PI / (double) steps; std::this_thread::sleep_for(std::chrono::milliseconds(100)); } diff --git a/examples/01_batman/CMakeLists.txt b/examples/01_batman/CMakeLists.txt index cf9ef35ce..cc24aecb4 100644 --- a/examples/01_batman/CMakeLists.txt +++ b/examples/01_batman/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) @@ -17,10 +17,6 @@ find_package(CODAC REQUIRED) message(STATUS "Found Codac version ${CODAC_VERSION}") -# Initializating Ibex - - ibex_init_common() - # Compilation if(FAST_RELEASE) diff --git a/examples/02_centered_form/CMakeLists.txt b/examples/02_centered_form/CMakeLists.txt index cf9ef35ce..cc24aecb4 100644 --- a/examples/02_centered_form/CMakeLists.txt +++ b/examples/02_centered_form/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) @@ -17,10 +17,6 @@ find_package(CODAC REQUIRED) message(STATUS "Found Codac version ${CODAC_VERSION}") -# Initializating Ibex - - ibex_init_common() - # Compilation if(FAST_RELEASE) diff --git a/examples/03_sivia/CMakeLists.txt b/examples/03_sivia/CMakeLists.txt index cf9ef35ce..cc24aecb4 100644 --- a/examples/03_sivia/CMakeLists.txt +++ b/examples/03_sivia/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) @@ -17,10 +17,6 @@ find_package(CODAC REQUIRED) message(STATUS "Found Codac version ${CODAC_VERSION}") -# Initializating Ibex - - ibex_init_common() - # Compilation if(FAST_RELEASE) diff --git a/examples/04_explored_area/CMakeLists.txt b/examples/04_explored_area/CMakeLists.txt index cf9ef35ce..cc24aecb4 100644 --- a/examples/04_explored_area/CMakeLists.txt +++ b/examples/04_explored_area/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) @@ -17,10 +17,6 @@ find_package(CODAC REQUIRED) message(STATUS "Found Codac version ${CODAC_VERSION}") -# Initializating Ibex - - ibex_init_common() - # Compilation if(FAST_RELEASE) diff --git a/examples/05_capd_solver/CMakeLists.txt b/examples/05_capd_solver/CMakeLists.txt index 4d720d238..a96e2d9ff 100644 --- a/examples/05_capd_solver/CMakeLists.txt +++ b/examples/05_capd_solver/CMakeLists.txt @@ -2,22 +2,12 @@ # codac / basics example - cmake configuration file # ================================================================== -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) -# Adding IBEX - -# In case you installed IBEX in a local directory, you need -# to specify its path with the CMAKE_PREFIX_PATH option. -# set(CMAKE_PREFIX_PATH "~/ibex-lib/build_install") - -find_package(IBEX REQUIRED) -ibex_init_common() # IBEX should have installed this function -message(STATUS "Found IBEX version ${IBEX_VERSION}") - # Adding Codac # In case you installed Codac in a local directory, you need @@ -41,5 +31,4 @@ target_link_libraries(${PROJECT_NAME} PRIVATE ${CODAC_LIBRARIES} ${CODAC_CAPD_LIBRARY} capd::capd - Ibex::ibex ) \ No newline at end of file diff --git a/examples/05_capd_solver/main.cpp b/examples/05_capd_solver/main.cpp index 0fac5e591..aeb0a02be 100644 --- a/examples/05_capd_solver/main.cpp +++ b/examples/05_capd_solver/main.cpp @@ -30,7 +30,7 @@ int main() // initial set capd::IVector c(2); - c[0] = -M_PI/2.; + c[0] = -PI/2.; c[1] = 0.; // take some box around c c[0] += capd::interval(-1,1)*1e-2; diff --git a/examples/06_graphics_3D/CMakeLists.txt b/examples/06_graphics_3D/CMakeLists.txt index cf9ef35ce..cc24aecb4 100644 --- a/examples/06_graphics_3D/CMakeLists.txt +++ b/examples/06_graphics_3D/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) @@ -17,10 +17,6 @@ find_package(CODAC REQUIRED) message(STATUS "Found Codac version ${CODAC_VERSION}") -# Initializating Ibex - - ibex_init_common() - # Compilation if(FAST_RELEASE) diff --git a/examples/07_centered_2D/CMakeLists.txt b/examples/07_centered_2D/CMakeLists.txt index cf9ef35ce..cc24aecb4 100644 --- a/examples/07_centered_2D/CMakeLists.txt +++ b/examples/07_centered_2D/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) @@ -17,10 +17,6 @@ find_package(CODAC REQUIRED) message(STATUS "Found Codac version ${CODAC_VERSION}") -# Initializating Ibex - - ibex_init_common() - # Compilation if(FAST_RELEASE) diff --git a/examples/08_centered_3D/CMakeLists.txt b/examples/08_centered_3D/CMakeLists.txt index cf9ef35ce..cc24aecb4 100644 --- a/examples/08_centered_3D/CMakeLists.txt +++ b/examples/08_centered_3D/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) @@ -17,10 +17,6 @@ find_package(CODAC REQUIRED) message(STATUS "Found Codac version ${CODAC_VERSION}") -# Initializating Ibex - - ibex_init_common() - # Compilation if(FAST_RELEASE) diff --git a/examples/09_robot_simu/CMakeLists.txt b/examples/09_robot_simu/CMakeLists.txt index cf9ef35ce..cc24aecb4 100644 --- a/examples/09_robot_simu/CMakeLists.txt +++ b/examples/09_robot_simu/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) @@ -17,10 +17,6 @@ find_package(CODAC REQUIRED) message(STATUS "Found Codac version ${CODAC_VERSION}") -# Initializating Ibex - - ibex_init_common() - # Compilation if(FAST_RELEASE) diff --git a/examples/09_robot_simu/main.cpp b/examples/09_robot_simu/main.cpp index 5882ff8d8..e23a55d08 100644 --- a/examples/09_robot_simu/main.cpp +++ b/examples/09_robot_simu/main.cpp @@ -14,8 +14,8 @@ int main() Figure2D g("Robot simulation", GraphicOutput::VIBES | GraphicOutput::IPE); g.set_axes( - {0,x.codomain()[0].inflate(1.)}, - {1,x.codomain()[1].inflate(1.)} + axis(0,x.codomain()[0].inflate(1.)), + axis(1,x.codomain()[1].inflate(1.)) ).auto_scale(); g.draw_tank(x(5.), 0.5, {Color::black(),Color::yellow()}); diff --git a/examples/10_lie_groups/CMakeLists.txt b/examples/10_lie_groups/CMakeLists.txt index 41d3ccba3..027a69fb1 100644 --- a/examples/10_lie_groups/CMakeLists.txt +++ b/examples/10_lie_groups/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) @@ -17,10 +17,6 @@ find_package(CODAC REQUIRED) message(STATUS "Found Codac version ${CODAC_VERSION}") -# Initializating Ibex - - ibex_init_common() - # Compilation if(FAST_RELEASE) diff --git a/examples/11_peibos/CMakeLists.txt b/examples/11_peibos/CMakeLists.txt index cf9ef35ce..cc24aecb4 100644 --- a/examples/11_peibos/CMakeLists.txt +++ b/examples/11_peibos/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) @@ -17,10 +17,6 @@ find_package(CODAC REQUIRED) message(STATUS "Found Codac version ${CODAC_VERSION}") -# Initializating Ibex - - ibex_init_common() - # Compilation if(FAST_RELEASE) diff --git a/examples/11_peibos/main.cpp b/examples/11_peibos/main.cpp index e55ad6d72..9d0701fa4 100644 --- a/examples/11_peibos/main.cpp +++ b/examples/11_peibos/main.cpp @@ -22,7 +22,7 @@ int main() Figure2D figure_2d ("Henon Map", GraphicOutput::VIBES); figure_2d.set_window_properties({25,50},{500,500}); - figure_2d.set_axes({0,{-1.4,2.2}}, {1,{-0.4,0.3}}); + figure_2d.set_axes(axis(0,{-1.4,2.2}), axis(1,{-0.4,0.3})); for (const auto& p : v_par_2d) { @@ -49,7 +49,7 @@ int main() Figure2D figure_3d_proj ("Conform projected", GraphicOutput::VIBES); figure_3d_proj.set_window_properties({25,600},{500,500}); - figure_3d_proj.set_axes({0,{-1.5,2.5}}, {1,{-2,2}}); + figure_3d_proj.set_axes(axis(0,{-1.5,2.5}), axis(1,{-2,2})); auto v_par_3d = PEIBOS(f_3d, psi0_3d, {id_3d,s1,s1*s1,s1.invert(),s2,s2.invert()}, 0.05, true); diff --git a/examples/12_peibos_capd/CMakeLists.txt b/examples/12_peibos_capd/CMakeLists.txt index 9091ca50a..b6a33d97d 100644 --- a/examples/12_peibos_capd/CMakeLists.txt +++ b/examples/12_peibos_capd/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) @@ -17,10 +17,6 @@ find_package(CODAC REQUIRED) message(STATUS "Found Codac version ${CODAC_VERSION}") -# Initializating Ibex - - ibex_init_common() - # Compilation if(FAST_RELEASE) @@ -35,5 +31,4 @@ ${CODAC_LIBRARIES} ${CODAC_CAPD_LIBRARY} capd::capd - Ibex::ibex ) \ No newline at end of file diff --git a/examples/12_peibos_capd/main.cpp b/examples/12_peibos_capd/main.cpp index f4b3a2b80..d7b44b5f8 100644 --- a/examples/12_peibos_capd/main.cpp +++ b/examples/12_peibos_capd/main.cpp @@ -49,7 +49,7 @@ int main() AnalyticFunction psi0_pend ({X_2d},{0.1*X_2d[0],0.1}); - auto peibos_output_pend = PEIBOS(vectorField_pend, tf_pend, dt_pend, psi0_pend, {id_2d,s,s*s,s.invert()}, 0.02, {-M_PI/2.,0.}, true); + auto peibos_output_pend = PEIBOS(vectorField_pend, tf_pend, dt_pend, psi0_pend, {id_2d,s,s*s,s.invert()}, 0.02, {-PI/2.,0.}, true); Figure2D output_pend ("Pendulum",GraphicOutput::VIBES | GraphicOutput::IPE); output_pend.set_axes(axis(0,{-2,2}),axis(1,{-3,3})); diff --git a/examples/13_qinter/CMakeLists.txt b/examples/13_qinter/CMakeLists.txt index cf9ef35ce..cc24aecb4 100644 --- a/examples/13_qinter/CMakeLists.txt +++ b/examples/13_qinter/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) @@ -17,10 +17,6 @@ find_package(CODAC REQUIRED) message(STATUS "Found Codac version ${CODAC_VERSION}") -# Initializating Ibex - - ibex_init_common() - # Compilation if(FAST_RELEASE) diff --git a/examples/14_lohner/CMakeLists.txt b/examples/14_lohner/CMakeLists.txt index cf9ef35ce..cc24aecb4 100644 --- a/examples/14_lohner/CMakeLists.txt +++ b/examples/14_lohner/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) @@ -17,10 +17,6 @@ find_package(CODAC REQUIRED) message(STATUS "Found Codac version ${CODAC_VERSION}") -# Initializating Ibex - - ibex_init_common() - # Compilation if(FAST_RELEASE) diff --git a/examples/15_sympy/CMakeLists.txt b/examples/15_sympy/CMakeLists.txt index 52197a061..956d0857e 100644 --- a/examples/15_sympy/CMakeLists.txt +++ b/examples/15_sympy/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) @@ -17,10 +17,6 @@ find_package(CODAC REQUIRED) message(STATUS "Found Codac version ${CODAC_VERSION}") -# Initializating Ibex - - ibex_init_common() - # Compilation if(FAST_RELEASE) @@ -37,5 +33,4 @@ ${CODAC_LIBRARIES} ${CODAC_SYMPY_LIBRARY} # linking to the codac-sympy extension pybind11::embed # linking to pybind11 - Ibex::ibex ) \ No newline at end of file diff --git a/examples/16_visibility/CMakeLists.txt b/examples/16_visibility/CMakeLists.txt index cf9ef35ce..cc24aecb4 100644 --- a/examples/16_visibility/CMakeLists.txt +++ b/examples/16_visibility/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) @@ -17,10 +17,6 @@ find_package(CODAC REQUIRED) message(STATUS "Found Codac version ${CODAC_VERSION}") -# Initializating Ibex - - ibex_init_common() - # Compilation if(FAST_RELEASE) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 2534bcf5f..69356542c 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -6,17 +6,174 @@ if(BUILD_TESTS AND TEST_EXAMPLES) -# add_test(NAME cpp_01_getting_started -# COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/tuto/01_getting_started/build/01_getting_started 0) -# if(WITH_PYTHON) -# add_test(NAME py_01_getting_started -# COMMAND python3 ${CMAKE_CURRENT_SOURCE_DIR}/tuto/01_getting_started/01_getting_started.py 0) -# endif() -# -# if(WITH_CAPD) -# # Lie group -# add_test(NAME lie_05 -# COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/lie_group/05_loc/build/codac_lie_05 0) -# endif() - -endif() \ No newline at end of file + # Every example ships a standalone CMakeLists.txt, so that a user can build it + # on its own against an installed codac. Those projects cannot be pulled in + # with add_subdirectory(): each calls project() and find_package(CODAC), which + # would look for a codac that is precisely the one being built. The examples + # are therefore rebuilt here from the same sources, against the in-tree + # library, the way tests/CMakeLists.txt builds the unit tests. Only the public + # headers are visible to them -- the generated umbrella "codac" and the + # installed headers next to it -- so an example that stops compiling here is + # an example a user could no longer build either. + # + # The lists below mirror the add_executable() calls of those standalone + # CMakeLists.txt: an example directory may hold sources that its own project + # does not build (02_centered_form/main_parabolas.cpp, for instance), and + # those are deliberately left out rather than silently compiled. + + set(CODAC_EXAMPLES_CPP + 00_graphics/graphic_examples + 00_graphics/graphic_animation + 01_batman/main + 02_centered_form/main + 03_sivia/main + 04_explored_area/main + 06_graphics_3D/main + 07_centered_2D/main + 08_centered_3D/main + 09_robot_simu/main + 11_peibos/main + 13_qinter/main + 14_lohner/main + 16_visibility/main + ellipsoid_example/main + ) + + set(CODAC_EXAMPLES_PY + 00_graphics/graphic_examples + 00_graphics/graphic_animation + 00_graphics/graphic_colors + 02_centered_form/main + 02_centered_form/main_parabolas + 03_sivia/main + 04_explored_area/main + 06_graphics_3D/main + 09_robot_simu/main + 11_peibos/main + 13_qinter/main + 14_lohner/main + 16_visibility/main + custom_sep/coloration + custom_sep/custom_ctc + custom_sep/custom_sep + ellipsoid_example/main + ) + + set(CODAC_EXAMPLE_LIBRARIES ${PROJECT_NAME}-core ${PROJECT_NAME}-graphics) + + if(WITH_CAPD) + # 10_lie_groups is in this list rather than in the one above because it + # includes , which its own CMakeLists.txt does not say: the + # standalone project builds it only where a codac with CAPD is installed. + list(APPEND CODAC_EXAMPLES_CPP + 05_capd_solver/main + 10_lie_groups/lie_01 + 12_peibos_capd/main + ) + list(APPEND CODAC_EXAMPLE_LIBRARIES ${PROJECT_NAME}-capd capd::capd) + endif() + + # BUILD_SYMPY_EMBED_TESTS is decided in tests/CMakeLists.txt, which the + # top-level CMakeLists.txt adds before this directory. It is turned off + # wherever PYBIND11_FINDPYTHON=OFF, which is every configuration this + # project's CI builds, so the sympy example follows the sympy unit test + # rather than being built on its own terms. + if(WITH_PYTHON AND BUILD_SYMPY_EMBED_TESTS) + list(APPEND CODAC_EXAMPLES_CPP 15_sympy/main) + list(APPEND CODAC_EXAMPLES_PY 15_sympy/main) + list(APPEND CODAC_EXAMPLE_LIBRARIES ${PROJECT_NAME}-sympy) + endif() + + # The examples write their figures (JSON for VIBes, .ipe, .png, .obj) into the + # current directory, so they are run from the build tree rather than from the + # sources they were copied from. Assets they read are found through __FILE__, + # which stays an absolute path, so nothing has to be copied over. + set(CODAC_EXAMPLES_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/output) + file(MAKE_DIRECTORY ${CODAC_EXAMPLES_OUTPUT_DIR}) + + foreach(SRC_EXAMPLE ${CODAC_EXAMPLES_CPP}) + + string(REPLACE "/" "_" EXAMPLE_NAME ${SRC_EXAMPLE}) + set(EXAMPLE_NAME codac2_examples_${EXAMPLE_NAME}) + + add_executable( + ${EXAMPLE_NAME} + ${CMAKE_CURRENT_SOURCE_DIR}/${SRC_EXAMPLE}.cpp + ) + + target_include_directories( + ${EXAMPLE_NAME} + SYSTEM PUBLIC + + # First, so that a header is opened at src/... rather than through + # build/include: the path a translation unit used is the path gcov records, + # and the two spellings would split every header into two entries in the + # coverage report. See codac_publish_include_dirs() in the top-level CMakeLists.txt. + ${CODAC_SOURCE_INCLUDE_DIRS} + + ${CMAKE_BINARY_DIR}/src # the generated "codac" umbrella header + ${CODAC_GENERATED_INCLUDE_DIRS} # and the per-module ones (codac-core.h, codac-graphics.h, etc.), each in its own module's binary directory + ) + + target_link_libraries( + ${EXAMPLE_NAME} + PUBLIC + + ${CODAC_EXAMPLE_LIBRARIES} + ) + + # The sympy example runs an embedded Python interpreter, which codac-sympy + # calls into: it links pybind11::embed, as its own CMakeLists.txt does and as + # the sympy unit test does in tests/CMakeLists.txt. + if(SRC_EXAMPLE MATCHES "^15_sympy/") + target_link_libraries(${EXAMPLE_NAME} PRIVATE pybind11::embed) + endif() + + add_test( + NAME ${EXAMPLE_NAME}_cpp + COMMAND ${EXAMPLE_NAME} + WORKING_DIRECTORY ${CODAC_EXAMPLES_OUTPUT_DIR} + ) + + if(CODAC_CPP_TEST_ENV) + set_tests_properties(${EXAMPLE_NAME}_cpp PROPERTIES + ENVIRONMENT "${CODAC_CPP_TEST_ENV}" + ) + endif() + + # The examples are part of the ctest suite here, so `make check` -- which + # rebuilds the suite before running it -- has to build them too. Without + # this it would run ctest against executables that may not exist yet, the + # way it does for the unit tests through the same call in + # tests/CMakeLists.txt. + if(TARGET check) + add_dependencies(check ${EXAMPLE_NAME}) + endif() + + endforeach() + + if(WITH_PYTHON) + + foreach(SRC_EXAMPLE ${CODAC_EXAMPLES_PY}) + + string(REPLACE "/" "_" EXAMPLE_NAME ${SRC_EXAMPLE}) + set(EXAMPLE_NAME codac2_examples_${EXAMPLE_NAME}) + + # PYTHON_TEST_ENV_ARGS carries whatever the Python interpreter needs to + # load a sanitized or a Windows build (LD_PRELOAD, PATH...); it is built + # in tests/CMakeLists.txt and exported from there. + add_test( + NAME ${EXAMPLE_NAME}_py + COMMAND + ${CMAKE_COMMAND} -E env + ${PYTHON_TEST_ENV_ARGS} + ${PYTHON_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/${SRC_EXAMPLE}.py + WORKING_DIRECTORY ${CODAC_EXAMPLES_OUTPUT_DIR} + ) + + endforeach() + + endif() + +endif() diff --git a/examples/custom_sep/custom_sep.py b/examples/custom_sep/custom_sep.py index 4bb71a10c..36cffa7bd 100644 --- a/examples/custom_sep/custom_sep.py +++ b/examples/custom_sep/custom_sep.py @@ -36,5 +36,6 @@ def separate(self, x): # # c = p.connected_subsets(outer_complem) -for bi in c[1].boxes(): - DefaultFigure.draw_box(bi,[Color.red(),Color.red()]) \ No newline at end of file +for ci in c: + for bi in ci.boxes(): + DefaultFigure.draw_box(bi,[Color.red(),Color.red()]) \ No newline at end of file diff --git a/examples/ellipsoid_example/CMakeLists.txt b/examples/ellipsoid_example/CMakeLists.txt index ce82f2152..0364999c8 100644 --- a/examples/ellipsoid_example/CMakeLists.txt +++ b/examples/ellipsoid_example/CMakeLists.txt @@ -2,43 +2,36 @@ # codac / ellipsoid example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) -# Adding IBEX - - # In case you installed IBEX in a local directory, you need - # to specify its path with the CMAKE_PREFIX_PATH option. - # set(CMAKE_PREFIX_PATH "~/ibex-lib/build_install") - - find_package(IBEX REQUIRED) - ibex_init_common() # IBEX should have installed this function - message(STATUS "Found IBEX version ${IBEX_VERSION}") - # Adding Eigen3 - # In case you installed Eigen3 in a local directory, you need - # to specify its path with the CMAKE_PREFIX_PATH option, e.g. - # set(CMAKE_PREFIX_PATH "~/eigen/build_install") - - find_package(Eigen3 3.4 REQUIRED NO_MODULE) - message(STATUS "Found Eigen3 version ${Eigen3_VERSION}") + # Codac builds and installs its own Eigen3 (see CODAC_INCLUDE_DIRS below), + # so no separate find_package(Eigen3) is needed -- and none would find it, + # since that build isn't registered as a system-discoverable package. + # Searching for an unrelated system Eigen3 here would risk pulling in a + # different version than the one codac-core/codac-graphics were compiled + # against, which is exactly the kind of ABI mismatch that broke examples + # like 01_batman, and that CODAC_CXX_FLAGS -- which carries -mfma when codac + # was built with it -- is there to prevent. # Adding Codac - # In case you installed Codac in a local directory, you need + # In case you installed Codac in a local directory, you need # to specify its path with the CMAKE_PREFIX_PATH option. - set(CMAKE_PREFIX_PATH "~/Documents/Code_these/codac/build_install") + # set(CMAKE_PREFIX_PATH "~/codac/build_install") find_package(CODAC REQUIRED) message(STATUS "Found Codac version ${CODAC_VERSION}") + # Compilation add_executable(${PROJECT_NAME} main.cpp) target_compile_options(${PROJECT_NAME} PUBLIC ${CODAC_CXX_FLAGS}) target_include_directories(${PROJECT_NAME} SYSTEM PUBLIC ${CODAC_INCLUDE_DIRS}) - target_link_libraries(${PROJECT_NAME} PUBLIC ${CODAC_LIBRARIES} Ibex::ibex Eigen3::Eigen) + target_link_libraries(${PROJECT_NAME} PUBLIC ${CODAC_LIBRARIES}) diff --git a/packages/choco/codac/codac.nuspec b/packages/choco/codac/codac.nuspec index 4b7c7933f..e0baeccf8 100644 --- a/packages/choco/codac/codac.nuspec +++ b/packages/choco/codac/codac.nuspec @@ -28,7 +28,7 @@ Codac is a library providing tools for constraint programming over reals, trajec ## Package parameters The following package parameters can be set: -- `/url:URL` - Will install the specified binary package (e.g. built for Visual Studio), see versions from https://github.com/codac-team/codac/releases (the Windows `PATH` might need to be updated manually with e.g. `C:\ProgramData\chocolatey\lib\codac\bin`, etc.). By default, only the MinGW libraries compatible with the corresponding MinGW Chocolatey package dependency are installed. Use the standard parameter `choco install --ignore-dependencies ...` to avoid installing the default MinGW and IBEX Chocolatey package dependencies if needed (you might want to install manually [IBEX](https://community.chocolatey.org/packages/ibex) package with the corresponding parameters, as well as the corresponding compiler). +- `/url:URL` - Will install the specified binary package (e.g. built for Visual Studio), see versions from https://github.com/codac-team/codac/releases (the Windows `PATH` might need to be updated manually with e.g. `C:\ProgramData\chocolatey\lib\codac\bin`, etc.). By default, only the MinGW libraries compatible with the corresponding MinGW Chocolatey package dependency are installed. Use the standard parameter `choco install --ignore-dependencies ...` to avoid installing the default MinGW Chocolatey package dependency if needed (you might want to install manually the corresponding compiler). - `/checksum:SHA256` - SHA256 checksum of the binary package specified by the `/url` parameter. If needed, use the standard parameter `choco install --ignore-checksums ...` for trusted sources. - `/urlX:URL` - Same as above, with X in [1,99], except this will not disable the installation of the MinGW libraries compatible with the corresponding MinGW Chocolatey package dependency. - `/checksumX:SHA256` - SHA256 checksum of the binary package specified by the `/urlX` parameter. If needed, use the standard parameter `choco install --ignore-checksums ...` for trusted sources. @@ -44,8 +44,7 @@ choco install -y --ignore-dependencies codac --params "'/url:https://github.com/ https://github.com/codac-team/codac/releases - - + diff --git a/packages/choco/codac/tools/chocolateyinstall.ps1 b/packages/choco/codac/tools/chocolateyinstall.ps1 index d4155c393..f21d6cc14 100644 --- a/packages/choco/codac/tools/chocolateyinstall.ps1 +++ b/packages/choco/codac/tools/chocolateyinstall.ps1 @@ -32,9 +32,9 @@ New-Item -ItemType Directory -Force -Path $root | Out-Null $AllowedUrlPrefix = 'https://github.com/codac-team/codac' if (!$pp['url']) { - $url = 'https://github.com/codac-team/codac/releases/download/v1/codac_x86_mingw13.zip' + $url = 'https://github.com/codac-team/codac/releases/download/v1/codac_x86_mingw15.zip' $checksum = 'EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE' - $url64 = 'https://github.com/codac-team/codac/releases/download/v1/codac_x64_mingw13.zip' + $url64 = 'https://github.com/codac-team/codac/releases/download/v1/codac_x64_mingw15.zip' $checksum64 = 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' $packageArgs = @{ packageName = $env:ChocolateyPackageName diff --git a/packages/deb/control b/packages/deb/control index 7d652a59b..782232b34 100644 --- a/packages/deb/control +++ b/packages/deb/control @@ -1,7 +1,6 @@ Package: libcodac-dev Version: 1 Architecture: amd64 -Depends: libibex-dev Section: math Priority: optional Description: Codac is a library providing tools for constraint programming over reals, trajectories and sets diff --git a/packages/temporary/gennewcodacpi_armhf.sh b/packages/temporary/gennewcodacpi_armhf.sh index 7ec2b7a02..81a016801 100644 --- a/packages/temporary/gennewcodacpi_armhf.sh +++ b/packages/temporary/gennewcodacpi_armhf.sh @@ -27,12 +27,6 @@ fi && \ sudo apt-get -q update --allow-releaseinfo-change ; sudo apt-get -y install python3-dev patchelf python3-pip python3-wheel python3-setuptools || true && \ python3 -m pip install \$PIP_OPTIONS --upgrade patchelf --prefer-binary --extra-index-url https://www.piwheels.org/simple || true && \ python3 -m pip install \$PIP_OPTIONS --upgrade auditwheel --prefer-binary --extra-index-url https://www.piwheels.org/simple && \ -# wget https://github.com/lebarsfa/ibex-lib/releases/download/ibex-2.8.9.20260819/ibex_armhf_\$(lsb_release -cs).zip --no-check-certificate -nv is causing illegal instruction on a Mac M1... \\ -curl -L -O https://github.com/lebarsfa/ibex-lib/releases/download/ibex-2.8.9.20260819/ibex_armhf_\$(lsb_release -cs).zip --insecure && \ -unzip -q ibex_armhf_\$(lsb_release -cs).zip && \ -rm -Rf ibex_armhf_\$(lsb_release -cs).zip && \ -sudo cp -Rf ibex/* /usr/local/ && \ -\ git config --global --add safe.directory /io && \ cd /io && \ \ @@ -43,7 +37,7 @@ python3 -m pip install \$PIP_OPTIONS --upgrade wheel --prefer-binary --extra-ind # With setuptools upgrade from pip, building the .whl might fail for bullseye... \\ #python3 -m pip install \$PIP_OPTIONS --upgrade setuptools --prefer-binary --extra-index-url https://www.piwheels.org/simple && \\ mkdir -p build_dir_\$(lsb_release -cs) && cd build_dir_\$(lsb_release -cs) && \ -cmake -E env CXXFLAGS=\"-fPIC\" CFLAGS=\"-fPIC\" cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON -DWITH_CAPD=OFF -DWITH_PYTHON=ON -DPYBIND11_FINDPYTHON=OFF .. && \ +cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON -DWITH_CAPD=OFF -DWITH_PYTHON=ON -DPYBIND11_FINDPYTHON=OFF .. && \ make -j4 && \ \ make pip_package && \ diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index bab5e84ba..28bbd8813 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -16,8 +16,35 @@ endif() # Adds pybind11::headers, pybind11::module, pybind11::embed + # The Python interpreter, which this directory, tests/ and examples/ run + # through PYTHON_EXECUTABLE: to generate the docstrings below, to build the + # wheel, and to run the Python tests and examples. pybind11 sets it where this + # directory sees it only with PYBIND11_FINDPYTHON=OFF, through + # FindPythonLibsNew.cmake. By default (COMPAT, or NEW) it uses FindPython, + # which sets Python_EXECUTABLE (or Python3_EXECUTABLE), and sets + # PYTHON_EXECUTABLE, in COMPAT, only within its own directory: here it stayed + # empty, execute_process() ran doxygen2docstring.py as a program, and the + # configuration stopped on "permission denied". It is initialized here with + # the interpreter pybind11 builds the modules for, unless it was given, and + # cached, so that tests/ and examples/, configured after this directory, get + # it too. + if(NOT PYTHON_EXECUTABLE) + if(Python_EXECUTABLE) + set(_codac_python_executable "${Python_EXECUTABLE}") + elseif(Python3_EXECUTABLE) + set(_codac_python_executable "${Python3_EXECUTABLE}") + else() + find_package(Python COMPONENTS Interpreter REQUIRED) + set(_codac_python_executable "${Python_EXECUTABLE}") + endif() + set(PYTHON_EXECUTABLE "${_codac_python_executable}" CACHE FILEPATH "Python interpreter" FORCE) + endif() + message(STATUS "Python interpreter: ${PYTHON_EXECUTABLE}") + set(PYTHON_PACKAGE_NAME ${PROJECT_NAME}) - set(PYTHON_PACKAGE_DIR "${CMAKE_CURRENT_BINARY_DIR}/python_package") + set(PYTHON_PACKAGE_DIR "${CMAKE_CURRENT_BINARY_DIR}/python_package" + CACHE INTERNAL "Codac Python package build directory" + ) file(MAKE_DIRECTORY ${PYTHON_PACKAGE_DIR}) execute_process(COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/${PYTHON_PACKAGE_NAME}/" "${PYTHON_PACKAGE_DIR}/${PYTHON_PACKAGE_NAME}") @@ -64,7 +91,11 @@ execute_process(COMMAND ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/scripts/doxygen/doxygen2docstring.py ${CMAKE_CURRENT_BINARY_DIR}/../doc/api/xml/ - ${CMAKE_CURRENT_BINARY_DIR}/docstring) + ${CMAKE_CURRENT_BINARY_DIR}/docstring + RESULT_VARIABLE DOCSTRING_RESULT) + if(NOT DOCSTRING_RESULT EQUAL 0) + message(FATAL_ERROR "doxygen2docstring.py failed (exit code ${DOCSTRING_RESULT}) while generating the Python docstring headers.") + endif() endif() @@ -79,8 +110,19 @@ add_custom_target(pip_package) + # Built through "python -m build" rather than by invoking setup.py directly: + # setuptools deprecated the latter, and bdist_wheel reaches the wheel by way + # of the install command, so each build printed "setup.py install is + # deprecated". Both are scheduled for removal. + # + # --no-isolation reuses the setuptools and wheel of the environment instead of + # creating a virtual environment and downloading them again for every wheel. + # Every place that reaches this target installs them first (the two + # scripts/docker/build_pybinding*.sh, and the macosmatrix/vcmatrix + # workflows), so the isolated environment would only cost a download and + # would silently build against a different setuptools than the one tested. add_custom_command(TARGET pip_package PRE_BUILD - COMMAND ${PYTHON_EXECUTABLE} ARGS setup.py bdist_wheel -d ${CMAKE_BINARY_DIR} + COMMAND ${PYTHON_EXECUTABLE} ARGS -m build --wheel --no-isolation --outdir ${CMAKE_BINARY_DIR} WORKING_DIRECTORY ${PYTHON_PACKAGE_DIR} ) diff --git a/python/setup.py.in b/python/setup.py.in index 633f927e3..117df3d56 100644 --- a/python/setup.py.in +++ b/python/setup.py.in @@ -40,11 +40,13 @@ setup( 'pip>=19.0.0', 'vibes' ], - license="LGPLv3+", + # PEP 639: the licence is carried by an SPDX expression, and the matching + # "License :: ..." classifier is deprecated -- setuptools warns about it on + # every wheel build. The two must not be given together. + license="LGPL-3.0-or-later", classifiers=[ "Development Status :: 3 - Alpha", "Topic :: Scientific/Engineering :: Mathematics", - "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)", ], include_package_data=True, zip_safe=False diff --git a/python/src/core/CMakeLists.txt b/python/src/core/CMakeLists.txt index 9d83a210d..dca29011e 100644 --- a/python/src/core/CMakeLists.txt +++ b/python/src/core/CMakeLists.txt @@ -133,8 +133,13 @@ ) target_include_directories(_core + # First, so that a header is opened at src/... rather than through + # build/include: the path a translation unit used is the path gcov records, + # and the two spellings would split every header into two entries in the + # coverage report. See codac_publish_include_dirs() in the top-level CMakeLists.txt. + PRIVATE ${CODAC_SOURCE_INCLUDE_DIRS} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/../../docstring - PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/../../../include + PRIVATE ${CMAKE_BINARY_DIR}/src PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../ PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/ PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/actions/ @@ -157,9 +162,65 @@ ) target_link_libraries(_core - PRIVATE ${PROJECT_NAME}-core ${PROJECT_NAME}-sympy ${LIBS} Ibex::ibex + PRIVATE ${PROJECT_NAME}-core ${PROJECT_NAME}-sympy Threads::Threads Codac::gaol ) + # -------------------------------------------------------------- + # Sanitizer runtime as a shared library (Debug builds only) + # -------------------------------------------------------------- + # + # _core.so is loaded via dlopen() by an unsanitized python + # interpreter. By default Clang/GCC embed the ASan/UBSan runtime + # statically into shared libraries, which leaves symbols like + # __ubsan_vptr_type_cache or __asan_* unresolved at dlopen() time + # (python has no sanitizer runtime of its own to provide them). + # -shared-libsan makes _core.so depend on the sanitizer runtime as + # a proper shared library instead; that runtime is then LD_PRELOAD-ed + # for the python tests (see tests/CMakeLists.txt). + # Not gated behind CMAKE_BUILD_TYPE (see the matching note in the root + # CMakeLists.txt): that variable is meaningless for multi-configuration + # generators, so -shared-libsan is applied via a $ + # generator expression instead, which CMake evaluates correctly for + # both single- and multi-config generators. + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + target_compile_options(_core PRIVATE $<$:-shared-libsan>) + target_link_options(_core PRIVATE $<$:-shared-libsan>) + endif() + + # -------------------------------------------------------------- + # MSVC AddressSanitizer runtime DLL (Debug builds only) + # -------------------------------------------------------------- + # + # /fsanitize=address (enabled in Debug mode only if a matching runtime + # was found -- see the MSVC branch in the root CMakeLists.txt, which + # sets CODAC_MSVC_ASAN_RUNTIME_DLL) makes _core.pyd import that runtime + # DLL. Unlike Clang/GCC on Linux, there is no LD_PRELOAD-style env var + # and no "must be the first library loaded" requirement to work around + # on Windows -- the loader simply needs to be able to find that DLL when + # Python calls LoadLibrary() on _core.pyd, which it does by searching + # the directory of the loading module before PATH. So it is enough to + # copy the runtime DLL next to the built _core.pyd, the same way + # _core.pyd itself is already copied into the package folder below. + # CODAC_MSVC_ASAN_RUNTIME_DLL is already empty (and this is then a + # no-op) whenever the root CMakeLists.txt couldn't locate that runtime, + # so /fsanitize=address was in that case never even added. + if(MSVC AND CODAC_MSVC_ASAN_RUNTIME_DLL) + # CODAC_MSVC_ASAN_RUNTIME_DLL is now found regardless of which + # configuration will be built (see the root CMakeLists.txt), since a + # multi-config generator does not know that yet at configure time. So + # the copy itself has to stay conditional on the configuration that is + # actually being built, which a plain if() cannot express: + # $,copy_if_different,echo> selects between the real + # "-E copy_if_different" and "-E echo", which merely prints the two + # paths, so the DLL is only copied for a Debug-configuration build, in + # any generator. "-E true" would read better, but it only exists since + # CMake 3.16 while this project accepts 3.14. + add_custom_command(TARGET _core POST_BUILD + COMMAND ${CMAKE_COMMAND} -E $,copy_if_different,echo> "${CODAC_MSVC_ASAN_RUNTIME_DLL}" "${PYTHON_PACKAGE_DIR}/${PYTHON_PACKAGE_NAME}" + ) + endif() + + # Copy the generated library in the package folder add_custom_command(TARGET _core POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy "$" "${PYTHON_PACKAGE_DIR}/${PYTHON_PACKAGE_NAME}" diff --git a/python/src/core/codac2_py_core.cpp b/python/src/core/codac2_py_core.cpp index afe9b27f3..9183b93eb 100644 --- a/python/src/core/codac2_py_core.cpp +++ b/python/src/core/codac2_py_core.cpp @@ -179,6 +179,7 @@ PYBIND11_MODULE(_core, m) m.doc() = string(FOR_MATLAB ? "Matlab" : "Python") + " binding of Codac (core)"; m.attr("oo") = oo; m.attr("PI") = PI; + m.attr("FOR_MATLAB") = FOR_MATLAB; export_TimePropag(m); diff --git a/python/src/core/contractors/codac2_py_CtcDeriv.cpp b/python/src/core/contractors/codac2_py_CtcDeriv.cpp index b67a504b1..45ca04137 100644 --- a/python/src/core/contractors/codac2_py_CtcDeriv.cpp +++ b/python/src/core/contractors/codac2_py_CtcDeriv.cpp @@ -27,7 +27,12 @@ void export_contract(py::class_& exported) exported .def("contract", - [](const CtcDeriv& ctc, Slice& x, const Slice& v, const std::vector& ctc_indices) + // ctc_indices is read only by the if constexpr branch taken for + // IntervalVector; in the Interval instantiation that branch is + // discarded and the parameter goes unread, which MSVC /W4 reports as + // C4100. It cannot be dropped -- pybind11 takes the Python signature + // from this lambda, and the argument is exposed just below. + [](const CtcDeriv& ctc, Slice& x, const Slice& v, [[maybe_unused]] const std::vector& ctc_indices) -> py::tuple { if constexpr(std::is_same_v) @@ -44,7 +49,9 @@ void export_contract(py::class_& exported) "x"_a, "v"_a, "ctc_indices"_a = std::vector()) .def("contract", - [](const CtcDeriv& ctc, SlicedTube& x, const SlicedTube& v, const std::vector& ctc_indices) + // Same as above: unread in the Interval instantiation, kept for the + // Python signature. + [](const CtcDeriv& ctc, SlicedTube& x, const SlicedTube& v, [[maybe_unused]] const std::vector& ctc_indices) -> py::tuple { if constexpr(std::is_same_v) diff --git a/python/src/core/contractors/codac2_py_CtcFixpoint.cpp b/python/src/core/contractors/codac2_py_CtcFixpoint.cpp index 989c4baa7..9743f82f5 100644 --- a/python/src/core/contractors/codac2_py_CtcFixpoint.cpp +++ b/python/src/core/contractors/codac2_py_CtcFixpoint.cpp @@ -26,7 +26,7 @@ void export_CtcFixpoint(py::module& m, py::class_,pyCtcI exported .def(py::init( - [](const pyCtcIntervalVector& c, double ratio) + [](const CtcBase& c, double ratio) { return std::make_unique(c.copy(),ratio); }), diff --git a/python/src/core/contractors/codac2_py_CtcInverse.h b/python/src/core/contractors/codac2_py_CtcInverse.h index 46a5b5fa9..4bada2e13 100644 --- a/python/src/core/contractors/codac2_py_CtcInverse.h +++ b/python/src/core/contractors/codac2_py_CtcInverse.h @@ -81,7 +81,7 @@ void export_CtcInverse(py::module& m, }; if(xs.size() == 0) - assert_release(false && "contract() expects at least one argument"); + assert_release_unreachable("contract() expects at least one argument"); // Fast paths already consistent with the typed overloads @@ -114,7 +114,7 @@ void export_CtcInverse(py::module& m, has_tube_args = true; else { - assert_release(false && "contract(): variadic mode only supports " + assert_release_unreachable("contract(): variadic mode only supports " "Interval / IntervalVector / SlicedTube / SlicedTube"); } } diff --git a/python/src/core/contractors/codac2_py_CtcLazy.cpp b/python/src/core/contractors/codac2_py_CtcLazy.cpp index 10f25c61e..8d995281a 100644 --- a/python/src/core/contractors/codac2_py_CtcLazy.cpp +++ b/python/src/core/contractors/codac2_py_CtcLazy.cpp @@ -26,7 +26,7 @@ void export_CtcLazy(py::module& m, py::class_,pyCtcInter exported .def(py::init( - [](const pyCtcIntervalVector& c) + [](const CtcBase& c) { return std::make_unique(c.copy()); }), diff --git a/python/src/core/contractors/codac2_py_CtcNot.cpp b/python/src/core/contractors/codac2_py_CtcNot.cpp index f7e3ac89e..0769e01e9 100644 --- a/python/src/core/contractors/codac2_py_CtcNot.cpp +++ b/python/src/core/contractors/codac2_py_CtcNot.cpp @@ -26,7 +26,7 @@ void export_CtcNot(py::module& m, py::class_,pyCtcInterv exported .def(py::init( - [](const pyCtcIntervalVector& c) + [](const CtcBase& c) { return std::make_unique(c.copy()); }), diff --git a/python/src/core/domains/interval/codac2_py_Interval.cpp b/python/src/core/domains/interval/codac2_py_Interval.cpp index 2e92ca484..2de7f80d3 100644 --- a/python/src/core/domains/interval/codac2_py_Interval.cpp +++ b/python/src/core/domains/interval/codac2_py_Interval.cpp @@ -20,6 +20,12 @@ #include "codac2_py_matlab.h" #include "codac2_py_deprecated.h" +// pybind11 binds the in-place operators as py::self |= py::self and the like, +// which Clang reads as a variable assigned to itself (-Wself-assign-overloaded) +#if defined(__clang__) + #pragma clang diagnostic ignored "-Wself-assign-overloaded" +#endif + using namespace std; using namespace codac2; namespace py = pybind11; diff --git a/python/src/core/domains/interval/codac2_py_IntervalMatrixBase.h b/python/src/core/domains/interval/codac2_py_IntervalMatrixBase.h index a5e4c7a24..3d770084e 100644 --- a/python/src/core/domains/interval/codac2_py_IntervalMatrixBase.h +++ b/python/src/core/domains/interval/codac2_py_IntervalMatrixBase.h @@ -16,6 +16,15 @@ #include "codac2_py_MatrixBase.h" +// pybind11 binds the in-place operators as py::self |= py::self and the like, +// which Clang reads as a variable assigned to itself (-Wself-assign-overloaded). +// Popped at the end of this file, so that the files including it keep the +// warning for their own code. +#if defined(__clang__) + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wself-assign-overloaded" +#endif + using namespace std; using namespace codac2; namespace py = pybind11; @@ -242,3 +251,7 @@ void export_IntervalMatrixBase(py::module& m, py::class_& pyclass) py::implicitly_convertible(); } + +#if defined(__clang__) + #pragma clang diagnostic pop +#endif diff --git a/python/src/core/domains/interval/codac2_py_Interval_operations.cpp b/python/src/core/domains/interval/codac2_py_Interval_operations.cpp index 3d276d446..44e70a351 100644 --- a/python/src/core/domains/interval/codac2_py_Interval_operations.cpp +++ b/python/src/core/domains/interval/codac2_py_Interval_operations.cpp @@ -157,7 +157,7 @@ void export_Interval_operations(py::module& m, py::class_& py_Interval INTERVAL_TAN_CONST_INTERVAL_REF, "x"_a) - .def("atan", &codac2::atan, + .def("acos", &codac2::acos, INTERVAL_ACOS_CONST_INTERVAL_REF, "x"_a) @@ -177,7 +177,7 @@ void export_Interval_operations(py::module& m, py::class_& py_Interval INTERVAL_COSH_CONST_INTERVAL_REF, "x"_a) - .def("asinh", &codac2::asinh, + .def("sinh", &codac2::sinh, INTERVAL_SINH_CONST_INTERVAL_REF, "x"_a) diff --git a/python/src/core/domains/tube/codac2_py_Slice.h b/python/src/core/domains/tube/codac2_py_Slice.h index 268ebe3f9..8f260e842 100644 --- a/python/src/core/domains/tube/codac2_py_Slice.h +++ b/python/src/core/domains/tube/codac2_py_Slice.h @@ -33,9 +33,11 @@ py::class_> export_Slice(py::module& m, const std::string& name) // Methods from class SliceBase .def("t0_tf", &Slice::t0_tf, + py::return_value_policy::reference_internal, CONST_INTERVAL_REF_SLICEBASE_T0_TF_CONST) .def("tslice", &Slice::tslice, + py::return_value_policy::reference_internal, CONST_TSLICE_REF_SLICEBASE_TSLICE_CONST) // Methods from class Slice diff --git a/python/src/core/domains/tube/codac2_py_SlicedTube.h b/python/src/core/domains/tube/codac2_py_SlicedTube.h index 85bfc96e4..584f19b34 100644 --- a/python/src/core/domains/tube/codac2_py_SlicedTube.h +++ b/python/src/core/domains/tube/codac2_py_SlicedTube.h @@ -84,9 +84,11 @@ py::class_,TubeBase> export_SlicedTube(py::module& m, const std::s py::return_value_policy::reference, SHARED_PTR_SLICE_T_SLICEDTUBE_T_LAST_SLICE) - .def("slice", [](SlicedTube& x, std::shared_ptr it) -> Slice& + .def("slice", [](SlicedTube& x, TSlice& it) -> Slice& { - return *x.slice(it); + return *x.slice( + std::shared_ptr(&it, [](TSlice*) {}) + ); }, py::return_value_policy::reference, SHARED_PTR_SLICE_T_SLICEDTUBE_T_SLICE_SHARED_PTR_TSLICE, diff --git a/python/src/core/domains/tube/codac2_py_SlicedTube_operations.cpp b/python/src/core/domains/tube/codac2_py_SlicedTube_operations.cpp index bb037bd37..034adbb3a 100644 --- a/python/src/core/domains/tube/codac2_py_SlicedTube_operations.cpp +++ b/python/src/core/domains/tube/codac2_py_SlicedTube_operations.cpp @@ -17,6 +17,12 @@ #include "codac2_py_SlicedTube_operations_docs.h" // Generated file from Doxygen XML (doxygen2docstring.py) #include "codac2_py_deprecated.h" +// pybind11 binds the in-place operators as py::self |= py::self and the like, +// which Clang reads as a variable assigned to itself (-Wself-assign-overloaded) +#if defined(__clang__) + #pragma clang diagnostic ignored "-Wself-assign-overloaded" +#endif + using namespace codac2; namespace py = pybind11; using namespace pybind11::literals; @@ -194,6 +200,23 @@ namespace SLICEDTUBE_T_REF_OPERATORMULEQ_SLICEDTUBE_T_REF_CONST_SLICEDTUBE_INTERVAL_REF, py::return_value_policy::reference_internal, py::is_operator()); + + if constexpr(!FOR_MATLAB && std::is_same_v) + { + pyclass + .def("__matmul__", (SlicedTube(*)(const SlicedTube&,const SlicedTube&)) &codac2::operator*, + SLICEDTUBE_INTERVALMATRIX_OPERATORMUL_CONST_SLICEDTUBE_INTERVALMATRIX_REF_CONST_SLICEDTUBE_INTERVALMATRIX_REF, + py::is_operator()) + + .def("__matmul__", (SlicedTube(*)(const SlicedTube&,const T&)) &codac2::operator*, + SLICEDTUBE_T_OPERATORMUL_CONST_SLICEDTUBE_T_REF_CONST_Q_REF, + py::is_operator()) + + .def("__rmatmul__", + [](const SlicedTube& x2, const T& x1) { return x1 * x2; }, + SLICEDTUBE_T_OPERATORMUL_CONST_Q_REF_CONST_SLICEDTUBE_T_REF, + py::is_operator()); + } } } @@ -401,6 +424,14 @@ void export_SlicedTube_operations( SLICEDTUBE_INTERVALVECTOR_OPERATORMUL_CONST_SLICEDTUBE_INTERVALMATRIX_REF_CONST_SLICEDTUBE_INTERVALVECTOR_REF, py::is_operator()); + if constexpr(!FOR_MATLAB) + { + py_SlicedTube_IntervalMatrix + .def("__matmul__", (SlicedTube(*)(const SlicedTube&,const SlicedTube&)) &codac2::operator*, + SLICEDTUBE_INTERVALVECTOR_OPERATORMUL_CONST_SLICEDTUBE_INTERVALMATRIX_REF_CONST_SLICEDTUBE_INTERVALVECTOR_REF, + py::is_operator()); + } + bind_scalar_slicedtube_functions(m); py_SlicedTube_Interval diff --git a/python/src/core/domains/tube/codac2_py_TDomain.cpp b/python/src/core/domains/tube/codac2_py_TDomain.cpp index c061ad9c5..3eadf7dfe 100644 --- a/python/src/core/domains/tube/codac2_py_TDomain.cpp +++ b/python/src/core/domains/tube/codac2_py_TDomain.cpp @@ -40,19 +40,21 @@ void export_TDomain(py::module& m) .def("tslices_vector", &TDomain::tslices_vector, VECTOR_TSLICE_TDOMAIN_TSLICES_VECTOR_CONST) - .def("tslice", [](TDomain& tdomain, double t) -> std::shared_ptr + .def("tslice", [](TDomain& tdomain, double t) -> TSlice& { auto it = tdomain.tslice(t); - return std::shared_ptr(&(*it), [](TSlice*){}); + return *it; }, - LIST_TSLICE_ITERATOR_TDOMAIN_TSLICE_DOUBLE + py::return_value_policy::reference_internal, + LIST_TSLICE_ITERATOR_TDOMAIN_TSLICE_DOUBLE, "t"_a) - .def("sample", [](TDomain& tdomain, double t, bool with_gate) -> std::shared_ptr + .def("sample", [](TDomain& tdomain, double t, bool with_gate) -> TSlice& { auto it = tdomain.sample(t, with_gate); - return std::shared_ptr(&(*it), [](TSlice*){}); + return *it; }, + py::return_value_policy::reference_internal, LIST_TSLICE_ITERATOR_TDOMAIN_SAMPLE_DOUBLE_BOOL, "t"_a, "with_gate"_a=false) diff --git a/python/src/core/domains/tube/codac2_py_tube_cart_prod.cpp b/python/src/core/domains/tube/codac2_py_tube_cart_prod.cpp index c7bf4e209..7ef0d2998 100644 --- a/python/src/core/domains/tube/codac2_py_tube_cart_prod.cpp +++ b/python/src/core/domains/tube/codac2_py_tube_cart_prod.cpp @@ -27,11 +27,11 @@ void export_tube_cart_prod(py::module& m) Index n = 0; for(const auto& li : l) { - assert_release(is_instance>(li) | is_instance>(li)); + assert_release(is_instance>(li) || is_instance>(li)); n += is_instance>(li) ? 1 : cast>(li).size(); } - assert_release(is_instance>(*l.begin()) | is_instance>(*l.begin())); + assert_release(is_instance>(*l.begin()) || is_instance>(*l.begin())); std::shared_ptr tdomain = is_instance>(*l.begin()) ? cast>(*l.begin()).tdomain() : cast>(*l.begin()).tdomain(); @@ -42,7 +42,7 @@ void export_tube_cart_prod(py::module& m) Index i = 0; for(const auto& li : l) { - assert_release(is_instance>(li) | is_instance>(li)); + assert_release(is_instance>(li) || is_instance>(li)); IntervalVector si = cart_prod( is_instance>(li) ? cart_prod(cast>(li).slice(it)->codomain()) : cast>(li).slice(it)->codomain()); diff --git a/python/src/core/functions/analytic/codac2_py_AnalyticExprWrapper.h b/python/src/core/functions/analytic/codac2_py_AnalyticExprWrapper.h index 652e398ef..0a161f0bb 100644 --- a/python/src/core/functions/analytic/codac2_py_AnalyticExprWrapper.h +++ b/python/src/core/functions/analytic/codac2_py_AnalyticExprWrapper.h @@ -129,6 +129,13 @@ inline void export_VectorExpr(py::module& m) ; + if constexpr(!FOR_MATLAB) + { + exported + .def("__rmatmul__", [](const VectorExpr& e1, const MatrixExpr& e2) { return e2*e1; }, py::is_operator()) + ; + } + py::implicitly_convertible(); py::implicitly_convertible(); py::implicitly_convertible(); @@ -181,6 +188,20 @@ inline void export_MatrixExpr(py::module& m) ; + if constexpr(!FOR_MATLAB) + { + exported + .def("__matmul__", [](const MatrixExpr& e1, const MatrixExpr& e2) { return e1*e2; }, py::is_operator()) + .def("__matmul__", [](const MatrixExpr& e1, const IntervalMatrix& e2) { return e1*e2; }, py::is_operator()) + .def("__matmul__", [](const MatrixExpr& e1, const MatrixVar& e2) { return e1*e2; }, py::is_operator()) + .def("__rmatmul__", [](const MatrixExpr& e1, const IntervalMatrix& e2) { return e2*e1; }, py::is_operator()) + .def("__rmatmul__", [](const MatrixExpr& e1, const MatrixVar& e2) { return e2*e1; }, py::is_operator()) + .def("__matmul__", [](const MatrixExpr& e1, const VectorVar& e2) { return e1*e2; }, py::is_operator()) + .def("__matmul__", [](const MatrixExpr& e1, const IntervalVector& e2) { return e1*e2; }, py::is_operator()) + .def("__matmul__", [](const MatrixExpr& e1, const VectorExpr& e2) { return e1*e2; }, py::is_operator()) + ; + } + py::implicitly_convertible(); py::implicitly_convertible(); py::implicitly_convertible(); diff --git a/python/src/core/functions/analytic/codac2_py_analytic_variables.cpp b/python/src/core/functions/analytic/codac2_py_analytic_variables.cpp index ff5ef04ef..16edf41cc 100644 --- a/python/src/core/functions/analytic/codac2_py_analytic_variables.cpp +++ b/python/src/core/functions/analytic/codac2_py_analytic_variables.cpp @@ -161,6 +161,14 @@ void export_VectorVar(py::module& m) .def("__truediv__", [](const VectorVar& e1, const ScalarExpr& e2) { return e1 / e2; }, py::is_operator()) .def("__truediv__", [](const VectorVar& e1, const Interval& e2) { return e1 / e2; }, py::is_operator()) ; + + if constexpr(!FOR_MATLAB) + { + exported + .def("__rmatmul__", [](const VectorVar& e1, const IntervalMatrix& e2) { return e2 * e1; }, py::is_operator()) + .def("__rmatmul__", [](const VectorVar& e1, const MatrixExpr& e2) { return e2 * e1; }, py::is_operator()) + ; + } py::implicitly_convertible(); } @@ -227,6 +235,20 @@ void export_MatrixVar(py::module& m) .def("__truediv__", [](const MatrixVar& e1, const ScalarExpr& e2) { return e1 / e2; }, py::is_operator()) .def("__truediv__", [](const MatrixVar& e1, const Interval& e2) { return e1 / e2; }, py::is_operator()) ; + + if constexpr(!FOR_MATLAB) + { + exported + .def("__matmul__", [](const MatrixVar& e1, const VectorVar& e2) { return e1 * e2; }, py::is_operator()) + .def("__matmul__", [](const MatrixVar& e1, const VectorExpr& e2) { return e1 * e2; }, py::is_operator()) + .def("__matmul__", [](const MatrixVar& e1, const IntervalVector& e2) { return e1 * e2; }, py::is_operator()) + .def("__matmul__", [](const MatrixVar& e1, const MatrixVar& e2) { return e1 * e2; }, py::is_operator()) + .def("__matmul__", [](const MatrixVar& e1, const MatrixExpr& e2) { return e1 * e2; }, py::is_operator()) + .def("__matmul__", [](const MatrixVar& e1, const IntervalMatrix& e2) { return e1 * e2; }, py::is_operator()) + .def("__rmatmul__", [](const MatrixVar& e1, const IntervalMatrix& e2) { return e2 * e1; }, py::is_operator()) + .def("__rmatmul__", [](const MatrixVar& e1, const MatrixExpr& e2) { return e2 * e1; }, py::is_operator()) + ; + } py::implicitly_convertible(); } \ No newline at end of file diff --git a/python/src/core/geometry/codac2_py_ConvexPolygon.cpp b/python/src/core/geometry/codac2_py_ConvexPolygon.cpp index 868750a6d..0ad631723 100644 --- a/python/src/core/geometry/codac2_py_ConvexPolygon.cpp +++ b/python/src/core/geometry/codac2_py_ConvexPolygon.cpp @@ -13,6 +13,12 @@ #include #include "codac2_py_ConvexPolygon_docs.h" // Generated file from Doxygen XML (doxygen2docstring.py): +// pybind11 binds the in-place operators as py::self |= py::self and the like, +// which Clang reads as a variable assigned to itself (-Wself-assign-overloaded) +#if defined(__clang__) + #pragma clang diagnostic ignored "-Wself-assign-overloaded" +#endif + using namespace std; using namespace codac2; namespace py = pybind11; diff --git a/python/src/core/matrices/codac2_py_MatrixBase.h b/python/src/core/matrices/codac2_py_MatrixBase.h index 457d03fbd..00f8ce98b 100644 --- a/python/src/core/matrices/codac2_py_MatrixBase.h +++ b/python/src/core/matrices/codac2_py_MatrixBase.h @@ -261,12 +261,12 @@ void export_MatrixBase(py::module& m, py::class_& pyclass) DOC_TO_BE_DEFINED, "nb_rows"_a, "nb_cols"_a) - .def("resize_save_values", [](S& x, Index_type nb_rows, Index_type nb_cols) + .def("conservativeResize", [](S& x, Index_type nb_rows, Index_type nb_cols) { matlab::test_integer(nb_rows, nb_cols); - x.resize_save_values(nb_rows, nb_cols); + x.conservativeResize(nb_rows, nb_cols); }, - MATRIX_ADDONS_MATRIXBASE_VOID_RESIZE_SAVE_VALUES_INDEX_INDEX, + "Resize the matrix while preserving the existing coefficients.", "nb_rows"_a, "nb_cols"_a) .def_static("zero", [](Index_type r, Index_type c) diff --git a/python/src/core/matrices/codac2_py_VectorBase.h b/python/src/core/matrices/codac2_py_VectorBase.h index 89b41e4cb..feea0fa83 100644 --- a/python/src/core/matrices/codac2_py_VectorBase.h +++ b/python/src/core/matrices/codac2_py_VectorBase.h @@ -105,12 +105,12 @@ void export_VectorBase([[maybe_unused]] py::module& m, py::class_& pyclass) DOC_TO_BE_DEFINED, "n"_a) - .def("resize_save_values", [](S& x, Index_type n) + .def("conservativeResize", [](S& x, Index_type n) { matlab::test_integer(n); - x.resize_save_values(n); + x.conservativeResize(n); }, - MATRIX_ADDONS_VECTORBASE_VOID_RESIZE_SAVE_VALUES_INDEX, + "Resize the vector while preserving the existing coefficients.", "n"_a) .def("put", [](S& x, Index_type start_id, const S& x1) diff --git a/python/src/core/matrices/codac2_py_arithmetic_mul.cpp b/python/src/core/matrices/codac2_py_arithmetic_mul.cpp index 12f56a671..a3f05ccf3 100644 --- a/python/src/core/matrices/codac2_py_arithmetic_mul.cpp +++ b/python/src/core/matrices/codac2_py_arithmetic_mul.cpp @@ -15,6 +15,7 @@ #include #include #include +#include "codac2_py_matlab.h" using namespace std; using namespace codac2; @@ -153,4 +154,49 @@ void export_arithmetic_mul( py_IM.def("__imul__", [](IntervalMatrix& x1, const IntervalMatrix& x2) { return x1*=x2; }, py::is_operator()); py_IM.def("__imul__", [](IntervalMatrix& x1, const IB& x2) { return x1*=x2; }, py::is_operator()); + // ====== Matrix products with the @ operator (not available in Matlab) + + if constexpr(!FOR_MATLAB) + { + //Vector operator*(const M& x1, const Vector& x2) + py_M.def("__matmul__", [](const Matrix& x1, const Vector& x2) -> Vector { return x1*x2; }, py::is_operator()); + py_B.def("__matmul__", [](const B& x1, const Vector& x2) -> Vector { return x1*x2; }, py::is_operator()); + + //Matrix operator*(const M& x1, const M_& x2) + py_M.def("__matmul__", [](const Matrix& x1, const Matrix& x2) -> Matrix { return x1*x2; }, py::is_operator()); + py_M.def("__matmul__", [](const Matrix& x1, const B& x2) -> Matrix { return x1*x2; }, py::is_operator()); + py_B.def("__matmul__", [](const B& x1, const Matrix& x2) -> Matrix { return x1*x2; }, py::is_operator()); + py_B.def("__matmul__", [](const B& x1, const B& x2) -> Matrix { return x1*x2; }, py::is_operator()); + + //IntervalVector operator*(const M& x1, const IntervalVector& x2) + py_M.def("__matmul__", [](const Matrix& x1, const IntervalVector& x2) -> IntervalVector { return x1*x2; }, py::is_operator()); + py_B.def("__matmul__", [](const B& x1, const IntervalVector& x2) -> IntervalVector { return x1*x2; }, py::is_operator()); + + //IntervalMatrix operator*(const M& x1, const IM& x2) + py_M.def("__matmul__", [](const Matrix& x1, const IntervalMatrix& x2) -> IntervalMatrix { return x1.template cast()*x2; }, py::is_operator()); + py_M.def("__matmul__", [](const Matrix& x1, const IB& x2) -> IntervalMatrix { return x1.template cast()*x2; }, py::is_operator()); + py_B.def("__matmul__", [](const B& x1, const IntervalMatrix& x2) -> IntervalMatrix { return x1.template cast()*x2; }, py::is_operator()); + py_B.def("__matmul__", [](const B& x1, const IB& x2) -> IntervalMatrix { return x1.template cast()*x2; }, py::is_operator()); + + //IntervalVector operator*(const IM& x1, const Vector& x2) + py_IM.def("__matmul__", [](const IntervalMatrix& x1, const Vector& x2) -> IntervalVector { return x1*x2.template cast(); }, py::is_operator()); + py_IB.def("__matmul__", [](const IB& x1, const Vector& x2) -> IntervalVector { return x1*x2.template cast(); }, py::is_operator()); + + //IntervalMatrix operator*(const IM& x1, const M& x2) + py_IM.def("__matmul__", [](const IntervalMatrix& x1, const Matrix& x2) -> IntervalMatrix { return x1*x2.template cast(); }, py::is_operator()); + py_IM.def("__matmul__", [](const IntervalMatrix& x1, const B& x2) -> IntervalMatrix { return x1*x2.template cast(); }, py::is_operator()); + py_IB.def("__matmul__", [](const IB& x1, const Matrix& x2) -> IntervalMatrix { return x1*x2.template cast(); }, py::is_operator()); + py_IB.def("__matmul__", [](const IB& x1, const B& x2) -> IntervalMatrix { return x1*x2.template cast(); }, py::is_operator()); + + //IntervalVector operator*(const IM& x1, const IntervalVector& x2) + py_IM.def("__matmul__", [](const IntervalMatrix& x1, const IntervalVector& x2) -> IntervalVector { return x1*x2; }, py::is_operator()); + py_IB.def("__matmul__", [](const IB& x1, const IntervalVector& x2) -> IntervalVector { return x1*x2; }, py::is_operator()); + + //IntervalMatrix operator*(const IM& x1, const IM_& x2) + py_IM.def("__matmul__", [](const IntervalMatrix& x1, const IntervalMatrix& x2) -> IntervalMatrix { return x1*x2; }, py::is_operator()); + py_IM.def("__matmul__", [](const IntervalMatrix& x1, const IB& x2) -> IntervalMatrix { return x1*x2; }, py::is_operator()); + py_IB.def("__matmul__", [](const IB& x1, const IntervalMatrix& x2) -> IntervalMatrix { return x1*x2; }, py::is_operator()); + py_IB.def("__matmul__", [](const IB& x1, const IB& x2) -> IntervalMatrix { return x1*x2; }, py::is_operator()); + } + } \ No newline at end of file diff --git a/python/src/core/tools/codac2_py_transformations.cpp b/python/src/core/tools/codac2_py_transformations.cpp index 7be3fae73..8f09c1846 100644 --- a/python/src/core/tools/codac2_py_transformations.cpp +++ b/python/src/core/tools/codac2_py_transformations.cpp @@ -15,6 +15,7 @@ #include #include "codac2_py_transformations_docs.h" // Generated file from Doxygen XML (doxygen2docstring.py): #include "codac2_py_doc.h" +#include "codac2_py_matlab.h" using namespace std; using namespace codac2; @@ -41,6 +42,21 @@ void export_transformations(py::module& m) py::is_operator()) ; + + if constexpr(!FOR_MATLAB) + { + exported_affine2d + + .def("__matmul__", [](const Eigen::Affine2d& x1, const Vector& x2) + { + assert_release(x2.size() == 2); + return Vector(x1*Eigen::Vector2d(x2)); + }, + DOC_TO_BE_DEFINED, + py::is_operator()) + + ; + } m diff --git a/python/src/core/trajectory/codac2_py_SampledTraj.cpp b/python/src/core/trajectory/codac2_py_SampledTraj.cpp index 546aeced3..43ff82172 100644 --- a/python/src/core/trajectory/codac2_py_SampledTraj.cpp +++ b/python/src/core/trajectory/codac2_py_SampledTraj.cpp @@ -236,8 +236,12 @@ void add_traj_operators(py::class_>& pyclass) .def("__rmul__", [](const SampledTraj& x2, double x1) { return x1*x2; }, SAMPLEDTRAJ_T_OPERATORMUL_DOUBLE_CONST_SAMPLEDTRAJ_T_REF, py::is_operator()) + + .def("__mul__", [](const SampledTraj& x1, const T& x2) { return x1*x2; }, + SAMPLEDTRAJ_T_OPERATORMUL_CONST_SAMPLEDTRAJ_T_REF_CONST_Q_REF, + py::is_operator()) - .def("__mul__", [](const T& x1, const SampledTraj& x2) { return x1*x2; }, + .def("__rmul__", [](const SampledTraj& x2, const T& x1) { return x1*x2; }, SAMPLEDTRAJ_T_OPERATORMUL_CONST_Q_REF_CONST_SAMPLEDTRAJ_T_REF, py::is_operator()) @@ -288,6 +292,25 @@ void export_SampledTraj(py::module& m) ; + if constexpr(!FOR_MATLAB) + { + py_SampledTraj_Matrix + + .def("__matmul__", [](const SampledTraj& x1, const SampledTraj& x2) { return x1*x2; }, + SAMPLEDTRAJ_VECTOR_OPERATORMUL_CONST_SAMPLEDTRAJ_MATRIX_REF_CONST_SAMPLEDTRAJ_VECTOR_REF, + py::is_operator()) + + .def("__matmul__", [](const SampledTraj& x1, const SampledTraj& x2) { return x1*x2; }, + SAMPLEDTRAJ_T_OPERATORMUL_CONST_SAMPLEDTRAJ_T_REF_CONST_SAMPLEDTRAJ_T_REF, + py::is_operator()) + + .def("__rmatmul__", [](const SampledTraj& x2, const Matrix& x1) { return x1*x2; }, + SAMPLEDTRAJ_T_OPERATORMUL_CONST_Q_REF_CONST_SAMPLEDTRAJ_T_REF, + py::is_operator()) + + ; + } + m .def("sqr", (SampledTraj (*)(const SampledTraj&)) &codac2::sqr, diff --git a/python/src/graphics/CMakeLists.txt b/python/src/graphics/CMakeLists.txt index 7b180a814..b35a6683c 100644 --- a/python/src/graphics/CMakeLists.txt +++ b/python/src/graphics/CMakeLists.txt @@ -18,8 +18,13 @@ ) target_include_directories(_graphics + # First, so that a header is opened at src/... rather than through + # build/include: the path a translation unit used is the path gcov records, + # and the two spellings would split every header into two entries in the + # coverage report. See codac_publish_include_dirs() in the top-level CMakeLists.txt. + PRIVATE ${CODAC_SOURCE_INCLUDE_DIRS} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/../../docstring - PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/../../../include + PRIVATE ${CMAKE_BINARY_DIR}/src PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../core/ PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../ PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/ @@ -28,7 +33,7 @@ ) target_link_libraries(_graphics - PRIVATE ${PROJECT_NAME}-graphics ${LIBS} Ibex::ibex + PRIVATE ${PROJECT_NAME}-graphics Threads::Threads Codac::gaol ) # Copy the generated library in the package folder diff --git a/python/src/unsupported/CMakeLists.txt b/python/src/unsupported/CMakeLists.txt index a413804dd..6b3e93ec6 100644 --- a/python/src/unsupported/CMakeLists.txt +++ b/python/src/unsupported/CMakeLists.txt @@ -9,14 +9,19 @@ ) target_include_directories(_unsupported + # First, so that a header is opened at src/... rather than through + # build/include: the path a translation unit used is the path gcov records, + # and the two spellings would split every header into two entries in the + # coverage report. See codac_publish_include_dirs() in the top-level CMakeLists.txt. + PRIVATE ${CODAC_SOURCE_INCLUDE_DIRS} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/../../docstring - PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/../../../include + PRIVATE ${CMAKE_BINARY_DIR}/src PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../ PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/ ) target_link_libraries(_unsupported - PRIVATE ${PROJECT_NAME}-unsupported ${LIBS} Ibex::ibex + PRIVATE ${PROJECT_NAME}-unsupported Threads::Threads Codac::gaol ) # Copy the generated library in the package folder diff --git a/scripts/CMakeModules/FindGAOL.cmake b/scripts/CMakeModules/FindGAOL.cmake new file mode 100644 index 000000000..a8f44ae50 --- /dev/null +++ b/scripts/CMakeModules/FindGAOL.cmake @@ -0,0 +1,208 @@ +# ================================================================== +# Codac - cmake module looking for an installed GAOL +# ================================================================== +# +# Looks for GAOL, the interval arithmetic library Codac is built upon +# (Frederic Goualard, https://frederic.goualard.net, +# https://github.com/goualard-f/GAOL), and for mathlib, the IBM Accurate +# Portable Mathematical Library GAOL computes its elementary functions with, +# which installs itself as libultim. +# +# Where to look, when GAOL is not installed in a directory CMake searches by +# default, is given by two cache entries -- or by CMAKE_PREFIX_PATH: +# +# GAOL_DIR prefix holding include/gaol/gaol.h and lib/ +# MATHLIB_DIR prefix holding include/MathLib.h and lib/; +# GAOL_DIR is searched for mathlib as well, the two being +# usually installed together +# +# What is found is written to the cache entries GAOL_INCDIR, GAOL_LIB, +# MATHLIB_INCDIR and MATHLIB_LIB, which can also be set by hand to bypass the +# search, and summarised in: +# +# GAOL_FOUND +# GAOL_VERSION when gaol/gaol_configuration.h states it +# GAOL_INCLUDE_DIRS +# GAOL_LIBRARIES gaol, then ultim, in link order +# +# No target is defined here. codac_gaol_find() (scripts/CMakeModules/codac_gaol.cmake) +# makes Codac::gaol out of these variables, with the flags of interval +# arithmetic it determines itself, for a GAOL that has neither a CMake package +# nor a gaol.pc, which it looks for first. +# +# Origin +# ------ +# This is the search IBEX runs before building a GAOL of its own, taken out of +# IBEX (https://github.com/ibex-team/ibex-lib, GNU LGPL v3) so that Codac can +# find GAOL without IBEX: +# +# - codac_find_header_custom() and codac_find_library_custom() are the +# functions find_header_custom() and find_library_custom() of IBEX's +# cmake.utils/IbexUtils.cmake, written by Cyril Bouvier for the CMake build +# of IBEX. They are renamed with a codac_ prefix so as not to clash with +# IBEX's own definitions in a project that loads both, and differ in +# two respects: they stay silent when find_package(GAOL QUIET) asks for it, +# and a header found on the default search path is returned as it is, +# rather than wrapped in $ -- that wrapping is how IBEX +# keeps such a directory out of the export files it generates, and Codac +# writes its configuration file by other means (see src/CMakeLists.txt). +# +# - The four calls below, the GAOL_DIR and MATHLIB_DIR hints and the names of +# the result variables are those of IBEX's +# interval_lib_wrapper/gaol/CMakeLists.txt, by Cyril Bouvier as well. +# +# - Reading the version out of gaol/gaol_configuration.h comes from +# interval_lib_wrapper/gaol/FindGaol.cmake of IBEX (Cyril Bouvier). It +# reads the GAOL_MAJOR_VERSION, GAOL_MINOR_VERSION and GAOL_MICRO_VERSION +# macros here, which both the autotools and the CMake builds of GAOL write. + + +################################################################################ +# Functions from IBEX (see above) +################################################################################ + + function(codac_find_header_custom prefix hdrname) + set(opt "") + set(oneArgs "") + set(multiArgs PATHS) + + cmake_parse_arguments(FHC "${opt}" "${oneArgs}" "${multiArgs}" ${ARGN}) + + if(FHC_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Unknown keywords given to codac_find_header_custom(): \"${FHC_UNPARSED_ARGUMENTS}\"") + endif() + + set(MSG "Looking for ${hdrname}") + if(NOT GAOL_FIND_QUIETLY) + message(STATUS "${MSG}") + endif() + + # First look only in PATHS if given + if(FHC_PATHS) + find_path(${prefix}_INCDIR ${hdrname} PATHS ${FHC_PATHS} + DOC "Set to exact include directory to bypass internal test" + PATH_SUFFIXES include NO_DEFAULT_PATH) + endif() + + if(NOT ${prefix}_INCDIR) + # Now look with system and cmake paths + find_path(${prefix}_INCDIR ${hdrname} + DOC "Set to exact include directory to bypass internal test" + PATH_SUFFIXES include) + endif() + + if(NOT GAOL_FIND_QUIETLY) + if(${prefix}_INCDIR) + message(STATUS "${MSG} -- found at ${${prefix}_INCDIR}") + else() + message(STATUS "${MSG} -- not found") + endif() + endif() + + set(${prefix}_INCDIR ${${prefix}_INCDIR} PARENT_SCOPE) + mark_as_advanced(${prefix}_INCDIR) + endfunction() + + + function(codac_find_library_custom prefix libname) + set(opt "") + set(oneArgs "") + set(multiArgs PATHS) + + cmake_parse_arguments(FLC "${opt}" "${oneArgs}" "${multiArgs}" ${ARGN}) + + if(FLC_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Unknown keywords given to codac_find_library_custom(): \"${FLC_UNPARSED_ARGUMENTS}\"") + endif() + + set(MSG "Looking for ${libname}") + if(NOT GAOL_FIND_QUIETLY) + message(STATUS "${MSG}") + endif() + + # First look only in PATHS if given + if(FLC_PATHS) + find_library(${prefix}_LIB ${libname} PATHS ${FLC_PATHS} + DOC "Set to exact lib directory to bypass internal test" + PATH_SUFFIXES lib NO_DEFAULT_PATH) + endif() + + if(NOT ${prefix}_LIB) + # Now look with system and cmake paths + find_library(${prefix}_LIB ${libname} + DOC "Set to exact lib directory to bypass internal test" + PATH_SUFFIXES lib) + endif() + + if(NOT GAOL_FIND_QUIETLY) + if(${prefix}_LIB) + message(STATUS "${MSG} -- found at ${${prefix}_LIB}") + else() + message(STATUS "${MSG} -- not found") + endif() + endif() + + set(${prefix}_LIB ${${prefix}_LIB} PARENT_SCOPE) + mark_as_advanced(${prefix}_LIB) + endfunction() + + +################################################################################ +# Options +################################################################################ + + set(MATHLIB_DIR "" CACHE PATH "Path to the Mathlib/ultim lib and include directories") + set(GAOL_DIR "" CACHE PATH "Path to the Gaol lib and include directories") + + +################################################################################ +# Looking for Mathlib/libultim +################################################################################ + + # Looking for MathLib.h, result is written in MATHLIB_INCDIR + codac_find_header_custom(MATHLIB "MathLib.h" PATHS "${MATHLIB_DIR}" "${GAOL_DIR}") + # Looking for ultim library, result is written in MATHLIB_LIB + codac_find_library_custom(MATHLIB "ultim" PATHS "${MATHLIB_DIR}" "${GAOL_DIR}") + + +################################################################################ +# Looking for Gaol +################################################################################ + + # Looking for gaol/gaol.h, result is written in GAOL_INCDIR + codac_find_header_custom(GAOL "gaol/gaol.h" PATHS "${GAOL_DIR}") + # Looking for gaol library, result is written in GAOL_LIB + codac_find_library_custom(GAOL "gaol" PATHS "${GAOL_DIR}") + + set(GAOL_VERSION "") + if(GAOL_INCDIR AND EXISTS "${GAOL_INCDIR}/gaol/gaol_configuration.h") + file(STRINGS "${GAOL_INCDIR}/gaol/gaol_configuration.h" _gaol_version_lines + REGEX "^#define GAOL_(MAJOR|MINOR|MICRO)_VERSION[ \t]+[0-9]+") + set(_gaol_version_parts "") + foreach(_gaol_part MAJOR MINOR MICRO) + string(REGEX MATCH "GAOL_${_gaol_part}_VERSION[ \t]+([0-9]+)" _gaol_match "${_gaol_version_lines}") + if(_gaol_match) + list(APPEND _gaol_version_parts ${CMAKE_MATCH_1}) + endif() + endforeach() + list(LENGTH _gaol_version_parts _gaol_version_length) + if(_gaol_version_length EQUAL 3) + string(REPLACE ";" "." GAOL_VERSION "${_gaol_version_parts}") + endif() + endif() + + +################################################################################ +# Result +################################################################################ + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(GAOL + REQUIRED_VARS GAOL_LIB GAOL_INCDIR MATHLIB_LIB MATHLIB_INCDIR + VERSION_VAR GAOL_VERSION) + + if(GAOL_FOUND) + set(GAOL_INCLUDE_DIRS ${GAOL_INCDIR} ${MATHLIB_INCDIR}) + list(REMOVE_DUPLICATES GAOL_INCLUDE_DIRS) + set(GAOL_LIBRARIES ${GAOL_LIB} ${MATHLIB_LIB}) + endif() diff --git a/scripts/CMakeModules/FindSphinx.cmake b/scripts/CMakeModules/FindSphinx.cmake index 6190fa010..bf1036215 100644 --- a/scripts/CMakeModules/FindSphinx.cmake +++ b/scripts/CMakeModules/FindSphinx.cmake @@ -1,9 +1,9 @@ include(FindPackageHandleStandardArgs) # We are likely to find Sphinx near the Python interpreter -find_package(PythonInterp) -if(PYTHONINTERP_FOUND) - get_filename_component(_PYTHON_DIR "${PYTHON_EXECUTABLE}" DIRECTORY) +find_package(Python COMPONENTS Interpreter) +if(Python_Interpreter_FOUND) + get_filename_component(_PYTHON_DIR "${Python_EXECUTABLE}" DIRECTORY) set( _PYTHON_PATHS "${_PYTHON_DIR}" diff --git a/scripts/CMakeModules/codac_gaol.cmake b/scripts/CMakeModules/codac_gaol.cmake new file mode 100644 index 000000000..9f5851460 --- /dev/null +++ b/scripts/CMakeModules/codac_gaol.cmake @@ -0,0 +1,745 @@ +# ================================================================== +# Codac - cmake module for GAOL, the interval arithmetic library +# ================================================================== +# +# codac2::Interval derives from gaol::interval (see +# src/core/domains/interval/codac2_Interval.h). GAOL is written by Frederic +# Goualard (https://frederic.goualard.net, https://github.com/goualard-f/GAOL) +# and distributed under the GNU LGPL v2. It computes its elementary functions +# with mathlib, the IBM Accurate Portable Mathematical Library (libultim), which +# Frederic Goualard distributes along with it, under the GNU GPL v2 or later. +# +# Codac builds the GAOL of Jordan Ninin's fork, https://github.com/Jordan08/GAOL, +# which adds to GAOL a CMake build, the fixes Codac depends on (those of the +# patch IBEX applies to GAOL, and those Visual C++, MinGW and ARM processors +# need) and tests of its bounds. Fixes of GAOL go into the fork rather than into +# Codac. The fork's CMake build downloads mathlib from Frederic Goualard's site, +# and refuses the compilers that do not compute GAOL's intervals right (see its +# README.md). +# +# GAOL's interval operations are inline: the code including its headers, Codac's +# and that of Codac's users, has to be compiled with the flags of interval +# arithmetic, and linked with GAOL and mathlib. Which flags and which libraries +# is GAOL's to say, and codac_gaol_find() takes them from the GAOL it finds or +# builds, in this order: +# +# 1. the CMake package of GAOL, which the CMake build of the fork installs: +# find_package(gaol CONFIG) gives gaol::gaol, which carries the include +# directory, the flags (PUBLIC), __GAOL_PUBLIC__= for Visual C++, and +# mathlib (gaol::ultim); +# 2. pkg-config: the gaol.pc the autotools and meson builds of the fork install +# carries the flags in its Cflags, and GAOL and mathlib in its Libs; +# 3. the files themselves (FindGAOL.cmake), for a GAOL installed with neither, +# whose flags Codac then determines itself (codac_gaol_interval_flags()); +# 4. when none is found, or with ENABLE_FIND_PACKAGE_GAOL OFF, codac_gaol_build() +# builds the fork with its CMake build and installs it with its installer, +# and find_package(gaol CONFIG) takes the package it installed, as in 1. +# +# All four end in Codac::gaol, the imported target the Codac libraries link +# PUBLIC, and which codac-config.cmake defines again for their users +# (codac_gaol_config_snippet()). +# +# Origin +# ------ +# What follows comes from IBEX (https://github.com/ibex-team/ibex-lib, GNU +# LGPL v3): +# +# - codac_gaol_portability_flags() and codac_gaol_interval_flags() are, divided +# in two, the part of ibex_init_common() (cmake.utils/ibex-config-utils.cmake) +# that sets the floating-point flags, in the version of the IBEX fork +# maintained by Fabrice Le Bars, https://github.com/lebarsfa/ibex-lib, tag +# ibex-2.8.9.20260819, which Codac was built against until now. +# ibex_init_common() was written by Cyril Bouvier for the CMake build of IBEX; +# the flags it sets -- -frounding-math and the others that IEEE 754 double +# support depends on, and their Visual Studio counterparts -- were added to it +# by Fabrice Le Bars. The condition under which -ffloat-store is added is the +# one of the CMake build of the fork of GAOL (its CMakeLists.txt). +# +# - codac_gaol_build() is the "not found, install it" branch of +# interval_lib_wrapper/gaol/CMakeLists.txt, written by Cyril Bouvier: build +# mathlib and GAOL, and install the result next to the library it serves, as +# IBEX does in include/ibex/3rd and lib/ibex/3rd. The CMake build of GAOL and +# mathlib it runs is the fork's, which says where it comes from: from IBEX +# (Cyril Bouvier, Gilles Chabert), with the portability fixes for Visual +# C++, MinGW and ARM of the forks of GAOL and mathlib by Fabrice Le Bars. +# +# - codac_gaol_config_snippet() does, for Codac, what +# create_target_import_and_export() of cmake.utils/IbexUtils.cmake (Cyril +# Bouvier) does for IBEX: the lines that define the imported target of GAOL +# again for a consumer of the installed library. +# +# What differs from IBEX +# ---------------------- +# IBEX extracts GAOL and mathlib from archives kept in its own repository, and +# builds them as a part of itself (add_subdirectory()). Here GAOL is downloaded +# from the fork, at the head of its master branch, and built as a project of its +# own when Codac is configured, for the reasons given at codac_gaol_build(); the +# fork's build downloads mathlib, checked against the SHA256 of its archive. The +# other differences are explained where they occur. + + +# Where a GAOL built by codac_gaol_build() is installed, under the installation +# prefix. IBEX keeps the libraries it builds for itself in include/ibex/3rd and +# lib/ibex/3rd, out of the way of a GAOL installed separately; include/codac +# cannot play that part here, being the name of Codac's umbrella header. GAOL's +# CMake package goes in lib/codac-3rd/cmake/gaol, where the search of +# find_package() does not look: only codac-config.cmake finds it there. +set(CODAC_INSTALL_INCLUDEDIR_3RD "${CMAKE_INSTALL_INCLUDEDIR}/codac-3rd") +set(CODAC_INSTALL_LIBDIR_3RD "${CMAKE_INSTALL_LIBDIR}/codac-3rd") + +# Where codac_gaol_build() downloads, builds and installs GAOL in the build tree +set(CODAC_GAOL_WORK_DIR "${CMAKE_BINARY_DIR}/_deps/gaol") + + +################################################################################ +# codac_gaol_portability_flags() +################################################################################ +# +# Returns in the flags of ibex_init_common() (see the top of this file) +# that are not those of interval arithmetic, and that Codac keeps whatever GAOL +# says: the top-level CMakeLists.txt puts them in CMAKE_CXX_FLAGS, where +# ibex_init_common() put them. They are returned as a list, one flag per +# element, because Codac also hands them over to its consumers, through +# CODAC_CXX_FLAGS and codac.pc; for the same reason, the Visual Studio "/D NAME" +# pairs of IBEX are spelt "/DNAME", which target_compile_options() cannot +# mistake for two duplicated "/D" and merge. The rest of ibex_init_common() is +# left out: the installation directories, the build type, the C++ standard and +# the uninstall target are Codac's own decisions, and the Debug-only -Wall +# -DDEBUG (/D DEBUG) have no use here, Codac choosing its own warnings and none +# of its code reading DEBUG. +# +# The two flags IBEX added for filib, which is not GAOL, are kept all the same, +# so that Codac goes on being compiled with the flags it had. +function(codac_gaol_portability_flags outvar) + + include(CheckCXXCompilerFlag) + set(flags "") + + if(MSVC) + list(APPEND flags /D_CRT_SECURE_NO_WARNINGS /D_CRT_NONSTDC_NO_WARNINGS /Zc:__cplusplus /Zc:strictStrings-) + elseif(APPLE) + # Due to warnings on macOS with filib + check_cxx_compiler_flag("-Wno-undefined-var-template" COMPILER_SUPPORTS_WNO_UNDEFINED_VAR_TEMPLATE) + if(COMPILER_SUPPORTS_WNO_UNDEFINED_VAR_TEMPLATE) + list(APPEND flags -Wno-undefined-var-template) + endif() + endif() + # Claim IEEE 754 double compatibility, for filib + list(APPEND flags -D__STDC_IEC_559__=1) + + set(${outvar} ${flags} PARENT_SCOPE) +endfunction() + + +################################################################################ +# codac_gaol_interval_flags() +################################################################################ +# +# Returns in the compilation flags interval arithmetic depends on, for +# a GAOL found by its files alone, without a CMake package or a gaol.pc to say +# them (see codac_gaol_find()). Without -frounding-math and its companions, the +# compiler is free to evaluate a floating-point expression at compile time in +# the default rounding mode, or to contract it, and a bound computed that way no +# longer encloses anything. +# +# From ibex_init_common() (see the top of this file), which appends the same +# flags to CMAKE_C_FLAGS and CMAKE_CXX_FLAGS, but for -ffloat-store, added only +# where the builds of GAOL add it. +function(codac_gaol_interval_flags outvar) + + include(CheckCXXCompilerFlag) + include(CheckCXXSourceCompiles) + set(flags "") + + if(MSVC) + list(APPEND flags /fp:strict) + else() + # Each flag is kept only where the compiler takes it. The check results are + # cached under the names ibex_init_common() gives them, e.g. + # COMPILER_SUPPORTS_FROUNDING_MATH for -frounding-math. + foreach(flag -frounding-math -fno-fast-math -ffp-contract=off + -ffp-mode=full -fp-model=strict -fp:strict -mpc64) + string(MAKE_C_IDENTIFIER "${flag}" _flag_id) + string(TOUPPER "COMPILER_SUPPORTS${_flag_id}" _flag_var) + check_cxx_compiler_flag("${flag}" ${_flag_var}) + if(${_flag_var}) + list(APPEND flags ${flag}) + endif() + endforeach() + + # Not from ibex_init_common(), but Codac's own: on a 32-bit x86 processor, + # doubles are computed in SSE2 rather than on the x87 FPU. Visual Studio, + # in the branch above, computes them in SSE2 already. + # + # Computed on the x87, GAOL's bounds and mathlib's results are only right + # while the precision of the x87 is set to 53 bits, and nothing keeps it + # so: mathlib's Init_Lib() sets it where mathlib has a version for 32-bit x86 + # (see cmake/mathlib/mathlib_configuration.h.in in the fork of GAOL), but + # GAOL, initialised right after, restores the default floating-point + # environment (gaol::init()), whose precision is 64 bits on Linux and + # with MinGW. Built for an i686 computing on the x87 (Clang 21 with + # -mcpu=i686), GAOL returned [1.99975, 1.99975] for exp([1,1]), and the + # bounds of exp, sin and cos missed the exact value for 4000, 3302 and 3913 + # of 4000 random arguments; built with the two flags below, the same + # program gave all of its 48000 bounds bit for bit as on x86_64. In SSE2, + # the precision is not a setting. SSE2 asks nothing more of the processor + # than GAOL's own builds do: they compile GAOL for SSE2 on these systems. + check_cxx_source_compiles(" + #if !defined(__i386__) + #error not a 32-bit x86 target + #endif + int main() { return 0; }" + CODAC_TARGET_IS_X86_32) + if(CODAC_TARGET_IS_X86_32) + check_cxx_compiler_flag("-msse2 -mfpmath=sse" COMPILER_SUPPORTS_MSSE2_MFPMATH_SSE) + if(COMPILER_SUPPORTS_MSSE2_MFPMATH_SSE) + list(APPEND flags -msse2 -mfpmath=sse) + endif() + endif() + + # -ffloat-store only where doubles are still computed in extended precision + # with the flags above (FLT_EVAL_METHOD not 0: a 32-bit x86 target computing + # on the x87), whose 80-bit registers keep more digits than a double: the + # condition under which the CMake, autotools and meson builds of the fork of + # GAOL add it. ibex_init_common() added it wherever the compiler took it. + # Where doubles are computed in double precision (SSE2, ARM and the other + # processors), it brings nothing to the bounds, and makes GCC store every + # floating-point variable in memory rather than in a register: with it, on + # x86_64 with GCC 9.4, x + y took 9.1 ns rather than 3.0, and sqrt(x) 41 ns + # rather than 8.2. + string(REPLACE ";" " " CMAKE_REQUIRED_FLAGS "${flags}") + check_cxx_source_compiles(" + #include + #if defined(FLT_EVAL_METHOD) && FLT_EVAL_METHOD == 0 + #error doubles are computed in double precision + #endif + int main() { return 0; }" + CODAC_DOUBLES_IN_EXTENDED_PRECISION) + unset(CMAKE_REQUIRED_FLAGS) + if(CODAC_DOUBLES_IN_EXTENDED_PRECISION) + check_cxx_compiler_flag(-ffloat-store COMPILER_SUPPORTS_FFLOAT_STORE) + if(COMPILER_SUPPORTS_FFLOAT_STORE) + list(APPEND flags -ffloat-store) + endif() + endif() + + # Not from ibex_init_common() either: a warning when the compiler takes + # -frounding-math but says it does not honour the rounding direction on the + # target, as Clang does for 32-bit ARM processors ("overriding currently + # unsupported rounding mode on this target"). It then optimises the + # negations by which GAOL rounds downward with the rounding direction set + # upward, and no flag or change to GAOL can prevent it. Built by Clang 21 for + # 32-bit ARM, 4556 of 16000 random products, squares and cubes computed by + # GAOL did not enclose their exact value; built by GCC 15, none. The builds + # of the fork of GAOL refuse such a compiler, and so do its headers; the + # warning remains for a GAOL found without its CMake package or its gaol.pc. + if(COMPILER_SUPPORTS_FROUNDING_MATH) + set(CMAKE_REQUIRED_FLAGS "-frounding-math") + check_cxx_source_compiles("int main() { return 0; }" CODAC_COMPILER_HONOURS_ROUNDING_MATH + FAIL_REGEX "unsupported rounding mode") + unset(CMAKE_REQUIRED_FLAGS) + if(NOT CODAC_COMPILER_HONOURS_ROUNDING_MATH) + message(WARNING "${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION} does not honour the rounding " + "direction on this target (-frounding-math): the intervals computed by Codac may not " + "enclose the values they should. Use a compiler that does, such as GCC.") + endif() + endif() + endif() + + set(${outvar} ${flags} PARENT_SCOPE) +endfunction() + + +################################################################################ +# codac_gaol_step( ...) +################################################################################ +# +# Runs one step of codac_gaol_build(), its output written to , and +# stops the configuration with that output when the step fails, as +# LOG_OUTPUT_ON_FAILURE does for an ExternalProject. +function(codac_gaol_step description log) + execute_process(COMMAND ${ARGN} + OUTPUT_FILE "${log}" ERROR_FILE "${log}" + RESULT_VARIABLE result) + if(NOT result EQUAL 0) + file(READ "${log}" output) + message(FATAL_ERROR "GAOL: ${description} failed (${result}). Its output, in ${log}:\n${output}") + endif() +endfunction() + + +################################################################################ +# codac_gaol_build() +################################################################################ +# +# Downloads GAOL, builds it and mathlib in Release with the CMake build of the +# fork, and installs them in the build tree with its installer, all while Codac +# is configured; sets CODAC_GAOL_INSTALL_TREE, where they are installed, in the +# caller's scope, for codac_gaol_find() to find the CMake package of GAOL there. +# GAOL and mathlib are also installed with Codac, by the same installer, in +# CODAC_INSTALL_INCLUDEDIR_3RD and CODAC_INSTALL_LIBDIR_3RD, since the Codac +# libraries are of no use without them. +# +# While Codac is configured, and not while it is built, as with an +# ExternalProject: the flags and the libraries of GAOL are read from the CMake +# package it installs, which has to exist for find_package() to read it. +# +# A project of its own, as in IBEX's build of GAOL with its autotools, rather +# than the FetchContent that Eigen and Catch2 are brought in with, which would +# build GAOL as a part of this project. Kept apart, GAOL is compiled with the +# flags it chooses, and not with Codac's warnings, sanitizers and coverage +# instrumentation, and it is always built in Release, whatever the +# configuration of Codac, which is what the MSVC runtime choice of the +# top-level CMakeLists.txt counts on. CMAKE_CXX_FLAGS and CMAKE_C_FLAGS are +# handed over as they are when this is called, before Codac adds its own flags +# to them. +# +# GAOL comes from the head of the master branch of the fork (version 4.3.1 of +# GAOL), so that the fixes pushed to the fork reach Codac without a change here. +# Cloned with Git, the sources are brought up to date with the branch at each +# configuration of Codac, which needs network access (without it, the sources +# already downloaded are built, with a warning), and only what a new commit +# changes is compiled again. Without Git, the archive GitHub makes of the +# branch is downloaded instead, only once per build directory: a new commit +# reaches such a build once _deps/gaol is deleted from it. The fork's CMake +# build downloads mathlib 2.1.1 from Frederic Goualard's site, checks its +# checksum, and builds and installs it along with GAOL. +function(codac_gaol_build) + + set(_work "${CODAC_GAOL_WORK_DIR}") + set(_source "${_work}/src") + set(_binary "${_work}/build") + set(_install "${_work}/install") + file(MAKE_DIRECTORY "${_work}") + + message(STATUS "GAOL: downloading the fork of GAOL, building it and installing it in ${_work}") + + # The sources + find_package(Git QUIET) + if(GIT_FOUND) + if(NOT EXISTS "${_source}/.git") + file(REMOVE_RECURSE "${_source}") + # The files as they are in the repository, whatever core.autocrlf says + # (true on the Windows runners of GitHub Actions). + codac_gaol_step("cloning https://github.com/Jordan08/GAOL.git" "${_work}/download.log" + "${GIT_EXECUTABLE}" clone --depth 1 --branch master --config core.autocrlf=false + https://github.com/Jordan08/GAOL.git "${_source}") + else() + execute_process(COMMAND "${GIT_EXECUTABLE}" -C "${_source}" fetch --depth 1 origin master + OUTPUT_FILE "${_work}/update.log" ERROR_FILE "${_work}/update.log" + RESULT_VARIABLE _fetch_result) + if(_fetch_result EQUAL 0) + codac_gaol_step("updating the sources with the master branch" "${_work}/update.log" + "${GIT_EXECUTABLE}" -C "${_source}" reset --hard FETCH_HEAD) + else() + message(WARNING "GAOL could not be brought up to date with the master branch of " + "https://github.com/Jordan08/GAOL.git (see ${_work}/update.log): " + "the sources downloaded before are built.") + endif() + endif() + elseif(NOT EXISTS "${_source}/CMakeLists.txt") + file(DOWNLOAD https://github.com/Jordan08/GAOL/archive/refs/heads/master.zip "${_work}/GAOL-master.zip" + STATUS _download_status LOG _download_log) + list(GET _download_status 0 _download_code) + if(NOT _download_code EQUAL 0) + message(FATAL_ERROR "GAOL: downloading https://github.com/Jordan08/GAOL/archive/refs/heads/master.zip " + "failed (${_download_status}):\n${_download_log}") + endif() + file(REMOVE_RECURSE "${_work}/GAOL-master" "${_source}") + codac_gaol_step("extracting GAOL-master.zip" "${_work}/download.log" + "${CMAKE_COMMAND}" -E chdir "${_work}" "${CMAKE_COMMAND}" -E tar xf GAOL-master.zip) + file(RENAME "${_work}/GAOL-master" "${_source}") + endif() + + # The configuration of GAOL, written as an initial cache (-C) rather than on + # the command line, where the ";" of a list such as CMAKE_OSX_ARCHITECTURES + # would split an argument in two. FORCE, so that a value changed since the + # last configuration of Codac replaces the one in GAOL's cache. + # + # Every value below has to come out the same at each configuration of Codac + # that changes nothing: GAOL is configured again as soon as one of them + # differs, and everything in Codac that includes an interval may then be + # recompiled. This is why the top-level CMakeLists.txt enables C in project() + # rather than leaving it to a dependency, which changed CMAKE_C_COMPILER + # between the first configuration and the second. + set(_cache "") + macro(codac_gaol_cache_entry name type value) + string(APPEND _cache "set(${name} [==[${value}]==] CACHE ${type} \"\" FORCE)\n") + endmacro() + + codac_gaol_cache_entry(CMAKE_BUILD_TYPE STRING Release) + # Codac's Python modules link these archives into shared libraries. + codac_gaol_cache_entry(CMAKE_POSITION_INDEPENDENT_CODE BOOL ON) + # Where Codac is installed, which the gaol.pc GAOL installs with Codac names; + # the installation in the build tree gives its own prefix. + codac_gaol_cache_entry(CMAKE_INSTALL_PREFIX PATH "${CMAKE_INSTALL_PREFIX}") + codac_gaol_cache_entry(CMAKE_INSTALL_INCLUDEDIR PATH "${CODAC_INSTALL_INCLUDEDIR_3RD}") + codac_gaol_cache_entry(CMAKE_INSTALL_LIBDIR PATH "${CODAC_INSTALL_LIBDIR_3RD}") + # The fork's continuous integration runs its tests + codac_gaol_cache_entry(GAOL_BUILD_TESTS BOOL OFF) + # The mathlib the fork downloads and builds, never one installed on this + # machine: the installation of Codac counts on it + codac_gaol_cache_entry(GAOL_FIND_MATHLIB BOOL OFF) + codac_gaol_cache_entry(CMAKE_CXX_FLAGS STRING "${CMAKE_CXX_FLAGS}") + codac_gaol_cache_entry(CMAKE_C_FLAGS STRING "${CMAKE_C_FLAGS}") + + # The generator is handed over below, with its platform (-A) and toolset (-T), + # but nothing else is: the compilers, the flags and the target have to be + # given explicitly, or GAOL would be built for another machine than the one + # Codac is built for. The Visual Studio and Xcode generators take their + # compilers from the toolset and ignore these. + if(NOT CMAKE_GENERATOR MATCHES "Visual Studio|Xcode") + codac_gaol_cache_entry(CMAKE_CXX_COMPILER FILEPATH "${CMAKE_CXX_COMPILER}") + codac_gaol_cache_entry(CMAKE_C_COMPILER FILEPATH "${CMAKE_C_COMPILER}") + codac_gaol_cache_entry(CMAKE_MAKE_PROGRAM FILEPATH "${CMAKE_MAKE_PROGRAM}") + endif() + foreach(_var CMAKE_TOOLCHAIN_FILE CMAKE_MSVC_RUNTIME_LIBRARY CMAKE_GENERATOR_INSTANCE + CMAKE_OSX_DEPLOYMENT_TARGET CMAKE_OSX_SYSROOT CMAKE_OSX_ARCHITECTURES) + # Quoted: a list, as CMAKE_OSX_ARCHITECTURES can be, would give if() as many + # arguments as elements + if(NOT "${${_var}}" STREQUAL "") + codac_gaol_cache_entry(${_var} STRING "${${_var}}") + endif() + endforeach() + # Only when this build is itself a cross-compilation (as the macOS jobs of + # .github/workflows are, by setting CMAKE_SYSTEM_NAME explicitly): passing + # the host's own name would make GAOL's build believe it cross-compiles. + if(CMAKE_CROSSCOMPILING) + codac_gaol_cache_entry(CMAKE_SYSTEM_NAME STRING "${CMAKE_SYSTEM_NAME}") + if(CMAKE_SYSTEM_PROCESSOR) + codac_gaol_cache_entry(CMAKE_SYSTEM_PROCESSOR STRING "${CMAKE_SYSTEM_PROCESSOR}") + endif() + endif() + file(WRITE "${_work}/initial-cache.cmake" "${_cache}") + + set(_generator -G "${CMAKE_GENERATOR}") + if(CMAKE_GENERATOR_PLATFORM) + list(APPEND _generator -A "${CMAKE_GENERATOR_PLATFORM}") + endif() + if(CMAKE_GENERATOR_TOOLSET) + list(APPEND _generator -T "${CMAKE_GENERATOR_TOOLSET}") + endif() + + codac_gaol_step("configuring" "${_work}/configure.log" + "${CMAKE_COMMAND}" ${_generator} -C "${_work}/initial-cache.cmake" -S "${_source}" -B "${_binary}") + # Release whatever the configuration of Codac: --config is what a + # multi-configuration generator reads, and what the others ignore. In + # parallel as CMAKE_BUILD_PARALLEL_LEVEL says, when it is set. + codac_gaol_step("building" "${_work}/build.log" + "${CMAKE_COMMAND}" --build "${_binary}" --config Release) + # The installer of GAOL's build (cmake_install.cmake, which cmake --install + # runs from CMake 3.15 on), with the prefix of the build tree + codac_gaol_step("installing" "${_work}/install.log" + "${CMAKE_COMMAND}" "-DCMAKE_INSTALL_PREFIX=${_install}" -DCMAKE_INSTALL_CONFIG_NAME=Release + -P "${_binary}/cmake_install.cmake") + + # And with Codac, by the same installer, with the prefix Codac is installed + # under, as the installation of Codac gives it (cmake --install --prefix, + # CPack), and into DESTDIR when it is set, which it inherits. + install(CODE " + # GAOL and mathlib, installed by the installer of their CMake build (see + # codac_gaol_build() in scripts/CMakeModules/codac_gaol.cmake) + execute_process(COMMAND \"${CMAKE_COMMAND}\" \"-DCMAKE_INSTALL_PREFIX=\${CMAKE_INSTALL_PREFIX}\" + -DCMAKE_INSTALL_CONFIG_NAME=Release -P \"${_binary}/cmake_install.cmake\" + RESULT_VARIABLE _codac_gaol_install_result) + if(NOT _codac_gaol_install_result EQUAL 0) + message(FATAL_ERROR \"The installation of GAOL failed (\${_codac_gaol_install_result})\") + endif() + ") + + set(CODAC_GAOL_INSTALL_TREE "${_install}" PARENT_SCOPE) +endfunction() + + +################################################################################ +# codac_gaol_find() +################################################################################ +# +# Finds GAOL in the order given at the top of this file, or builds it, and +# defines Codac::gaol, the imported target through which the Codac libraries, +# and the targets linking them, get GAOL's include directories, flags and +# libraries. Sets in the caller's scope: +# +# CODAC_GAOL_FROM "package", "pkg-config" or "files" +# CODAC_GAOL_BUILT_HERE TRUE when codac_gaol_build() built it +# CODAC_GAOL_INSTALL_TREE where codac_gaol_build() installed it +# GAOL_VERSION +# +# An INTERFACE library rather than GAOL's own target, whatever provided GAOL: +# the exported Codac targets name it, and codac-config.cmake defines it again +# (codac_gaol_config_snippet()), which it could not do under the name gaol::gaol +# without clashing with a find_package(gaol) of the consumer. +function(codac_gaol_find) + + include(CheckCXXCompilerFlag) + set(_from "") + set(_built_here FALSE) + set(_version "") + + if(ENABLE_FIND_PACKAGE_GAOL) + + # 1. The CMake package of GAOL (gaol_DIR, or CMAKE_PREFIX_PATH). A gaol_DIR + # left by an earlier configuration of this build, naming the GAOL + # codac_gaol_build() installed in it, is not a GAOL of this machine: it is + # forgotten, and that GAOL is built again, up to date, if none is found. + string(FIND "${gaol_DIR}" "${CODAC_GAOL_WORK_DIR}/" _in_work_dir) + if(_in_work_dir EQUAL 0) + unset(gaol_DIR CACHE) + endif() + find_package(gaol CONFIG QUIET) + if(gaol_FOUND) + set(_from package) + set(_version "${gaol_VERSION}") + message(STATUS "Found GAOL ${gaol_VERSION}, CMake package in ${gaol_DIR}") + endif() + + # 2. pkg-config (PKG_CONFIG_PATH, or CMAKE_PREFIX_PATH). Not with Visual C++, + # whose libraries pkg-config does not name. A gaol.pc whose Cflags lack + # -frounding-math, as the one the meson build of Frederic Goualard's GAOL + # installs, does not describe the flags GAOL needs, and is not used. + if(NOT _from AND NOT MSVC) + find_package(PkgConfig QUIET) + if(PKG_CONFIG_FOUND) + pkg_check_modules(CODAC_GAOL_PC QUIET IMPORTED_TARGET gaol) + if(CODAC_GAOL_PC_FOUND) + check_cxx_compiler_flag(-frounding-math COMPILER_SUPPORTS_FROUNDING_MATH) + if(COMPILER_SUPPORTS_FROUNDING_MATH AND NOT "-frounding-math" IN_LIST CODAC_GAOL_PC_CFLAGS_OTHER) + message(STATUS "Found gaol.pc in ${CODAC_GAOL_PC_PREFIX}, whose Cflags lack -frounding-math: not used") + else() + set(_from pkg-config) + set(_version "${CODAC_GAOL_PC_VERSION}") + message(STATUS "Found GAOL ${CODAC_GAOL_PC_VERSION}, gaol.pc in ${CODAC_GAOL_PC_PREFIX}") + endif() + endif() + endif() + endif() + + # 3. The files (GAOL_DIR and MATHLIB_DIR, or CMAKE_PREFIX_PATH) + if(NOT _from) + find_package(GAOL MODULE QUIET) + if(GAOL_FOUND) + set(_from files) + set(_version "${GAOL_VERSION}") + message(STATUS "Found GAOL ${GAOL_VERSION} in ${GAOL_INCDIR}, without a CMake package or a gaol.pc: " + "compiled with the flags of interval arithmetic Codac determines") + endif() + endif() + endif() + + # 4. Built, and found as in 1 + if(NOT _from) + codac_gaol_build() + unset(gaol_DIR CACHE) + find_package(gaol CONFIG REQUIRED NO_DEFAULT_PATH + PATHS "${CODAC_GAOL_INSTALL_TREE}/${CODAC_INSTALL_LIBDIR_3RD}/cmake/gaol") + set(_from package) + set(_built_here TRUE) + set(_version "${gaol_VERSION}") + message(STATUS "GAOL ${gaol_VERSION} built and installed, CMake package in ${gaol_DIR}") + endif() + + add_library(Codac::gaol INTERFACE IMPORTED) + if(_from STREQUAL "package") + set_target_properties(Codac::gaol PROPERTIES INTERFACE_LINK_LIBRARIES gaol::gaol) + elseif(_from STREQUAL "pkg-config") + set_target_properties(Codac::gaol PROPERTIES INTERFACE_LINK_LIBRARIES PkgConfig::CODAC_GAOL_PC) + else() + codac_gaol_interval_flags(_interval_flags) + set_target_properties(Codac::gaol PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${GAOL_INCLUDE_DIRS}" + INTERFACE_COMPILE_OPTIONS "${_interval_flags}" + INTERFACE_LINK_LIBRARIES "${GAOL_LIBRARIES}") + # For Visual C++, GAOL declares its classes and functions + # __declspec(dllimport), as for a DLL, unless __GAOL_PUBLIC__ is defined + # (gaol/gaol_config.h). None of GAOL's builds -- its autotools and meson + # builds, and the CMake build of the fork -- defines + # _COMPILING__GAOL_PUBLIC__, with which GAOL would export a DLL: the GAOL + # Visual C++ links is a static library, which every file including its + # headers has to be told. gaol::gaol, of the CMake package, tells it the + # same way. + if(MSVC) + set_target_properties(Codac::gaol PROPERTIES INTERFACE_COMPILE_DEFINITIONS "__GAOL_PUBLIC__=") + endif() + endif() + + set(CODAC_GAOL_FROM "${_from}" PARENT_SCOPE) + set(CODAC_GAOL_BUILT_HERE ${_built_here} PARENT_SCOPE) + set(CODAC_GAOL_INSTALL_TREE "${CODAC_GAOL_INSTALL_TREE}" PARENT_SCOPE) + set(GAOL_VERSION "${_version}" PARENT_SCOPE) +endfunction() + + +################################################################################ +# codac_gaol_usage( ) +################################################################################ +# +# Returns what Codac::gaol gives the targets linking it, walked out of it and of +# the targets it links: its include directories, its compilation flags (with +# its definitions, as -D or /D) and what goes on the link line (library files, +# and flags). This is for what cannot link Codac::gaol: CODAC_CXX_FLAGS, codac.pc, +# and codac-config.cmake when GAOL has no CMake package. Generator expressions +# are left out, but for $, which is unwrapped: GAOL's targets +# have no other. +function(codac_gaol_usage include_dirs_var flags_var link_var) + + set(include_dirs "") + set(flags "") + set(link "") + set(queue Codac::gaol) + set(seen "") + + while(queue) + list(GET queue 0 item) + list(REMOVE_AT queue 0) + if(item MATCHES "^\\$$") + set(item "${CMAKE_MATCH_1}") + endif() + if(item IN_LIST seen OR item MATCHES "\\$<") + continue() + endif() + list(APPEND seen "${item}") + + if(NOT TARGET "${item}") + # A library file, a flag, or the name of a library of the system + if(item MATCHES "^-" OR IS_ABSOLUTE "${item}") + list(APPEND link "${item}") + else() + list(APPEND link "-l${item}") + endif() + continue() + endif() + + get_target_property(_value "${item}" INTERFACE_INCLUDE_DIRECTORIES) + if(_value) + list(APPEND include_dirs ${_value}) + endif() + get_target_property(_value "${item}" INTERFACE_COMPILE_OPTIONS) + if(_value) + list(APPEND flags ${_value}) + endif() + get_target_property(_value "${item}" INTERFACE_COMPILE_DEFINITIONS) + if(_value) + foreach(_definition ${_value}) + if(MSVC) + list(APPEND flags "/D${_definition}") + else() + list(APPEND flags "-D${_definition}") + endif() + endforeach() + endif() + + # An imported archive names its file either outright or per configuration. + # An INTERFACE library has no file at all, and before CMake 3.19 merely + # asking one for IMPORTED_LOCATION is a fatal error rather than an empty + # answer ("INTERFACE_LIBRARY targets may only have whitelisted + # properties") -- which is what Debian Bullseye, on CMake 3.18.4, reported. + # TYPE is whitelisted, so it can be asked first, as can the INTERFACE_ + # properties read above and below. + get_target_property(_type "${item}" TYPE) + if(NOT _type STREQUAL "INTERFACE_LIBRARY") + get_target_property(_location "${item}" IMPORTED_LOCATION) + if(NOT _location) + get_target_property(_configurations "${item}" IMPORTED_CONFIGURATIONS) + if(_configurations) + list(GET _configurations 0 _configuration) + get_target_property(_location "${item}" IMPORTED_LOCATION_${_configuration}) + endif() + endif() + if(_location) + list(APPEND link "${_location}") + endif() + endif() + + get_target_property(_value "${item}" INTERFACE_LINK_LIBRARIES) + if(_value) + list(APPEND queue ${_value}) + endif() + get_target_property(_value "${item}" INTERFACE_LINK_OPTIONS) + if(_value) + list(APPEND link ${_value}) + endif() + endwhile() + + foreach(_list include_dirs flags link) + list(FILTER ${_list} EXCLUDE REGEX "\\$<") + endforeach() + if(include_dirs) + list(REMOVE_DUPLICATES include_dirs) + endif() + if(flags) + list(REMOVE_DUPLICATES flags) + endif() + + set(${include_dirs_var} ${include_dirs} PARENT_SCOPE) + set(${flags_var} ${flags} PARENT_SCOPE) + set(${link_var} ${link} PARENT_SCOPE) +endfunction() + + +################################################################################ +# codac_gaol_pkg_config_path() +################################################################################ +# +# When is a path of the GAOL codac_gaol_build() installed in the build +# tree, turns it into the path GAOL is installed at with Codac, under the +# ${prefix} of codac.pc. +function(codac_gaol_pkg_config_path var) + if(CODAC_GAOL_BUILT_HERE) + string(FIND "${${var}}" "${CODAC_GAOL_INSTALL_TREE}/" _position) + if(_position EQUAL 0) + string(LENGTH "${CODAC_GAOL_INSTALL_TREE}/" _length) + string(SUBSTRING "${${var}}" ${_length} -1 _rest) + set(${var} "\${prefix}/${_rest}" PARENT_SCOPE) + endif() + endif() +endfunction() + + +################################################################################ +# codac_gaol_config_snippet() +################################################################################ +# +# Returns the lines of codac-config.cmake that define Codac::gaol for a consumer +# of the installed Codac, which the exported Codac targets name -- the +# counterpart of the ibex-config-gaol.cmake and ibex-config-ultim.cmake files +# that create_target_import_and_export() writes for IBEX. +# +# - A GAOL with a CMake package is found again by find_package(gaol CONFIG): +# the GAOL Codac built under the prefix Codac is installed under, by a path +# relative to the configuration file, since the prefix may be moved; a GAOL +# found on this machine where it was found, before the usual search. +# - A GAOL found by pkg-config or by its files is named by the paths it was +# found at, with the flags it was compiled with. +function(codac_gaol_config_snippet outvar) + + if(CODAC_GAOL_FROM STREQUAL "package") + if(CODAC_GAOL_BUILT_HERE) + file(RELATIVE_PATH _to_prefix "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_CMAKE}" "${CMAKE_INSTALL_PREFIX}") + set(_find "get_filename_component(_codac_prefix \"\${CMAKE_CURRENT_LIST_DIR}/${_to_prefix}\" ABSOLUTE) + find_package(gaol CONFIG REQUIRED NO_DEFAULT_PATH + PATHS \"\${_codac_prefix}/${CODAC_INSTALL_LIBDIR_3RD}/cmake/gaol\")") + else() + set(_find "find_package(gaol CONFIG REQUIRED HINTS \"${gaol_DIR}\")") + endif() + set(_properties "INTERFACE_LINK_LIBRARIES gaol::gaol") + else() + codac_gaol_usage(_include_dirs _flags _link) + set(_find "") + set(_properties "INTERFACE_INCLUDE_DIRECTORIES \"${_include_dirs}\" + INTERFACE_COMPILE_OPTIONS \"${_flags}\" + INTERFACE_LINK_LIBRARIES \"${_link}\"") + endif() + + set(${outvar} " + # GAOL, the interval arithmetic library Codac is built upon, with mathlib + # (libultim) and the flags of interval arithmetic, as Codac::gaol. The + # exported Codac targets link it, so it has to be defined before they are + # included. + ${_find} + if(NOT TARGET Codac::gaol) + add_library(Codac::gaol INTERFACE IMPORTED) + set_target_properties(Codac::gaol PROPERTIES + ${_properties}) + endif() +" PARENT_SCOPE) +endfunction() diff --git a/scripts/dependencies/install_ibex.sh b/scripts/dependencies/install_ibex.sh deleted file mode 100644 index 375c96500..000000000 --- a/scripts/dependencies/install_ibex.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -cd $HOME -echo 'Installing IBEX in ' $HOME '...'; -if [ ! -e "ibex-lib/README.md" ]; then - #git clone -b master https://github.com/lebarsfa/ibex-lib.git ; - git clone -b ibex-2.8.9.20260819 https://github.com/lebarsfa/ibex-lib.git ; # To test a specific version of IBEX... - cd ibex-lib ; - mkdir build && cd build ; - cmake -E env CXXFLAGS="-fPIC" CFLAGS="-fPIC" cmake -DCMAKE_INSTALL_PREFIX=$HOME/ibex-lib/build_install -DCMAKE_BUILD_TYPE=Release .. ; - make -j 4 ; -else - echo 'Using cached directory.' ; -fi -cd $HOME/ibex-lib/build -make install -cd $HOME \ No newline at end of file diff --git a/scripts/docker/build_pybinding.sh b/scripts/docker/build_pybinding.sh index ab98904f3..e4fae0f05 100755 --- a/scripts/docker/build_pybinding.sh +++ b/scripts/docker/build_pybinding.sh @@ -2,11 +2,6 @@ set -e -x -wget https://github.com/lebarsfa/ibex-lib/releases/download/ibex-2.8.9.20260819/ibex_$(uname -m)_manylinux_2_28.zip --no-check-certificate -nv -unzip -q ibex_$(uname -m)_manylinux_2_28.zip -rm -Rf ibex_$(uname -m)_manylinux_2_28.zip -sudo cp -Rf ibex/* /usr/local/ - git config --global --add safe.directory /io cd /io @@ -83,9 +78,9 @@ for PYBIN in /opt/python/cp3*/bin; do fi "${PYBIN}/python" -m pip install --upgrade pip - "${PYBIN}/python" -m pip install --upgrade wheel setuptools + "${PYBIN}/python" -m pip install --upgrade wheel setuptools build mkdir -p build_dir && cd build_dir - cmake -E env CXXFLAGS="-fPIC" CFLAGS="-fPIC" cmake -DPYTHON_EXECUTABLE=${PYBIN}/python -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON -DWITH_CAPD=OFF -DWITH_PYTHON=ON -DPYBIND11_FINDPYTHON=OFF .. + cmake -DPYTHON_EXECUTABLE=${PYBIN}/python -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON -DTEST_EXAMPLES=ON -DWITH_CAPD=OFF -DWITH_PYTHON=ON -DPYBIND11_FINDPYTHON=OFF .. make -j4 make pip_package diff --git a/scripts/docker/build_pybinding_codac4matlab.sh b/scripts/docker/build_pybinding_codac4matlab.sh index b25fd262d..fd73bdf66 100644 --- a/scripts/docker/build_pybinding_codac4matlab.sh +++ b/scripts/docker/build_pybinding_codac4matlab.sh @@ -2,11 +2,6 @@ set -e -x -wget https://github.com/lebarsfa/ibex-lib/releases/download/ibex-2.8.9.20260819/ibex_$(uname -m)_manylinux_2_28.zip --no-check-certificate -nv -unzip -q ibex_$(uname -m)_manylinux_2_28.zip -rm -Rf ibex_$(uname -m)_manylinux_2_28.zip -sudo cp -Rf ibex/* /usr/local/ - git config --global --add safe.directory /io cd /io @@ -83,9 +78,9 @@ for PYBIN in /opt/python/cp3*/bin; do fi "${PYBIN}/python" -m pip install --upgrade pip - "${PYBIN}/python" -m pip install --upgrade wheel setuptools + "${PYBIN}/python" -m pip install --upgrade wheel setuptools build mkdir -p build_dir && cd build_dir - cmake -E env CXXFLAGS="-fPIC" CFLAGS="-fPIC" cmake -DPYTHON_EXECUTABLE=${PYBIN}/python -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON -DWITH_CAPD=OFF -DWITH_PYTHON=ON -DPYBIND11_FINDPYTHON=OFF .. + cmake -DPYTHON_EXECUTABLE=${PYBIN}/python -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON -DWITH_CAPD=OFF -DWITH_PYTHON=ON -DPYBIND11_FINDPYTHON=OFF .. make -j4 make pip_package diff --git a/scripts/doxygen/doxygen2docstring.py b/scripts/doxygen/doxygen2docstring.py index af062d6c8..ff05cf1a5 100644 --- a/scripts/doxygen/doxygen2docstring.py +++ b/scripts/doxygen/doxygen2docstring.py @@ -27,14 +27,35 @@ def normalize_template_label(str_template_label): .replace(" >", "") \ .replace(">", "") +# Turns the definition text of a declaration into the name of the macro that +# will carry its docstring. That text comes from doxygen, so anything doxygen +# words differently from one version to the next has to be removed here, or the +# macro ends up named differently depending on which doxygen ran and the +# bindings that spell the name out no longer compile. "typedef" is one of those: +# older doxygen writes the definition of an alias as "using X = typedef Y" +# where newer ones write "using X = Y", which gave the bindings a +# USING_..._EQ_TYPEDEF_... macro on the CentOS manylinux images of +# .github/workflows/dockercentos.yml where they expected USING_..._EQ_.... def normalize_label(str_label): + # Runs of whitespace are collapsed first, because the text below turns every + # space into an underscore and doxygen does not word its definitions the same + # way from one version to the next: 1.15.0 renders the definition of an alias + # with one space more than 1.17.0 does, which alone turned a + # USING_..._EQ_... macro into USING_..._EQ__... and stopped the bindings from + # compiling on Ubuntu 26.04. Collapsing the underscores afterwards would not + # do: plenty of macro names hold a legitimate double underscore, from a + # trailing underscore in an identifier followed by _REF or _CONST + # (NODEVALUE__REF and its like), and squeezing those would rename them all. + str_label = re.sub(r'\s+', ' ', str_label) + str_label = str_label \ .replace("or<<", "OROUT") \ .replace("or>>", "ORIN") return normalize_template_label(str_label) \ .replace("constexpr ", "") \ + .replace("typedef ", "") \ .replace("codac::", "") \ .replace("codac2::", "") \ .replace("std::", "") \ @@ -91,7 +112,8 @@ def docstring_varname(memberdef, prefix=''): def get_originate_file(m): location_file = m.find(".//location").get("file").split("/")[-1] - return location_file.replace(".h", "_docs.h").replace("codac2_", "codac2_py_") + location_stem = os.path.splitext(location_file)[0] + return (location_stem + "_docs.h").replace("codac2_", "codac2_py_") if not os.path.exists(sys.argv[1]): diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 28fd1c4fc..38b77d347 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -6,6 +6,48 @@ # Compiling sources # ================= + # Emptied here, then appended to by each module below through + # codac_publish_include_dirs(). It is a cache entry, so it survives a + # reconfigure: without this reset it would grow a duplicate of every + # directory on each run, and would keep the directories of a module that has + # since been switched off (capd, sympy). + set(CODAC_SOURCE_INCLUDE_DIRS "" CACHE INTERNAL + "Directories holding the public headers of the codac modules") + + # Same treatment for the directories of the generated umbrella headers. + set(CODAC_GENERATED_INCLUDE_DIRS "" CACHE INTERNAL + "Directories holding the umbrella headers generated for the codac modules") + + # Seeded before the modules are added, because each of them appends its own + # flags to these through PARENT_SCOPE (see the pkg-config section below). + # They used to be set after the add_subdirectory() calls, which threw every + # module's contribution away: the installed codac.pc then named no Codac + # library and no Codac include directory at all. + # + # The two directories the modules do not add themselves: the include root, + # through which is reached, and include/eigen3, where the Eigen + # headers the public headers include are installed. Without them a + # pkg-config consumer cannot compile a single translation unit -- this is + # the same pair that codac-core carries in its $. + # + # GAOL's directories are not among them: where they are depends on where GAOL + # was found, or on its being built with Codac, and the part on the + # dependencies below adds them as Codac::gaol gives them. The -I\${includedir}/ibex that + # used to sit here, when Codac reached GAOL through IBEX, named a directory + # under *Codac's* prefix, which only ever existed when the two happened to + # share one. + set(CODAC_PKG_CONFIG_CFLAGS "-I\${includedir} -I\${includedir}/eigen3") + set(CODAC_PKG_CONFIG_MODULE_LIBS "") + + # The "Requires:" line: a module that depends on an outside library appends + # its pkg-config name here and lets that library's own .pc answer for its + # flags. GAOL is not named there: a GAOL built with Codac installs its gaol.pc + # under lib/codac-3rd, where pkg-config does not look, and a GAOL found by its + # files has none; its flags and libraries are written below instead, as + # Codac::gaol gives them. The line starts empty; CAPD joins it from + # src/extensions/capd/CMakeLists.txt when that module is built. + set(CODAC_PKG_CONFIG_REQUIRES "") + add_subdirectory(core) add_subdirectory(graphics) add_subdirectory(unsupported) @@ -24,8 +66,82 @@ set(CODAC_PKG_CONFIG_FILE ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc) - set(CODAC_PKG_CONFIG_CFLAGS "-I\${includedir}/ibex") - set(CODAC_PKG_CONFIG_LIBS "-L\${libdir}") + # The modules appended their "-l" flags in dependency order: core first, then + # the modules that link against it. GNU ld resolves static archives left to + # right and keeps from each only what is undefined by the time it reads it, + # so a consumer's link line needs them the other way round -- a module before + # the module it depends on. Reversing the list is what turns the order the + # modules were configured in into a usable link order. CODAC_LIBRARIES, in + # the CMake config file below, gets there instead by exporting real targets + # and letting CMake order them. + if(CODAC_PKG_CONFIG_MODULE_LIBS) + string(STRIP "${CODAC_PKG_CONFIG_MODULE_LIBS}" CODAC_PKG_CONFIG_LIBS) + string(REPLACE " " ";" CODAC_PKG_CONFIG_LIBS "${CODAC_PKG_CONFIG_LIBS}") + list(REVERSE CODAC_PKG_CONFIG_LIBS) + string(REPLACE ";" " " CODAC_PKG_CONFIG_LIBS "${CODAC_PKG_CONFIG_LIBS}") + else() + set(CODAC_PKG_CONFIG_LIBS "") + endif() + + # Codac's own flags, which this build puts in CMAKE_CXX_FLAGS + # (codac_gaol_portability_flags() in scripts/CMakeModules/codac_gaol.cmake), + # and the flags GAOL gives through Codac::gaol, CODAC_INTERVAL_CXX_FLAGS, set + # in the top-level CMakeLists.txt: those of interval arithmetic, and those of + # the fused multiply-add instructions GAOL was compiled with (-mfma and the + # like). A CMake consumer gets them through CODAC_CXX_FLAGS, which is what + # every example uses and what the manual tells users to use; a pkg-config + # consumer has no equivalent. Compiling Codac's headers without + # -frounding-math silently gives up the guarantee the whole library rests on, + # and compiling Eigen's headers under other instruction-set assumptions than + # the prebuilt archives were built with is an ODR/ABI mismatch. + foreach(_codac_flags CODAC_PORTABILITY_CXX_FLAGS CODAC_INTERVAL_CXX_FLAGS) + if(${_codac_flags}) + string(REPLACE ";" " " _codac_pkg_config_flags "${${_codac_flags}}") + string(APPEND CODAC_PKG_CONFIG_CFLAGS " ${_codac_pkg_config_flags}") + endif() + endforeach() + + # GAOL's include directories and libraries, as Codac::gaol gives them + # (codac_gaol_usage(), in the top-level CMakeLists.txt): where the GAOL found + # on this machine is, or, for the GAOL codac_gaol_build() built, where it is + # installed with Codac, under ${prefix}. The directories the compiler searches + # of its own accord are left out, as CMake leaves them out of its command + # lines. + foreach(dir ${CODAC_GAOL_INCLUDE_DIRS}) + if(NOT dir IN_LIST CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES) + codac_gaol_pkg_config_path(dir) + string(APPEND CODAC_PKG_CONFIG_CFLAGS " -I${dir}") + endif() + endforeach() + # The libraries are named by their full path, as CMake hands them to the + # linker on the find_package side, rather than as -L plus -l: there is then no + # chance of another directory on the search path answering for libgaol first, + # and nothing to guess about how the file is named on each platform. They come + # after the Codac archives, which depend on them, and gaol before ultim, which + # it depends on. + foreach(item ${CODAC_GAOL_LINK_ITEMS}) + codac_gaol_pkg_config_path(item) + string(APPEND CODAC_PKG_CONFIG_LIBS " ${item}") + endforeach() + + # codac-core links PUBLIC against Threads::Threads, and so must anything that + # links codac-core: without it, a program that reaches the peibos threading + # code (the 11_peibos example, say) fails with an undefined reference to + # pthread_create. codac-config.cmake gets there through find_package(Threads); + # a .pc file has to name what that produces itself. + # + # What it produces, and not -pthread on principle: since glibc 2.34 the + # pthread functions are in libc, FindThreads finds them there, and + # Threads::Threads then contributes nothing at all -- so a .pc file naming + # -pthread would be describing a flag the CMake side does not pass. The two + # variables below are FindThreads' own answer: whether the compiler needs the + # flag, and what has to go on the link line, either of which may be empty. + if(THREADS_HAVE_PTHREAD_ARG) + string(APPEND CODAC_PKG_CONFIG_CFLAGS " -pthread") + endif() + if(CMAKE_THREAD_LIBS_INIT) + string(APPEND CODAC_PKG_CONFIG_LIBS " ${CMAKE_THREAD_LIBS_INIT}") + endif() file(GENERATE OUTPUT ${CODAC_PKG_CONFIG_FILE} CONTENT " prefix=${CMAKE_INSTALL_PREFIX} @@ -36,9 +152,9 @@ Description: ${PROJECT_DESCRIPTION} Url: ${PROJECT_HOMEPAGE_URL} Version: ${PROJECT_VERSION} - Requires: ibex + Requires: ${CODAC_PKG_CONFIG_REQUIRES} Cflags: ${CODAC_PKG_CONFIG_CFLAGS} - Libs: ${CODAC_PKG_CONFIG_LIBS} + Libs: -L\${libdir} ${CODAC_PKG_CONFIG_LIBS} ") install(FILES ${CODAC_PKG_CONFIG_FILE} DESTINATION ${CMAKE_INSTALL_PKGCONFIG}) @@ -47,8 +163,103 @@ # Generating codac-config.cmake file # ================================== + # The modules are installed as a CMake export set rather than as a handful of + # loose library files, and two things follow from that. + # + # - The dependencies between them are exported with them. codac-graphics + # declares target_link_libraries(... PUBLIC codac-core), so a consumer + # that links Codac::codac-graphics gets Codac::codac-core behind it, in + # that order and repeated as often as the linker needs. Handing out plain + # library paths could not carry that: CODAC_LIBRARIES listed them in + # declaration order, core first, and GNU ld -- which resolves a static + # archive against whatever is undefined when it reads it, and never looks + # back -- left every reference from codac-graphics into codac-core + # unresolved. Every Linux and MinGW job that built an example against an + # installed Codac failed on it; macOS and MSVC, whose linkers rescan, did + # not. + # + # - The rest of the usage requirements travel too: include directories, + # Threads, GAOL, CAPD. + # + # What an export set cannot carry are the dependencies this build fetches + # rather than finds, Eigen and pybind11: they are targets of this project, so + # no consumer's find_package() could define them. The modules keep those as + # build-tree-only dependencies (see src/core/CMakeLists.txt), and the install + # tree reaches Eigen's headers as a plain include directory instead. + install(EXPORT ${PROJECT_NAME}-targets + FILE ${PROJECT_NAME}-targets.cmake + NAMESPACE Codac:: + DESTINATION ${CMAKE_INSTALL_CMAKE}) + + # Builds CODAC_LIBRARIES and CODAC_INCLUDE_DIRS in a single place + function(codac_build_config_vars) + # Base modules, always included. These name the imported targets that + # codac-targets.cmake defines, rather than library paths: naming the + # targets is what lets CMake work the link order out for itself. + set(libraries + Codac::${PROJECT_NAME}-core + Codac::${PROJECT_NAME}-graphics + Codac::${PROJECT_NAME}-unsupported + Codac::gaol + Threads::Threads + ) + + set(include_dirs + \${CODAC_CORE_INCLUDE_DIR}/../ # header root, for + \${CODAC_CORE_INCLUDE_DIR}/../eigen3/ + \${CODAC_CORE_INCLUDE_DIR} + \${CODAC_GRAPHICS_INCLUDE_DIR} + \${CODAC_UNSUPPORTED_INCLUDE_DIR} + ) + + # Optional dependencies (CAPD, SymPy). Both lists are local until every + # module has been added, and only then written to the cache: list(APPEND) + # on a cache variable leaves a local copy behind and the cache entry as it + # was, so the optional modules never used to reach the generated file. + if(WITH_CAPD) + list(APPEND libraries Codac::${PROJECT_NAME}-capd) + list(APPEND include_dirs \${CODAC_CAPD_INCLUDE_DIR}) + # CODAC_CAPD_LIBRARY is kept as its own variable too, for any consumer + # that prefers to opt into CAPD explicitly rather than pull it in via + # the general CODAC_LIBRARIES. The target brings capd::capd and + # codac-core along with it. + set(CODAC_CAPD_LIBRARY Codac::${PROJECT_NAME}-capd CACHE INTERNAL "CAPD module of Codac, with all its dependencies") + endif() + + if(WITH_PYTHON) + list(APPEND libraries Codac::${PROJECT_NAME}-sympy) + list(APPEND include_dirs \${CODAC_SYMPY_INCLUDE_DIR}) + set(CODAC_SYMPY_LIBRARY Codac::${PROJECT_NAME}-sympy CACHE INTERNAL "SymPy module of Codac, with all its dependencies") + endif() + + set(CODAC_LIBRARIES ${libraries} CACHE INTERNAL "Libraries to link against for CODAC") + set(CODAC_INCLUDE_DIRS ${include_dirs} CACHE INTERNAL "Include directories for CODAC") + + # Propagate Codac's own flags (codac_gaol_portability_flags()) and the flags + # GAOL gives (-frounding-math, -mfma, etc.) to external projects. In that + # order, which is the order this build hands them to the compiler in -- the + # first through CMAKE_CXX_FLAGS, the second through Codac::gaol, whose + # options come after a target's own -- and decides which of two conflicting + # flags wins. + set(cxx_flags ${CODAC_PORTABILITY_CXX_FLAGS} ${CODAC_INTERVAL_CXX_FLAGS}) + set(CODAC_CXX_FLAGS "${cxx_flags}" CACHE INTERNAL "C++ flags for CODAC") + endfunction() + + # Build the variables written into codac-config.cmake below + codac_build_config_vars() + set(CODAC_CMAKE_CONFIG_FILE ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake) + # The definition of Codac::gaol, written by + # scripts/CMakeModules/codac_gaol.cmake, which knows where GAOL came from. + codac_gaol_config_snippet(CODAC_GAOL_CONFIG_SNIPPET) + + # The file is written in three passes: what locates the installed pieces + # first, then the optional modules, then -- once every dependency the export + # set names is in scope -- the targets file itself and the variables that + # describe it. That last pass has to come last: the CAPD and SymPy blocks + # used to be appended after CODAC_LIBRARIES had already been written out, so + # the libraries they look for could never appear in it. file(WRITE ${CODAC_CMAKE_CONFIG_FILE} "# Try to find Codac # This file has been generated by CMake @@ -67,14 +278,26 @@ find_library(CODAC_UNSUPPORTED_LIBRARY NAMES ${PROJECT_NAME}-unsupported PATH_SUFFIXES lib) - find_package(IBEX REQUIRED) - - set(CODAC_VERSION ${PROJECT_VERSION}) - set(CODAC_LIBRARIES \${CODAC_CORE_LIBRARY} \${CODAC_GRAPHICS_LIBRARY} \${CODAC_UNSUPPORTED_LIBRARY} Ibex::ibex) - set(CODAC_INCLUDE_DIRS \${CODAC_CORE_INCLUDE_DIR}/../ \${CODAC_CORE_INCLUDE_DIR}/../eigen3/ \${CODAC_CORE_INCLUDE_DIR} \${CODAC_GRAPHICS_INCLUDE_DIR} \${CODAC_UNSUPPORTED_INCLUDE_DIR}) - - set(CODAC_C_FLAGS \"\") - set(CODAC_CXX_FLAGS \"\") +${CODAC_GAOL_CONFIG_SNIPPET} + # Projects written for a Codac that depended on IBEX call ibex_init_common() + # after find_package(CODAC), as the manual used to tell them to, for the + # interval arithmetic flags. Those flags now come with CODAC_CXX_FLAGS, and + # this ibex_init_common() does nothing: it is only there so that such + # projects still configure. A project that finds IBEX itself, before or + # after Codac, gets IBEX's own function instead. + if(NOT COMMAND ibex_init_common) + function(ibex_init_common) + endfunction() + endif() + + # codac-core links PUBLIC against Threads::Threads (see src/core/CMakeLists.txt), + # e.g. for peibos/threading code -- without it, a downstream target that + # actually pulls in that code (such as the 11_peibos example) fails to link + # with an undefined reference to pthread_create. The imported targets + # included below name Threads::Threads, so it also has to be in scope by + # the time they are defined. + set(THREADS_PREFER_PTHREAD_FLAG ON) + find_package(Threads REQUIRED) ") if(WITH_PYTHON) @@ -87,8 +310,6 @@ PATH_SUFFIXES include/${PROJECT_NAME}-sympy) find_library(CODAC_SYMPY_LIBRARY NAMES ${PROJECT_NAME}-sympy PATH_SUFFIXES lib) - - set(CODAC_SYMPY_LIBRARY \${CODAC_SYMPY_LIBRARY} \${CODAC_LIBRARIES}) ") endif() @@ -104,15 +325,23 @@ PATH_SUFFIXES include/${PROJECT_NAME}-capd) find_library(CODAC_CAPD_LIBRARY NAMES ${PROJECT_NAME}-capd PATH_SUFFIXES lib) - - set(CODAC_CAPD_LIBRARY \${CODAC_CAPD_LIBRARY} \${CODAC_LIBRARIES}) ") endif() file(APPEND ${CODAC_CMAKE_CONFIG_FILE} " - set(CODAC_LIBRARIES \${CODAC_LIBRARIES} \${CODAC_GRAPHICS_LIBRARY} \${CODAC_CORE_LIBRARY}) - ") + + # The Codac modules, as imported targets: + + include(\${CMAKE_CURRENT_LIST_DIR}/${PROJECT_NAME}-targets.cmake) + + set(CODAC_VERSION ${PROJECT_VERSION}) + set(CODAC_LIBRARIES ${CODAC_LIBRARIES}) + set(CODAC_INCLUDE_DIRS ${CODAC_INCLUDE_DIRS}) + + set(CODAC_C_FLAGS \"\") + set(CODAC_CXX_FLAGS \"${CODAC_CXX_FLAGS}\") + ") install(FILES ${CODAC_CMAKE_CONFIG_FILE} DESTINATION ${CMAKE_INSTALL_CMAKE}) @@ -122,10 +351,28 @@ # ==================================== set(CODAC_MAIN_HEADER ${CMAKE_CURRENT_BINARY_DIR}/codac) - file(WRITE ${CODAC_MAIN_HEADER} "/* This file is generated by CMake */\n\n") - file(APPEND ${CODAC_MAIN_HEADER} "#pragma once\n\n") + + set(CODAC_MAIN_HEADER_CONTENT "/* This file is generated by CMake */\n\n") + string(APPEND CODAC_MAIN_HEADER_CONTENT "#pragma once\n\n") foreach(header_path ${CODAC_MAIN_SUBHEADERS}) get_filename_component(header_name ${header_path} NAME) - file(APPEND ${CODAC_MAIN_HEADER} "#include <${header_name}>\n") + string(APPEND CODAC_MAIN_HEADER_CONTENT "#include <${header_name}>\n") endforeach() + + # Compare with the existing file, if any + if(EXISTS "${CODAC_MAIN_HEADER}") + file(READ "${CODAC_MAIN_HEADER}" EXISTING_CONTENT) + string(MD5 EXISTING_HASH "${EXISTING_CONTENT}") + string(MD5 NEW_HASH "${CODAC_MAIN_HEADER_CONTENT}") + else() + set(EXISTING_HASH "") + set(NEW_HASH "new") + endif() + + # Only rewrite it when it changed: 24 examples #include , and an + # unconditional rewrite moves its timestamp on every cmake run, rebuilding + # all of them although nothing changed. + if(NOT EXISTING_HASH STREQUAL NEW_HASH) + file(WRITE "${CODAC_MAIN_HEADER}" "${CODAC_MAIN_HEADER_CONTENT}") + endif() install(FILES ${CODAC_MAIN_HEADER} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/) \ No newline at end of file diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index db02e9526..fb99d18c1 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -276,8 +276,6 @@ ${CMAKE_CURRENT_SOURCE_DIR}/tools/codac2_trunc.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tools/codac2_trunc.h ${CMAKE_CURRENT_SOURCE_DIR}/tools/codac2_TypeInfo.h - ${CMAKE_CURRENT_SOURCE_DIR}/tools/ibex/codac2_ibex.h - ${CMAKE_CURRENT_SOURCE_DIR}/tools/ibex/codac2_ibex_impl.h ${CMAKE_CURRENT_SOURCE_DIR}/trajectory/codac2_AnalyticTraj.h ${CMAKE_CURRENT_SOURCE_DIR}/trajectory/codac2_SampledTraj.h @@ -293,13 +291,27 @@ # Create the target for libcodac-core ################################################################################ - #if(NOT CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 20) - set(CMAKE_CXX_STANDARD_REQUIRED ON) - #endif() - add_library(${PROJECT_NAME}-core ${CODAC_CORE_SRC}) - target_include_directories(${PROJECT_NAME}-core PUBLIC + + set_target_properties(${PROJECT_NAME}-core PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON + CXX_EXTENSIONS OFF + ) + + # The directories holding the public core headers, in their canonical + # location under src/core. Published as a cache variable so that the tests, + # the examples and the Python bindings all reach the headers by the same + # path as codac-core itself, instead of each keeping its own copy of this + # list (which had already drifted). + # + # The path a translation unit used is the path gcov records, so a header + # reached both through src/ and through a copy in the build tree ends up as + # two unrelated entries in the coverage report, each showing only the part + # its own callers exercised, and is reported well below its real coverage. + # Consumers therefore list these directories first; the build tree stays on + # the path for the generated umbrella headers (codac-core.h and the rest). + set(CODAC_CORE_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/actions ${CMAKE_CURRENT_SOURCE_DIR}/contractors ${CMAKE_CURRENT_SOURCE_DIR}/domains @@ -326,8 +338,27 @@ ${CMAKE_CURRENT_SOURCE_DIR}/separators ${CMAKE_CURRENT_SOURCE_DIR}/tools ${CMAKE_CURRENT_SOURCE_DIR}/trajectory + CACHE INTERNAL "Directories holding the public headers of codac-core") + + # The source directories describe the build tree only: they are gone once + # Codac is installed, and install(EXPORT) below refuses to export a target + # whose interface names a path inside the source tree. The install tree + # answers with the directories the headers were copied into -- include/ + # itself, through which the umbrella header is reached, and + # include/eigen3, where Eigen's headers land. + # + # Eigen is a build-tree dependency for the same reason: it is fetched by this + # build rather than found, so Eigen3::Eigen is a target of ours that no + # consumer's find_package() could produce. Downstream it is reached as the + # plain include directory above, which is all a header-only library needs. + target_include_directories(${PROJECT_NAME}-core PUBLIC + "$" + $ + $ + $ ) - target_link_libraries(${PROJECT_NAME}-core PUBLIC Ibex::ibex Eigen3::Eigen Threads::Threads) + codac_publish_include_dirs(${CODAC_CORE_INCLUDE_DIRS}) + target_link_libraries(${PROJECT_NAME}-core PUBLIC Codac::gaol $ Threads::Threads) ################################################################################ @@ -335,39 +366,59 @@ ################################################################################ set(CODAC_PKG_CONFIG_CFLAGS "${CODAC_PKG_CONFIG_CFLAGS} -I\${includedir}/${PROJECT_NAME}-core" PARENT_SCOPE) - set(CODAC_PKG_CONFIG_LIBS "${CODAC_PKG_CONFIG_LIBS} -l${PROJECT_NAME}-core" PARENT_SCOPE) + set(CODAC_PKG_CONFIG_MODULE_LIBS "${CODAC_PKG_CONFIG_MODULE_LIBS} -l${PROJECT_NAME}-core" PARENT_SCOPE) + - ################################################################################ -# Installation of libcodac-core files +# Installation / build include tree ################################################################################ - -# Getting header files from sources foreach(srcfile ${CODAC_CORE_SRC}) if(srcfile MATCHES "\\.h$" OR srcfile MATCHES "\\.hpp$") list(APPEND CODAC_CORE_HDR ${srcfile}) - file(COPY ${srcfile} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/../../include) endif() endforeach() + # Generating the file codac-core.h +# ================================ set(CODAC_CORE_MAIN_HEADER ${CMAKE_CURRENT_BINARY_DIR}/codac-core.h) + codac_publish_generated_include_dirs(${CMAKE_CURRENT_BINARY_DIR}) set(CODAC_MAIN_SUBHEADERS ${CODAC_MAIN_SUBHEADERS} "codac-core.h" PARENT_SCOPE) - file(WRITE ${CODAC_CORE_MAIN_HEADER} "/* This file is generated by CMake */\n\n") - file(APPEND ${CODAC_CORE_MAIN_HEADER} "#pragma once\n\n") + + # Generate the content of the umbrella header + set(CODAC_CORE_HEADER_CONTENT "/* This file is generated by CMake */\n\n#pragma once\n\n") foreach(header_path ${CODAC_CORE_HDR}) get_filename_component(header_name ${header_path} NAME) if((NOT header_name MATCHES "^.*_addons.*$") AND (NOT header_name MATCHES "^.*_impl.*$")) - file(APPEND ${CODAC_CORE_MAIN_HEADER} "#include <${header_name}>\n") + string(APPEND CODAC_CORE_HEADER_CONTENT "#include <${header_name}>\n") endif() endforeach() - file(COPY ${CODAC_CORE_MAIN_HEADER} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/../../include) + + # Compare with the existing file, if any + if(EXISTS "${CODAC_CORE_MAIN_HEADER}") + file(READ "${CODAC_CORE_MAIN_HEADER}" EXISTING_CONTENT) + string(MD5 EXISTING_HASH "${EXISTING_CONTENT}") + string(MD5 NEW_HASH "${CODAC_CORE_HEADER_CONTENT}") + else() + set(EXISTING_HASH "") + set(NEW_HASH "new") + endif() + + # Only rewrite it when it changed + if(NOT EXISTING_HASH STREQUAL NEW_HASH) + file(WRITE "${CODAC_CORE_MAIN_HEADER}" "${CODAC_CORE_HEADER_CONTENT}") + endif() # Install files in system directories +# ==================================== - install(TARGETS ${PROJECT_NAME}-core DESTINATION ${CMAKE_INSTALL_LIBDIR}) + install(TARGETS ${PROJECT_NAME}-core + EXPORT ${PROJECT_NAME}-targets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES ${CODAC_CORE_HDR} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}-core) install(FILES ${CODAC_CORE_MAIN_HEADER} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}-core) diff --git a/src/core/actions/codac2_OctaSym.h b/src/core/actions/codac2_OctaSym.h index 81cdb1d7c..db0bd840f 100644 --- a/src/core/actions/codac2_OctaSym.h +++ b/src/core/actions/codac2_OctaSym.h @@ -23,7 +23,7 @@ namespace codac2 class SepBase; class SepAction; class SetExpr; - class OctaSymOp; + struct OctaSymOp; /** * \class Action @@ -168,4 +168,4 @@ namespace codac2 return str; } }; -} \ No newline at end of file +} diff --git a/src/core/contractors/codac2_CtcDist.h b/src/core/contractors/codac2_CtcDist.h index 6a55e6820..9ee1fc55e 100644 --- a/src/core/contractors/codac2_CtcDist.h +++ b/src/core/contractors/codac2_CtcDist.h @@ -37,6 +37,7 @@ namespace codac2 */ CtcDist(); + using CtcBase::contract; /** * \brief Applies \f$\mathcal{C}_{\textrm{dist}}\big([\mathbf{x}]\big)\f$. * @@ -55,4 +56,4 @@ namespace codac2 */ void contract(Interval& a1, Interval& a2, Interval& b1, Interval& b2, Interval& d) const; }; -} \ No newline at end of file +} diff --git a/src/core/contractors/codac2_CtcPolar.h b/src/core/contractors/codac2_CtcPolar.h index 23f3f704c..6bc78549d 100644 --- a/src/core/contractors/codac2_CtcPolar.h +++ b/src/core/contractors/codac2_CtcPolar.h @@ -42,6 +42,7 @@ namespace codac2 : Ctc(4) { } + using CtcBase::contract; /** * \brief Applies \f$\mathcal{C}_{\textrm{polar}}\big([\mathbf{x}]\big)\f$. * @@ -59,4 +60,4 @@ namespace codac2 */ void contract(Interval& x, Interval& y, Interval& rho, Interval& theta) const; }; -} \ No newline at end of file +} diff --git a/src/core/contractors/codac2_CtcProj.h b/src/core/contractors/codac2_CtcProj.h index 20fc5c91d..88b89c2e1 100644 --- a/src/core/contractors/codac2_CtcProj.h +++ b/src/core/contractors/codac2_CtcProj.h @@ -39,7 +39,8 @@ namespace codac2 assert_release(size_of(c) >= (Index)_xi.size() && "cannot compute a projection of a set into a superset"); assert_release(default_eps > 0.); } - + + using CtcBase::contract; void contract(IntervalVector& x) const; void contract(IntervalVector& x, double eps) const; diff --git a/src/core/domains/codac2_Domain.h b/src/core/domains/codac2_Domain.h index f7827d96e..77d0fb63b 100644 --- a/src/core/domains/codac2_Domain.h +++ b/src/core/domains/codac2_Domain.h @@ -62,7 +62,7 @@ namespace codac2 }; template - static double total_volume(const X&... x) + inline double total_volume(const X&... x) { double v = 0; ((v += x.volume()), ...); diff --git a/src/core/domains/ellipsoid/codac2_Ellipsoid.cpp b/src/core/domains/ellipsoid/codac2_Ellipsoid.cpp index 2c9dd5e31..b2760d2ba 100644 --- a/src/core/domains/ellipsoid/codac2_Ellipsoid.cpp +++ b/src/core/domains/ellipsoid/codac2_Ellipsoid.cpp @@ -8,6 +8,7 @@ */ #include "codac2_Ellipsoid.h" +#include "codac2_math.h" #include using namespace std; @@ -114,9 +115,9 @@ namespace codac2 { vector vx, vy; double dr = 0.2; - for(double t = -M_PI ; t < M_PI+dr ; t+=dr) + for(double t = -PI ; t < PI+dr ; t+=dr) { - if(t > M_PI) t = M_PI; + if(t > PI) t = PI; Vector p = x.mu+V*Vector({std::cos(t),std::sin(t)}).cwiseProduct(D_); vx.push_back(p[0]); vy.push_back(p[1]); diff --git a/src/core/domains/interval/codac2_Interval_impl.h b/src/core/domains/interval/codac2_Interval_impl.h index 8e2bd0cfc..880fbb14b 100644 --- a/src/core/domains/interval/codac2_Interval_impl.h +++ b/src/core/domains/interval/codac2_Interval_impl.h @@ -26,10 +26,7 @@ namespace codac2 inline Interval::Interval(double a) : gaol::interval(a) - { - if(a == -oo || a == oo) - set_empty(); - } + { } inline Interval::Interval(double a, double b) : gaol::interval(a,b) @@ -75,7 +72,7 @@ namespace codac2 else { - assert_release("'Interval' can only be defined by one or two 'double' values."); + assert_release_unreachable("'Interval' can only be defined by one or two 'double' values."); } return *this; @@ -83,11 +80,7 @@ namespace codac2 inline Interval& Interval::operator=(double x) { - if(x == -oo || x == oo) - set_empty(); - else - gaol::interval::operator=(x); - + gaol::interval::operator=(x); return *this; } @@ -135,9 +128,7 @@ namespace codac2 inline double Interval::mid() const { - double m = gaol::interval::midpoint(); - gaol::round_upward(); - return m; + return gaol::interval::midpoint(); } inline double Interval::mag() const @@ -152,7 +143,7 @@ namespace codac2 inline double Interval::smag() const { - return (abs(lb()) > abs(ub())) ? lb() : ub(); + return (std::abs(lb()) > std::abs(ub())) ? lb() : ub(); } inline double Interval::smig() const @@ -192,15 +183,7 @@ namespace codac2 inline double Interval::diam() const { - if(is_empty()) - return std::numeric_limits::quiet_NaN(); - - else - { - double d = gaol::interval::width(); - gaol::round_upward(); - return d; - } + return gaol::interval::width(); } inline double Interval::volume() const @@ -414,20 +397,12 @@ namespace codac2 inline Interval operator+(const Interval& x, double y) { - if(y == -oo || y == oo) - return Interval::empty(); - - else - return gaol::operator+(x,y); + return gaol::operator+(x,y); } inline Interval operator+(double x, const Interval& y) { - if(x == -oo || x == oo) - return Interval::empty(); - - else - return gaol::operator+(x,y); + return gaol::operator+(x,y); } inline Interval operator+(const Interval& x, const Interval& y) @@ -437,20 +412,12 @@ namespace codac2 inline Interval operator-(const Interval& x, double y) { - if(y == -oo || y == oo) - return Interval::empty(); - - else - return gaol::operator-(x, y); + return gaol::operator-(x, y); } inline Interval operator-(double x, const Interval& y) { - if(x == -oo || x == oo) - return Interval::empty(); - - else - return gaol::operator-(x, y); + return gaol::operator-(x, y); } inline Interval operator-(const Interval& x, const Interval& y) @@ -460,20 +427,12 @@ namespace codac2 inline Interval operator*(const Interval& x, double y) { - if(y == -oo || y == oo) - return Interval::empty(); - - else - return gaol::operator*(x,y); + return gaol::operator*(x,y); } inline Interval operator*(double x, const Interval& y) { - if(x == -oo || x == oo) - return Interval::empty(); - - else - return gaol::operator*(x,y); + return gaol::operator*(x,y); } inline Interval operator*(const Interval& x, const Interval& y) @@ -483,20 +442,12 @@ namespace codac2 inline Interval operator/(const Interval& x, double y) { - if(y == -oo || y == oo) - return Interval::empty(); - - else - return gaol::operator/(x,y); + return gaol::operator/(x,y); } inline Interval operator/(double x, const Interval& y) { - if(x == -oo || x == oo) - return Interval::empty(); - - else - return gaol::operator/(x,y); + return gaol::operator/(x,y); } inline Interval operator/(const Interval& x, const Interval& y) @@ -518,10 +469,7 @@ namespace codac2 inline Interval& Interval::operator+=(double x) { - if(x == -oo || x == oo) - set_empty(); - else - gaol::interval::operator+=(x); + gaol::interval::operator+=(x); return *this; } @@ -538,10 +486,7 @@ namespace codac2 inline Interval& Interval::operator-=(double x) { - if(x == -oo || x == oo) - set_empty(); - else - gaol::interval::operator-=(x); + gaol::interval::operator-=(x); return *this; } @@ -553,10 +498,7 @@ namespace codac2 inline Interval& Interval::operator*=(double x) { - if(x == -oo || x == oo) - set_empty(); - else - gaol::interval::operator*=(x); + gaol::interval::operator*=(x); return *this; } @@ -568,10 +510,7 @@ namespace codac2 inline Interval& Interval::operator/=(double x) { - if(x == -oo || x == oo) - set_empty(); - else - gaol::interval::operator/=(x); + gaol::interval::operator/=(x); return *this; } @@ -624,7 +563,7 @@ namespace codac2 inline Interval operator""_i(long double x) { - return Interval(x); + return Interval(static_cast(x)); } inline double prev_float(double x) diff --git a/src/core/domains/interval/codac2_Interval_operations_impl.h b/src/core/domains/interval/codac2_Interval_operations_impl.h index 53e33a4ac..1c5120df2 100644 --- a/src/core/domains/interval/codac2_Interval_operations_impl.h +++ b/src/core/domains/interval/codac2_Interval_operations_impl.h @@ -25,240 +25,107 @@ namespace codac2 inline Interval sqrt(const Interval& x) { - Interval y = gaol::sqrt(x); - gaol::round_upward(); - return y; + return gaol::sqrt(x); } inline Interval pow(const Interval& x, int p) { - Interval y = gaol::pow(x,p); - //gaol::round_upward(); // not necessary? - return y; + return gaol::pow(x,p); } inline Interval pow(const Interval& x, double p) { - if(p == -oo || p == oo) - return Interval::empty(); - - else - { - Interval y = gaol::pow(x,p); - gaol::round_upward(); - return y; - } + return gaol::pow(x,p); } inline Interval pow(const Interval& x, const Interval& p) { - Interval y = gaol::pow(x,p); - gaol::round_upward(); - return y; + return gaol::pow(x,p); } inline Interval root(const Interval& x, int p) { - // Get the root of the positive part (gaol does - // not consider negative values to be in the definition - // domain of the root function) - - gaol::interval y = gaol::nth_root(x, p>=0 ? p : -p); - - if(p%2 == 1 && x.lb() < 0) - y |= -gaol::nth_root(-x, p >= 0 ? p : -p); + Interval y = gaol::nth_root(x, p >= 0 ? p : -p); if(p < 0) y = 1.0/y; - gaol::round_upward(); return y; } inline Interval exp(const Interval& x) { - Interval y = gaol::exp(x); - gaol::round_upward(); - return y; + return gaol::exp(x); } inline Interval log(const Interval& x) { - if(x.ub() <= 0) // gaol returns (-oo,-DBL_MAX) if x.ub()==0, instead of empty set - return Interval::empty(); - - else - { - Interval y = gaol::log(x); - gaol::round_upward(); - return y; - } + return gaol::log(x); } inline Interval cos(const Interval& x) { - Interval y = gaol::cos(x); - gaol::round_upward(); - return y; + return gaol::cos(x); } inline Interval sin(const Interval& x) { - Interval y = gaol::sin(x); - gaol::round_upward(); - return y; + return gaol::sin(x); } inline Interval tan(const Interval& x) { - Interval y = gaol::tan(x); - gaol::round_upward(); - return y; + return gaol::tan(x); } inline Interval acos(const Interval& x) { - Interval y = gaol::acos(x); - gaol::round_upward(); - return y; + return gaol::acos(x); } inline Interval asin(const Interval& x) { - Interval y = gaol::asin(x); - gaol::round_upward(); - return y; + return gaol::asin(x); } inline Interval atan(const Interval& x) { - Interval y = gaol::atan(x); - gaol::round_upward(); - return y; + return gaol::atan(x); } inline Interval atan2(const Interval& y, const Interval& x) { - if(y.is_empty() || x.is_empty()) - return Interval::empty(); - - // We handle the special case x=[0,0] separately - else if(x == Interval::zero()) - { - if(y.lb() >= 0) - { - if(y.ub() == 0) - return Interval::empty(); // atan2(0,0) is undefined - else - return Interval::half_pi(); - } - - else if(y.ub() <= 0) - return -Interval::half_pi(); - - else - return Interval(-1,1)*Interval::half_pi(); - } - - else if(x.lb() >= 0) - return atan(y/x); // now, x.ub()>0 -> atan does not give an empty set - - else if(x.ub() <= 0) - { - if(y.lb() >= 0) - return atan(y/x) + Interval::pi(); // x.lb()<0 - else if(y.ub() < 0) - return atan(y/x) - Interval::pi(); - else - return Interval(-1,1)*Interval::pi(); - } - - else - { - if(y.lb() >= 0) - return atan(y/x.ub()) | (atan(y/x.lb()) + Interval::pi()); - - else if(y.ub() <= 0) - { - if(x.lb() != -oo) - { - if(x.ub() != oo) - return (atan(y/x.lb())-Interval::pi()) | atan(y/x.ub()); - else - return (atan(y/x.lb())-Interval::pi()) | Interval::zero(); - } - - else - { - if(x.ub() != oo) - return (-Interval::pi()) | atan(y/x.ub()); - else - return -Interval::pi() | Interval::zero(); - } - } - - else - return Interval(-1,1)*Interval::pi(); - } + return gaol::atan2(y,x); } inline Interval cosh(const Interval& x) { - Interval y; - if(x.is_unbounded()) - y = Interval(gaol::cosh(x).left(),oo); - else - y = gaol::cosh(x); - gaol::round_upward(); - return y; + return gaol::cosh(x); } inline Interval sinh(const Interval& x) { - Interval y = gaol::sinh(x); - gaol::round_upward(); - return y; + return gaol::sinh(x); } inline Interval tanh(const Interval& x) { - Interval y = gaol::tanh(x); - gaol::round_upward(); - return y; + return gaol::tanh(x); } inline Interval acosh(const Interval& x) { - Interval y = gaol::acosh(x); - gaol::round_upward(); - return y; + return gaol::acosh(x); } inline Interval asinh(const Interval& x) { - if(x.is_empty()) - return Interval::empty(); - - else if(x.lb() >= 0) - return gaol::asinh(x); - - else if(x.ub() <= 0) - return -gaol::asinh(-x); - - else - return { - -gaol::asinh(gaol::interval(0,-x.lb())).right(), - gaol::asinh(gaol::interval(0,x.ub())).right() - }; - - // no round_upward? + return gaol::asinh(x); } inline Interval atanh(const Interval& x) { - Interval y = gaol::atanh(x); - gaol::round_upward(); - return y; + return gaol::atanh(x); } inline Interval abs(const Interval& x) diff --git a/src/core/domains/paving/codac2_Paving.cpp b/src/core/domains/paving/codac2_Paving.cpp index c14c2142f..5d39a102f 100644 --- a/src/core/domains/paving/codac2_Paving.cpp +++ b/src/core/domains/paving/codac2_Paving.cpp @@ -18,14 +18,17 @@ namespace codac2 // PavingOut class PavingOut::PavingOut(Index n) - : Paving(n) + : Paving() { assert_release(n > 0); + this->init_tree(IntervalVector(n)); } PavingOut::PavingOut(const IntervalVector& x) - : Paving(x) - { } + : Paving() + { + this->init_tree(x); + } std::list PavingOut::connected_subsets(const PavingOut::NodeValue_& node_value) const { @@ -77,20 +80,23 @@ namespace codac2 IntervalVector operator&(const IntervalVector& x, const PavingOut& p) { return p & x; - }; + } // PavingInOut class PavingInOut::PavingInOut(Index n) - : Paving(n) + : Paving() { assert_release(n > 0); + this->init_tree(IntervalVector(n)); } PavingInOut::PavingInOut(const IntervalVector& x) - : Paving(x) - { } + : Paving() + { + this->init_tree(x); + } std::list PavingInOut::connected_subsets(const PavingInOut::NodeValue_& node_value) const { @@ -141,4 +147,4 @@ namespace codac2 l.push_back(n->unknown()); return l; }; -} \ No newline at end of file +} diff --git a/src/core/domains/paving/codac2_Paving.h b/src/core/domains/paving/codac2_Paving.h index 25f5c8f39..7f55aa161 100644 --- a/src/core/domains/paving/codac2_Paving.h +++ b/src/core/domains/paving/codac2_Paving.h @@ -32,16 +32,19 @@ namespace codac2 using NodeValue_ = std::function(Node_)>; using ConnectedSubset_ = Subpaving

; - Paving(Index n) - : Paving(IntervalVector(n)) - { - assert_release(n > 0); - } - - Paving(const IntervalVector& x) - : _tree(std::make_shared>(*static_cast(this), x)) - { } - + protected: + // Paving(Index n) + // : Paving(IntervalVector(n)) + // { + // assert_release(n > 0); + // } + // Paving(const IntervalVector& x) + // : _tree(std::make_shared>(*static_cast(this), x)) + // { } + + Paving () { } + + public: inline Index size() const { return std::get<0>(_tree->boxes()).size(); @@ -128,6 +131,12 @@ namespace codac2 friend class PavingNode

; + inline void init_tree(const IntervalVector& x) + { + _tree = std::make_shared>(*static_cast(this), x); + } + + inline static NodeTuple_ init_tuple(const IntervalVector& x) { return std::make_tuple(((X)x)...); @@ -191,4 +200,4 @@ namespace codac2 static const NodeValue_ outer, outer_complem, inner, bound, all; }; -} \ No newline at end of file +} diff --git a/src/core/domains/paving/codac2_PavingNode.h b/src/core/domains/paving/codac2_PavingNode.h index e294e1bd7..aa69a0108 100644 --- a/src/core/domains/paving/codac2_PavingNode.h +++ b/src/core/domains/paving/codac2_PavingNode.h @@ -61,7 +61,7 @@ namespace codac2 std::shared_ptr> top() const { - return _top; + return _top.lock(); } std::shared_ptr> top() @@ -102,7 +102,7 @@ namespace codac2 void visit(std::function>)> visitor) const { - if(!_top && !_right && _left && left()->boxes() == _x) + if(_top.expired() && !_right && _left && left()->boxes() == _x) left()->visit(visitor); else if(visitor(this->shared_from_this())) @@ -114,7 +114,7 @@ namespace codac2 void visit(std::function>)> visitor) { - if(!_top && !_right && _left && left()->boxes() == _x) + if(_top.expired() && !_right && _left && left()->boxes() == _x) _left->visit(visitor); else if(visitor(this->shared_from_this())) @@ -157,7 +157,7 @@ namespace codac2 const P& _paving; typename P::NodeTuple_ _x; - std::shared_ptr> _top = nullptr; + std::weak_ptr> _top; std::shared_ptr> _left = nullptr, _right = nullptr; }; -} \ No newline at end of file +} diff --git a/src/core/domains/tube/codac2_Slice.h b/src/core/domains/tube/codac2_Slice.h index 4d74f0389..29c6af000 100644 --- a/src/core/domains/tube/codac2_Slice.h +++ b/src/core/domains/tube/codac2_Slice.h @@ -295,7 +295,7 @@ namespace codac2 * * No propagation is performed on adjacent slices. */ - inline void init() + inline void init() override { this->T::init(); // Nothing to propagate to adjacent codomains @@ -306,7 +306,7 @@ namespace codac2 * * Adjacent gates are updated accordingly. */ - inline void set_empty() + inline void set_empty() override { set_empty(true); } diff --git a/src/core/domains/tube/codac2_SlicedTube.h b/src/core/domains/tube/codac2_SlicedTube.h index f8dca4c91..dd075a492 100644 --- a/src/core/domains/tube/codac2_SlicedTube.h +++ b/src/core/domains/tube/codac2_SlicedTube.h @@ -382,7 +382,7 @@ namespace codac2 { return eval_common(t, [this](auto it, const Interval& t_) { - return slice(it)->operator()(t_); + return this->slice(it)->operator()(t_); }); } @@ -398,7 +398,8 @@ namespace codac2 { return eval_common(t, [this,&v](auto it, const Interval& t_) { - return slice(it)->operator()(t_, *v.slice(it)); + return this->slice(it)->operator()(t_, *v.slice(it)); + }); } @@ -783,7 +784,7 @@ namespace codac2 { return invert_common(y, t, [this,&y](auto it, const Interval& t_) { - return slice(it)->invert(y, t_); + return this->slice(it)->invert(y, t_); }); } @@ -812,7 +813,7 @@ namespace codac2 { return invert_common_subsets(y, v_t, t, [this,&y](auto it, const Interval& t_) { - return slice(it)->invert(y, t_); + return this->slice(it)->invert(y, t_); }); } @@ -849,7 +850,7 @@ namespace codac2 { return invert_common(y, t, [this,&v,&y](auto it, const Interval& t_) { - return slice(it)->invert(y, *v.slice(it), t_); + return this->slice(it)->invert(y, *v.slice(it), t_); }); } @@ -886,7 +887,7 @@ namespace codac2 { return invert_common_subsets(y, v_t, t, [this,&v,&y](auto it, const Interval& t_) { - return slice(it)->invert(y, *v.slice(it), t_); + return this->slice(it)->invert(y, *v.slice(it), t_); }); } @@ -1265,4 +1266,4 @@ namespace codac2 } } -#include "codac2_SlicedTube_integral_impl.h" \ No newline at end of file +#include "codac2_SlicedTube_integral_impl.h" diff --git a/src/core/domains/tube/codac2_SlicedTube_operations.h b/src/core/domains/tube/codac2_SlicedTube_operations.h index f9e46d761..2412e2935 100644 --- a/src/core/domains/tube/codac2_SlicedTube_operations.h +++ b/src/core/domains/tube/codac2_SlicedTube_operations.h @@ -16,6 +16,12 @@ namespace codac2 { + // These macros stand in for the body of the functions declared below. Doxygen + // parses this header to produce the Python docstrings, and it cannot name a + // function whose body it does not recognise, so every macro used that way is + // listed in EXPAND_AS_DEFINED in doc/api/Doxyfile.in. Add any new one there + // too, otherwise the bindings lose the docstring identifiers they expect and + // stop compiling. #define macro_unary_tube(f) \ { \ auto y = x1; \ @@ -142,7 +148,7 @@ namespace codac2 */ template inline SlicedTube& operator|=(SlicedTube& x1, const SlicedTube& x2) - macro_member_binary_tube_tube(operator_tube_union); + macro_member_binary_tube_tube(operator_tube_union) /** * \brief Pointwise hull-union of two tubes with the same codomain type. @@ -158,7 +164,7 @@ namespace codac2 */ template inline SlicedTube operator|(const SlicedTube& x1, const SlicedTube& x2) - macro_binary_tube_tube(operator_tube_union,x1); + macro_binary_tube_tube(operator_tube_union,x1) /** * \brief Pointwise intersection assignment of two tubes with the same codomain type. @@ -174,7 +180,7 @@ namespace codac2 */ template inline SlicedTube& operator&=(SlicedTube& x1, const SlicedTube& x2) - macro_member_binary_tube_tube(operator_tube_intersection); + macro_member_binary_tube_tube(operator_tube_intersection) /** * \brief Pointwise intersection of two tubes with the same codomain type. @@ -190,7 +196,7 @@ namespace codac2 */ template inline SlicedTube operator&(const SlicedTube& x1, const SlicedTube& x2) - macro_binary_tube_tube(operator_tube_intersection,x1); + macro_binary_tube_tube(operator_tube_intersection,x1) /** * \brief Unary plus. @@ -221,7 +227,7 @@ namespace codac2 */ template inline SlicedTube operator+(const SlicedTube& x1, const SlicedTube& x2) - macro_binary_tube_tube(operator_tube_add,x1); + macro_binary_tube_tube(operator_tube_add,x1) /** * \brief Pointwise sum of a tube and a constant object with matching codomain type. @@ -238,7 +244,7 @@ namespace codac2 template requires NonSlicedTube inline SlicedTube operator+(const SlicedTube& x1, const Q& x2) - macro_binary_tube_real(operator_tube_add); + macro_binary_tube_real(operator_tube_add) /** * \brief Pointwise sum of a constant object and a tube with matching codomain type. @@ -255,7 +261,7 @@ namespace codac2 template requires NonSlicedTube inline SlicedTube operator+(const Q& x1, const SlicedTube& x2) - macro_binary_real_tube(operator_tube_add); + macro_binary_real_tube(operator_tube_add) /** * \brief Pointwise addition assignment with a constant object of matching codomain type. @@ -267,7 +273,7 @@ namespace codac2 template requires NonSlicedTube inline SlicedTube& operator+=(SlicedTube& x1, const Q& x2) - macro_member_binary_tube_real(operator_tube_add); + macro_member_binary_tube_real(operator_tube_add) /** * \brief Pointwise addition assignment with a tube of the same codomain type. @@ -278,7 +284,7 @@ namespace codac2 */ template inline SlicedTube& operator+=(SlicedTube& x1, const SlicedTube& x2) - macro_member_binary_tube_tube(operator_tube_add); + macro_member_binary_tube_tube(operator_tube_add) /** * \brief Unary minus. @@ -309,7 +315,7 @@ namespace codac2 */ template inline SlicedTube operator-(const SlicedTube& x1, const SlicedTube& x2) - macro_binary_tube_tube(operator_tube_sub,x1); + macro_binary_tube_tube(operator_tube_sub,x1) /** * \brief Pointwise difference between a tube and a constant object with matching codomain type. @@ -326,7 +332,7 @@ namespace codac2 template requires NonSlicedTube inline SlicedTube operator-(const SlicedTube& x1, const Q& x2) - macro_binary_tube_real(operator_tube_sub); + macro_binary_tube_real(operator_tube_sub) /** * \brief Pointwise difference between a constant object and a tube with matching codomain type. @@ -343,7 +349,7 @@ namespace codac2 template requires NonSlicedTube inline SlicedTube operator-(const Q& x1, const SlicedTube& x2) - macro_binary_real_tube(operator_tube_sub); + macro_binary_real_tube(operator_tube_sub) /** * \brief Pointwise subtraction assignment with a constant object of matching codomain type. @@ -355,7 +361,7 @@ namespace codac2 template requires NonSlicedTube inline SlicedTube& operator-=(SlicedTube& x1, const Q& x2) - macro_member_binary_tube_real(operator_tube_sub); + macro_member_binary_tube_real(operator_tube_sub) /** * \brief Pointwise subtraction assignment with a tube of the same codomain type. @@ -366,7 +372,7 @@ namespace codac2 */ template inline SlicedTube& operator-=(SlicedTube& x1, const SlicedTube& x2) - macro_member_binary_tube_tube(operator_tube_sub); + macro_member_binary_tube_tube(operator_tube_sub) /** * \brief Pointwise multiplication of a scalar interval and a tube. @@ -383,7 +389,7 @@ namespace codac2 template requires (!std::is_same_v) inline SlicedTube operator*(const Interval& x1, const SlicedTube& x2) - macro_binary_real_tube(operator_tube_scal_mul); + macro_binary_real_tube(operator_tube_scal_mul) /** * \brief Pointwise multiplication of a tube by a scalar interval. @@ -400,7 +406,7 @@ namespace codac2 template requires (!std::is_same_v) inline SlicedTube operator*(const SlicedTube& x1, const Interval& x2) - macro_binary_tube_real(operator_tube_mul_scal); + macro_binary_tube_real(operator_tube_mul_scal) /** * \brief Pointwise multiplication of two scalar interval tubes. @@ -412,7 +418,7 @@ namespace codac2 * \return The resulting scalar interval tube. */ inline SlicedTube operator*(const SlicedTube& x1, const SlicedTube& x2) - macro_binary_tube_tube(operator_tube_mul,x1); + macro_binary_tube_tube(operator_tube_mul,x1) /** * \brief Pointwise multiplication of two interval-vector tubes. @@ -424,7 +430,7 @@ namespace codac2 * \return The resulting interval-vector tube. */ inline SlicedTube operator*(const SlicedTube& x1, const SlicedTube& x2) - macro_binary_tube_tube(operator_tube_mul,x1); + macro_binary_tube_tube(operator_tube_mul,x1) /** * \brief Pointwise multiplication of two interval-matrix tubes. @@ -436,7 +442,7 @@ namespace codac2 * \return The resulting interval-matrix tube. */ inline SlicedTube operator*(const SlicedTube& x1, const SlicedTube& x2) - macro_binary_tube_tube(operator_tube_mul,x1); + macro_binary_tube_tube(operator_tube_mul,x1) /** * \brief Pointwise multiplication of a scalar interval tube and a non-scalar tube. @@ -452,7 +458,7 @@ namespace codac2 template requires NonScalarTubeCodomain inline SlicedTube operator*(const SlicedTube& x1, const SlicedTube& x2) - macro_binary_tube_tube(operator_tube_mul,x2); + macro_binary_tube_tube(operator_tube_mul,x2) /** * \brief Pointwise multiplication of a non-scalar tube and a scalar interval tube. @@ -468,7 +474,7 @@ namespace codac2 template requires NonScalarTubeCodomain inline SlicedTube operator*(const SlicedTube& x1, const SlicedTube& x2) - macro_binary_tube_tube(operator_tube_mul,x1); + macro_binary_tube_tube(operator_tube_mul,x1) /** * \brief Pointwise multiplication of a tube and a constant object with matching codomain type. @@ -485,7 +491,7 @@ namespace codac2 template requires NonSlicedTube inline SlicedTube operator*(const SlicedTube& x1, const Q& x2) - macro_binary_tube_real(operator_tube_mul); + macro_binary_tube_real(operator_tube_mul) /** * \brief Pointwise multiplication of a constant object and a tube with matching codomain type. @@ -502,7 +508,7 @@ namespace codac2 template requires NonSlicedTube inline SlicedTube operator*(const Q& x1, const SlicedTube& x2) - macro_binary_real_tube(operator_tube_mul); + macro_binary_real_tube(operator_tube_mul) /** * \brief Pointwise multiplication of an interval-matrix tube by an interval-vector tube. @@ -514,7 +520,7 @@ namespace codac2 * \return The resulting interval-vector tube. */ inline SlicedTube operator*(const SlicedTube& x1, const SlicedTube& x2) - macro_binary_tube_tube(operator_tube_mul_vec,x2); + macro_binary_tube_tube(operator_tube_mul_vec,x2) /** * \brief Pointwise multiplication assignment with a constant object of matching codomain type. @@ -526,7 +532,7 @@ namespace codac2 template requires NonSlicedTube inline SlicedTube& operator*=(SlicedTube& x1, const Q& x2) - macro_member_binary_tube_real(operator_tube_mul); + macro_member_binary_tube_real(operator_tube_mul) /** * \brief Pointwise multiplication assignment with a tube of the same codomain type. @@ -537,7 +543,7 @@ namespace codac2 */ template inline SlicedTube& operator*=(SlicedTube& x1, const SlicedTube& x2) - macro_member_binary_tube_tube(operator_tube_mul); + macro_member_binary_tube_tube(operator_tube_mul) /** * \brief Pointwise multiplication assignment by a scalar interval tube. @@ -553,7 +559,7 @@ namespace codac2 template requires NonScalarTubeCodomain inline SlicedTube& operator*=(SlicedTube& x1, const SlicedTube& x2) - macro_member_binary_tube_tube(operator_tube_mul); + macro_member_binary_tube_tube(operator_tube_mul) /** * \brief Pointwise division of a tube by a scalar interval. @@ -570,7 +576,7 @@ namespace codac2 template requires (!std::is_same_v) inline SlicedTube operator/(const SlicedTube& x1, const Interval& x2) - macro_binary_tube_real(operator_tube_div_scal); + macro_binary_tube_real(operator_tube_div_scal) /** * \brief Pointwise division of two scalar interval tubes. @@ -582,7 +588,7 @@ namespace codac2 * \return The resulting scalar interval tube. */ inline SlicedTube operator/(const SlicedTube& x1, const SlicedTube& x2) - macro_binary_tube_tube(operator_tube_div,x1); + macro_binary_tube_tube(operator_tube_div,x1) /** * \brief Pointwise division of a non-scalar tube by a scalar interval tube. @@ -598,7 +604,7 @@ namespace codac2 template requires NonScalarTubeCodomain inline SlicedTube operator/(const SlicedTube& x1, const SlicedTube& x2) - macro_binary_tube_tube(operator_tube_div,x1); + macro_binary_tube_tube(operator_tube_div,x1) /** * \brief Pointwise division of a tube by a constant object with matching codomain type. @@ -615,7 +621,7 @@ namespace codac2 template requires NonSlicedTube inline SlicedTube operator/(const SlicedTube& x1, const Q& x2) - macro_binary_tube_real(operator_tube_div); + macro_binary_tube_real(operator_tube_div) /** * \brief Pointwise division of a constant object by a tube with matching codomain type. @@ -632,7 +638,7 @@ namespace codac2 template requires NonSlicedTube inline SlicedTube operator/(const Q& x1, const SlicedTube& x2) - macro_binary_real_tube(operator_tube_div); + macro_binary_real_tube(operator_tube_div) /** * \brief Pointwise division assignment with a constant object of matching codomain type. @@ -644,7 +650,7 @@ namespace codac2 template requires NonSlicedTube inline SlicedTube& operator/=(SlicedTube& x1, const Q& x2) - macro_member_binary_tube_real(operator_tube_div); + macro_member_binary_tube_real(operator_tube_div) /** * \brief Pointwise division assignment with a tube of the same codomain type. @@ -655,7 +661,7 @@ namespace codac2 */ template inline SlicedTube& operator/=(SlicedTube& x1, const SlicedTube& x2) - macro_member_binary_tube_tube(operator_tube_div); + macro_member_binary_tube_tube(operator_tube_div) /** * \brief Pointwise division assignment by a scalar interval tube. @@ -671,7 +677,7 @@ namespace codac2 template requires NonScalarTubeCodomain inline SlicedTube& operator/=(SlicedTube& x1, const SlicedTube& x2) - macro_member_binary_tube_tube(operator_tube_div); + macro_member_binary_tube_tube(operator_tube_div) /** * \brief Pointwise square of a scalar interval tube. diff --git a/src/core/domains/tube/codac2_TDomain.cpp b/src/core/domains/tube/codac2_TDomain.cpp index 3d6260949..16281ed98 100644 --- a/src/core/domains/tube/codac2_TDomain.cpp +++ b/src/core/domains/tube/codac2_TDomain.cpp @@ -244,7 +244,7 @@ namespace codac2 list::const_iterator it1 = tdom1->cbegin(), it2 = tdom2->cbegin(); while(it1 != tdom1->cend()) { - if(*it1 != *it2) return false; + if(!((*it1) == (*it2))) return false; it1++; it2++; } return true; diff --git a/src/core/domains/tube/codac2_TDomain.h b/src/core/domains/tube/codac2_TDomain.h index ceccefab1..90d440914 100644 --- a/src/core/domains/tube/codac2_TDomain.h +++ b/src/core/domains/tube/codac2_TDomain.h @@ -183,8 +183,8 @@ namespace codac2 protected: - friend std::shared_ptr create_tdomain(const Interval&); - friend std::shared_ptr create_tdomain(const Interval&,double,bool); + friend std::shared_ptr create_tdomain(const Interval& t0_tf); + friend std::shared_ptr create_tdomain(const Interval& t0_tf,double dt,bool with_gates); }; /** diff --git a/src/core/domains/tube/codac2_TSlice.cpp b/src/core/domains/tube/codac2_TSlice.cpp index 0377e24ad..97410e475 100644 --- a/src/core/domains/tube/codac2_TSlice.cpp +++ b/src/core/domains/tube/codac2_TSlice.cpp @@ -35,4 +35,6 @@ namespace codac2 { return _slices; } + + TSlice::~TSlice() = default; } \ No newline at end of file diff --git a/src/core/domains/tube/codac2_TSlice.h b/src/core/domains/tube/codac2_TSlice.h index 81edf9c59..0d4d2d445 100644 --- a/src/core/domains/tube/codac2_TSlice.h +++ b/src/core/domains/tube/codac2_TSlice.h @@ -75,6 +75,12 @@ namespace codac2 using Interval::operator==; + bool operator==(const TSlice& x) const { + return Interval::operator==(x); + } + + ~TSlice() override; + protected: /** diff --git a/src/core/functions/analytic/codac2_AnalyticExpr.h b/src/core/functions/analytic/codac2_AnalyticExpr.h index 7de355569..3c8ebeb8b 100644 --- a/src/core/functions/analytic/codac2_AnalyticExpr.h +++ b/src/core/functions/analytic/codac2_AnalyticExpr.h @@ -76,17 +76,17 @@ namespace codac2 : OperationExprBase...>(e) { } - std::shared_ptr copy() const + std::shared_ptr copy() const override { return std::make_shared>(*this); } - void replace_arg(const ExprID& old_arg_id, const std::shared_ptr& new_expr) + void replace_arg(const ExprID& old_arg_id, const std::shared_ptr& new_expr) override { return OperationExprBase...>::replace_arg(old_arg_id, new_expr); } - Y fwd_eval(ValuesMap& v, Index total_input_size, bool natural_eval) const + Y fwd_eval(ValuesMap& v, Index total_input_size, bool natural_eval) const override { return std::apply( [this,&v,total_input_size,natural_eval](auto &&... x) @@ -102,7 +102,7 @@ namespace codac2 this->_x); } - void bwd_eval(ValuesMap& v) const + void bwd_eval(ValuesMap& v) const override { auto y = AnalyticExpr::value(v); @@ -117,7 +117,7 @@ namespace codac2 }, this->_x); } - virtual std::string str(bool in_parentheses = false) const + virtual std::string str(bool in_parentheses = false) const override { std::string s = std::apply([](auto &&... x) { return C::str(x...); @@ -125,12 +125,12 @@ namespace codac2 return in_parentheses ? "(" + s + ")" : s; } - virtual bool is_str_leaf() const + virtual bool is_str_leaf() const override { return false; } - std::pair output_shape() const + std::pair output_shape() const override { std::pair s; std::apply([&s](auto &&... x) @@ -140,7 +140,7 @@ namespace codac2 return s; } - virtual bool belongs_to_args_list(const FunctionArgsList& args) const + virtual bool belongs_to_args_list(const FunctionArgsList& args) const override { bool b = true; diff --git a/src/core/functions/analytic/codac2_analytic_constants.h b/src/core/functions/analytic/codac2_analytic_constants.h index ae446bfda..45db2c9a8 100644 --- a/src/core/functions/analytic/codac2_analytic_constants.h +++ b/src/core/functions/analytic/codac2_analytic_constants.h @@ -28,12 +28,12 @@ namespace codac2 return _x; } - std::shared_ptr copy() const + std::shared_ptr copy() const override { return std::make_shared>(*this); } - T fwd_eval(ValuesMap& v, Index total_input_size, bool natural_eval) const + T fwd_eval(ValuesMap& v, Index total_input_size, bool natural_eval) const override { if(natural_eval) return AnalyticExpr::init_value(v, T( @@ -55,36 +55,39 @@ namespace codac2 )); } - void bwd_eval(ValuesMap& v) const + void bwd_eval(ValuesMap& v) const override { AnalyticExpr::value(v).a &= _x; } - std::pair output_shape() const + std::pair output_shape() const override { if constexpr(std::is_same_v) return {1,1}; - if constexpr(std::is_same_v) + else if constexpr(std::is_same_v) return {_x.size(),1}; - if constexpr(std::is_same_v) + else if constexpr(std::is_same_v) return {_x.rows(),_x.cols()}; - assert_release_constexpr(false && "unknow output shape for constant"); + // In an else branch, discarded along with it for the types above: + // after their return, Visual C++ reported it as unreachable (C4702) + else + assert_release_constexpr(false && "unknow output shape for constant"); } - void replace_arg([[maybe_unused]] const ExprID& old_arg_id, [[maybe_unused]] const std::shared_ptr& new_expr) + void replace_arg([[maybe_unused]] const ExprID& old_arg_id, [[maybe_unused]] const std::shared_ptr& new_expr) override { } - virtual bool belongs_to_args_list([[maybe_unused]] const FunctionArgsList& args) const + virtual bool belongs_to_args_list([[maybe_unused]] const FunctionArgsList& args) const override { return true; } virtual std::string str(bool in_parentheses = false) const override; - virtual bool is_str_leaf() const + virtual bool is_str_leaf() const override { return true; } diff --git a/src/core/functions/analytic/codac2_analytic_flat_input_layout.cpp b/src/core/functions/analytic/codac2_analytic_flat_input_layout.cpp index 31365483d..57352603e 100644 --- a/src/core/functions/analytic/codac2_analytic_flat_input_layout.cpp +++ b/src/core/functions/analytic/codac2_analytic_flat_input_layout.cpp @@ -55,7 +55,7 @@ namespace codac2 } else - assert_release(false && "FlatInputLayout: unsupported variable type in function argument list"); + assert_release_unreachable("FlatInputLayout: unsupported variable type in function argument list"); } _size = flat; diff --git a/src/core/functions/analytic/codac2_analytic_flat_input_layout.h b/src/core/functions/analytic/codac2_analytic_flat_input_layout.h index 7bc5622d0..2d515e03b 100644 --- a/src/core/functions/analytic/codac2_analytic_flat_input_layout.h +++ b/src/core/functions/analytic/codac2_analytic_flat_input_layout.h @@ -174,7 +174,7 @@ namespace codac2 }; template - static std::shared_ptr as_expr_base(const AnalyticExprWrapper& e) + inline std::shared_ptr as_expr_base(const AnalyticExprWrapper& e) { return std::static_pointer_cast( std::shared_ptr>(e) diff --git a/src/core/geometry/codac2_Polygon.cpp b/src/core/geometry/codac2_Polygon.cpp index efdc4ec8c..a7f909549 100644 --- a/src/core/geometry/codac2_Polygon.cpp +++ b/src/core/geometry/codac2_Polygon.cpp @@ -239,8 +239,7 @@ namespace codac2 return { {p[0],prev_float(oo)}, p }; default: { - assert_release(false && - "failed to test if the point is contained in polygon"); + assert_release_unreachable("failed to test if the point is contained in polygon"); return Segment(IntervalVector(2),IntervalVector(2)); } } diff --git a/src/core/geometry/codac2_geometry.cpp b/src/core/geometry/codac2_geometry.cpp index 439de9a54..66b8882cd 100644 --- a/src/core/geometry/codac2_geometry.cpp +++ b/src/core/geometry/codac2_geometry.cpp @@ -69,8 +69,7 @@ namespace codac2 }); pts.erase(unique(pts.begin(), pts.end()), pts.end()); - if(pts.size() < 3) - return pts; + if(pts.size() < 3) return pts; // Implementation of a Graham scan method. // Based on some sources from OpenGenus Foundation. diff --git a/src/core/matrices/codac2_matrices.h b/src/core/matrices/codac2_matrices.h index a92cd6a1f..4714ee234 100644 --- a/src/core/matrices/codac2_matrices.h +++ b/src/core/matrices/codac2_matrices.h @@ -19,6 +19,61 @@ #pragma once +/* Eigen's blocked product kernel accumulates in two different ways. When + * EIGEN_HAS_SINGLE_INSTRUCTION_MADD is defined it writes c = pmadd(a,b,c), + * which is exactly the fused multiply-add the accumulator expects. Otherwise + * it multiplies into a temporary declared with the type of the right-hand + * operand, tmp = pmul(a,tmp), then adds it with padd(c,tmp). That second path + * is a register-allocation workaround for compilers without a fused + * multiply-add, and it silently assumes that the product has the type of the + * right-hand operand. Codac breaks that assumption everywhere it multiplies + * matrices of two different scalar types -- a Matrix by an IntervalMatrix, + * for instance, whose product is an IntervalMatrix. The kernel then either + * fails to compile (no padd overload accepts the two types) or, worse, + * narrows the product down to the type of the right-hand operand. + * + * Every architecture Eigen vectorizes defines the macro in its packet-math + * header, so this path is normally unreachable. MSVC on arm64 is the exception: + * Eigen only enables NEON on __ARM_NEON, which that compiler does not define, + * so the whole library falls back on scalar packets and the arm64 builds fail + * to compile those mixed-type products. Defining the macro here selects the + * pmadd path unconditionally, which is the one all the other targets already + * take, and keeps the same results everywhere. It must be defined before Eigen + * is included, hence its position at the very top of this file. + */ +#ifndef EIGEN_HAS_SINGLE_INSTRUCTION_MADD +#define EIGEN_HAS_SINGLE_INSTRUCTION_MADD +#endif + +/* Eigen's own EIGEN_ASSUME_ALIGNED(PTR, ALIGN_BYTES), defined in + * Eigen/src/Core/util/Memory.h, hints the compiler that a packet load/store + * is aligned by calling std::assume_aligned<8 * (ALIGN_BYTES)>(PTR) when the + * standard library provides it (as libc++ does). ALIGN_BYTES is always + * called with an already-byte-valued Alignment enumerator (Aligned16 = 16, + * Aligned32 = 32...), so that extra "8 *" claims eight times the alignment + * Eigen itself computed -- e.g. a NEON Packet2d, whose unpacket_traits + * report Aligned16, ends up asserted as 128-byte aligned. libc++'s + * std::assume_aligned() is, among major standard libraries, the one that + * actually verifies such a claim at run time under -fsanitize=alignment + * (GCC/libstdc++'s __builtin_assume_aligned() treats it as a silent + * optimizer hint instead), which is why every heap-allocated Eigen::Matrix + * op trips it under UBSan on macOS/Clang -- e.g. in codac2::gauss_jordan() + * or any matrix product -- while the same code is silent on + * Linux/GCC. The buffers themselves are never actually misaligned for the + * SIMD width Eigen emits; only this hint's arithmetic is wrong. Memory.h + * guards its own definition with #ifndef EIGEN_ASSUME_ALIGNED, so defining + * it here first -- before Eigen is included, like EIGEN_HAS_SINGLE_ + * INSTRUCTION_MADD above -- replaces the miscomputed hint with a no-op + * (Eigen's own fallback for compilers lacking std::assume_aligned/ + * __builtin_assume_aligned) rather than trying to patch the multiplier, + * so this keeps working even if a future Eigen release changes it again. + * It does not change which aligned/unaligned load or store instruction + * Eigen selects for a given expression, only this compiler-hint call. + */ +#ifndef EIGEN_ASSUME_ALIGNED +#define EIGEN_ASSUME_ALIGNED(PTR, ALIGN_BYTES) +#endif + #include #include "codac2_Interval.h" #include "codac2_Interval_operations.h" @@ -30,7 +85,7 @@ namespace Eigen concept IsVectorOrRow = (C == 1 || R == 1); template - concept IsIntervalDomain = std::is_same_v; + concept IsIntervalDomain = codac2::is_interval_based::value; } #define EIGEN_MATRIXBASE_PLUGIN "codac2_MatrixBase_addons_include.h" diff --git a/src/core/matrices/eigen/Matrix_addons/codac2_Matrix_addons_IntervalMatrixBase.h b/src/core/matrices/eigen/Matrix_addons/codac2_Matrix_addons_IntervalMatrixBase.h index 64ef607a3..a53447794 100644 --- a/src/core/matrices/eigen/Matrix_addons/codac2_Matrix_addons_IntervalMatrixBase.h +++ b/src/core/matrices/eigen/Matrix_addons/codac2_Matrix_addons_IntervalMatrixBase.h @@ -141,7 +141,7 @@ inline bool operator==(const MatrixBase& x) const inline void set_empty() requires IsIntervalDomain { - this->init(codac2::Interval::empty()); + this->init(Scalar::empty()); } /** @@ -342,4 +342,4 @@ template inline auto bisect_largest(double ratio = 0.49, const std::vector& among_indices = {}) const { return bisect(this->max_diam_index(among_indices), ratio); -} \ No newline at end of file +} diff --git a/src/core/matrices/eigen/Matrix_addons/codac2_Matrix_addons_MatrixBase.h b/src/core/matrices/eigen/Matrix_addons/codac2_Matrix_addons_MatrixBase.h index b56b95309..969c8cd9e 100644 --- a/src/core/matrices/eigen/Matrix_addons/codac2_Matrix_addons_MatrixBase.h +++ b/src/core/matrices/eigen/Matrix_addons/codac2_Matrix_addons_MatrixBase.h @@ -154,25 +154,25 @@ inline static Matrix random(Index r, Index c) return DenseBase>::Random(r,c); } -/** - * \brief Resizes the matrix to (``r``,``c``), preserving existing values where possible. - * - * \param r New number of rows. - * \param c New number of columns. - * - * \details - * This function resizes the matrix while preserving the data in the overlapping region - * of the old and new sizes. Unlike Eigen's ``resize()``, which discards old data, this - * function copies existing values into the resized matrix. - */ -template - requires (!IsVectorOrRow) -inline void resize_save_values(Index r, Index c) -{ - // With resize() of Eigen, the data is reallocated and all previous values are lost. - auto copy = *this; - this->resize(r,c); - for(Index i = 0 ; i < std::min((Index)copy.rows(),r) ; i++) - for(Index j = 0 ; j < std::min((Index)copy.cols(),c) ; j++) - (*this)(i,j) = copy(i,j); -} \ No newline at end of file +// /** +// * \brief Resizes the matrix to (``r``,``c``), preserving existing values where possible. +// * +// * \param r New number of rows. +// * \param c New number of columns. +// * +// * \details +// * This function resizes the matrix while preserving the data in the overlapping region +// * of the old and new sizes. Unlike Eigen's ``resize()``, which discards old data, this +// * function copies existing values into the resized matrix. +// */ +// template +// requires (!IsVectorOrRow) +// inline void resize_save_values(Index r, Index c) +// { +// // With resize() of Eigen, the data is reallocated and all previous values are lost. +// auto copy = *this; +// this->resize(r,c); +// for(Index i = 0 ; i < std::min((Index)copy.rows(),r) ; i++) +// for(Index j = 0 ; j < std::min((Index)copy.cols(),c) ; j++) +// (*this)(i,j) = copy(i,j); +// } \ No newline at end of file diff --git a/src/core/matrices/eigen/Matrix_addons/codac2_Matrix_addons_VectorBase.h b/src/core/matrices/eigen/Matrix_addons/codac2_Matrix_addons_VectorBase.h index f5021ebf5..63e706508 100644 --- a/src/core/matrices/eigen/Matrix_addons/codac2_Matrix_addons_VectorBase.h +++ b/src/core/matrices/eigen/Matrix_addons/codac2_Matrix_addons_VectorBase.h @@ -195,24 +195,24 @@ inline void put(Index start_id, const MatrixBase& x) this->segment(start_id,x.size()) << x; } -/** - * \brief Resizes the vector or row matrix to size \p n, preserving existing values. - * - * \pre The matrix is a vector or row vector. - * - * \param n The new size. - * - * \details - * Eigen's ``resize()`` discards existing data, so this function copies existing - * values before resizing and restores them afterward. - */ -template - requires IsVectorOrRow -inline void resize_save_values(Index n) -{ - // With resize() of Eigen, the data is reallocated and all previous values are lost. - auto copy = *this; - this->resize(n); - for(Index i = 0 ; i < std::min((Index)copy.size(),n) ; i++) - (*this)[i] = copy[i]; -} \ No newline at end of file +// /** +// * \brief Resizes the vector or row matrix to size \p n, preserving existing values. +// * +// * \pre The matrix is a vector or row vector. +// * +// * \param n The new size. +// * +// * \details +// * Eigen's ``resize()`` discards existing data, so this function copies existing +// * values before resizing and restores them afterward. +// */ +// template +// requires IsVectorOrRow +// inline void resize_save_values(Index n) +// { +// // With resize() of Eigen, the data is reallocated and all previous values are lost. +// auto copy = *this; +// this->resize(n); +// for(Index i = 0 ; i < std::min((Index)copy.size(),n) ; i++) +// (*this)[i] = copy[i]; +// } \ No newline at end of file diff --git a/src/core/operators/codac2_component.h b/src/core/operators/codac2_component.h index 16ce50dcc..5558209b0 100644 --- a/src/core/operators/codac2_component.h +++ b/src/core/operators/codac2_component.h @@ -71,17 +71,17 @@ namespace codac2 : OperationExprBase>(e), _i(e._i) { } - std::shared_ptr copy() const + std::shared_ptr copy() const override { return std::make_shared>(*this); } - void replace_arg(const ExprID& old_arg_id, const std::shared_ptr& new_expr) + void replace_arg(const ExprID& old_arg_id, const std::shared_ptr& new_expr) override { return OperationExprBase>::replace_arg(old_arg_id, new_expr); } - ScalarType fwd_eval(ValuesMap& v, Index total_input_size, bool natural_eval) const + ScalarType fwd_eval(ValuesMap& v, Index total_input_size, bool natural_eval) const override { if(natural_eval) return AnalyticExpr::init_value( @@ -91,29 +91,29 @@ namespace codac2 v, ComponentOp::fwd_centered(std::get<0>(this->_x)->fwd_eval(v, total_input_size, natural_eval), _i)); } - void bwd_eval(ValuesMap& v) const + void bwd_eval(ValuesMap& v) const override { ComponentOp::bwd(AnalyticExpr::value(v).a, std::get<0>(this->_x)->value(v).a, _i); std::get<0>(this->_x)->bwd_eval(v); } - std::pair output_shape() const + std::pair output_shape() const override { return ComponentOp::output_shape(std::get<0>(this->_x),_i); } - virtual bool belongs_to_args_list(const FunctionArgsList& args) const + virtual bool belongs_to_args_list(const FunctionArgsList& args) const override { return std::get<0>(this->_x)->belongs_to_args_list(args); } - std::string str(bool in_parentheses = false) const + std::string str(bool in_parentheses = false) const override { std::string s = ComponentOp::str(std::get<0>(this->_x), _i); return in_parentheses ? "(" + s + ")" : s; } - virtual bool is_str_leaf() const + virtual bool is_str_leaf() const override { return true; } @@ -146,17 +146,17 @@ namespace codac2 : OperationExprBase>(e), _i(e._i), _j(e._j) { } - std::shared_ptr copy() const + std::shared_ptr copy() const override { return std::make_shared>(*this); } - void replace_arg(const ExprID& old_arg_id, const std::shared_ptr& new_expr) + void replace_arg(const ExprID& old_arg_id, const std::shared_ptr& new_expr) override { return OperationExprBase>::replace_arg(old_arg_id, new_expr); } - ScalarType fwd_eval(ValuesMap& v, Index total_input_size, bool natural_eval) const + ScalarType fwd_eval(ValuesMap& v, Index total_input_size, bool natural_eval) const override { if(natural_eval) return AnalyticExpr::init_value( @@ -166,29 +166,29 @@ namespace codac2 v, ComponentOp::fwd_centered(std::get<0>(this->_x)->fwd_eval(v, total_input_size, natural_eval), _i, _j)); } - void bwd_eval(ValuesMap& v) const + void bwd_eval(ValuesMap& v) const override { ComponentOp::bwd(AnalyticExpr::value(v).a, std::get<0>(this->_x)->value(v).a, _i, _j); std::get<0>(this->_x)->bwd_eval(v); } - std::pair output_shape() const + std::pair output_shape() const override { return ComponentOp::output_shape(std::get<0>(this->_x),_i,_j); } - virtual bool belongs_to_args_list(const FunctionArgsList& args) const + virtual bool belongs_to_args_list(const FunctionArgsList& args) const override { return std::get<0>(this->_x)->belongs_to_args_list(args); } - std::string str(bool in_parentheses = false) const + std::string str(bool in_parentheses = false) const override { std::string s = ComponentOp::str(std::get<0>(this->_x), _i, _j); return in_parentheses ? "(" + s + ")" : s; } - virtual bool is_str_leaf() const + virtual bool is_str_leaf() const override { return true; } diff --git a/src/core/operators/codac2_pow.h b/src/core/operators/codac2_pow.h index e797c2118..01c44aa0e 100644 --- a/src/core/operators/codac2_pow.h +++ b/src/core/operators/codac2_pow.h @@ -139,6 +139,6 @@ namespace codac2 { assert_release(x2.is_degenerated() && "PowOp::bwd(y,x1,x2) (with x1 and x2 intervals) not implemented yet with Gaol"); assert_release(x2 == (int)(x2.mid()) && "PowOp::bwd(y,x1,x2) (x2 not integer) not implemented yet with Gaol"); - PowOp::bwd(y, x1, x2.mid()); + PowOp::bwd(y, x1, (int)(x2.mid())); } } diff --git a/src/core/paver/codac2_pave.cpp b/src/core/paver/codac2_pave.cpp index c2d4cabad..29e8b01e7 100644 --- a/src/core/paver/codac2_pave.cpp +++ b/src/core/paver/codac2_pave.cpp @@ -75,7 +75,7 @@ namespace codac2 time = (double)(clock()-t_start)/CLOCKS_PER_SEC; if(verbose) - printf("Computation time: %.4fs, %ld boxes\n", time, n_boundary); + printf("Computation time: %.4fs, %td boxes\n", time, n_boundary); return p; } diff --git a/src/core/peibos/codac2_peibos.h b/src/core/peibos/codac2_peibos.h index 840421bae..d486997cd 100644 --- a/src/core/peibos/codac2_peibos.h +++ b/src/core/peibos/codac2_peibos.h @@ -17,7 +17,7 @@ namespace codac2 { // Forward declarations to reduce compilation load caused by heavy template use: - class AnalyticTypeBase; + struct AnalyticTypeBase; template requires std::is_base_of_v @@ -65,4 +65,4 @@ namespace codac2 * \return A vector of Parallelepipeds enclosing \f$\mathbf{f}(\sigma(\psi_0([-1,1]^m))+ offset)\f$ for each symmetry \f$\sigma\f$ in the set of symmetries \f$\Sigma\f$. */ std::vector PEIBOS(const AnalyticFunction& f, const AnalyticFunction& psi_0, const std::vector& Sigma, double epsilon, const Vector& offset, bool verbose = false); -} \ No newline at end of file +} diff --git a/src/core/tools/codac2_Approx.h b/src/core/tools/codac2_Approx.h index 721da8249..ef0583af5 100644 --- a/src/core/tools/codac2_Approx.h +++ b/src/core/tools/codac2_Approx.h @@ -40,7 +40,25 @@ namespace codac2 friend bool operator==(const T& x1, const Approx& x2) { if constexpr(std::is_same_v) - return std::fabs(x1-x2._x) < x2._eps; + { + if(std::isnan(x1) && std::isnan(x2._x)) + return true; + + else if(x2._x == std::numeric_limits::max()) + return x1 == std::numeric_limits::max(); + + else if(x2._x == -std::numeric_limits::max()) + return x1 == -std::numeric_limits::max(); + + else if(std::isinf(x2._x)) + return x1 == x2._x; + + else if(x2._x < 1.0 && x2._x > -1.0) + return std::fabs(x1-x2._x) < x2._eps; // absolute error + + else + return std::fabs(x1-x2._x) < x2._eps*std::max(std::fabs(x1),std::fabs(x2._x)); // relative error + } else if(x1.size() != x2._x.size()) return false; diff --git a/src/core/tools/codac2_TypeInfo.h b/src/core/tools/codac2_TypeInfo.h index 860c43474..977d94707 100644 --- a/src/core/tools/codac2_TypeInfo.h +++ b/src/core/tools/codac2_TypeInfo.h @@ -9,6 +9,8 @@ #pragma once +#include + namespace codac2 { template diff --git a/src/core/tools/codac2_assert.h b/src/core/tools/codac2_assert.h index 18d5bc891..bccf40ade 100644 --- a/src/core/tools/codac2_assert.h +++ b/src/core/tools/codac2_assert.h @@ -20,6 +20,7 @@ namespace codac2 #define assert_release(ignore_test) ((void)0) #define assert_release_constexpr(ignore_test) ((void)0) + #define assert_release_unreachable(ignore_msg) ((void)0) #else @@ -52,6 +53,17 @@ namespace codac2 } \ } while (0) + // For a branch that must never be reached. Written as + // assert_release(false && "...") until now, whose condition is a + // compile-time constant: MSVC reported one C4127 ("conditional + // expression is constant") per site. Failing unconditionally states the + // intent directly and leaves no condition to warn about; the message is + // also reported on its own, rather than as the text of a false test. + #define assert_release_unreachable(msg) \ + do { \ + CODAC_ASSERT_MESSAGE(msg, __FILE__, __LINE__, __PRETTY_FUNCTION__); \ + } while (0) + #else #define assert_release(test) \ @@ -68,6 +80,12 @@ namespace codac2 } \ } while (0) + // See the comment on the __PRETTY_FUNCTION__ variant above. + #define assert_release_unreachable(msg) \ + do { \ + CODAC_ASSERT_MESSAGE(msg, __FILE__, __LINE__, __func__); \ + } while (0) + #endif #endif diff --git a/src/core/tools/ibex/codac2_ibex.h b/src/core/tools/ibex/codac2_ibex.h deleted file mode 100644 index 021b7156b..000000000 --- a/src/core/tools/ibex/codac2_ibex.h +++ /dev/null @@ -1,110 +0,0 @@ -/** - * \file codac2_ibex.h - * ---------------------------------------------------------------------------- - * \date 2024 - * \author Simon Rohou - * \copyright Copyright 2024 Codac Team - * \license GNU Lesser General Public License (LGPL) - */ - -#pragma once - -#ifdef _IBEX_INTERVAL_H_ // only available if IBEX has been included beforehand - -#include -#include -#include -#include -#include -#include "codac2_Interval.h" -#include "codac2_Vector.h" -#include "codac2_IntervalVector.h" -#include "codac2_Matrix.h" -#include "codac2_IntervalMatrix.h" - -namespace codac2 -{ - /** - * \brief Casts a Codac Interval object into an IBEX Interval object - * - * \param x const Codac type Interval - * \return const IBEX type Interval - */ - ibex::Interval to_ibex(const codac2::Interval& x); - - /** - * \brief Casts an IBEX Interval object into a Codac Interval object - * - * \param x const IBEX type Interval - * \return Codac type Interval - */ - codac2::Interval to_codac(const ibex::Interval& x); - - /** - * \brief Casts a Codac Vector object into an IBEX Vector object - * - * \param x const Codac type Vector - * \return IBEX type Vector - */ - ibex::Vector to_ibex(const codac2::Vector& x); - - /** - * \brief Casts an IBEX Vector object into a Codac Vector object - * - * \param x const IBEX type Vector - * \return Codac type Vector - */ - codac2::Vector to_codac(const ibex::Vector& x); - - /** - * \brief Casts a Codac IntervalVector object into an IBEX IntervalVector object - * - * \param x const Codac type IntervalVector - * \return IBEX type IntervalVector - */ - ibex::IntervalVector to_ibex(const codac2::IntervalVector& x); - - /** - * \brief Casts an IBEX IntervalVector object into a Codac IntervalVector object - * - * \param x const IBEX type IntervalVector - * \return Codac type IntervalVector - */ - codac2::IntervalVector to_codac(const ibex::IntervalVector& x); - - /** - * \brief Casts a Codac Matrix object into an IBEX Matrix object - * - * \param x const Codac type Matrix - * \return IBEX type Matrix - */ - ibex::Matrix to_ibex(const codac2::Matrix& x); - - /** - * \brief Casts an IBEX Matrix object into a Codac Matrix object - * - * \param x const IBEX type Matrix - * \return Codac type Matrix - */ - codac2::Matrix to_codac(const ibex::Matrix& x); - - /** - * \brief Casts a Codac IntervalMatrix object into an IBEX IntervalMatrix object - * - * \param x const Codac type IntervalMatrix - * \return IBEX type IntervalMatrix - */ - ibex::IntervalMatrix to_ibex(const codac2::IntervalMatrix& x); - - /** - * \brief Casts an IBEX IntervalMatrix object into a Codac IntervalMatrix object - * - * \param x const IBEX type IntervalMatrix - * \return Codac type IntervalMatrix - */ - codac2::IntervalMatrix to_codac(const ibex::IntervalMatrix& x); -} - -#include "codac2_ibex_impl.h" - -#endif \ No newline at end of file diff --git a/src/core/tools/ibex/codac2_ibex_impl.h b/src/core/tools/ibex/codac2_ibex_impl.h deleted file mode 100644 index 406472a46..000000000 --- a/src/core/tools/ibex/codac2_ibex_impl.h +++ /dev/null @@ -1,82 +0,0 @@ -/** - * codac2_ibex_impl.h - * ---------------------------------------------------------------------------- - * \date 2024 - * \author Gilles Chabert, (Simon Rohou) - * \copyright Copyright 2024 Codac Team - * \license GNU Lesser General Public License (LGPL) - */ - -#pragma once -#include - -// Inline functions - -namespace codac2 -{ - inline ibex::Interval to_ibex(const Interval& x) - { - return { x.lb(), x.ub() }; - } - - inline Interval to_codac(const ibex::Interval& x) - { - return { x.lb(), x.ub() }; - } - - #define cast_vector(OutputType,convert) \ - \ - OutputType x_(x.size()); \ - for(Index i = 0 ; i < (Index)x.size() ; i++) \ - x_[i] = convert(x[i]); \ - return x_; \ - - inline ibex::Vector to_ibex(const codac2::Vector& x) - { - cast_vector(ibex::Vector,double); - } - - inline codac2::Vector to_codac(const ibex::Vector& x) - { - cast_vector(codac2::Vector,double); - } - - inline ibex::IntervalVector to_ibex(const codac2::IntervalVector& x) - { - cast_vector(ibex::IntervalVector,to_ibex); - } - - inline codac2::IntervalVector to_codac(const ibex::IntervalVector& x) - { - cast_vector(codac2::IntervalVector,to_codac); - } - - #define cast_matrix(OutputType,convert_f,output_ij,input_ij,rows_,cols_) \ - \ - OutputType x_(x.rows_(), x.cols_()); \ - for(Index i = 0 ; i < (Index)x.rows_() ; i++) \ - for(Index j = 0 ; j < (Index)x.cols_() ; j++) \ - output_ij = convert_f(input_ij); \ - return x_; \ - - inline ibex::Matrix to_ibex(const codac2::Matrix& x) - { - cast_matrix(ibex::Matrix,double,x_[i][j],x(i,j),rows,cols); - } - - inline codac2::Matrix to_codac(const ibex::Matrix& x) - { - cast_matrix(codac2::Matrix,double,x_(i,j),x[i][j],nb_rows,nb_cols); - } - - inline ibex::IntervalMatrix to_ibex(const codac2::IntervalMatrix& x) - { - cast_matrix(ibex::IntervalMatrix,to_ibex,x_[i][j],x(i,j),rows,cols); - } - - inline codac2::IntervalMatrix to_codac(const ibex::IntervalMatrix& x) - { - cast_matrix(codac2::IntervalMatrix,to_codac,x_(i,j),x[i][j],nb_rows,nb_cols); - } - -} \ No newline at end of file diff --git a/src/core/trajectory/codac2_SampledTraj.h b/src/core/trajectory/codac2_SampledTraj.h index 3aaaa0537..e230abdd2 100644 --- a/src/core/trajectory/codac2_SampledTraj.h +++ b/src/core/trajectory/codac2_SampledTraj.h @@ -280,6 +280,7 @@ namespace codac2 SampledTraj primitive() const { T s = [this]() { + (void)this; if constexpr(std::is_same_v) return 0.; else @@ -454,4 +455,4 @@ namespace codac2 } } -#include "codac2_TrajBase_impl.h" \ No newline at end of file +#include "codac2_TrajBase_impl.h" diff --git a/src/core/trajectory/codac2_SampledTraj_operations.h b/src/core/trajectory/codac2_SampledTraj_operations.h index 8a02901d8..707755407 100644 --- a/src/core/trajectory/codac2_SampledTraj_operations.h +++ b/src/core/trajectory/codac2_SampledTraj_operations.h @@ -14,13 +14,19 @@ namespace codac2 { + // These macros stand in for the body of the functions declared below. Doxygen + // parses this header to produce the Python docstrings, and it cannot name a + // function whose body it does not recognise, so every macro used that way is + // listed in EXPAND_AS_DEFINED in doc/api/Doxyfile.in. Add any new one there + // too, otherwise the bindings lose the docstring identifiers they expect and + // stop compiling. #define macro_unary_traj(f) \ { \ auto y = x1; \ for(auto it = y.begin() ; it != y.end() ; it++) \ it->second = f(it->second); \ return y; \ - }; \ + } \ #define macro_binary_traj_traj(f) \ { \ @@ -121,7 +127,7 @@ namespace codac2 */ template inline SampledTraj operator+(const SampledTraj& x1, const SampledTraj& x2) - macro_binary_traj_traj(operator_add); + macro_binary_traj_traj(operator_add) /** \brief \f$x_1(\cdot)+x_2\f$ * \param x1 @@ -130,7 +136,7 @@ namespace codac2 */ template inline SampledTraj operator+(const SampledTraj& x1, const Q& x2) - macro_binary_traj_real(operator_add); + macro_binary_traj_real(operator_add) /** \brief \f$x+x_2(\cdot)\f$ * \param x1 @@ -139,7 +145,7 @@ namespace codac2 */ template inline SampledTraj operator+(const Q& x1, const SampledTraj& x2) - macro_binary_real_traj(operator_add); + macro_binary_real_traj(operator_add) /** * \brief Operates += @@ -149,7 +155,7 @@ namespace codac2 */ template inline SampledTraj& operator+=(SampledTraj& x1, const Q& x2) - macro_member_binary_traj_real(operator_add); + macro_member_binary_traj_real(operator_add) /** * \brief Operates += @@ -159,7 +165,7 @@ namespace codac2 */ template inline SampledTraj& operator+=(SampledTraj& x1, const SampledTraj& x2) - macro_member_binary_traj_traj(operator_add); + macro_member_binary_traj_traj(operator_add) /** \brief \f$-x_1(\cdot)\f$ * \param x1 @@ -177,7 +183,7 @@ namespace codac2 */ template inline SampledTraj operator-(const SampledTraj& x1, const SampledTraj& x2) - macro_binary_traj_traj(operator_sub); + macro_binary_traj_traj(operator_sub) /** \brief \f$x_1(\cdot)-x_2\f$ * \param x1 @@ -186,7 +192,7 @@ namespace codac2 */ template inline SampledTraj operator-(const SampledTraj& x1, const Q& x2) - macro_binary_traj_real(operator_sub); + macro_binary_traj_real(operator_sub) /** \brief \f$x-x_2(\cdot)\f$ * \param x1 @@ -195,7 +201,7 @@ namespace codac2 */ template inline SampledTraj operator-(const Q& x1, const SampledTraj& x2) - macro_binary_real_traj(operator_sub); + macro_binary_real_traj(operator_sub) /** * \brief Operates -= @@ -205,7 +211,7 @@ namespace codac2 */ template inline SampledTraj& operator-=(SampledTraj& x1, const Q& x2) - macro_member_binary_traj_real(operator_sub); + macro_member_binary_traj_real(operator_sub) /** * \brief Operates -= @@ -215,7 +221,7 @@ namespace codac2 */ template inline SampledTraj& operator-=(SampledTraj& x1, const SampledTraj& x2) - macro_member_binary_traj_traj(operator_sub); + macro_member_binary_traj_traj(operator_sub) /** \brief \f$x_1\cdot x_2(\cdot)\f$ * \param x1 @@ -225,7 +231,7 @@ namespace codac2 template requires (!std::is_same_v) inline SampledTraj operator*(double x1, const SampledTraj& x2) - macro_binary_real_traj(operator_mul_scal); + macro_binary_real_traj(operator_mul_scal) /** \brief \f$x_1(\cdot)\cdot x_2\f$ * \param x1 @@ -235,7 +241,7 @@ namespace codac2 template requires (!std::is_same_v) inline SampledTraj operator*(const SampledTraj& x1, double x2) - macro_binary_traj_real(operator_mul_scal); + macro_binary_traj_real(operator_mul_scal) /** \brief \f$x_1(\cdot)\cdot x_2(\cdot)\f$ * \param x1 @@ -244,7 +250,7 @@ namespace codac2 */ template inline SampledTraj operator*(const SampledTraj& x1, const SampledTraj& x2) - macro_binary_traj_traj(operator_mul); + macro_binary_traj_traj(operator_mul) /** \brief \f$x_1(\cdot)\cdot x_2\f$ * \param x1 @@ -253,7 +259,7 @@ namespace codac2 */ template inline SampledTraj operator*(const SampledTraj& x1, const Q& x2) - macro_binary_traj_real(operator_mul); + macro_binary_traj_real(operator_mul) /** \brief \f$x\cdot x_2(\cdot)\f$ * \param x1 @@ -262,7 +268,7 @@ namespace codac2 */ template inline SampledTraj operator*(const Q& x1, const SampledTraj& x2) - macro_binary_real_traj(operator_mul); + macro_binary_real_traj(operator_mul) /** \brief \f$x_1(\cdot)\cdot x_2\f$ * \param x1 @@ -270,7 +276,7 @@ namespace codac2 * \return trajectory output */ inline SampledTraj operator*(const SampledTraj& x1, const SampledTraj& x2) - macro_binary_traj_traj(operator_mul_vec); + macro_binary_traj_traj(operator_mul_vec) /** \brief \f$x_1(\cdot)\cdot x_2\f$ * \param x1 @@ -278,7 +284,7 @@ namespace codac2 * \return trajectory output */ inline SampledTraj operator*(const Eigen::Affine2d& x1, const SampledTraj& x2) - macro_binary_real_traj(operator_mul_aff); + macro_binary_real_traj(operator_mul_aff) /** * \brief Operates *= @@ -288,7 +294,7 @@ namespace codac2 */ template inline SampledTraj& operator*=(SampledTraj& x1, const Q& x2) - macro_member_binary_traj_real(operator_mul); + macro_member_binary_traj_real(operator_mul) /** * \brief Operates *= @@ -298,7 +304,7 @@ namespace codac2 */ template inline SampledTraj& operator*=(SampledTraj& x1, const SampledTraj& x2) - macro_member_binary_traj_traj(operator_mul); + macro_member_binary_traj_traj(operator_mul) /** \brief \f$x_2(\cdot)/x_1\f$ * \param x1 @@ -308,7 +314,7 @@ namespace codac2 template requires (!std::is_same_v) inline SampledTraj operator/(const SampledTraj& x1, double x2) - macro_binary_traj_real(operator_div_scal); + macro_binary_traj_real(operator_div_scal) /** \brief \f$x_1(\cdot)/x_2(\cdot)\f$ * \param x1 @@ -317,7 +323,7 @@ namespace codac2 */ template inline SampledTraj operator/(const SampledTraj& x1, const SampledTraj& x2) - macro_binary_traj_traj(operator_div); + macro_binary_traj_traj(operator_div) /** \brief \f$x_1(\cdot)/x_2\f$ * \param x1 @@ -326,7 +332,7 @@ namespace codac2 */ template inline SampledTraj operator/(const SampledTraj& x1, const Q& x2) - macro_binary_traj_real(operator_div); + macro_binary_traj_real(operator_div) /** \brief \f$x/x_2(\cdot)\f$ * \param x1 @@ -335,7 +341,7 @@ namespace codac2 */ template inline SampledTraj operator/(const Q& x1, const SampledTraj& x2) - macro_binary_real_traj(operator_div); + macro_binary_real_traj(operator_div) /** * \brief Operates /= @@ -345,7 +351,7 @@ namespace codac2 */ template inline SampledTraj& operator/=(SampledTraj& x1, const Q& x2) - macro_member_binary_traj_real(operator_div); + macro_member_binary_traj_real(operator_div) /** * \brief Operates /= @@ -355,7 +361,7 @@ namespace codac2 */ template inline SampledTraj& operator/=(SampledTraj& x1, const SampledTraj& x2) - macro_member_binary_traj_traj(operator_div); + macro_member_binary_traj_traj(operator_div) /** \brief \f$x^2(\cdot)\f$ * \param x1 diff --git a/src/core/trajectory/codac2_TrajBase.h b/src/core/trajectory/codac2_TrajBase.h index 46e12f645..dba040afa 100644 --- a/src/core/trajectory/codac2_TrajBase.h +++ b/src/core/trajectory/codac2_TrajBase.h @@ -27,6 +27,8 @@ namespace codac2 TrajBase() { } + virtual ~TrajBase() = default; + virtual Index size() const = 0; virtual std::pair shape() const = 0; virtual bool is_empty() const = 0; @@ -43,4 +45,4 @@ namespace codac2 SampledTraj sampled_as(const SampledTraj& x) const; SampledTraj primitive(double dt) const; }; -} \ No newline at end of file +} diff --git a/src/core/trajectory/codac2_TrajBase_impl.h b/src/core/trajectory/codac2_TrajBase_impl.h index 50d123e78..483845d67 100644 --- a/src/core/trajectory/codac2_TrajBase_impl.h +++ b/src/core/trajectory/codac2_TrajBase_impl.h @@ -55,6 +55,7 @@ namespace codac2 assert_release(!is_empty()); T s = [this]() { + (void)this; if constexpr(std::is_same_v) return 0.; else diff --git a/src/extensions/capd/CMakeLists.txt b/src/extensions/capd/CMakeLists.txt index 0214cb955..05326b2f5 100644 --- a/src/extensions/capd/CMakeLists.txt +++ b/src/extensions/capd/CMakeLists.txt @@ -22,7 +22,23 @@ list(APPEND CODAC_CAPD_SRC #endif() add_library(${PROJECT_NAME}-capd ${CODAC_CAPD_SRC}) - target_link_libraries(${PROJECT_NAME}-capd PUBLIC ${PROJECT_NAME}-core Ibex::ibex Eigen3::Eigen capd::capd) + target_link_libraries(${PROJECT_NAME}-capd PUBLIC ${PROJECT_NAME}-core Codac::gaol $ capd::capd) + + # The directory holding this module's public headers, in its canonical + # location under src/. It had none declared at all: its headers were only + # reachable through their copy in build/include. Publishing it lets every + # consumer open them at their source path instead -- see + # codac_publish_include_dirs() in the top-level CMakeLists.txt for why that + # decides what the coverage report is worth. + # + # Build-tree paths on one side, install-tree paths on the other: see the + # same split in src/core/CMakeLists.txt for why install(EXPORT) needs it. + set(CODAC_CAPD_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}) + target_include_directories(${PROJECT_NAME}-capd PUBLIC + "$" + $ + ) + codac_publish_include_dirs(${CODAC_CAPD_INCLUDE_DIRS}) ################################################################################ @@ -30,7 +46,14 @@ list(APPEND CODAC_CAPD_SRC ################################################################################ set(CODAC_PKG_CONFIG_CFLAGS "${CODAC_PKG_CONFIG_CFLAGS} -I\${includedir}/${PROJECT_NAME}-capd" PARENT_SCOPE) - set(CODAC_PKG_CONFIG_LIBS "${CODAC_PKG_CONFIG_LIBS} -l${PROJECT_NAME}-capd" PARENT_SCOPE) + set(CODAC_PKG_CONFIG_MODULE_LIBS "${CODAC_PKG_CONFIG_MODULE_LIBS} -l${PROJECT_NAME}-capd" PARENT_SCOPE) + + # libcodac-capd carries unresolved references into CAPD, so a consumer has to + # link CAPD as well -- codac-config.cmake gets there through the capd::capd + # that the exported target names, and this is the pkg-config counterpart. + # "capd" is the single module CAPD installs (capdMake/libcapd/CMakeLists.txt + # writes one capd.pc, into /lib/pkgconfig). + set(CODAC_PKG_CONFIG_REQUIRES "${CODAC_PKG_CONFIG_REQUIRES} capd" PARENT_SCOPE) ################################################################################ @@ -42,23 +65,42 @@ list(APPEND CODAC_CAPD_SRC foreach(srcfile ${CODAC_CAPD_SRC}) if(srcfile MATCHES "\\.h$" OR srcfile MATCHES "\\.hpp$") list(APPEND CODAC_CAPD_HDR ${srcfile}) - file(COPY ${srcfile} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/../../../include) endif() endforeach() # Generating the file codac-capd.h set(CODAC_CAPD_MAIN_HEADER ${CMAKE_CURRENT_BINARY_DIR}/codac-capd.h) - file(WRITE ${CODAC_CAPD_MAIN_HEADER} "/* This file is generated by CMake */\n\n") - file(APPEND ${CODAC_CAPD_MAIN_HEADER} "#pragma once\n\n") + codac_publish_generated_include_dirs(${CMAKE_CURRENT_BINARY_DIR}) + + # Generate the content of the umbrella header + set(CODAC_CAPD_HEADER_CONTENT "/* This file is generated by CMake */\n\n#pragma once\n\n") foreach(header_path ${CODAC_CAPD_HDR}) get_filename_component(header_name ${header_path} NAME) - file(APPEND ${CODAC_CAPD_MAIN_HEADER} "#include <${header_name}>\n") + string(APPEND CODAC_CAPD_HEADER_CONTENT "#include <${header_name}>\n") endforeach() - file(COPY ${CODAC_CAPD_MAIN_HEADER} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/../../../include) + + # Compare with the existing file, if any + if(EXISTS "${CODAC_CAPD_MAIN_HEADER}") + file(READ "${CODAC_CAPD_MAIN_HEADER}" EXISTING_CONTENT) + string(MD5 EXISTING_HASH "${EXISTING_CONTENT}") + string(MD5 NEW_HASH "${CODAC_CAPD_HEADER_CONTENT}") + else() + set(EXISTING_HASH "") + set(NEW_HASH "new") + endif() + + # Only rewrite it when it changed + if(NOT EXISTING_HASH STREQUAL NEW_HASH) + file(WRITE "${CODAC_CAPD_MAIN_HEADER}" "${CODAC_CAPD_HEADER_CONTENT}") + endif() # Install files in system directories - install(TARGETS ${PROJECT_NAME}-capd DESTINATION ${CMAKE_INSTALL_LIBDIR}) + install(TARGETS ${PROJECT_NAME}-capd + EXPORT ${PROJECT_NAME}-targets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES ${CODAC_CAPD_HDR} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}-capd) install(FILES ${CODAC_CAPD_MAIN_HEADER} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}-capd) \ No newline at end of file diff --git a/src/extensions/capd/codac2_peibos_capd.cpp b/src/extensions/capd/codac2_peibos_capd.cpp index 48eed323d..41e80c19b 100644 --- a/src/extensions/capd/codac2_peibos_capd.cpp +++ b/src/extensions/capd/codac2_peibos_capd.cpp @@ -19,12 +19,12 @@ using namespace std; namespace codac2 { - std::map> PEIBOS(const capd::IMap& i_map, double tf, double dt, const AnalyticFunction& psi_0, const vector& Sigma, double epsilon, bool verbose) + std::map> PEIBOS(const capd::IMap& i_map, double tf, double dt, const AnalyticFunction& psi_0, const vector& Sigma, double epsilon, bool verbose) { return PEIBOS(i_map, tf, dt, psi_0, Sigma, epsilon, Vector::zero(psi_0.output_size()), verbose); } - std::map> PEIBOS(const capd::IMap& i_map, double tf, double dt, const AnalyticFunction& psi_0, const vector& Sigma, double epsilon, const Vector& offset, bool verbose) + std::map> PEIBOS(const capd::IMap& i_map, double tf, double dt, const AnalyticFunction& psi_0, const vector& Sigma, double epsilon, const Vector& offset, bool verbose) { std::vector time_points; for (double t = 0.; t <= tf; t += dt) @@ -48,7 +48,7 @@ namespace codac2 double true_eps = split(Interval(-1.,1.)*IntervalVector::Ones(m), epsilon, boxes); int nthreads = nb_threads(); - std::vector>> thread_outputs(nthreads); + std::vector>> thread_outputs(nthreads); struct WorkItem { const OctaSym* sigma; const IntervalVector* box; }; std::vector work; @@ -109,7 +109,7 @@ namespace codac2 for (auto& th : threads) th.join(); - std::map> output; + std::map> output; for (auto& vec : thread_outputs) for (auto t : time_points) @@ -130,7 +130,7 @@ namespace codac2 return output; } - std::map> reach_set(const std::map>& peibos_output) + std::map> reach_set(const std::map>& peibos_output) { std::map> output; diff --git a/src/extensions/capd/codac2_peibos_capd.h b/src/extensions/capd/codac2_peibos_capd.h index 9aab7cfd0..3cfe4609d 100644 --- a/src/extensions/capd/codac2_peibos_capd.h +++ b/src/extensions/capd/codac2_peibos_capd.h @@ -36,7 +36,7 @@ namespace codac2 Vector offset; }; - using T = std::tuple; + using PEIBOS_CAPD_Result = std::tuple; /** * \brief PEIBOS algorithm using CAPD for guaranteed ODE propagation. @@ -54,7 +54,7 @@ namespace codac2 * \li The interval vector \f$\mathbf{z}\f$ containing the image \f$\bar{\mathbf{x}}(t))\f$ * \li The interval Jacobian matrix \f$\mathbf{J_f}\f$ containing \f$D\mathbf{\left[x\right]}(t)\f$ */ - std::map> PEIBOS(const capd::IMap& i_map, double tf, double dt, const AnalyticFunction& psi_0, const std::vector& Sigma, double epsilon, bool verbose = false); + std::map> PEIBOS(const capd::IMap& i_map, double tf, double dt, const AnalyticFunction& psi_0, const std::vector& Sigma, double epsilon, bool verbose = false); /** * \brief PEIBOS algorithm using CAPD for guaranteed ODE propagation. @@ -73,7 +73,7 @@ namespace codac2 * \li The interval vector \f$\mathbf{z}\f$ containing the image \f$\bar{\mathbf{x}}(t))\f$ * \li The interval Jacobian matrix \f$\mathbf{J_f}\f$ containing \f$D\mathbf{\left[x\right]}(t)\f$ */ - std::map> PEIBOS(const capd::IMap& i_map, double tf, double dt, const AnalyticFunction& psi_0, const std::vector& Sigma, double epsilon, const Vector& offset, bool verbose = false); + std::map> PEIBOS(const capd::IMap& i_map, double tf, double dt, const AnalyticFunction& psi_0, const std::vector& Sigma, double epsilon, const Vector& offset, bool verbose = false); /** @@ -84,5 +84,5 @@ namespace codac2 * \return A timed map of reach set parallelepipeds. At each time \f$t\f$, the value is a vector of Parallelepipeds enclosing the reach set at time \f$t\f$. * The function \ref parallelepiped_inclusion is used to compute each Parallelepiped from the PEIBOS CAPD output. */ - std::map> reach_set(const std::map>& peibos_output); + std::map> reach_set(const std::map>& peibos_output); } \ No newline at end of file diff --git a/src/extensions/sympy/CMakeLists.txt b/src/extensions/sympy/CMakeLists.txt index d92bae572..27c821323 100644 --- a/src/extensions/sympy/CMakeLists.txt +++ b/src/extensions/sympy/CMakeLists.txt @@ -31,33 +31,70 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) add_library(${PROJECT_NAME}-sympy ${CODAC_SYMPY_SRC}) - target_link_libraries(${PROJECT_NAME}-sympy PUBLIC ${PROJECT_NAME}-core Ibex::ibex Eigen3::Eigen) - target_link_libraries(${PROJECT_NAME}-sympy PRIVATE pybind11::pybind11) + target_link_libraries(${PROJECT_NAME}-sympy PUBLIC ${PROJECT_NAME}-core Codac::gaol $) + # pybind11 is fetched by this build too, so it is a build-tree dependency for + # the same reason as Eigen -- see src/core/CMakeLists.txt. A PRIVATE link on + # a static library is still recorded in the interface (as $), + # which is enough for install(EXPORT) to refuse the target otherwise. + target_link_libraries(${PROJECT_NAME}-sympy PRIVATE $) + + # The directory holding this module's public headers, in its canonical + # location under src/. It had none declared at all: its headers were only + # reachable through their copy in build/include. Publishing it lets every + # consumer open them at their source path instead -- see + # codac_publish_include_dirs() in the top-level CMakeLists.txt for why that + # decides what the coverage report is worth. + # + # Build-tree paths on one side, install-tree paths on the other: see the + # same split in src/core/CMakeLists.txt for why install(EXPORT) needs it. + set(CODAC_SYMPY_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}) + target_include_directories(${PROJECT_NAME}-sympy PUBLIC + "$" + $ + ) + codac_publish_include_dirs(${CODAC_SYMPY_INCLUDE_DIRS}) ################################################################################ # For the generation of the PKG file ################################################################################ set(CODAC_PKG_CONFIG_CFLAGS "${CODAC_PKG_CONFIG_CFLAGS} -I\${includedir}/${PROJECT_NAME}-sympy" PARENT_SCOPE) - set(CODAC_PKG_CONFIG_LIBS "${CODAC_PKG_CONFIG_LIBS} -l${PROJECT_NAME}-sympy" PARENT_SCOPE) + set(CODAC_PKG_CONFIG_MODULE_LIBS "${CODAC_PKG_CONFIG_MODULE_LIBS} -l${PROJECT_NAME}-sympy" PARENT_SCOPE) ################################################################################ # Installation of libcodac-sympy files ################################################################################ - foreach(srcfile ${CODAC_SYMPY_PUBLIC_HDR}) - file(COPY ${srcfile} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/../../../include) - endforeach() - + # Generating the file codac-sympy.h set(CODAC_SYMPY_MAIN_HEADER ${CMAKE_CURRENT_BINARY_DIR}/codac-sympy.h) - file(WRITE ${CODAC_SYMPY_MAIN_HEADER} "/* This file is generated by CMake */\n\n") - file(APPEND ${CODAC_SYMPY_MAIN_HEADER} "#pragma once\n\n") + codac_publish_generated_include_dirs(${CMAKE_CURRENT_BINARY_DIR}) + + # Generate the content of the umbrella header + set(CODAC_SYMPY_HEADER_CONTENT "/* This file is generated by CMake */\n\n#pragma once\n\n") foreach(header_path ${CODAC_SYMPY_PUBLIC_HDR}) get_filename_component(header_name ${header_path} NAME) - file(APPEND ${CODAC_SYMPY_MAIN_HEADER} "#include <${header_name}>\n") + string(APPEND CODAC_SYMPY_HEADER_CONTENT "#include <${header_name}>\n") endforeach() - file(COPY ${CODAC_SYMPY_MAIN_HEADER} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/../../../include) + + # Compare with the existing file, if any + if(EXISTS "${CODAC_SYMPY_MAIN_HEADER}") + file(READ "${CODAC_SYMPY_MAIN_HEADER}" EXISTING_CONTENT) + string(MD5 EXISTING_HASH "${EXISTING_CONTENT}") + string(MD5 NEW_HASH "${CODAC_SYMPY_HEADER_CONTENT}") + else() + set(EXISTING_HASH "") + set(NEW_HASH "new") + endif() + + # Only rewrite it when it changed + if(NOT EXISTING_HASH STREQUAL NEW_HASH) + file(WRITE "${CODAC_SYMPY_MAIN_HEADER}" "${CODAC_SYMPY_HEADER_CONTENT}") + endif() - install(TARGETS ${PROJECT_NAME}-sympy DESTINATION ${CMAKE_INSTALL_LIBDIR}) + install(TARGETS ${PROJECT_NAME}-sympy + EXPORT ${PROJECT_NAME}-targets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES ${CODAC_SYMPY_PUBLIC_HDR} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}-sympy) install(FILES ${CODAC_SYMPY_MAIN_HEADER} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}-sympy) diff --git a/src/extensions/sympy/codac2_sympy_bridge.cpp b/src/extensions/sympy/codac2_sympy_bridge.cpp index 38799f90e..1da249ac4 100644 --- a/src/extensions/sympy/codac2_sympy_bridge.cpp +++ b/src/extensions/sympy/codac2_sympy_bridge.cpp @@ -238,7 +238,7 @@ namespace codac2 continue; } - assert_release(false && "Unsupported variable type in FlatSymbolTable"); + assert_release_unreachable("Unsupported variable type in FlatSymbolTable"); } } @@ -492,7 +492,7 @@ namespace codac2 return export_matrix_component(child_at(ch,0), op->i(), op->j()); } - assert_release(false && "Unsupported Codac scalar node in SympyExporter"); + assert_release_unreachable("Unsupported Codac scalar node in SympyExporter"); return pybind11::none(); } @@ -509,8 +509,7 @@ namespace codac2 return export_node(child_at(children,i)); } - assert_release(false - && "Unsupported vector-component expression. Supported cases: direct VectorVar components, or vector expressions exposing scalar children via children_expr_base()."); + assert_release_unreachable("Unsupported vector-component expression. Supported cases: direct VectorVar components, or vector expressions exposing scalar children via children_expr_base()."); return pybind11::none(); } @@ -528,8 +527,7 @@ namespace codac2 return export_vector_component(child_at(children,j), i); } - assert_release(false - && "Unsupported matrix-component expression. Supported cases: direct MatrixVar components, or matrix expressions exposing column children via children_expr_base()."); + assert_release_unreachable("Unsupported matrix-component expression. Supported cases: direct MatrixVar components, or matrix expressions exposing column children via children_expr_base()."); return pybind11::none(); } @@ -681,7 +679,7 @@ namespace codac2 if(func.is(sympy.attr("atan2"))) return atan2(x1, x2); } - assert_release(false && "Unsupported SymPy node in importer"); + assert_release_unreachable("Unsupported SymPy node in importer"); return const_value(0.); } } diff --git a/src/graphics/3rd/ipe/codac2_Figure2D_IPE.cpp b/src/graphics/3rd/ipe/codac2_Figure2D_IPE.cpp index 3b5da24b3..585130497 100644 --- a/src/graphics/3rd/ipe/codac2_Figure2D_IPE.cpp +++ b/src/graphics/3rd/ipe/codac2_Figure2D_IPE.cpp @@ -108,7 +108,13 @@ const std::string format_number(double num, double step) result += "."; double remainder = num - ((double) int_part); // remainder to add int remainder_to_int = std::round(remainder * std::pow(10, -precision)); - int length_of_remainder = std::floor(std::log10(remainder_to_int)) + 1; // for example 12 has a length of 2 + // log10(0) is -inf, which cannot be converted to int: remainder_to_int + // is 0 whenever num is (numerically) an exact integer, e.g. num == 2. + // and step forces a negative precision anyway. Treating that case as + // a length of 0 matches what the "approximation of 0" guard below + // already expects, instead of formatting spurious digits for it. + int length_of_remainder = (remainder_to_int == 0) + ? 0 : std::floor(std::log10(remainder_to_int)) + 1; // for example 12 has a length of 2 // this part is need for the specific case where a number like 1. is represented as 0.999... (int part gives 0 instead of 1) if (length_of_remainder > -precision) diff --git a/src/graphics/3rd/vibes/vibes.h b/src/graphics/3rd/vibes/vibes.h index 4c46b0f35..908d7bfd6 100644 --- a/src/graphics/3rd/vibes/vibes.h +++ b/src/graphics/3rd/vibes/vibes.h @@ -391,10 +391,10 @@ namespace vibes { /// The color used for transparency is throw the pen color VIBES_FUNC_COLOR_PARAM_5(drawRaster, const std::string&, rasterFilename, const double &,ulb, const double &, yub, - const double &,width, const double &, height); + const double &,width, const double &, height) VIBES_FUNC_COLOR_PARAM_6(drawRaster, const std::string&, rasterFilename, const double &,ulb, const double &, yub, - const double &,width, const double &, height, const double &, rot); + const double &,width, const double &, height, const double &, rot) /// Draw a Cake at position (cx,cy) VIBES_FUNC_COLOR_PARAM_4(drawCake,const double &,cx, const double &,cy, const double &,rot, const double &,length) @@ -462,15 +462,6 @@ namespace vibes { /** @} */ // end of group figure - // Ibex enabled functions - #ifdef _IBEX_INTERVAL_H_ - VIBES_FUNC_COLOR_PARAM_2(drawBox,const ibex::Interval &,x, const ibex::Interval &,y) - #endif //#ifdef _IBEX_INTERVAL_H_ - #ifdef __IBEX_INTERVAL_VECTOR_H__ - VIBES_FUNC_COLOR_PARAM_1(drawBox,const ibex::IntervalVector &,box); - VIBES_FUNC_COLOR_PARAM_1(drawBoxes, const std::vector &, boxes); - #endif //#ifdef __IBEX_INTERVAL_VECTOR_H__ - // // Inline Implementations @@ -521,31 +512,6 @@ namespace vibes { setObjectProperties(objectName, Params(key, value)); } - // Ibex enabled functions - #ifdef _IBEX_INTERVAL_H_ - inline void drawBox(const ibex::Interval &x, const ibex::Interval &y, Params params) { - drawBox(x.lb(),x.ub(),y.lb(),y.ub(),params); - } - #endif //#ifdef _IBEX_INTERVAL_H_ - #ifdef __IBEX_INTERVAL_VECTOR_H__ - /// \todo N-dimensionanl Ibex Inteval vector support - inline void drawBox(const ibex::IntervalVector &box, Params params) { - drawBox(box[0], box[1], params); - } - inline void drawBoxes(const std::vector &boxes, Params params){ - std::vector > bounds; - for(unsigned int i=0;i boundsI; - boundsI.push_back(boxes[i][0].lb()); - boundsI.push_back(boxes[i][0].ub()); - boundsI.push_back(boxes[i][1].lb()); - boundsI.push_back(boxes[i][1].ub()); - bounds.push_back(boundsI); - } - vibes::drawBoxes(bounds, params); - } - #endif //#ifdef __IBEX_INTERVAL_VECTOR_H__ } /// diff --git a/src/graphics/CMakeLists.txt b/src/graphics/CMakeLists.txt index 453a547d1..da754f829 100644 --- a/src/graphics/CMakeLists.txt +++ b/src/graphics/CMakeLists.txt @@ -46,14 +46,28 @@ #endif() add_library(${PROJECT_NAME}-graphics ${CODAC_GRAPHICS_SRC}) - target_include_directories(${PROJECT_NAME}-graphics PUBLIC + + # The directories holding the public graphics headers, in their canonical + # location under src/graphics. Published so that the tests, the examples and + # the Python bindings reach them by this path rather than through their link + # in build/include -- see codac_publish_include_dirs() in the top-level + # CMakeLists.txt for why that decides what the coverage report is worth. + set(CODAC_GRAPHICS_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rd/ipe ${CMAKE_CURRENT_SOURCE_DIR}/3rd/vibes ${CMAKE_CURRENT_SOURCE_DIR}/figures ${CMAKE_CURRENT_SOURCE_DIR}/paver # deprecated, to be removed ${CMAKE_CURRENT_SOURCE_DIR}/styles ) - target_link_libraries(${PROJECT_NAME}-graphics PUBLIC ${PROJECT_NAME}-core Ibex::ibex Eigen3::Eigen ${PROJECT_NAME}-core) + + # Build-tree paths on one side, install-tree paths on the other: see the + # same split in src/core/CMakeLists.txt for why install(EXPORT) needs it. + target_include_directories(${PROJECT_NAME}-graphics PUBLIC + "$" + $ + ) + codac_publish_include_dirs(${CODAC_GRAPHICS_INCLUDE_DIRS}) + target_link_libraries(${PROJECT_NAME}-graphics PUBLIC ${PROJECT_NAME}-core Codac::gaol $) ################################################################################ @@ -61,7 +75,7 @@ ################################################################################ set(CODAC_PKG_CONFIG_CFLAGS "${CODAC_PKG_CONFIG_CFLAGS} -I\${includedir}/${PROJECT_NAME}-graphics" PARENT_SCOPE) - set(CODAC_PKG_CONFIG_LIBS "${CODAC_PKG_CONFIG_LIBS} -l${PROJECT_NAME}-graphics" PARENT_SCOPE) + set(CODAC_PKG_CONFIG_MODULE_LIBS "${CODAC_PKG_CONFIG_MODULE_LIBS} -l${PROJECT_NAME}-graphics" PARENT_SCOPE) ################################################################################ @@ -73,24 +87,43 @@ foreach(srcfile ${CODAC_GRAPHICS_SRC}) if(srcfile MATCHES "\\.h$" OR srcfile MATCHES "\\.hpp$") list(APPEND CODAC_GRAPHICS_HDR ${srcfile}) - file(COPY ${srcfile} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/../../include) endif() endforeach() # Generating the file codac-graphics.h set(CODAC_GRAPHICS_MAIN_HEADER ${CMAKE_CURRENT_BINARY_DIR}/codac-graphics.h) + codac_publish_generated_include_dirs(${CMAKE_CURRENT_BINARY_DIR}) set(CODAC_MAIN_SUBHEADERS ${CODAC_MAIN_SUBHEADERS} "codac-graphics.h" PARENT_SCOPE) - file(WRITE ${CODAC_GRAPHICS_MAIN_HEADER} "/* This file is generated by CMake */\n\n") - file(APPEND ${CODAC_GRAPHICS_MAIN_HEADER} "#pragma once\n\n") + + # Generate the content of the umbrella header + set(CODAC_GRAPHICS_HEADER_CONTENT "/* This file is generated by CMake */\n\n#pragma once\n\n") foreach(header_path ${CODAC_GRAPHICS_HDR}) get_filename_component(header_name ${header_path} NAME) - file(APPEND ${CODAC_GRAPHICS_MAIN_HEADER} "#include <${header_name}>\n") + string(APPEND CODAC_GRAPHICS_HEADER_CONTENT "#include <${header_name}>\n") endforeach() - file(COPY ${CODAC_GRAPHICS_MAIN_HEADER} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/../../include) + + # Compare with the existing file, if any + if(EXISTS "${CODAC_GRAPHICS_MAIN_HEADER}") + file(READ "${CODAC_GRAPHICS_MAIN_HEADER}" EXISTING_CONTENT) + string(MD5 EXISTING_HASH "${EXISTING_CONTENT}") + string(MD5 NEW_HASH "${CODAC_GRAPHICS_HEADER_CONTENT}") + else() + set(EXISTING_HASH "") + set(NEW_HASH "new") + endif() + + # Only rewrite it when it changed + if(NOT EXISTING_HASH STREQUAL NEW_HASH) + file(WRITE "${CODAC_GRAPHICS_MAIN_HEADER}" "${CODAC_GRAPHICS_HEADER_CONTENT}") + endif() # Install files in system directories - install(TARGETS ${PROJECT_NAME}-graphics DESTINATION ${CMAKE_INSTALL_LIBDIR}) + install(TARGETS ${PROJECT_NAME}-graphics + EXPORT ${PROJECT_NAME}-targets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES ${CODAC_GRAPHICS_HDR} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}-graphics) install(FILES ${CODAC_GRAPHICS_MAIN_HEADER} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}-graphics) \ No newline at end of file diff --git a/src/graphics/figures/codac2_Figure2D.cpp b/src/graphics/figures/codac2_Figure2D.cpp index 2658e715b..1be37308e 100644 --- a/src/graphics/figures/codac2_Figure2D.cpp +++ b/src/graphics/figures/codac2_Figure2D.cpp @@ -517,7 +517,7 @@ void Figure2D::draw_tube(const SlicedTube& x, const StyleGradien { draw_tube_common(*this, x, max_nb_slices_to_display, [&style](const Interval& tube_t0tf, std::list::reverse_iterator it) { - auto c = style.cmap.color((it->mid()-tube_t0tf.lb())/tube_t0tf.diam()); + auto c = style.cmap.color(static_cast((it->mid()-tube_t0tf.lb())/tube_t0tf.diam())); return StyleProperties({c,c}, style.layer, style.line_style, "w:"+to_string(style.line_width), "z:"+to_string(style.z_value)); }); } diff --git a/src/graphics/figures/codac2_Figure2D_pave.h b/src/graphics/figures/codac2_Figure2D_pave.h index 17d4c34c4..93f07e542 100644 --- a/src/graphics/figures/codac2_Figure2D_pave.h +++ b/src/graphics/figures/codac2_Figure2D_pave.h @@ -70,7 +70,7 @@ namespace codac2 } } - printf("Computation time: %.4fs, %ld boxes\n", (double)(clock()-t_start)/CLOCKS_PER_SEC, n); + printf("Computation time: %.4fs, %td boxes\n", (double)(clock()-t_start)/CLOCKS_PER_SEC, n); } template diff --git a/src/graphics/figures/codac2_Figure3D.h b/src/graphics/figures/codac2_Figure3D.h index 442293832..8acc9de0b 100644 --- a/src/graphics/figures/codac2_Figure3D.h +++ b/src/graphics/figures/codac2_Figure3D.h @@ -206,7 +206,7 @@ namespace codac2 * \param style Style (color) */ void draw_plane(const Vector &c, const Matrix &A, bool yaw_is_up=true, - const StyleProperties& style = { Color::dark_gray(0.8) }); + const StyleProperties& style = { Color::dark_gray(0.8f) }); // Pavings @@ -228,7 +228,7 @@ namespace codac2 * \param in_style Style of the inside of the paving */ void draw_paving(const PavingInOut& p, - const StyleProperties& bound_style = { Color::yellow(0.3), "paving_bound" }, + const StyleProperties& bound_style = { Color::yellow(0.3f), "paving_bound" }, const StyleProperties& in_style = { Color::green(0.5) , "paving_in" }); /** diff --git a/src/graphics/styles/codac2_Color.cpp b/src/graphics/styles/codac2_Color.cpp index 9cbb508a3..be93f531f 100644 --- a/src/graphics/styles/codac2_Color.cpp +++ b/src/graphics/styles/codac2_Color.cpp @@ -67,9 +67,9 @@ Color Color::rgb() const float r = 0., g = 0., b = 0.; // Normalisation des valeurs - float h = (*this)[0] / 360.; // Hue normalisée (0 à 1) - float s = (*this)[1] / 100.; // Saturation normalisée (0 à 1) - float v = (*this)[2] / 100.; // Value normalisée (0 à 1) + float h = (*this)[0] / 360.f; // Hue normalisée (0 à 1) + float s = (*this)[1] / 100.f; // Saturation normalisée (0 à 1) + float v = (*this)[2] / 100.f; // Value normalisée (0 à 1) int i = static_cast(h * 6); float f = (h * 6) - i; @@ -93,7 +93,7 @@ Color Color::rgb() const g *= 255.; b *= 255.; - return Color({r, g, b,std::min(255.,((*this)[3]*2.55))},Model::RGB); + return Color({r, g, b,std::min(255.f,((*this)[3]*2.55f))},Model::RGB); } } @@ -103,9 +103,9 @@ Color Color::hsv() const return *this; else { - float r = (*this)[0]/255.; - float g = (*this)[1]/255.; - float b = (*this)[2]/255.; + float r = (*this)[0]/255.f; + float g = (*this)[1]/255.f; + float b = (*this)[2]/255.f; float c_max = std::max({r, g, b}); float c_min = std::min({r, g, b}); float delta = c_max - c_min; @@ -113,11 +113,11 @@ Color Color::hsv() const float h = 0.0; if (delta != 0) { if (c_max == r) { - h = fmod((g - b) / delta, 6.0); + h = std::fmod((g - b) / delta, 6.0f); } else if (c_max == g) { - h = (b - r) / delta + 2.0; + h = (b - r) / delta + 2.0f; } else if (c_max == b) { - h = (r - g) / delta + 4.0; + h = (r - g) / delta + 4.0f; } h /= 6.0; if (h < 0) { @@ -133,7 +133,7 @@ Color Color::hsv() const s*=100.; v*=100.; - return Color({h, s, v,std::min(100.,((*this)[3]/2.55))},Model::HSV); + return Color({h, s, v,std::min(100.f,((*this)[3]/2.55f))},Model::HSV); } } diff --git a/src/graphics/styles/codac2_ColorMap.cpp b/src/graphics/styles/codac2_ColorMap.cpp index 728264b9c..6567f6eba 100644 --- a/src/graphics/styles/codac2_ColorMap.cpp +++ b/src/graphics/styles/codac2_ColorMap.cpp @@ -24,7 +24,7 @@ Color ColorMap::color(float r) const assert(Interval(0.,1.).contains(r)); Interval map_domain = Interval(this->begin()->first,prev(this->end())->first); - float real_index = map_domain.lb() + r*map_domain.diam(); + float real_index = static_cast(map_domain.lb() + r*map_domain.diam()); if(this->find(real_index) == this->end()) // color interpolation { diff --git a/src/graphics/styles/codac2_ColorMap.h b/src/graphics/styles/codac2_ColorMap.h index 26525003d..fbf1b28b5 100644 --- a/src/graphics/styles/codac2_ColorMap.h +++ b/src/graphics/styles/codac2_ColorMap.h @@ -160,7 +160,7 @@ namespace codac2 int i = 0; for(int h = 300 ; h > 0 ; h-=10) { - cmap[i]=Color({(float)h,100.,100.,100.f*alpha},Model::HSV); + cmap[(float)i]=Color({(float)h,100.,100.,100.f*alpha},Model::HSV); i++; } return cmap; diff --git a/src/graphics/styles/codac2_StylePropertiesBase.cpp b/src/graphics/styles/codac2_StylePropertiesBase.cpp index 07552fd8c..76e66240b 100644 --- a/src/graphics/styles/codac2_StylePropertiesBase.cpp +++ b/src/graphics/styles/codac2_StylePropertiesBase.cpp @@ -7,6 +7,7 @@ * \license GNU Lesser General Public License (LGPL) */ +#include #include "codac2_StylePropertiesBase.h" using namespace std; @@ -35,6 +36,7 @@ void StylePropertiesBase::parse_parameter(const std::string& param) } catch (const std::invalid_argument& e) { + std::cerr << "codac2: invalid line width value \"" << param.substr(2) << "\": " << e.what() << std::endl; assert_release_constexpr(false && "Invalid line width value"); } } @@ -47,6 +49,7 @@ void StylePropertiesBase::parse_parameter(const std::string& param) } catch (const std::invalid_argument& e) { + std::cerr << "codac2: invalid z-value \"" << param.substr(2) << "\": " << e.what() << std::endl; assert_release_constexpr(false && "Invalid z-value"); } } diff --git a/src/unsupported/CMakeLists.txt b/src/unsupported/CMakeLists.txt index a0a0f8d8f..0f49db62d 100644 --- a/src/unsupported/CMakeLists.txt +++ b/src/unsupported/CMakeLists.txt @@ -18,14 +18,23 @@ #endif() add_library(${PROJECT_NAME}-unsupported ${CODAC_UNSUPPORTED_SRC}) + + # The directory holding the public unsupported headers. The list used to be + # a copy of the graphics one (3rd/ipe, figures, paver, styles), none of which + # exists here: this module keeps its single header at its own root, so that + # is what has to be on the include path -- and what consumers must reach it + # through rather than through its link in build/include, see + # codac_publish_include_dirs() in the top-level CMakeLists.txt. + set(CODAC_UNSUPPORTED_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}) + + # Build-tree paths on one side, install-tree paths on the other: see the + # same split in src/core/CMakeLists.txt for why install(EXPORT) needs it. target_include_directories(${PROJECT_NAME}-unsupported PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/3rd/ipe - ${CMAKE_CURRENT_SOURCE_DIR}/3rd/vibes - ${CMAKE_CURRENT_SOURCE_DIR}/figures - ${CMAKE_CURRENT_SOURCE_DIR}/paver - ${CMAKE_CURRENT_SOURCE_DIR}/styles + "$" + $ ) - target_link_libraries(${PROJECT_NAME}-unsupported PUBLIC ${PROJECT_NAME}-core Eigen3::Eigen) + codac_publish_include_dirs(${CODAC_UNSUPPORTED_INCLUDE_DIRS}) + target_link_libraries(${PROJECT_NAME}-unsupported PUBLIC ${PROJECT_NAME}-core $) ################################################################################ @@ -33,7 +42,7 @@ ################################################################################ set(CODAC_PKG_CONFIG_CFLAGS "${CODAC_PKG_CONFIG_CFLAGS} -I\${includedir}/${PROJECT_NAME}-unsupported" PARENT_SCOPE) - set(CODAC_PKG_CONFIG_LIBS "${CODAC_PKG_CONFIG_LIBS} -l${PROJECT_NAME}-unsupported" PARENT_SCOPE) + set(CODAC_PKG_CONFIG_MODULE_LIBS "${CODAC_PKG_CONFIG_MODULE_LIBS} -l${PROJECT_NAME}-unsupported" PARENT_SCOPE) ################################################################################ @@ -45,23 +54,42 @@ foreach(srcfile ${CODAC_UNSUPPORTED_SRC}) if(srcfile MATCHES "\\.h$" OR srcfile MATCHES "\\.hpp$") list(APPEND CODAC_UNSUPPORTED_HDR ${srcfile}) - file(COPY ${srcfile} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/../../include) endif() endforeach() # Generating the file codac-unsupported.h set(CODAC_UNSUPPORTED_MAIN_HEADER ${CMAKE_CURRENT_BINARY_DIR}/codac-unsupported.h) - file(WRITE ${CODAC_UNSUPPORTED_MAIN_HEADER} "/* This file is generated by CMake */\n\n") - file(APPEND ${CODAC_UNSUPPORTED_MAIN_HEADER} "#pragma once\n\n") + codac_publish_generated_include_dirs(${CMAKE_CURRENT_BINARY_DIR}) + + # Generate the content of the umbrella header + set(CODAC_UNSUPPORTED_HEADER_CONTENT "/* This file is generated by CMake */\n\n#pragma once\n\n") foreach(header_path ${CODAC_UNSUPPORTED_HDR}) get_filename_component(header_name ${header_path} NAME) - file(APPEND ${CODAC_UNSUPPORTED_MAIN_HEADER} "#include <${header_name}>\n") + string(APPEND CODAC_UNSUPPORTED_HEADER_CONTENT "#include <${header_name}>\n") endforeach() - file(COPY ${CODAC_UNSUPPORTED_MAIN_HEADER} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/../../include) + + # Compare with the existing file, if any + if(EXISTS "${CODAC_UNSUPPORTED_MAIN_HEADER}") + file(READ "${CODAC_UNSUPPORTED_MAIN_HEADER}" EXISTING_CONTENT) + string(MD5 EXISTING_HASH "${EXISTING_CONTENT}") + string(MD5 NEW_HASH "${CODAC_UNSUPPORTED_HEADER_CONTENT}") + else() + set(EXISTING_HASH "") + set(NEW_HASH "new") + endif() + + # Only rewrite it when it changed + if(NOT EXISTING_HASH STREQUAL NEW_HASH) + file(WRITE "${CODAC_UNSUPPORTED_MAIN_HEADER}" "${CODAC_UNSUPPORTED_HEADER_CONTENT}") + endif() # Install files in system directories - install(TARGETS ${PROJECT_NAME}-unsupported DESTINATION ${CMAKE_INSTALL_LIBDIR}) + install(TARGETS ${PROJECT_NAME}-unsupported + EXPORT ${PROJECT_NAME}-targets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES ${CODAC_UNSUPPORTED_HDR} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}-unsupported) install(FILES ${CODAC_UNSUPPORTED_MAIN_HEADER} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}-unsupported) \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index dff12a6f4..aa283e802 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -15,19 +15,39 @@ else() FetchContent_Declare(Catch2 URL https://github.com/catchorg/Catch2/archive/refs/tags/v3.6.0.zip) #FetchContent_Declare(Catch2 URL ${CMAKE_CURRENT_SOURCE_DIR}/../3rd/Catch2-3.6.0.zip) # If needed to be self-contained... FetchContent_MakeAvailable(Catch2) + # See codac_mark_target_system() in the top-level CMakeLists.txt: without + # this, Catch2's headers inherit this project's warning level. Only the + # FetchContent path needs it -- a find_package() target is IMPORTED, and + # CMake already treats those as system. + codac_mark_target_system(Catch2) + codac_mark_target_system(Catch2::Catch2WithMain) endif() # Adds Catch2::Catch2WithMain + +# Test sources +# ================================================================== + list(APPEND SRC_TESTS # listing files without extension + # ---------------------------------------------------------------- # 3rd - - # Core + # ---------------------------------------------------------------- core/3rd/codac2_tests_eigen + + # ---------------------------------------------------------------- + # Actions + # ---------------------------------------------------------------- + core/actions/codac2_tests_OctaSym + + # ---------------------------------------------------------------- + # Contractors + # ---------------------------------------------------------------- + core/contractors/codac2_tests_CtcAction core/contractors/codac2_tests_CtcCartProd core/contractors/codac2_tests_CtcCtcBoundary @@ -45,36 +65,67 @@ list(APPEND SRC_TESTS # listing files without extension core/contractors/codac2_tests_CtcUnion core/contractors/codac2_tests_CtcVisible core/contractors/codac2_tests_linear_ctc + ../doc/manual/manual/contractors/geometric/src ../doc/manual/manual/contractors/analytic/src ../doc/manual/manual/contractors/set/src + ../doc/manual/manual/contractors/shape/src + + + # ---------------------------------------------------------------- + # Domains + # ---------------------------------------------------------------- core/domains/codac2_tests_BoolInterval + core/domains/ellipsoid/codac2_tests_Ellipsoid + core/domains/interval/codac2_tests_Interval core/domains/interval/codac2_tests_Interval_operations core/domains/interval/codac2_tests_IntervalMatrix core/domains/interval/codac2_tests_IntervalVector + ../doc/manual/manual/intervals/src + core/domains/zonotope/codac2_tests_Parallelepiped_eval core/domains/zonotope/codac2_tests_Parallelepiped core/domains/zonotope/codac2_tests_Zonotope + core/domains/tube/codac2_tests_TDomain core/domains/tube/codac2_tests_Slice core/domains/tube/codac2_tests_Slice_polygon core/domains/tube/codac2_tests_SlicedTube core/domains/tube/codac2_tests_SlicedTube_integral + ../doc/manual/manual/tubes/src + + # ---------------------------------------------------------------- + # Functions + # ---------------------------------------------------------------- + core/functions/analytic/codac2_tests_AnalyticFunction + ../doc/manual/manual/functions/analytic/src + ../doc/manual/manual/functions/parallelepiped/src + + + # ---------------------------------------------------------------- + # Geometry + # ---------------------------------------------------------------- core/geometry/codac2_tests_ConvexPolygon core/geometry/codac2_tests_geometry core/geometry/codac2_tests_Polygon core/geometry/codac2_tests_Segment + ../doc/manual/manual/geometry/src + + # ---------------------------------------------------------------- + # Matrices + # ---------------------------------------------------------------- + core/matrices/codac2_tests_arithmetic_add core/matrices/codac2_tests_arithmetic_div core/matrices/codac2_tests_arithmetic_mul @@ -86,12 +137,30 @@ list(APPEND SRC_TESTS # listing files without extension core/matrices/codac2_tests_inversion core/matrices/codac2_tests_IntFullPivLU core/matrices/codac2_tests_GaussJordan + ../doc/manual/manual/linear/src + + # ---------------------------------------------------------------- + # Operators + # ---------------------------------------------------------------- + core/operators/codac2_tests_operators + + # ---------------------------------------------------------------- + # Peibos + # ---------------------------------------------------------------- + core/peibos/codac2_tests_peibos + ../doc/manual/manual/functions/peibos/src + + + # ---------------------------------------------------------------- + # Separators + # ---------------------------------------------------------------- + core/separators/codac2_tests_SepCartProd core/separators/codac2_tests_SepCtcBoundary core/separators/codac2_tests_SepInter @@ -102,20 +171,39 @@ list(APPEND SRC_TESTS # listing files without extension core/separators/codac2_tests_SepTransform core/separators/codac2_tests_SepUnion core/separators/codac2_tests_SepVisible - + + + # ---------------------------------------------------------------- + # Tools + # ---------------------------------------------------------------- + core/tools/codac2_tests_Approx core/tools/codac2_tests_serialization core/tools/codac2_tests_transformations core/tools/codac2_tests_trunc - core/tools/ibex/codac2_tests_ibex + ../doc/manual/manual/tools/src - + + + # ---------------------------------------------------------------- + # Trajectory + # ---------------------------------------------------------------- + core/trajectory/codac2_tests_AnalyticTraj core/trajectory/codac2_tests_SampledTraj + + # ---------------------------------------------------------------- + # Graphics + # ---------------------------------------------------------------- + graphics/styles/codac2_tests_Color ) + +# Python files required by tests +# ================================================================== + file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/core/domains/tube/codac2_tests_predefined_tubes.py # Add here other Python files that are not tests but need to be exported to Python package @@ -125,14 +213,26 @@ file(COPY ${CMAKE_BINARY_DIR}/python/python_package/codac/tests ) + +# Libraries +# ================================================================== + set(CODAC_LIBRARIES ${PROJECT_NAME}-core ${PROJECT_NAME}-graphics) +# Sympy +# ================================================================== + option(BUILD_SYMPY_EMBED_TESTS "Build C++ tests that require embedded Python for Sympy" ON) if(WITH_PYTHON AND DEFINED PYBIND11_FINDPYTHON AND NOT PYBIND11_FINDPYTHON) message(STATUS "Disabling Sympy C++ embed tests because PYBIND11_FINDPYTHON=OFF") set(BUILD_SYMPY_EMBED_TESTS OFF) endif() +# BUILD_SYMPY_EMBED_TESTS is an option(), hence a cache entry: the set() above +# only overrides it for this directory, and examples/ would otherwise read the +# cached ON again and build the sympy example where its own test is disabled. +set(BUILD_SYMPY_EMBED_TESTS "${BUILD_SYMPY_EMBED_TESTS}" PARENT_SCOPE) + # Sympy test if (WITH_PYTHON AND BUILD_SYMPY_EMBED_TESTS) list(APPEND SRC_TESTS @@ -141,7 +241,9 @@ if (WITH_PYTHON AND BUILD_SYMPY_EMBED_TESTS) ) endif() -# CAPD test + +# CAPD +# ================================================================== if (WITH_CAPD) list(APPEND SRC_TESTS extensions/capd/codac2_tests_capd @@ -151,33 +253,394 @@ if (WITH_CAPD) set(CODAC_LIBRARIES ${CODAC_LIBRARIES} ${PROJECT_NAME}-capd capd::capd) endif() -# IBEX test -#if (WITH_IBEX) - #find_package(IBEX REQUIRED) - #ibex_init_common() # IBEX should have installed this function - #message(STATUS "Found IBEX version ${IBEX_VERSION}") -#endif() + + + +# Common Codac include directories +# ================================================================== +# +# IMPORTANT: +# Use the source tree as the canonical header tree. +# +# The headers are now used directly from the source tree. + +# CODAC_SOURCE_INCLUDE_DIRS is filled in by the src/*/CMakeLists.txt of every +# module (core, graphics, unsupported, and capd/sympy when enabled), which are +# all configured before this directory. Using it rather than a second copy of +# the same list keeps the tests compiling against exactly the headers the +# libraries were built from; the copy that used to live here covered core only +# and had already drifted from it. +set(CODAC_CORE_SOURCE_INCLUDE_DIRS ${CODAC_SOURCE_INCLUDE_DIRS}) + + +# ${CMAKE_BINARY_DIR}/src holds the generated "codac" umbrella header; +# CODAC_GENERATED_INCLUDE_DIRS, filled in by the src/*/CMakeLists.txt of every +# module, holds the per-module ones (codac-core.h and the rest), each of which +# is written into its own module's binary directory rather than into this one. +set( + CODAC_HEADERS_DIR + ${CMAKE_BINARY_DIR}/src + ${CODAC_GENERATED_INCLUDE_DIRS} +) + + + +# ------------------------------------------------------------------ +# Sanitizer runtime preload for Python tests (Debug builds only) +# ------------------------------------------------------------------ +# +# In Debug mode, _core.so (the pybind11 module) is compiled with +# -fsanitize=address,undefined and depends on a *shared* sanitizer +# runtime rather than one statically embedded in the .so: +# - Clang needs an explicit -shared-libsan flag to get that (see +# python/src/core/CMakeLists.txt) -- its default is to embed the +# runtime statically into shared libraries. +# - GCC needs no flag at all: unless -static-libasan/-static-libubsan +# is passed (which codac never does), GCC already links libasan.so / +# libubsan.so dynamically by default. Passing Clang's -shared-libsan +# to GCC is not an option either way -- GCC's driver rejects it +# outright as an unrecognized flag. +# Either way, since python3 itself has no sanitizer runtime, that shared +# runtime must be preloaded before Python imports codac, otherwise symbols +# like __ubsan_vptr_type_cache/__asan_* stay unresolved -- or, on GCC, the +# module load aborts immediately with "ASan runtime does not come first in +# initial library list" (reproduced locally: a plain dlopen(), from an +# unsanitized host, of a GCC ASan-built .so fails exactly this way without +# LD_PRELOAD, and succeeds once libasan.so is preloaded). + +set(PYTHON_TEST_ENV_ARGS "PYTHONPATH=${PYTHON_PACKAGE_DIR}") + +# Not gated behind a plain CMAKE_BUILD_TYPE STREQUAL "Debug" check (see the +# matching note in the root CMakeLists.txt): that variable is meaningless +# for multi-configuration generators, which instead pick the actual +# configuration later, at build time. CMAKE_CONFIGURATION_TYPES is set +# precisely on those generators, so probing here also runs whenever it is +# non-empty -- the individual LD_PRELOAD/ASAN_OPTIONS/UBSAN_OPTIONS entries +# appended below are themselves wrapped in a $ generator +# expression (see how PYTHON_TEST_ENV_ARGS is consumed via the ENVIRONMENT +# test property further down), so they still only take effect for an +# actual Debug-configuration test run. +if(WITH_PYTHON AND (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_CONFIGURATION_TYPES) + AND CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU" AND CMAKE_SYSTEM_NAME STREQUAL "Linux") + + # This whole block is Linux-specific by construction, not just by + # "is the compiler Clang or GCC": LD_PRELOAD is a Linux/glibc loader + # feature with no equivalent environment variable on Windows, and the + # shared runtime file names it looks for below only exist in that form + # on Linux -- macOS uses DYLD_INSERT_LIBRARIES and ships its runtimes + # under different names (e.g. Clang's combined, no-arch-suffix + # libclang_rt.asan_osx_dynamic.dylib), and Windows uses neither an + # env-var preload mechanism nor this naming (clang-cl names them + # clang_rt.asan_dynamic-.dll; MinGW-w64 GCC ships asan/ubsan as + # DLLs with their own layout). Real MSVC (cl.exe) is already excluded + # by the CMAKE_CXX_COMPILER_ID check above, but clang-cl (Windows) and + # AppleClang (macOS) both also match "Clang" (and MinGW GCC still + # matches "GNU"), so without this explicit Linux check they would + # silently fall through to the "no shared runtime found" warning below + # instead of making clear that the feature just does not apply there. + + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + + # Note: `clang --print-runtime-dir` assumes a per-target-runtime-dir + # layout (lib//) which some distro packages (e.g. Ubuntu/ + # Debian's llvm.org apt packages) do not use, instead keeping the legacy + # lib/linux/ layout: the reported directory would not exist there, even + # though the runtime libraries are actually installed. `-print-file-name` + # asks the driver to resolve each file the way it would at link time, + # which works regardless of which layout the distro packaging uses. + # + # The runtime file names themselves are not portable either: Clang embeds + # its own architecture name in them (e.g. libclang_rt.asan-aarch64.so on + # an AArch64 Raspberry Pi OS, libclang_rt.asan-armhf.so on a 32-bit + # ARMv7/ARMv8 hard-float userland). Rather than maintaining our own + # CMAKE_SYSTEM_PROCESSOR -> Clang-runtime-name table (which would drift + # out of sync as LLVM adds targets), ask the compiler itself: + # `-dumpmachine` prints its default target triple (understood by both + # Clang and GCC, and already normalized -- e.g. a 32-bit x86 compiler + # reports "i386", never "i686"), and its first field is, for essentially + # every architecture, precisely the suffix Clang's runtime uses. + # + # The one documented exception is 32-bit ARM, where the suffix instead + # follows the float ABI recorded in the triple's environment field + # (.../gnueabihf -> "armhf" hard-float, vs. plain "arm" otherwise) rather + # than the architecture field (which is typically "arm"/"armv7"/...) -- + # this is an LLVM naming convention, not something any compiler flag + # reports directly, so it is the one case still special-cased below. + execute_process( + COMMAND ${CMAKE_CXX_COMPILER} -dumpmachine + OUTPUT_VARIABLE _clang_target_triple + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + set(_sanitizer_rt_arch "") + if(_clang_target_triple) + string(REPLACE "-" ";" _clang_triple_fields "${_clang_target_triple}") + list(GET _clang_triple_fields 0 _sanitizer_rt_arch) + if(_sanitizer_rt_arch MATCHES "^armv?[0-9]*l?$") + if(_clang_target_triple MATCHES "eabihf$") + set(_sanitizer_rt_arch "armhf") + else() + set(_sanitizer_rt_arch "arm") + endif() + endif() + endif() + + # The ASan runtime only, deliberately: built with -fsanitize=address, + # undefined, Clang puts the UndefinedBehaviorSanitizer inside the ASan + # runtime, and ships libclang_rt.ubsan_standalone-* for the builds that + # use UBSan on its own. Preloading both therefore loads two copies of the + # same initialisation and the same interceptors into one process. Clang 18 + # tolerated it; Clang 21 wedges the interpreter so thoroughly that even + # SIGKILL does not remove it, which is what made every Python test of the + # Clang jobs of .github/workflows/unixdebug.yml run into its timeout while + # the GCC jobs of the same matrix passed them in about two seconds -- + # GCC's libasan and libubsan being genuinely separate libraries, designed + # to be loaded together. Preloading ASan alone still gives both sets of + # checks, since one runtime implements them both. + set(_sanitizer_rt_candidates "") + if(_sanitizer_rt_arch) + set(_sanitizer_rt_candidates + libclang_rt.asan-${_sanitizer_rt_arch}.so + ) + endif() + + else() # GNU (GCC) + + # Unlike Clang, GCC's driver is built for one target only, so its + # runtime file names carry no architecture suffix -- `-print-file-name` + # already resolves to the right library for whatever target this GCC + # was built for, on every architecture. GCC also splits ASan and UBSan + # into two separately-named libraries (libasan.so / libubsan.so), + # rather than Clang's libclang_rt.asan-*/libclang_rt.ubsan_standalone-* + # naming. + set(_sanitizer_rt_candidates libasan.so libubsan.so) + + endif() + + set(SANITIZER_RT_LIBS "") + foreach(_rt_lib_name ${_sanitizer_rt_candidates}) + execute_process( + COMMAND ${CMAKE_CXX_COMPILER} -print-file-name=${_rt_lib_name} + OUTPUT_VARIABLE _rt_lib_path + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if(EXISTS "${_rt_lib_path}") + list(APPEND SANITIZER_RT_LIBS "${_rt_lib_path}") + endif() + endforeach() + + if(SANITIZER_RT_LIBS) + list(JOIN SANITIZER_RT_LIBS ":" SANITIZER_RT_LIBS_JOINED) + list(APPEND PYTHON_TEST_ENV_ARGS "$<$:LD_PRELOAD=${SANITIZER_RT_LIBS_JOINED}>") + + # LeakSanitizer (part of ASan) flags many long-lived, process-lifetime + # allocations made by the Python interpreter and by pybind11 module + # init (interned strings, type objects, strdup'd docstrings...) as + # "leaks", since it can't trace Python's own object graph. This is a + # well-known false-positive source for embedded Python interpreters, + # not something codac's code controls. UBSan and ASan's other checks + # (use-after-free, buffer overflow, UB...) remain fully active. + list(APPEND PYTHON_TEST_ENV_ARGS "$<$:ASAN_OPTIONS=detect_leaks=0>") + list(APPEND PYTHON_TEST_ENV_ARGS "$<$:UBSAN_OPTIONS=print_stacktrace=1>") + message(STATUS "Python tests: LD_PRELOAD=${SANITIZER_RT_LIBS_JOINED}") + else() + message(WARNING + "Debug build with WITH_PYTHON: no shared ASan/UBSan runtime found " + "for ${CMAKE_CXX_COMPILER}. With Clang, make sure the _core target " + "is linked with -shared-libsan; with GCC, make sure it is not " + "linked with -static-libasan/-static-libubsan. Otherwise Python " + "tests will fail with 'undefined symbol' errors, or abort with " + "'ASan runtime does not come first in initial library list'.") + endif() + +endif() + +# ------------------------------------------------------------------ +# Sanitizer runtime discoverability for MSVC (Debug builds only) +# ------------------------------------------------------------------ +# +# CODAC_MSVC_ASAN_RUNTIME_DLL is set by the root CMakeLists.txt (empty if +# no matching runtime could be found, in which case /fsanitize=address was +# never even added -- nothing to do here either). Windows has no +# LD_PRELOAD-style env var and no "must load first" requirement: the +# loader just needs to be able to find the runtime DLL via PATH or next +# to the binary that imports it, when that binary is loaded. Python's +# _core.pyd gets its own copy placed next to it (see +# python/src/core/CMakeLists.txt); the plain C++ test executables and the +# python.exe launched below have no such fixed "next to" location, so +# their directory search is extended via PATH instead, the direct +# Windows analogue of prepending to LD_PRELOAD above. +set(CODAC_MSVC_ASAN_PATH_ENV "") +if(MSVC AND CODAC_MSVC_ASAN_RUNTIME_DLL) + get_filename_component(_msvc_asan_dll_dir "${CODAC_MSVC_ASAN_RUNTIME_DLL}" DIRECTORY) + # Both ctest's ENVIRONMENT test property and the COMMAND argument list + # passed to `cmake -E env` below are themselves ';'-separated CMake + # lists, so the ';' that Windows uses to separate PATH entries has to be + # escaped as '\;' here -- otherwise CMake would silently split this one + # "PATH=......" value into several unrelated list entries. + # + # NOTE: $ENV{PATH} is captured at CMake *configure* time, i.e. whatever + # PATH was active when `cmake` was run to configure the project. If + # ctest is later invoked from a shell with a materially different PATH, + # this won't reflect that -- a limitation inherent to baking an + # environment value into generated build files, not specific to this + # check. + string(REPLACE ";" "\\;" _msvc_asan_path_escaped "${_msvc_asan_dll_dir};$ENV{PATH}") + set(CODAC_MSVC_ASAN_PATH_ENV "PATH=${_msvc_asan_path_escaped}") + # Appended for every configuration rather than wrapped in a $ + # generator expression. `cmake -E env` reads its arguments as VAR=value + # assignments up to the first one that is not, which it then takes for the + # command to run: an element that a generator expression reduced to nothing + # leaves an empty argument in the middle of the list, and the command becomes + # the empty string. CODAC_MSVC_ASAN_RUNTIME_DLL is computed whatever the + # configuration (see the root CMakeLists.txt), so on MSVC the element was + # always there and always empty outside Debug -- every Python test of + # .github/workflows/vcmatrix.yml died on "no such file or directory" while + # the C++ ones passed. Prepending the sanitizer directory to PATH costs a + # Release run nothing, which is what the C++ tests below already rely on. + list(APPEND PYTHON_TEST_ENV_ARGS "${CODAC_MSVC_ASAN_PATH_ENV}") +endif() + + +# Environment of the C++ tests and examples +# ================================================================== +# +# The same environment is needed by every C++ binary this project runs under +# ctest, the examples of examples/CMakeLists.txt included, so it is built once +# here and exported to the parent scope rather than recomputed per test. +# +# UBSAN_OPTIONS only means something to the GCC/Clang single-configuration +# builds, where CMAKE_BUILD_TYPE holds the answer. The MSVC AddressSanitizer +# runtime is a DLL that has to be discoverable at load time by every binary +# compiled with /fsanitize=address: without it on PATH a Debug test cannot even +# start, Windows failing the process creation with 0xC0000135 +# (STATUS_DLL_NOT_FOUND) before a single assertion runs. It is added whatever +# the configuration, because the multi-configuration generators MSVC is driven +# through only choose theirs at build time, and prepending a toolchain +# directory to PATH costs a Release run nothing. + +set(CODAC_CPP_TEST_ENV "") + +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + list(APPEND CODAC_CPP_TEST_ENV "UBSAN_OPTIONS=print_stacktrace=1") +endif() + +if(CODAC_MSVC_ASAN_PATH_ENV) + list(APPEND CODAC_CPP_TEST_ENV "${CODAC_MSVC_ASAN_PATH_ENV}") +endif() + +# examples/ is added by the top-level CMakeLists.txt right after this directory +# and runs the same kind of binaries, so both environments are handed over to it. +set(CODAC_CPP_TEST_ENV "${CODAC_CPP_TEST_ENV}" PARENT_SCOPE) +set(PYTHON_TEST_ENV_ARGS "${PYTHON_TEST_ENV_ARGS}" PARENT_SCOPE) + +# Build every test +# ================================================================== foreach(SRC_TEST ${SRC_TESTS}) string(REPLACE "/" "_" TEST_NAME ${SRC_TEST}) string(REPLACE "codac2_tests_" "" TEST_NAME ${TEST_NAME}) set(TEST_NAME codac2_tests_${TEST_NAME}) + + # --------------------------------------------------------------- # C++ test - add_executable(${TEST_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/${SRC_TEST}.cpp) - set(CODAC_HEADERS_DIR ${CMAKE_CURRENT_BINARY_DIR}/../include) - target_include_directories(${TEST_NAME} SYSTEM PUBLIC ${CODAC_HEADERS_DIR}) - target_link_libraries(${TEST_NAME} PUBLIC Ibex::ibex ${CODAC_LIBRARIES} PRIVATE Catch2::Catch2WithMain) + # --------------------------------------------------------------- + + add_executable( + ${TEST_NAME} + ${CMAKE_CURRENT_SOURCE_DIR}/${SRC_TEST}.cpp + ) + + + # Use the same canonical source headers as the libraries, and reach them by + # the same path: the source directories come first so that a header is + # opened at src/... Both compile identically, but the path used is the path gcov + # records, and different paths would split every header into two unrelated + # entries in the coverage report (see codac_publish_include_dirs() in the + # top-level CMakeLists.txt). + # + # CODAC_HEADERS_DIR is kept, after them, because it holds the generated + # umbrella headers: codac, codac-core.h, codac-graphics.h and the rest. + + target_include_directories( + ${TEST_NAME} + SYSTEM PUBLIC + + ${CODAC_CORE_SOURCE_INCLUDE_DIRS} + + ${CODAC_HEADERS_DIR} + ) + + + target_link_libraries( + ${TEST_NAME} + PUBLIC + + ${CODAC_LIBRARIES} + + PRIVATE + + Catch2::Catch2WithMain + ) + + + # Sympy embedded tests + if( + WITH_PYTHON + AND SRC_TEST MATCHES "extensions/sympy/" + ) + + target_link_libraries( + ${TEST_NAME} + PRIVATE + + ${PROJECT_NAME}-sympy + pybind11::embed + ) - if(WITH_PYTHON AND SRC_TEST MATCHES "extensions/sympy/") - target_link_libraries(${TEST_NAME} PRIVATE ${PROJECT_NAME}-sympy pybind11::embed) endif() - add_dependencies(check ${TEST_NAME}) - add_test(NAME ${TEST_NAME}_cpp COMMAND ${TEST_NAME}) + + # C++20 + set_target_properties( + ${TEST_NAME} + PROPERTIES + + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON + CXX_EXTENSIONS OFF + ) + + + add_dependencies( + check + ${TEST_NAME} + ) + + add_test( + NAME ${TEST_NAME}_cpp + COMMAND ${TEST_NAME} + ) + + if(CODAC_CPP_TEST_ENV) + set_tests_properties(${TEST_NAME}_cpp PROPERTIES + ENVIRONMENT "${CODAC_CPP_TEST_ENV}" + ) + endif() + + # --------------------------------------------------------------- # Python test - if(WITH_PYTHON) - add_test(NAME ${TEST_NAME}_py COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/${SRC_TEST}.py) + # --------------------------------------------------------------- + + if(WITH_PYTHON AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${SRC_TEST}.py") + add_test( + NAME ${TEST_NAME}_py + COMMAND + ${CMAKE_COMMAND} -E env + ${PYTHON_TEST_ENV_ARGS} + ${PYTHON_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/${SRC_TEST}.py + ) endif() endforeach() diff --git a/tests/core/contractors/codac2_tests_CtcInverse.cpp b/tests/core/contractors/codac2_tests_CtcInverse.cpp index 952b8a57c..627a4f934 100644 --- a/tests/core/contractors/codac2_tests_CtcInverse.cpp +++ b/tests/core/contractors/codac2_tests_CtcInverse.cpp @@ -19,6 +19,63 @@ using namespace std; using namespace codac2; + +// ============================================================================ +// 1. Paving: tree lifecycle and parent/child reference-counting regression +// ============================================================================ + +TEST_CASE("Paving tree structure after bisection", "[Paving]") +{ + PavingOut p(IntervalVector({{-1.,1.},{-1.,1.}})); + auto root = p.tree(); + + REQUIRE(root->is_leaf()); + CHECK(root->top() == nullptr); + + root->bisect(); + REQUIRE_FALSE(root->is_leaf()); + REQUIRE(root->left() != nullptr); + REQUIRE(root->right() != nullptr); + CHECK(root->left()->top() == root); + CHECK(root->right()->top() == root); + CHECK(root->left()->is_leaf()); + CHECK(root->right()->is_leaf()); + + root->left()->bisect(); + REQUIRE(root->left()->left() != nullptr); + CHECK(root->left()->left()->top() == root->left()); +} + +TEST_CASE("Paving nodes do not leak through a parent/child reference cycle", "[Paving][regression]") +{ + std::weak_ptr w_root, w_left, w_left_left; + + { + PavingOut p(IntervalVector({{-1.,1.},{-1.,1.}})); + auto root = p.tree(); + root->bisect(); + root->left()->bisect(); + + w_root = root; + w_left = root->left(); + w_left_left = root->left()->left(); + + // Sanity check: while 'p' (and so the whole tree) is alive, every + // node must still resolve through its weak_ptr. + REQUIRE_FALSE(w_root.expired()); + REQUIRE_FALSE(w_left.expired()); + REQUIRE_FALSE(w_left_left.expired()); + } + // 'p' is destroyed here. Before the fix, every node below the root + // would still be kept alive by its child's (owning) _top pointing back + // up through the cycle, so none of the weak_ptr's below would expire. + + CHECK(w_root.expired()); + CHECK(w_left.expired()); + CHECK(w_left_left.expired()); +} + + TEST_CASE("CtcInverse") { { diff --git a/tests/core/domains/interval/codac2_tests_Interval.cpp b/tests/core/domains/interval/codac2_tests_Interval.cpp index e98d6ca55..76c673f4f 100644 --- a/tests/core/domains/interval/codac2_tests_Interval.cpp +++ b/tests/core/domains/interval/codac2_tests_Interval.cpp @@ -16,6 +16,7 @@ #include #include #include +#include using namespace std; using namespace codac2; @@ -53,6 +54,19 @@ TEST_CASE("Interval - tests from IBEX") Interval x, y, z; + // An interval is a point or a pair of bounds; any other list length is a + // caller error and is reported through the assertion layer. A FAST_RELEASE + // build compiles that layer out, which is the one configuration where + // nothing is raised -- the guard mirrors the one in codac2_assert.h. +#if !(defined(FAST_RELEASE) && defined(NDEBUG)) + { + Interval from_list(0.,1.); + CHECK_THROWS_AS(from_list.init_from_list({1.,2.,3.}), std::invalid_argument); + CHECK_THROWS_AS(from_list.init_from_list({}), std::invalid_argument); + CHECK_THROWS_AS(Interval({1.,2.,3.}), std::invalid_argument); + } +#endif + x = Interval(0,1); x.set_empty(); CHECK(x == Interval::empty()); CHECK(x.is_empty()); diff --git a/tests/core/domains/interval/codac2_tests_Interval.py b/tests/core/domains/interval/codac2_tests_Interval.py index 9a505e469..01222b996 100644 --- a/tests/core/domains/interval/codac2_tests_Interval.py +++ b/tests/core/domains/interval/codac2_tests_Interval.py @@ -45,6 +45,21 @@ def test_interval(self): self.assertTrue(Interval([-oo,oo]) == Interval(-oo,oo)); self.assertTrue(Interval([oo,-oo]) == Interval.empty()); + # An interval is a point or a pair of bounds; any other list length is a + # caller error, reported by the C++ assertion layer as a + # std::invalid_argument, which pybind11 surfaces as a ValueError. A + # FAST_RELEASE build compiles that layer out and leaves the interval + # untouched instead; Python cannot see that switch, so both outcomes are + # accepted here and only the one that actually happened is checked. + for bad_list in ([1.,2.,3.], []): + x = Interval(0.,1.) + try: + x.init_from_list(bad_list) + except ValueError: + pass + else: + self.assertTrue(x == Interval(0.,1.)) + x = Interval() ; y = Interval() ; z = Interval() x = Interval(0,1); x.set_empty() diff --git a/tests/core/domains/interval/codac2_tests_IntervalMatrix.cpp b/tests/core/domains/interval/codac2_tests_IntervalMatrix.cpp index f54c0825f..7ff99ddff 100644 --- a/tests/core/domains/interval/codac2_tests_IntervalMatrix.cpp +++ b/tests/core/domains/interval/codac2_tests_IntervalMatrix.cpp @@ -304,7 +304,7 @@ TEST_CASE("IntervalMatrix") IntervalVector r2({{-1,0},{-2,0}}); m1.row(0) = r1.transpose().eval(); m1.row(1) = r2.transpose().eval(); - m1.resize_save_values(2,3); + m1.conservativeResize(2,3); m1(0,2) = Interval(0,3); m1(1,2) = Interval(-3,0); @@ -315,7 +315,7 @@ TEST_CASE("IntervalMatrix") IntervalMatrix m1(1,3); IntervalVector r1({{0,1},{0,2},{0,3}}); m1.row(0) = r1.transpose().eval(); - m1.resize_save_values(2,3); + m1.conservativeResize(2,3); m1(1,0) = Interval(-1,0); m1(1,1) = Interval(-2,0); m1(1,2) = Interval(-3,0); @@ -325,7 +325,7 @@ TEST_CASE("IntervalMatrix") { IntervalMatrix e(IntervalMatrix::empty(1,1)); - e.resize_save_values(2,3); + e.conservativeResize(2,3); CHECK(e.is_empty()); } @@ -505,8 +505,8 @@ TEST_CASE("IntervalMatrix - mixing type") { { Matrix m1 { - { {1},{2} }, - { {3},{4} } + { 1,2 }, + { 3,4 } }; // Interval - Matrix @@ -611,13 +611,13 @@ TEST_CASE("IntervalMatrix - mixing type") { Matrix m1 { - { {1},{2} }, - { {3},{4} } + { 1,2 }, + { 3,4 } }; Matrix m2 { - { {2},{3} }, - { {4},{5} } + { 2,3 }, + { 4,5 } }; IntervalMatrix im(m1,m2); diff --git a/tests/core/domains/interval/codac2_tests_IntervalMatrix.py b/tests/core/domains/interval/codac2_tests_IntervalMatrix.py index f68cc037b..6af49f47b 100644 --- a/tests/core/domains/interval/codac2_tests_IntervalMatrix.py +++ b/tests/core/domains/interval/codac2_tests_IntervalMatrix.py @@ -262,7 +262,7 @@ def test_intervalmatrix(self): r2 = IntervalVector([[-1,0],[-2,0]]) m1.set_row(0,r1.transpose()) m1.set_row(1,r2.transpose()) - m1.resize_save_values(2,3) + m1.conservativeResize(2,3) m1[0,2] = Interval(0,3) m1[1,2] = Interval(-3,0) @@ -271,7 +271,7 @@ def test_intervalmatrix(self): m1 = IntervalMatrix(1,3) r1 = IntervalVector([[0,1],[0,2],[0,3]]) m1.set_row(0,r1.transpose()) - m1.resize_save_values(2,3) + m1.conservativeResize(2,3) m1[1,0] = Interval(-1,0) m1[1,1] = Interval(-2,0) m1[1,2] = Interval(-3,0) @@ -279,7 +279,7 @@ def test_intervalmatrix(self): self.assertTrue(m1 == self.M1()) e = IntervalMatrix.empty(1,1) - e.resize_save_values(2,3) + e.conservativeResize(2,3) self.assertTrue(e.is_empty()) m1 = IntervalMatrix(self.M1()) diff --git a/tests/core/domains/interval/codac2_tests_IntervalVector.cpp b/tests/core/domains/interval/codac2_tests_IntervalVector.cpp index 04e4be366..7fa1f9341 100644 --- a/tests/core/domains/interval/codac2_tests_IntervalVector.cpp +++ b/tests/core/domains/interval/codac2_tests_IntervalVector.cpp @@ -112,7 +112,7 @@ TEST_CASE("IntervalVector") { IntervalVector x(1); x[0] = Interval(1,2); - x.resize_save_values(3); + x.conservativeResize(3); CHECK(x.size() == 3); CHECK(x[0] == Interval(1,2)); CHECK(x[1] == Interval(-oo,oo)); @@ -122,7 +122,7 @@ TEST_CASE("IntervalVector") { IntervalVector x(1); x[0] = Interval(1,2); - x.resize_save_values(1); + x.conservativeResize(1); CHECK(x.size() == 1); CHECK(x[0] == Interval(1,2)); } @@ -131,7 +131,7 @@ TEST_CASE("IntervalVector") IntervalVector x(2); x[0] = Interval(1,2); x.set_empty(); - x.resize_save_values(3); + x.conservativeResize(3); CHECK(x.size() == 3); CHECK(x.is_empty()); CHECK(x[2] == Interval(-oo,oo)); @@ -141,7 +141,7 @@ TEST_CASE("IntervalVector") IntervalVector x(5); x[0] = Interval(1,2); x[1] = Interval(3,4); - x.resize_save_values(2); + x.conservativeResize(2); CHECK(x.size() == 2); CHECK(x[0] == Interval(1,2)); CHECK(x[1] == Interval(3,4)); @@ -638,9 +638,9 @@ TEST_CASE("IntervalVector") { IntervalVector a{Interval::empty(),{-1,1}}; - IntervalVector b{{-1,1},Interval::empty()}; - IntervalVector c = a | b; - CHECK(c.is_empty()); + IntervalVector b2{{-1,1},Interval::empty()}; + IntervalVector c2 = a | b2; + CHECK(c2.is_empty()); } } diff --git a/tests/core/domains/interval/codac2_tests_IntervalVector.py b/tests/core/domains/interval/codac2_tests_IntervalVector.py index 168b4254a..05d22f692 100644 --- a/tests/core/domains/interval/codac2_tests_IntervalVector.py +++ b/tests/core/domains/interval/codac2_tests_IntervalVector.py @@ -92,7 +92,7 @@ def test_intervalvector(self): x = IntervalVector(1) x[0] = Interval(1,2) - x.resize_save_values(3) + x.conservativeResize(3) self.assertTrue(x.size() == 3) self.assertTrue(x[0] == Interval(1,2)) self.assertTrue(x[1] == Interval(-oo,oo)) @@ -100,14 +100,14 @@ def test_intervalvector(self): x = IntervalVector(1) x[0] = Interval(1,2) - x.resize_save_values(1) + x.conservativeResize(1) self.assertTrue(x.size() == 1) self.assertTrue(x[0] == Interval(1,2)) x = IntervalVector(2) x[0] = Interval(1,2) x.set_empty() - x.resize_save_values(3) + x.conservativeResize(3) self.assertTrue(x.size() == 3) self.assertTrue(x.is_empty()) self.assertTrue(x[2] == Interval(-oo,oo)) @@ -115,7 +115,7 @@ def test_intervalvector(self): x = IntervalVector(5) x[0] = Interval(1,2) x[1] = Interval(3,4) - x.resize_save_values(2) + x.conservativeResize(2) self.assertTrue(x.size() == 2) self.assertTrue(x[0] == Interval(1,2)) self.assertTrue(x[1] == Interval(3,4)) diff --git a/tests/core/domains/tube/codac2_tests_SlicedTube.py b/tests/core/domains/tube/codac2_tests_SlicedTube.py index 8b0e39a51..f7854adbf 100644 --- a/tests/core/domains/tube/codac2_tests_SlicedTube.py +++ b/tests/core/domains/tube/codac2_tests_SlicedTube.py @@ -557,6 +557,24 @@ def test_inversion_vector_tube(self): inv = x.invert(inv_val, restricted) self.assertTrue(inv == Interval(15.2,38)) + @unittest.skipIf(FOR_MATLAB, "the @ operator is not available in Matlab") + def test_matrix_tube_matmul_operator(self): + + tdomain = create_tdomain(Interval(0,1), 0.5, False) + M,N = IntervalMatrix([[1,2],[3,4]]),IntervalMatrix([[0,1],[1,0]]) + A = SlicedTube(tdomain, M) + B = SlicedTube(tdomain, N) + x = SlicedTube(tdomain, IntervalVector([5,6])) + + self.assertTrue((A@B).codomain() == IntervalMatrix([[2,1],[4,3]])) + self.assertTrue((A@N).codomain() == IntervalMatrix([[2,1],[4,3]])) + self.assertTrue((M@B).codomain() == IntervalMatrix([[2,1],[4,3]])) + self.assertTrue((Matrix([[1,2],[3,4]])@B).codomain() == IntervalMatrix([[2,1],[4,3]])) + self.assertTrue((A@x).codomain() == IntervalVector([17,39])) + self.assertTrue((A@x).codomain() == (A*x).codomain()) + with self.assertRaises(TypeError): + x@x + if __name__ == '__main__': unittest.main() \ No newline at end of file diff --git a/tests/core/domains/tube/codac2_tests_SlicedTube_integral.cpp b/tests/core/domains/tube/codac2_tests_SlicedTube_integral.cpp index 4cba879ab..c5bd5294b 100644 --- a/tests/core/domains/tube/codac2_tests_SlicedTube_integral.cpp +++ b/tests/core/domains/tube/codac2_tests_SlicedTube_integral.cpp @@ -151,8 +151,8 @@ TEST_CASE("Computing integration from 0, interval argument") CHECK(Approx(x.integral(Interval(12.5))) == Interval(6.5,20.5)); CHECK(Approx(x.integral(Interval(14.5))) == Interval(7,23.5)); auto p_intv = x.partial_integral(Interval(12.5,14.5)); - CHECK(p_intv.first == Interval(6.,7.)); - CHECK(p_intv.second == Interval(20.5,23.5)); + CHECK(Approx(p_intv.first) == Interval(6.,7.)); + CHECK(Approx(p_intv.second) == Interval(20.5,23.5)); CHECK(Approx(x.integral(Interval(12.5,14.5))) == Interval(6.0,23.5)); CHECK(Approx(x.integral(Interval(0))) == Interval(0)); CHECK(Approx(x.integral(Interval(10.2))) == Interval(9.3,19.7)); diff --git a/tests/core/domains/tube/codac2_tests_predefined_tubes.cpp b/tests/core/domains/tube/codac2_tests_predefined_tubes.cpp index f6580fd56..0435b2290 100644 --- a/tests/core/domains/tube/codac2_tests_predefined_tubes.cpp +++ b/tests/core/domains/tube/codac2_tests_predefined_tubes.cpp @@ -174,7 +174,7 @@ SlicedTube tube_test4() x.set({1,2}, {0,9}); x.set({0.5,1.5}, {9,11}); - x.set({-1,1}, {10.2}); // degenerate time interval + x.set({-1,1}, Interval(10.2)); // degenerate time interval x.set({-1.5,-0.5}, {11,12}); x.set({-1,1}, {12,13}); x.set({0.5,1.5}, {13,14}); diff --git a/tests/core/functions/analytic/codac2_tests_AnalyticFunction.py b/tests/core/functions/analytic/codac2_tests_AnalyticFunction.py index 7704aa720..afa5243e4 100644 --- a/tests/core/functions/analytic/codac2_tests_AnalyticFunction.py +++ b/tests/core/functions/analytic/codac2_tests_AnalyticFunction.py @@ -437,6 +437,53 @@ def test_eval(i,f,*args): self.assertTrue(f.eval(Interval(0.0,4.0))==Interval(-4.0,4.0)) self.assertTrue(f.eval(Interval(0.0))==Interval(0.0)) + @unittest.skipIf(FOR_MATLAB, "the @ operator is not available in Matlab") + def test_AnalyticFunction_matmul_operator(self): + + A,B = MatrixVar(2,2),MatrixVar(2,2) + x = VectorVar(2) + M,N = Matrix([[1,2],[3,4]]),Matrix([[0,1],[1,0]]) + v = Vector([5,6]) + + # eval() takes arguments of a single type: the matrix argument is set by composition + eval_Mv = lambda e: AnalyticFunction([x], AnalyticFunction([A,x], e)(M,x)).eval(v) + + # MatrixVar @ ... + self.assertTrue(AnalyticFunction([A,B], A@B).eval(M,N) == IntervalMatrix([[2,1],[4,3]])) + self.assertTrue(AnalyticFunction([A,B], A@(2*B)).eval(M,N) == IntervalMatrix([[4,2],[8,6]])) + self.assertTrue(AnalyticFunction([A], A@IntervalMatrix(N)).eval(M) == IntervalMatrix([[2,1],[4,3]])) + self.assertTrue(eval_Mv(A@x) == IntervalVector([17,39])) + self.assertTrue(eval_Mv(A@(2*x)) == IntervalVector([34,78])) + self.assertTrue(AnalyticFunction([A], A@IntervalVector(v)).eval(M) == IntervalVector([17,39])) + self.assertTrue(AnalyticFunction([B], IntervalMatrix(M)@B).eval(N) == IntervalMatrix([[2,1],[4,3]])) + self.assertTrue(AnalyticFunction([B], M@B).eval(N) == IntervalMatrix([[2,1],[4,3]])) + + # MatrixExpr @ ... + self.assertTrue(AnalyticFunction([A,B], (2*A)@(2*B)).eval(M,N) == IntervalMatrix([[8,4],[16,12]])) + self.assertTrue(AnalyticFunction([A,B], (2*A)@B).eval(M,N) == IntervalMatrix([[4,2],[8,6]])) + self.assertTrue(AnalyticFunction([A], (2*A)@IntervalMatrix(N)).eval(M) == IntervalMatrix([[4,2],[8,6]])) + self.assertTrue(eval_Mv((2*A)@x) == IntervalVector([34,78])) + self.assertTrue(eval_Mv((2*A)@(2*x)) == IntervalVector([68,156])) + self.assertTrue(AnalyticFunction([A], (2*A)@IntervalVector(v)).eval(M) == IntervalVector([34,78])) + self.assertTrue(AnalyticFunction([B], IntervalMatrix(M)@(2*B)).eval(N) == IntervalMatrix([[4,2],[8,6]])) + + # ... @ VectorVar, ... @ VectorExpr + self.assertTrue(AnalyticFunction([x], IntervalMatrix(M)@x).eval(v) == IntervalVector([17,39])) + self.assertTrue(AnalyticFunction([x], M@x).eval(v) == IntervalVector([17,39])) + self.assertTrue(AnalyticFunction([x], IntervalMatrix(M)@(2*x)).eval(v) == IntervalVector([34,78])) + + # Same results as with the * operator + h = AnalyticFunction([A], A@A) + f = AnalyticFunction([x,A], h(A)@x) + g = AnalyticFunction([x], f(x,Matrix([[0,2],[-1,0]]))) + self.assertTrue(g.eval(IntervalVector([[-1,1],[2,3]])) == IntervalVector([[-2,2],[-6,-4]])) + + # Matrix product only: no @ with scalar expressions + with self.assertRaises(TypeError): + A@ScalarVar() + with self.assertRaises(TypeError): + x@x + if __name__ == '__main__': unittest.main() diff --git a/tests/core/matrices/codac2_tests_arithmetic_mul.py b/tests/core/matrices/codac2_tests_arithmetic_mul.py index 2b14004b3..6fd387f15 100644 --- a/tests/core/matrices/codac2_tests_arithmetic_mul.py +++ b/tests/core/matrices/codac2_tests_arithmetic_mul.py @@ -110,5 +110,56 @@ def test_ArithmeticMul(self): self.assertTrue(IntervalMatrix([[[5,6],[6,7]],[[7,8],[8,9]]])*IntervalMatrix([[[1,2],[2,3]],[[3,4],[4,5]]]).block(0,0,2,2) == IntervalMatrix([[[23,40],[34,53]],[[31,52],[46,69]]])) self.assertTrue(IntervalMatrix([[[5,6],[6,7]],[[7,8],[8,9]]]).block(0,0,2,2)*IntervalMatrix([[[1,2],[2,3]],[[3,4],[4,5]]]).block(0,0,2,2) == IntervalMatrix([[[23,40],[34,53]],[[31,52],[46,69]]])) + @unittest.skipIf(FOR_MATLAB, "the @ operator is not available in Matlab") + def test_ArithmeticMul_matmul_operator(self): + + # Vector operator*(const M& x1, const Vector& x2) + self.assertTrue(Matrix([[1,2],[3,4]])@Vector([5,6]) == Vector([17,39])) + self.assertTrue(Matrix([[1,2],[3,4]]).block(0,0,2,2)@Vector([5,6]) == Vector([17,39])) + + # Matrix operator*(const M& x1, const M_& x2) + self.assertTrue(Matrix([[1,2],[3,4]])@Matrix([[5,6],[7,8]]) == Matrix([[19,22],[43,50]])) + self.assertTrue(Matrix([[1,2],[3,4]]).block(0,0,2,2)@Matrix([[5,6],[7,8]]) == Matrix([[19,22],[43,50]])) + self.assertTrue(Matrix([[1,2],[3,4]])@Matrix([[5,6],[7,8]]).block(0,0,2,2) == Matrix([[19,22],[43,50]])) + self.assertTrue(Matrix([[1,2],[3,4]]).block(0,0,2,2)@Matrix([[5,6],[7,8]]).block(0,0,2,2) == Matrix([[19,22],[43,50]])) + + # IntervalVector operator*(const M& x1, const IntervalVector& x2) + self.assertTrue(Matrix([[1,2],[3,4]])@IntervalVector([5,6]) == IntervalVector([17,39])) + self.assertTrue(Matrix([[1,2],[3,4]]).block(0,0,2,2)@IntervalVector([5,6]) == IntervalVector([17,39])) + + # IntervalMatrix operator*(const M& x1, const IM& x2) + self.assertTrue(Matrix([[1,2],[3,4]])@IntervalMatrix([[[5,6],[6,7]],[[7,8],[8,9]]]) == IntervalMatrix([[[19,22],[22,25]],[[43,50],[50,57]]])) + self.assertTrue(Matrix([[1,2],[3,4]]).block(0,0,2,2)@IntervalMatrix([[[5,6],[6,7]],[[7,8],[8,9]]]) == IntervalMatrix([[[19,22],[22,25]],[[43,50],[50,57]]])) + self.assertTrue(Matrix([[1,2],[3,4]])@IntervalMatrix([[[5,6],[6,7]],[[7,8],[8,9]]]).block(0,0,2,2) == IntervalMatrix([[[19,22],[22,25]],[[43,50],[50,57]]])) + self.assertTrue(Matrix([[1,2],[3,4]]).block(0,0,2,2)@IntervalMatrix([[[5,6],[6,7]],[[7,8],[8,9]]]).block(0,0,2,2) == IntervalMatrix([[[19,22],[22,25]],[[43,50],[50,57]]])) + + # IntervalVector operator*(const IM& x1, const Vector& x2) + self.assertTrue(IntervalMatrix([[[1,2],[2,3]],[[3,4],[4,5]]])@Vector([5,6]) == IntervalVector([[17,28],[39,50]])) + self.assertTrue(IntervalMatrix([[[1,2],[2,3]],[[3,4],[4,5]]]).block(0,0,2,2)@Vector([5,6]) == IntervalVector([[17,28],[39,50]])) + + # IntervalMatrix operator*(const IM& x1, const M& x2) + self.assertTrue(IntervalMatrix([[[5,6],[6,7]],[[7,8],[8,9]]])@Matrix([[1,2],[3,4]]) == IntervalMatrix([[[23,27],[34,40]],[[31,35],[46,52]]])) + self.assertTrue(IntervalMatrix([[[5,6],[6,7]],[[7,8],[8,9]]]).block(0,0,2,2)@Matrix([[1,2],[3,4]]) == IntervalMatrix([[[23,27],[34,40]],[[31,35],[46,52]]])) + self.assertTrue(IntervalMatrix([[[5,6],[6,7]],[[7,8],[8,9]]])@Matrix([[1,2],[3,4]]).block(0,0,2,2) == IntervalMatrix([[[23,27],[34,40]],[[31,35],[46,52]]])) + self.assertTrue(IntervalMatrix([[[5,6],[6,7]],[[7,8],[8,9]]]).block(0,0,2,2)@Matrix([[1,2],[3,4]]).block(0,0,2,2) == IntervalMatrix([[[23,27],[34,40]],[[31,35],[46,52]]])) + + # IntervalVector operator*(const IM& x1, const IntervalVector& x2) + self.assertTrue(IntervalMatrix([[[1,2],[2,3]],[[3,4],[4,5]]])@IntervalVector([[5,6],[7,8]]) == IntervalVector([[19,36],[43,64]])) + self.assertTrue(IntervalMatrix([[[1,2],[2,3]],[[3,4],[4,5]]]).block(0,0,2,2)@IntervalVector([[5,6],[7,8]]) == IntervalVector([[19,36],[43,64]])) + + # IntervalMatrix operator*(const IM& x1, const IM_& x2) + self.assertTrue(IntervalMatrix([[[5,6],[6,7]],[[7,8],[8,9]]])@IntervalMatrix([[[1,2],[2,3]],[[3,4],[4,5]]]) == IntervalMatrix([[[23,40],[34,53]],[[31,52],[46,69]]])) + self.assertTrue(IntervalMatrix([[[5,6],[6,7]],[[7,8],[8,9]]]).block(0,0,2,2)@IntervalMatrix([[[1,2],[2,3]],[[3,4],[4,5]]]) == IntervalMatrix([[[23,40],[34,53]],[[31,52],[46,69]]])) + self.assertTrue(IntervalMatrix([[[5,6],[6,7]],[[7,8],[8,9]]])@IntervalMatrix([[[1,2],[2,3]],[[3,4],[4,5]]]).block(0,0,2,2) == IntervalMatrix([[[23,40],[34,53]],[[31,52],[46,69]]])) + self.assertTrue(IntervalMatrix([[[5,6],[6,7]],[[7,8],[8,9]]]).block(0,0,2,2)@IntervalMatrix([[[1,2],[2,3]],[[3,4],[4,5]]]).block(0,0,2,2) == IntervalMatrix([[[23,40],[34,53]],[[31,52],[46,69]]])) + + # The @ operator is only defined for matrix products, not for scalar ones + with self.assertRaises(TypeError): + Matrix([[1,2],[3,4]])@2. + with self.assertRaises(TypeError): + IntervalMatrix([[[1,2],[2,3]],[[3,4],[4,5]]])@Interval(-1,1) + with self.assertRaises(TypeError): + 2.@Vector([1,2]) + if __name__ == '__main__': unittest.main() \ No newline at end of file diff --git a/tests/core/operators/codac2_tests_operators.cpp b/tests/core/operators/codac2_tests_operators.cpp index 029463b82..17cb73fd1 100644 --- a/tests/core/operators/codac2_tests_operators.cpp +++ b/tests/core/operators/codac2_tests_operators.cpp @@ -22,7 +22,7 @@ using namespace std; using namespace codac2; -const double MAX_DOUBLE = std::numeric_limits::max(); +//const double MAX_DOUBLE = std::numeric_limits::max(); void CHECK_bwd_trigo(const Interval& y, const Interval& x, const Interval& expected_x) { @@ -475,4 +475,4 @@ TEST_CASE("Interval bwd modulo") ModOp::bwd({0.1,0.2}, x, p); CHECK(x == Interval::empty()); CHECK(p == Interval::empty()); -} \ No newline at end of file +} diff --git a/tests/core/tools/codac2_tests_transformations.py b/tests/core/tools/codac2_tests_transformations.py index 6344e1625..2851c18dc 100644 --- a/tests/core/tools/codac2_tests_transformations.py +++ b/tests/core/tools/codac2_tests_transformations.py @@ -47,6 +47,8 @@ def test_affine_transformation(self): dst_estim = SampledTraj_Vector() for ti,src_i in src: dst_estim.set(tr*src_i, ti) + if not FOR_MATLAB: # the @ operator is not available in Matlab + self.assertTrue(tr@src_i == tr*src_i) scale = tr.linear().col(0).norm() rotation_matrix = tr.linear() / scale diff --git a/tests/core/tools/ibex/codac2_tests_ibex.cpp b/tests/core/tools/ibex/codac2_tests_ibex.cpp deleted file mode 100644 index be5c869d4..000000000 --- a/tests/core/tools/ibex/codac2_tests_ibex.cpp +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Codac tests - * ---------------------------------------------------------------------------- - * \date 2024 - * \author Simon Rohou - * \copyright Copyright 2024 Codac Team - * \license GNU Lesser General Public License (LGPL) - */ - -#include -#include -#include - -using namespace std; -using codac2::oo; - -TEST_CASE("IBEX") -{ - // [Interval] Codac -> IBEX - { - CHECK(codac2::to_ibex(codac2::Interval(1,2)) == ibex::Interval(1,2)); - CHECK(codac2::to_ibex(codac2::Interval(-oo,oo)) == ibex::Interval()); - CHECK(codac2::to_ibex(codac2::Interval::empty()) == ibex::Interval::empty_set()); - } - - // [Interval] IBEX -> Codac - { - CHECK(codac2::to_codac(ibex::Interval(1,2)) == codac2::Interval(1,2)); - CHECK(codac2::to_codac(ibex::Interval()) == codac2::Interval(-oo,oo)); - CHECK(codac2::to_codac(ibex::Interval::empty_set()) == codac2::Interval::empty()); - } - - // [Vector] Codac -> IBEX - { - CHECK(codac2::to_ibex(codac2::Vector::zero(2)) == ibex::Vector(2)); - CHECK(codac2::to_ibex(codac2::Vector({1,2,3})) == ibex::Vector({1,2,3})); - } - - // [Vector] IBEX -> Codac - { - CHECK(codac2::to_codac(ibex::Vector::zeros(2)) == codac2::Vector::zero(2)); - CHECK(codac2::to_codac(ibex::Vector({1,2,3})) == codac2::Vector({1,2,3})); - } - - // [IntervalVector] Codac -> IBEX - { - CHECK(codac2::to_ibex(codac2::IntervalVector(2)) == ibex::IntervalVector(2)); - CHECK(codac2::to_ibex(codac2::IntervalVector({{1,2},{2,3},{-oo,oo}})) - == ibex::IntervalVector({{1,2},{2,3},ibex::Interval()})); - } - - // [IntervalVector] IBEX -> Codac - { - CHECK(codac2::to_codac(ibex::IntervalVector(2)) == codac2::IntervalVector(2)); - CHECK(codac2::to_codac(ibex::IntervalVector({{1,2},{2,3},ibex::Interval()})) - == codac2::IntervalVector({{1,2},{2,3},{-oo,oo}})); - } - - // [Matrix] Codac -> IBEX - { - CHECK(codac2::to_ibex(codac2::Matrix::zero(2,3)) == ibex::Matrix(2,3)); - CHECK(codac2::to_ibex(codac2::Matrix({{1,2,3},{4,5,6}})) - == ibex::Matrix({{1,2,3},{4,5,6}})); - } - - // [Matrix] IBEX -> Codac - { - CHECK(codac2::to_codac(ibex::Matrix(2,3)) == codac2::Matrix::zero(2,3)); - CHECK(codac2::to_codac(ibex::Matrix({{1,2,3},{4,5,6}})) - == codac2::Matrix({{1,2,3},{4,5,6}})); - } - - // [IntervalMatrix] Codac -> IBEX - { - CHECK(codac2::to_ibex(codac2::IntervalMatrix(2,3)) == ibex::IntervalMatrix(2,3)); - CHECK(codac2::to_ibex(codac2::IntervalMatrix({{{-oo,1},{-oo,2},{-oo,3}},{{-oo,4},{-oo,5},{-oo,6}}})) - == ibex::IntervalMatrix({{{-oo,1},{-oo,2},{-oo,3}},{{-oo,4},{-oo,5},{-oo,6}}})); - } - - // [IntervalMatrix] IBEX -> Codac - { - CHECK(codac2::to_codac(ibex::IntervalMatrix(2,3)) == codac2::IntervalMatrix(2,3)); - CHECK(codac2::to_codac(ibex::IntervalMatrix({{{-oo,1},{-oo,2},{-oo,3}},{{-oo,4},{-oo,5},{-oo,6}}})) - == codac2::IntervalMatrix({{{-oo,1},{-oo,2},{-oo,3}},{{-oo,4},{-oo,5},{-oo,6}}})); - } -} \ No newline at end of file diff --git a/tests/core/tools/ibex/codac2_tests_ibex.py b/tests/core/tools/ibex/codac2_tests_ibex.py deleted file mode 100644 index 462f43bea..000000000 --- a/tests/core/tools/ibex/codac2_tests_ibex.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env python - -# Codac tests -# ---------------------------------------------------------------------------- -# \date 2024 -# \author Simon Rohou -# \copyright Copyright 2024 Codac Team -# \license GNU Lesser General Public License (LGPL) - -import unittest -from codac import * - -class TestIbex(unittest.TestCase): - - def test_ibex(self): - self.assertTrue(True) # IBEX is not available in Python - -if __name__ == '__main__': - unittest.main() \ No newline at end of file diff --git a/tests/core/trajectory/codac2_tests_SampledTraj.py b/tests/core/trajectory/codac2_tests_SampledTraj.py index da0072f37..8090a86e1 100644 --- a/tests/core/trajectory/codac2_tests_SampledTraj.py +++ b/tests/core/trajectory/codac2_tests_SampledTraj.py @@ -131,5 +131,26 @@ def test_SampledTraj(self): for i in np.arange(0, 10, 1e-1): self.assertTrue(Approx(p(i),1e-2) == x(i)) + # SampledTraj, matrix products + + A = SampledTraj({ 0.:Matrix([[1,2],[3,4]]), 1.:Matrix([[0,1],[1,0]]) }) + B = SampledTraj({ 0.:Matrix([[0,1],[1,0]]), 1.:Matrix([[1,2],[3,4]]) }) + x = SampledTraj({ 0.:Vector([5,6]), 1.:Vector([1,2]) }) + + self.assertTrue((A*x)(0.) == Vector([17,39])) + self.assertTrue((A*x)(1.) == Vector([2,1])) + self.assertTrue((A*B)(0.) == Matrix([[2,1],[4,3]])) + self.assertTrue((A*B)(1.) == Matrix([[3,4],[1,2]])) + self.assertTrue((Matrix([[0,1],[1,0]])*A)(0.) == Matrix([[3,4],[1,2]])) + + if not FOR_MATLAB: # the @ operator is not available in Matlab + self.assertTrue((A@x)(0.) == Vector([17,39])) + self.assertTrue((A@x)(1.) == Vector([2,1])) + self.assertTrue((A@B)(0.) == Matrix([[2,1],[4,3]])) + self.assertTrue((A@B)(1.) == Matrix([[3,4],[1,2]])) + self.assertTrue((Matrix([[0,1],[1,0]])@A)(0.) == Matrix([[3,4],[1,2]])) + with self.assertRaises(TypeError): + x@x + if __name__ == '__main__': unittest.main() \ No newline at end of file diff --git a/tests/extensions/capd/codac2_tests_peibos_capd.cpp b/tests/extensions/capd/codac2_tests_peibos_capd.cpp index ffa1eaaa8..fb4a21702 100644 --- a/tests/extensions/capd/codac2_tests_peibos_capd.cpp +++ b/tests/extensions/capd/codac2_tests_peibos_capd.cpp @@ -9,6 +9,7 @@ #include #include +#include #include using namespace std; @@ -20,7 +21,7 @@ TEST_CASE("PEIBOS_CAPD") capd::IMap vectorField_pend("var:t,w;fun:w,-sin(t) - 0.5*w;"); VectorVar X(1); AnalyticFunction psi0_pend ({X},{0.1*X[0],0.1}); - auto peibos_output_pend = PEIBOS(vectorField_pend, 1.0, 0.5, psi0_pend, {OctaSym({1,2})}, 0.2, {-M_PI/2.,0.}); + auto peibos_output_pend = PEIBOS(vectorField_pend, 1.0, 0.5, psi0_pend, {OctaSym({1,2})}, 0.2, {-PI/2.,0.}); auto m_v_par_2d_pend = reach_set(peibos_output_pend); } } \ No newline at end of file diff --git a/tests/packaging/CMakeLists.txt b/tests/packaging/CMakeLists.txt new file mode 100644 index 000000000..2583b8a2b --- /dev/null +++ b/tests/packaging/CMakeLists.txt @@ -0,0 +1,25 @@ +# ================================================================== +# Codac - the find_package() half of the packaging check +# ================================================================== +# +# Built against an installed Codac, exactly the way a user's project is and the +# way every examples/*/CMakeLists.txt is: find_package(CODAC), then the three +# variables its config file publishes. check_consumer_parity.sh builds this, +# builds the same main.cpp from pkg-config alone, and compares the two. + + cmake_minimum_required(VERSION 3.14) + project(codac_packaging_check LANGUAGES CXX) + + set(CMAKE_CXX_STANDARD 20) + set(CMAKE_CXX_STANDARD_REQUIRED ON) + + find_package(CODAC REQUIRED) + message(STATUS "Found Codac version ${CODAC_VERSION}") + + # As in the examples: the interval arithmetic flags (-frounding-math and the + # rest) come with CODAC_CXX_FLAGS below. codac.pc carries the same set, which + # is part of what the parity check compares. + add_executable(${PROJECT_NAME} main.cpp) + target_compile_options(${PROJECT_NAME} PUBLIC ${CODAC_CXX_FLAGS}) + target_include_directories(${PROJECT_NAME} SYSTEM PUBLIC ${CODAC_INCLUDE_DIRS}) + target_link_libraries(${PROJECT_NAME} PUBLIC ${CODAC_LIBRARIES}) diff --git a/tests/packaging/check_consumer_parity.sh b/tests/packaging/check_consumer_parity.sh new file mode 100755 index 000000000..acc744c9b --- /dev/null +++ b/tests/packaging/check_consumer_parity.sh @@ -0,0 +1,457 @@ +#!/usr/bin/env bash +# +# Checks that the two ways of consuming an installed Codac describe the same +# library: find_package(CODAC), which reads share/codac/cmake/codac-config.cmake +# and the export set beside it, and pkg-config, which reads +# share/pkgconfig/codac.pc. The two are written by hand in different places of +# src/CMakeLists.txt and had drifted apart in both directions -- the .pc file +# named no library at all for a while, and once it did, it was still missing the +# include root, Eigen's headers, -pthread and the interval-arithmetic flags, so +# a pkg-config consumer either could not compile or silently got unsound +# rounding. Nothing in CI compared them, because nothing built anything against +# an installed Codac at all. +# +# It builds the same main.cpp both ways, runs both binaries, and compares: +# - the set of include directories, +# - the set of other compile flags, +# - the set of libraries on the link line, +# - the set of other link flags. +# Sets, not sequences: CMake repeats an archive to satisfy a dependency cycle +# while pkg-config orders dependents first, and both are correct. +# +# Usage: +# check_consumer_parity.sh --prefix [options] +# +# --prefix DIR where Codac was installed (required) +# --build-dir DIR scratch directory for the two builds (default: ./build_packaging_check) +# --cmake-prefix-path P passed to the consumer's cmake, for dependencies installed elsewhere +# --pkg-config-path P prepended to PKG_CONFIG_PATH +# --require-pkgconfig fail instead of skipping when pkg-config cannot be used +# --generator NAME generator for the consumer's cmake; "" leaves the choice +# to cmake (which is what the Visual Studio jobs want) +# --cmake-arg ARG extra argument for the consumer's cmake (repeatable) + +set -euo pipefail + +here=$(cd "$(dirname "$0")" && pwd) + +prefix="" +build_dir="$PWD/build_packaging_check" +cmake_prefix_path="" +extra_pkg_config_path="" +require_pkgconfig=0 +generator_set=0 +generator="" +cmake_args=() + +while [ $# -gt 0 ]; do + case "$1" in + --prefix) prefix="$2" ; shift 2 ;; + --build-dir) build_dir="$2" ; shift 2 ;; + --cmake-prefix-path) cmake_prefix_path="$2" ; shift 2 ;; + --pkg-config-path) extra_pkg_config_path="$2" ; shift 2 ;; + --require-pkgconfig) require_pkgconfig=1 ; shift ;; + --generator) generator="$2" ; generator_set=1 ; shift 2 ;; + --cmake-arg) cmake_args+=("$2") ; shift 2 ;; + *) echo "unknown argument: $1" >&2 ; exit 2 ;; + esac +done + +[ -n "$prefix" ] || { echo "--prefix is required" >&2 ; exit 2 ; } + +echo "==============================================================" +echo " Codac packaging check" +echo " install prefix : $prefix" +echo " build dir : $build_dir" +echo "==============================================================" + +# -------------------------------------------------------------------------- +# What the install tree is expected to contain +# -------------------------------------------------------------------------- +missing=0 +for f in share/codac/cmake/codac-config.cmake \ + share/codac/cmake/codac-targets.cmake \ + share/pkgconfig/codac.pc \ + include/codac ; do + if [ -e "$prefix/$f" ]; then + echo " present: $f" + else + echo " MISSING: $f" + missing=1 + fi +done +[ "$missing" -eq 0 ] || { echo "The install tree is incomplete." >&2 ; exit 1 ; } + +echo +echo "--- codac.pc as installed ---" +cat "$prefix/share/pkgconfig/codac.pc" +echo + +# -------------------------------------------------------------------------- +# Half one: find_package(CODAC) +# -------------------------------------------------------------------------- +cmake_build="$build_dir/via_cmake" +rm -rf "$cmake_build" + +# A Makefile generator by default, whatever the generator Codac itself was +# built with: link.txt and compile_commands.json are where the flags actually +# handed to the compiler can be read back, and only the Makefile and Ninja +# generators write them. The Visual Studio generators write neither, which is +# one of the two reasons the comparison is skipped there (the other being that +# pkg-config speaks -I/-L/-l, which cl.exe does not); those jobs pass +# --generator "" and get the find_package half alone. +if [ "$generator_set" -eq 0 ]; then + generator="Unix Makefiles" + case "${OSTYPE:-}" in + msys*|cygwin*|win32*) command -v mingw32-make >/dev/null 2>&1 && generator="MinGW Makefiles" ;; + esac +fi + +# CMAKE_BUILD_TYPE is for the single-configuration generators; a Visual Studio +# generator does not read it, and --no-warn-unused-cli keeps CMake from warning +# about it there. +cmake_cmd=(cmake -S "$here" -B "$cmake_build" --no-warn-unused-cli + -D CMAKE_BUILD_TYPE=Release + -D CMAKE_EXPORT_COMPILE_COMMANDS=ON) +if [ -n "$generator" ]; then + cmake_cmd+=(-G "$generator") +fi +if [ -n "$cmake_prefix_path" ]; then + cmake_cmd+=(-D "CMAKE_PREFIX_PATH=$prefix;$cmake_prefix_path") +else + cmake_cmd+=(-D "CMAKE_PREFIX_PATH=$prefix") +fi +if [ ${#cmake_args[@]} -gt 0 ]; then + cmake_cmd+=("${cmake_args[@]}") +fi + +echo "--- configuring the find_package consumer ---" +"${cmake_cmd[@]}" +echo "--- building it ---" +# --config is what a multi-config generator needs and what a single-config one +# ignores, so it can be passed either way. +cmake --build "$cmake_build" --config Release + +# Single-config generators put the binary at the top of the build tree, +# multi-config ones under a directory named after the configuration. +cmake_bin="" +for candidate in "$cmake_build/codac_packaging_check" \ + "$cmake_build/codac_packaging_check.exe" \ + "$cmake_build/Release/codac_packaging_check.exe" \ + "$cmake_build/Release/codac_packaging_check" ; do + [ -f "$candidate" ] && { cmake_bin="$candidate" ; break ; } +done +[ -n "$cmake_bin" ] || { echo "Built, but the executable is not where it was looked for." >&2 ; exit 1 ; } +# Run from the build directory: the figure the program draws is written to the +# current directory, and it has no business landing wherever the check was +# started from. +echo "--- running it ---" +( cd "$cmake_build" && "$cmake_bin" ) | tee "$build_dir/via_cmake.out" +grep -q "codac-packaging-check-ok" "$build_dir/via_cmake.out" \ + || { echo "The find_package consumer did not run to completion." >&2 ; exit 1 ; } +echo "find_package: OK" +echo + +# -------------------------------------------------------------------------- +# Is pkg-config usable here? +# -------------------------------------------------------------------------- +# The directories are handed to pkg-config in the form it reads: cygpath -m +# turns the "/c/..." of a shell running under MSYS into the "C:/..." a native +# Windows binary expects, and is absent, hence a no-op, everywhere else. +to_pkg_config_path() { + if command -v cygpath >/dev/null 2>&1 ; then cygpath -m "$1" ; else printf '%s' "$1" ; fi +} + +# Captured before the loop below starts overwriting PKG_CONFIG_PATH, so that +# the second attempt joins the same three pieces as the first rather than the +# result of the first. +inherited_pkg_config_path="${PKG_CONFIG_PATH:-}" + +join_pkg_config_path() { + local sep="$1" joined + joined=$(to_pkg_config_path "$prefix/share/pkgconfig") + if [ -n "$extra_pkg_config_path" ]; then + joined="$joined$sep$(to_pkg_config_path "$extra_pkg_config_path")" + fi + if [ -n "$inherited_pkg_config_path" ]; then + joined="$joined$sep$inherited_pkg_config_path" + fi + printf '%s' "$joined" +} + +# The separator is ";" for the native Windows pkg-config -- the one +# chocolatey's pkgconfiglite installs -- and ":" everywhere else. It is settled +# by trying rather than by reading $OSTYPE, which said "msys" nowhere the first +# time this ran on a GitHub Windows runner and left every directory joined by a +# ":" that pkg-config could not take apart, a path there beginning "C:". +# Whichever separator lets pkg-config find codac is the right one, and on a +# platform where it cannot be found at all the loop simply leaves the preferred +# spelling in place for the diagnostic below. +if command -v pkg-config >/dev/null 2>&1 ; then + for sep in ";" ":" ; do + export PKG_CONFIG_PATH="$(join_pkg_config_path "$sep")" + if pkg-config --exists codac 2>/dev/null ; then + break + fi + done +else + export PKG_CONFIG_PATH="$(join_pkg_config_path ":")" +fi +echo "PKG_CONFIG_PATH=$PKG_CONFIG_PATH" + +skip_reason="" +# The comparison reads the command line CMake recorded, so it needs a generator +# that records one; the Visual Studio generators write neither +# compile_commands.json nor link.txt. +if [ ! -f "$cmake_build/compile_commands.json" ] || \ + [ ! -f "$cmake_build/CMakeFiles/codac_packaging_check.dir/link.txt" ] ; then + skip_reason="this generator records no compile or link command to compare against" +# And cl.exe does not take -I/-L/-l, so there would be nothing to transpose a +# pkg-config answer into even if it did. +elif grep -qiE '"command": *"[^"]*cl\.exe' "$cmake_build/compile_commands.json" ; then + skip_reason="the compiler is MSVC, which does not take pkg-config's -I/-L/-l" +elif ! command -v pkg-config >/dev/null 2>&1 ; then + skip_reason="pkg-config is not installed" +# "--exists codac" also resolves everything on the Requires: line, so a module +# named there that no .pc answers for (CAPD when that module is built) is +# caught right here. +elif ! pkg-config --exists codac ; then + skip_reason="pkg-config cannot resolve codac: $(pkg-config --print-errors --exists codac 2>&1 || true)" +fi + +if [ -n "$skip_reason" ]; then + echo "pkg-config half skipped: $skip_reason" + if [ "$require_pkgconfig" -eq 1 ]; then + echo "This platform is expected to support it (--require-pkgconfig)." >&2 + exit 1 + fi + echo "PARITY CHECK: find_package half only, as configured for this platform." + exit 0 +fi + +# -------------------------------------------------------------------------- +# Half two: pkg-config +# -------------------------------------------------------------------------- +compiler=$(sed -n 's/.*"command": *"\([^ "]*\).*/\1/p' "$cmake_build/compile_commands.json" | head -1) +[ -n "$compiler" ] || { echo "Could not read the compiler out of compile_commands.json." >&2 ; exit 1 ; } + +pc_bin="$build_dir/via_pkgconfig" +echo "--- building the same main.cpp from pkg-config alone ---" +echo " compiler: $compiler" +echo " cflags : $(pkg-config --cflags codac)" +echo " libs : $(pkg-config --libs codac)" +# -std and the optimisation level are the consumer's own business in both +# halves (CMAKE_CXX_STANDARD and CMAKE_BUILD_TYPE set them on the other side), +# so they are given here rather than expected from codac.pc. +# shellcheck disable=SC2046 +"$compiler" -std=c++20 -O3 -DNDEBUG $(pkg-config --cflags codac) \ + "$here/main.cpp" -o "$pc_bin" $(pkg-config --libs codac) +echo "--- running it ---" +( cd "$build_dir" && "$pc_bin" ) | tee "$build_dir/via_pkgconfig.out" +grep -q "codac-packaging-check-ok" "$build_dir/via_pkgconfig.out" \ + || { echo "The pkg-config consumer did not run to completion." >&2 ; exit 1 ; } +echo "pkg-config: OK" +echo + +# -------------------------------------------------------------------------- +# Comparison +# -------------------------------------------------------------------------- +# CMake does not always put the flags on the command line: past a certain +# length, and as a matter of course for the MinGW Makefiles generator, it +# writes them into a response file and passes "@that_file" instead. Unexpanded, +# the find_package half then looks empty and every flag of the pkg-config half +# reads as a difference -- which is exactly what the first Windows run of this +# check reported. +expand_response_files() { + local base="$1" tok file + while IFS= read -r tok ; do + case "$tok" in + @*) + file=${tok#@} + case "$file" in + /*|[A-Za-z]:*) ;; + *) file="$base/$file" ;; + esac + if [ -f "$file" ]; then + tr -d '\r' < "$file" | tr ' \t' '\n\n' | sed -e 's/^"//' -e 's/"$//' | grep . || true + else + printf '%s\n' "$tok" + fi + ;; + *) printf '%s\n' "$tok" ;; + esac + done +} + +# Collapses "a/b/../c" to "a/c" so that the two spellings of one directory -- +# codac-config.cmake reaches the include root as /include/codac-core/.. +# while codac.pc names /include -- compare equal. +# A response file on Windows may spell its directories with backslashes while +# pkg-config answers with forward ones; only the path-bearing lines are +# rewritten, so that a flag containing a backslash is left alone. +normalize_paths() { + sed -e '/^\(INC\|LIB\) /s#\\#/#g' \ + -e ':a' -e 's#/[^/][^/]*/\.\./#/#; ta' \ + -e ':b' -e 's#/[^/][^/]*/\.\.$##; tb' \ + -e 's#/\./#/#g' -e 's#/$##' +} + +# Everything neither file has any business carrying: the consumer's own build +# type, language level, target architecture and position independence, and the +# mechanics of a single command line. "-o" takes the name of the output next to +# it, and "-MT"/"-MF"/"-arch" likewise, so those have to go in pairs rather +# than one token at a time. +# +# -arch is why this lists an architecture at all: on Apple Silicon CMake passes +# "-arch arm64", and on the Intel image it passes nothing, so leaving it in +# made the arm64 job the only one to disagree. Which architecture a consumer +# builds for is its own business, as much as its optimisation level. +drop_neutral() { + awk ' + /^(-o|-MT|-MF|-isysroot|--sysroot|-arch|--target)$/ { getline ; next } + /^(-O[0-9s]?|-DNDEBUG|-g[0-9]?|-fPIC|-c|-MD|-MMD)$/ { next } + /^-std=/ { next } + /\.(o|obj|cpp|cc|d|json)$/ { next } + /^$/ { next } + { print } + ' +} + +# find_package side: the compile command CMake recorded, and the link command it +# wrote, with the pieces above removed. +cmake_compile=$(sed -n 's/.*"command": *"\(.*\)",\{0,1\}$/\1/p' "$cmake_build/compile_commands.json" \ + | head -1 | sed 's/\\"/"/g' | tr ' ' '\n' | tail -n +2) +cmake_link=$(cat "$cmake_build/CMakeFiles/codac_packaging_check.dir/link.txt" | tr ' ' '\n' | tail -n +2) + +# Turns a stream of tokens into two sorted sets: include directories, and the +# remaining compile flags. +split_cflags() { + awk ' + /^-isystem$/ { getline d; print "INC " d; next } + /^-I/ { d = substr($0,3); if (d == "") { getline d } print "INC " d; next } + /^$/ { next } + { print "FLAG " $0 } + ' +} + +# Archives and -l entries become bare library names; -L directories are dropped, +# the two halves reaching the same files by different routes (absolute paths on +# one side, -L plus -l on the other). +# +# Only flags that mean something at link time are kept beside them. CMake puts +# CMAKE_CXX_FLAGS on the link command as well, so the compile flags reappear +# there; they are compared as part of the compile half and would otherwise show +# up here as a difference against "pkg-config --libs", which rightly has none +# of them. +split_libs() { + awk ' + # -Wl, options are how the generator drives the linker on this platform -- + # --whole-archive, --out-implib, --major-image-version and the like on + # MinGW. They describe the executable being produced, not the library being + # consumed, and no .pc file has any business carrying them. This comes + # first because --out-implib names a file ending in .dll.a, which the + # archive rule below would otherwise take for a library. + /^-Wl,/ { next } + # Anything under CMakeFiles belongs to the consumer being built, not to + # Codac: on MinGW the generator bundles the objects into + # CMakeFiles/.dir/objects.a and links that. + /CMakeFiles[\/\\]/ { next } + /^-l/ { print "LIB " substr($0,3); next } + /^-L/ { next } + /\.(a|lib)$/ { + n = $0 + sub(/.*[\/\\]/, "", n) + sub(/^lib/, "", n) + sub(/\.(a|lib)$/, "", n) + print "LIB " n + next + } + /^(-pthread|-rdynamic|-shared|-static.*)$/ { print "FLAG " $0; next } + { next } + ' +} + +tmp="$build_dir/parity" +mkdir -p "$tmp" + +# A -I naming a directory that is not there contributes nothing to the +# compile, so it is set aside rather than compared -- but printed, because it +# is a defect in whatever wrote it. It was written for the ibex.pc of the +# prebuilt IBEX packages Codac used to depend on, whose prefix= was the +# directory IBEX had been built in rather than the one it was installed under. +partition_existing() { + local keep="$1" gone="$2" line dir + : > "$keep" ; : > "$gone" + while IFS= read -r line ; do + case "$line" in + "INC "*) + dir=${line#INC } + if [ -d "$dir" ]; then echo "$line" >> "$keep" ; else echo "$line" >> "$gone" ; fi + ;; + *) echo "$line" >> "$keep" ;; + esac + done +} + +printf '%s\n' "$cmake_compile" | expand_response_files "$cmake_build" | drop_neutral | split_cflags | normalize_paths | sort -u \ + | partition_existing "$tmp/cmake_cflags.txt" "$tmp/cmake_cflags_missing.txt" +pkg-config --cflags codac | tr ' ' '\n' | drop_neutral | split_cflags | normalize_paths | sort -u \ + | partition_existing "$tmp/pc_cflags.txt" "$tmp/pc_cflags_missing.txt" + +# The libraries the toolchain puts on every link line of its own accord -- +# kernel32, user32 and the rest of them on Windows. They are the platform's, +# not Codac's, the compiler driver adds them whether or not anyone asks, and no +# .pc file lists them. Read from the consumer's own cache rather than hardcoded, +# so this stays true of whatever platform the check is run on. +sed -n 's/^CMAKE_CXX_STANDARD_LIBRARIES:[^=]*=//p' "$cmake_build/CMakeCache.txt" 2>/dev/null \ + | tr ' ' '\n' | sed -n 's/^-l/LIB /p' | sort -u > "$tmp/standard_libs.txt" + +printf '%s\n' "$cmake_link" | expand_response_files "$cmake_build" | drop_neutral | split_libs | normalize_paths | sort -u \ + | comm -23 - "$tmp/standard_libs.txt" > "$tmp/cmake_libs.txt" +pkg-config --libs codac | tr ' ' '\n' | drop_neutral | split_libs | normalize_paths | sort -u \ + | comm -23 - "$tmp/standard_libs.txt" > "$tmp/pc_libs.txt" + +status=0 +report() { + local what="$1" a="$2" b="$3" + echo "--- $what ---" + if diff -q "$a" "$b" >/dev/null ; then + echo " identical" + else + status=1 + echo " find_package only:" ; comm -23 "$a" "$b" | sed 's/^/ /' ; : + echo " pkg-config only:" ; comm -13 "$a" "$b" | sed 's/^/ /' ; : + fi +} + +report "compile: include directories and flags" "$tmp/cmake_cflags.txt" "$tmp/pc_cflags.txt" +report "link: libraries and flags" "$tmp/cmake_libs.txt" "$tmp/pc_libs.txt" + +if [ -s "$tmp/cmake_cflags_missing.txt" ] || [ -s "$tmp/pc_cflags_missing.txt" ]; then + echo "--- include directories that do not exist (not compared) ---" + [ -s "$tmp/cmake_cflags_missing.txt" ] && sed 's/^/ find_package: /' "$tmp/cmake_cflags_missing.txt" + [ -s "$tmp/pc_cflags_missing.txt" ] && sed 's/^/ pkg-config: /' "$tmp/pc_cflags_missing.txt" + echo " Harmless to the compiler, but each is a wrong path in whatever file" + echo " named it: typically a .pc file whose prefix= is the directory it was" + echo " built in rather than the one it was installed under." +fi + +echo +if [ "$status" -eq 0 ]; then + echo "PARITY CHECK PASSED: find_package(CODAC) and pkg-config agree." +else + # The raw command lines, so that a difference reported on a platform that + # cannot be reproduced at hand can still be read rather than guessed at. + echo "--- the two command lines, as they were read ---" + echo " find_package compile:" + printf '%s\n' "$cmake_compile" | expand_response_files "$cmake_build" | sed 's/^/ /' + echo " find_package link:" + printf '%s\n' "$cmake_link" | expand_response_files "$cmake_build" | sed 's/^/ /' + echo " pkg-config --cflags: $(pkg-config --cflags codac)" + echo " pkg-config --libs: $(pkg-config --libs codac)" + echo + echo "PARITY CHECK FAILED: the two ways of consuming Codac disagree." >&2 + echo "Both are written in src/CMakeLists.txt; whatever one of them gained," >&2 + echo "the other needs too." >&2 +fi +exit "$status" diff --git a/tests/packaging/main.cpp b/tests/packaging/main.cpp new file mode 100644 index 000000000..fa53d74d7 --- /dev/null +++ b/tests/packaging/main.cpp @@ -0,0 +1,49 @@ +/* Consumer of an *installed* Codac, used by check_consumer_parity.sh to build + * the same program twice -- once through find_package(CODAC), once through + * pkg-config -- and check that the two describe the same library. + * + * It deliberately reaches across the module boundary: every call below is made + * from codac-graphics into codac-core. That is the edge a consumer's link line + * has to get right, and the one that used to be wrong -- CODAC_LIBRARIES named + * codac-core before codac-graphics, and GNU ld, which resolves a static archive + * against what is undefined when it reads it and never looks back, left every + * such reference unresolved. Keeping the calls here means a regression shows up + * as a failed link rather than as nothing at all. + */ + +#include +#include + +using namespace std; +using namespace codac2; + +int main() +{ + // Named without a space, and the check runs this from its build directory: + // a Figure2D writes its output file into the current one, under the figure's + // own name, and neither belongs in whatever directory the check was started + // from. + Figure2D fig("codac_packaging_check", GraphicOutput::IPE); + fig.set_axes(axis(0, {-3, 3}), axis(1, {-3, 3})); + + fig.draw_box(IntervalVector({{-1, 1}, {-2, 2}}), Color::blue()); + + Polygon p({{-1, -1}, {1, -1}, {0, 1}}); // Polygon::vertices() + fig.draw_polygon(p, Color::red()); + + Zonotope z(Vector({0, 0}), Matrix({{1, 0.5}, {0, 1}})); // Zonotope::is_empty() + fig.draw_zonotope(z, Color::green()); + + Ellipsoid e(Vector({0, 0}), Matrix({{0.8, 0.0}, {0.0, 0.4}})); // Ellipsoid::proj_2d() + fig.draw_ellipsoid(e, Color::purple()); + + auto tdom = create_tdomain(Interval(0, 5), 0.5); // TDomain::t0_tf(), Segment + SlicedTube tube(tdom, Interval(-1, 1)); + fig.plot_tube(tube, Color::dark_gray()); + + cout << "polygon vertices: " << p.vertices().size() << endl; + cout << "ellipsoid size: " << e.size() << endl; + cout << "tube tdomain: " << tube.tdomain()->t0_tf() << endl; + cout << "codac-packaging-check-ok" << endl; + return 0; +} diff --git a/tests/test_codac/CMakeLists.txt b/tests/test_codac/CMakeLists.txt index 0f4479bc9..fd82398c7 100644 --- a/tests/test_codac/CMakeLists.txt +++ b/tests/test_codac/CMakeLists.txt @@ -2,23 +2,12 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(my_project LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) -# Adding IBEX - - # In case you installed IBEX in a local directory, you need - # to specify its path with the CMAKE_PREFIX_PATH option. - # set(CMAKE_PREFIX_PATH "~/ibex-lib/build_install") - set(CMAKE_PREFIX_PATH "../ibex") - - find_package(IBEX REQUIRED) - ibex_init_common() # IBEX should have installed this function - message(STATUS "Found IBEX version ${IBEX_VERSION}") - # Adding Codac # In case you installed Codac in a local directory, you need @@ -39,4 +28,4 @@ add_executable(${PROJECT_NAME} main.cpp) target_compile_options(${PROJECT_NAME} PUBLIC ${CODAC_CXX_FLAGS}) target_include_directories(${PROJECT_NAME} SYSTEM PUBLIC ${CODAC_INCLUDE_DIRS}) - target_link_libraries(${PROJECT_NAME} PUBLIC ${CODAC_LIBRARIES} Ibex::ibex) + target_link_libraries(${PROJECT_NAME} PUBLIC ${CODAC_LIBRARIES})