diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0ecb1e2..028ec1b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,33 +1,18 @@ -############################################################################## -# -# QuantLibAAD CI Workflow -# -# Tests QuantLibAAD with XAD automatic differentiation, and optionally -# with Forge JIT acceleration on Linux and Windows. -# -# Jobs: -# - xad-linux: XAD tests (C++17, C++20, AAD ON/OFF) -# - xad-win: XAD tests (C++17, C++20, AAD ON/OFF) -# - xad-macos: XAD tests (C++17, C++20, AAD ON/OFF) -# - xad-linux-std-classes: XAD with QL_USE_STD_CLASSES -# - forge-linux: XAD + Forge JIT tests (C++17, Release/Debug) -# - forge-windows: XAD + Forge JIT tests (C++17, Release/Debug) -# -# Copyright (C) 2010-2026 Xcelerit Computing Limited -# -# SPDX-License-Identifier: AGPL-3.0-or-later -# -############################################################################## - +# Workflow to run against the corresponding QuantLib release, +# testing if the build and tests are working fine +# Note: In addition to pushes/pull requests, this workflow +# can also be executed manually, and the repositories / branches +# for QuantLib and XAD can be provided in this case. name: CI - -on: +on: + repository_dispatch: + types: [xad-ci-trigger] push: pull_request: workflow_dispatch: inputs: ql_repo: - description: QuantLib repository in / format + description: Quantlib repository in / format required: true default: lballabio/QuantLib ql_branch: @@ -42,41 +27,15 @@ on: description: Branch or tag for XAD repository required: true default: main - forge_repo: - description: Forge repository in / format - required: true - default: da-roth/forge - forge_branch: - description: Branch or tag for Forge repository - required: true - default: main - xad_forge_repo: - description: xad-forge repository in / format - required: true - default: da-roth/xad-forge - xad_forge_branch: - description: Branch or tag for xad-forge repository - required: true - default: main - + schedule: + - cron: '02 5 * * *' # 5:02 every day env: ql_repo: ${{ github.event.inputs.ql_repo || 'lballabio/QuantLib' }} ql_branch: ${{ github.event.inputs.ql_branch || 'master' }} xad_repo: ${{ github.event.inputs.xad_repo || 'auto-differentiation/xad' }} xad_branch: ${{ github.event.inputs.xad_branch || 'main' }} - forge_repo: ${{ github.event.inputs.forge_repo || 'da-roth/forge' }} - forge_branch: ${{ github.event.inputs.forge_branch || 'main' }} - xad_forge_repo: ${{ github.event.inputs.xad_forge_repo || 'da-roth/xad-forge' }} - xad_forge_branch: ${{ github.event.inputs.xad_forge_branch || 'main' }} - jobs: - ############################################################################## - # Linux - XAD - # - # Note on disable_aad naming: This uses the original QuantLibAAD - # convention where QLAAD_DISABLE_AAD=OFF means AAD is ENABLED (not disabled). - # The confusing double-negative is preserved for compatibility. - ############################################################################## + xad-linux: strategy: fail-fast: false @@ -85,97 +44,87 @@ jobs: cxx: ["17", "20"] runs-on: ubuntu-latest container: ghcr.io/lballabio/quantlib-devenv:rolling - steps: - - uses: actions/checkout@v4 - with: - repository: ${{ env.ql_repo }} - ref: ${{ env.ql_branch }} - path: QuantLib - - - uses: actions/checkout@v4 - with: - repository: ${{ env.xad_repo }} - ref: ${{ env.xad_branch }} - path: xad - - - uses: actions/checkout@v4 - with: - path: QuantLibAAD - - - name: ccache - uses: hendrikmuhs/ccache-action@v1.2.12 - with: - key: linux-xad-${{ matrix.cxx }}-${{ matrix.disable_aad }} - max-size: 650M - - - name: Setup - run: | - apt-get update && apt install -y ccache ninja-build - - - name: Configure - run: | - rm -rf ${{ github.workspace }}/install - cd QuantLib - mkdir build - cd build - cmake -G Ninja -DBOOST_ROOT=/usr \ - -DCMAKE_CXX_STANDARD=${{ matrix.cxx }} \ - -DQLAAD_DISABLE_AAD=${{ matrix.disable_aad }} \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ - -DQL_EXTERNAL_SUBDIRECTORIES="$(pwd)/../../xad;$(pwd)/../../QuantLibAAD" \ - -DQL_EXTRA_LINK_LIBRARIES=QuantLibAAD \ - -DQL_NULL_AS_FUNCTIONS=ON \ - -DCMAKE_INSTALL_PREFIX=$(pwd)/../../install \ - .. + - uses: actions/checkout@v4 + with: + repository: ${{ env.ql_repo }} + ref: ${{ env.ql_branch }} + path: QuantLib + - uses: actions/checkout@v4 + with: + repository: ${{ env.xad_repo }} + ref: ${{ env.xad_branch }} + path: xad + - uses: actions/checkout@v4 + with: + path: QuantLibAAD-Cpp + - name: ccache + uses: hendrikmuhs/ccache-action@v1.2.12 + with: + key: linux-${{ matrix.disable_aad }} + max-size: 650M + - name: Setup + run: | + apt-get update \ + && apt install -y ccache ninja-build + - name: Configure + run: | + rm -rf ${{ github.workspace }}/install + cd QuantLib + mkdir build + cd build + cmake -G Ninja -DBOOST_ROOT=/usr \ + -DCMAKE_CXX_STANDARD=${{ matrix.cxx }} \ + -DQLAAD_DISABLE_AAD=${{ matrix.disable_aad }} \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ + -DQL_EXTERNAL_SUBDIRECTORIES="$(pwd)/../../xad;$(pwd)/../../QuantLibAAD-Cpp" \ + -DQL_EXTRA_LINK_LIBRARIES=QuantLibAAD \ + -DQL_NULL_AS_FUNCTIONS=ON \ + -DCMAKE_INSTALL_PREFIX=$(pwd)/../../install \ + .. + - name: Compile + run: | + cd QuantLib/build + cmake --build . + - name: Test QuantLib + run: | + cd QuantLib/build + ./test-suite/quantlib-test-suite --log_level=message + - name: Test QuantLibAAD + if: ${{ matrix.disable_aad == 'OFF' }} + run: | + cd QuantLib/build + ./QuantLibAAD-Cpp/test-suite/QuantLibAAD-test-suite --log_level=message + - name: Install + if: ${{ matrix.disable_aad == 'OFF' }} + run: | + cd QuantLib/build + cmake --install . + - name: Test Install + if: ${{ matrix.disable_aad == 'OFF' }} + run: | + mkdir installtest + cp QuantLibAAD-Cpp/Examples/AdjointSwap/AdjointSwapXAD.cpp installtest + cd installtest + echo "cmake_minimum_required(VERSION 3.15.2)" > CMakeLists.txt + echo "project(QlTest LANGUAGES CXX)" >> CMakeLists.txt + echo "find_package(QuantLibAAD REQUIRED)" >> CMakeLists.txt + echo "add_executable(AdjointSwapXAD AdjointSwapXAD.cpp)" >> CMakeLists.txt + echo "target_link_libraries(AdjointSwapXAD PRIVATE QuantLib::QuantLib)" >> CMakeLists.txt + echo "target_compile_features(AdjointSwapXAD PUBLIC cxx_std_17)" >> CMakeLists.txt + mkdir build + cd build + cmake -G Ninja -DBOOST_ROOT=/usr \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_PREFIX_PATH=$(pwd)/../../install \ + .. + cmake --build . + ./AdjointSwapXAD - - name: Compile - run: | - cd QuantLib/build - cmake --build . - - name: Test QuantLib - run: | - cd QuantLib/build - ./test-suite/quantlib-test-suite --log_level=message - - name: Test QuantLibAAD - if: ${{ matrix.disable_aad == 'OFF' }} - run: | - cd QuantLib/build - ./QuantLibAAD/test-suite/quantlib-aad-test-suite --log_level=message - - name: Install - if: ${{ matrix.disable_aad == 'OFF' }} - run: | - cd QuantLib/build - cmake --install . - - - name: Test Install - if: ${{ matrix.disable_aad == 'OFF' }} - run: | - mkdir installtest - cp QuantLibAAD/Examples/AdjointSwap/AdjointSwapXAD.cpp installtest - cd installtest - echo "cmake_minimum_required(VERSION 3.15.2)" > CMakeLists.txt - echo "project(QlTest LANGUAGES CXX)" >> CMakeLists.txt - echo "find_package(QuantLibAAD REQUIRED)" >> CMakeLists.txt - echo "add_executable(AdjointSwapXAD AdjointSwapXAD.cpp)" >> CMakeLists.txt - echo "target_link_libraries(AdjointSwapXAD PRIVATE QuantLib::QuantLib)" >> CMakeLists.txt - echo "target_compile_features(AdjointSwapXAD PUBLIC cxx_std_17)" >> CMakeLists.txt - mkdir build - cd build - cmake -G Ninja -DBOOST_ROOT=/usr \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_PREFIX_PATH=$(pwd)/../../install \ - .. - cmake --build . - ./AdjointSwapXAD - - ############################################################################## - # Windows - XAD - ############################################################################## xad-win: strategy: fail-fast: false @@ -183,115 +132,100 @@ jobs: disable_aad: ["ON", "OFF"] cxx: ["17", "20"] runs-on: windows-2022 - env: vsvarsall: C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat - steps: - - uses: actions/checkout@v4 - with: - repository: ${{ env.ql_repo }} - ref: ${{ env.ql_branch }} - path: QuantLib - - - uses: actions/checkout@v4 - with: - repository: ${{ env.xad_repo }} - ref: ${{ env.xad_branch }} - path: xad + - uses: actions/checkout@v3 + with: + repository: ${{ env.ql_repo }} + ref: ${{ env.ql_branch }} + path: QuantLib + - uses: actions/checkout@v3 + with: + repository: ${{ env.xad_repo }} + ref: ${{ env.xad_branch }} + path: xad + - uses: actions/checkout@v3 + with: + path: QuantLibAAD-Cpp + - name: sccache + uses: hendrikmuhs/ccache-action@v1.2.12 + with: + key: windows-${{ matrix.disable_aad }} + variant: sccache + max-size: 650M + - name: Setup + run: | + choco install -y ninja + $Url = "https://downloads.sourceforge.net/project/boost/boost-binaries/1.84.0/boost_1_84_0-msvc-14.3-64.exe" + (New-Object System.Net.WebClient).DownloadFile($Url, "$RUNNER_TEMP\boost.exe") + Start-Process -Wait -FilePath "$RUNNER_TEMP\boost.exe" "/SILENT","/SP-","/SUPPRESSMSGBOXES","/DIR=C:\local\boost" + - name: Configure + env: + BOOST_ROOT: C:\local\boost + shell: cmd + run: | + cd QuantLib + mkdir build + cd build + call "${{ env.vsvarsall }}" amd64 + cmake .. -G Ninja -DQLAAD_DISABLE_AAD=${{ matrix.disable_aad }} ^ + -DCMAKE_CXX_STANDARD=${{ matrix.cxx }} ^ + -DCMAKE_CXX_COMPILER_LAUNCHER=sccache ^ + -DCMAKE_BUILD_TYPE=Release ^ + -DQL_EXTERNAL_SUBDIRECTORIES="${{ github.workspace }}/xad;${{ github.workspace }}/QuantLibAAD-Cpp" ^ + -DQL_EXTRA_LINK_LIBRARIES=QuantLibAAD ^ + -DQL_NULL_AS_FUNCTIONS=ON ^ + -DXAD_STATIC_MSVC_RUNTIME=ON ^ + -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/install + - name: Build + shell: cmd + run: | + cd QuantLib\build + call "${{ env.vsvarsall }}" amd64 + cmake --build . + - name: Test QuantLib + shell: cmd + run: | + cd QuantLib\build + call "${{ env.vsvarsall }}" amd64 + .\test-suite\quantlib-test-suite --log_level=message + - name: Test QuantLibAAD + if: ${{ matrix.disable_aad == 'OFF' }} + shell: cmd + run: | + cd QuantLib\build + call "${{ env.vsvarsall }}" amd64 + .\QuantLibAAD-Cpp\test-suite\QuantLibAAD-test-suite --log_level=message + - name: Install + if: ${{ matrix.disable_aad == 'OFF' }} + run: | + cd QuantLib/build + cmake --install . + - name: Test Install + if: ${{ matrix.disable_aad == 'OFF' }} + env: + BOOST_ROOT: C:\local\boost + shell: cmd + run: | + mkdir installtest + copy QuantLibAAD-Cpp\Examples\AdjointSwap\AdjointSwapXAD.cpp installtest + cd installtest + echo cmake_minimum_required(VERSION 3.15.2) > CMakeLists.txt + echo project(QlTest LANGUAGES CXX) >> CMakeLists.txt + echo find_package(QuantLibAAD REQUIRED) >> CMakeLists.txt + echo add_executable(AdjointSwapXAD AdjointSwapXAD.cpp) >> CMakeLists.txt + echo target_link_libraries(AdjointSwapXAD PRIVATE QuantLib::QuantLib) >> CMakeLists.txt + echo set_target_properties(AdjointSwapXAD PROPERTIES MSVC_RUNTIME_LIBRARY MultiThreaded) >> CMakeLists.txt + echo target_compile_features(AdjointSwapXAD PUBLIC cxx_std_17) >> CMakeLists.txt + mkdir build + cd build + call "${{ env.vsvarsall }}" amd64 + cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=${{ github.workspace }}/install .. + cmake --build . + AdjointSwapXAD.exe - - uses: actions/checkout@v4 - with: - path: QuantLibAAD - - name: sccache - uses: hendrikmuhs/ccache-action@v1.2.12 - with: - key: windows-xad-${{ matrix.cxx }}-${{ matrix.disable_aad }} - variant: sccache - max-size: 650M - - - name: Setup - run: | - choco install -y ninja - $Url = "https://downloads.sourceforge.net/project/boost/boost-binaries/1.86.0/boost_1_86_0-msvc-14.3-64.exe" - (New-Object System.Net.WebClient).DownloadFile($Url, "$RUNNER_TEMP\boost.exe") - Start-Process -Wait -FilePath "$RUNNER_TEMP\boost.exe" "/SILENT","/SP-","/SUPPRESSMSGBOXES","/DIR=C:\local\boost" - - - name: Configure - env: - BOOST_ROOT: C:\local\boost - shell: cmd - run: | - cd QuantLib - mkdir build - cd build - call "${{ env.vsvarsall }}" amd64 - cmake .. -G Ninja -DQLAAD_DISABLE_AAD=${{ matrix.disable_aad }} ^ - -DCMAKE_CXX_STANDARD=${{ matrix.cxx }} ^ - -DCMAKE_CXX_COMPILER_LAUNCHER=sccache ^ - -DCMAKE_BUILD_TYPE=Release ^ - -DQL_EXTERNAL_SUBDIRECTORIES="${{ github.workspace }}/xad;${{ github.workspace }}/QuantLibAAD" ^ - -DQL_EXTRA_LINK_LIBRARIES=QuantLibAAD ^ - -DQL_NULL_AS_FUNCTIONS=ON ^ - -DXAD_STATIC_MSVC_RUNTIME=ON ^ - -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/install - - - name: Build - shell: cmd - run: | - cd QuantLib\build - call "${{ env.vsvarsall }}" amd64 - cmake --build . - - - name: Test QuantLib - shell: cmd - run: | - cd QuantLib\build - call "${{ env.vsvarsall }}" amd64 - .\test-suite\quantlib-test-suite --log_level=message - - - name: Test QuantLibAAD - if: ${{ matrix.disable_aad == 'OFF' }} - shell: cmd - run: | - cd QuantLib\build - call "${{ env.vsvarsall }}" amd64 - .\QuantLibAAD\test-suite\quantlib-aad-test-suite --log_level=message - - - name: Install - if: ${{ matrix.disable_aad == 'OFF' }} - run: | - cd QuantLib/build - cmake --install . - - - name: Test Install - if: ${{ matrix.disable_aad == 'OFF' }} - env: - BOOST_ROOT: C:\local\boost - shell: cmd - run: | - mkdir installtest - copy QuantLibAAD\Examples\AdjointSwap\AdjointSwapXAD.cpp installtest - cd installtest - echo cmake_minimum_required(VERSION 3.15.2) > CMakeLists.txt - echo project(QlTest LANGUAGES CXX) >> CMakeLists.txt - echo find_package(QuantLibAAD REQUIRED) >> CMakeLists.txt - echo add_executable(AdjointSwapXAD AdjointSwapXAD.cpp) >> CMakeLists.txt - echo target_link_libraries(AdjointSwapXAD PRIVATE QuantLib::QuantLib) >> CMakeLists.txt - echo set_target_properties(AdjointSwapXAD PROPERTIES MSVC_RUNTIME_LIBRARY MultiThreaded) >> CMakeLists.txt - echo target_compile_features(AdjointSwapXAD PUBLIC cxx_std_17) >> CMakeLists.txt - mkdir build - cd build - call "${{ env.vsvarsall }}" amd64 - cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=${{ github.workspace }}/install .. - cmake --build . - AdjointSwapXAD.exe - - ############################################################################## - # macOS - XAD - # Note: Forge not supported on macOS ARM yet - ############################################################################## xad-macos: strategy: fail-fast: false @@ -299,372 +233,134 @@ jobs: disable_aad: ["ON", "OFF"] cxx: ["17", "20"] runs-on: macos-latest - steps: - - uses: actions/checkout@v4 - with: - repository: ${{ env.ql_repo }} - ref: ${{ env.ql_branch }} - path: QuantLib - - - uses: actions/checkout@v4 - with: - repository: ${{ env.xad_repo }} - ref: ${{ env.xad_branch }} - path: xad - - - uses: actions/checkout@v4 - with: - path: QuantLibAAD + - uses: actions/checkout@v4 + with: + repository: ${{ env.ql_repo }} + ref: ${{ env.ql_branch }} + path: QuantLib + - uses: actions/checkout@v4 + with: + repository: ${{ env.xad_repo }} + ref: ${{ env.xad_branch }} + path: xad + - uses: actions/checkout@v4 + with: + path: QuantLibAAD-Cpp + - name: Setup + run: | + brew install boost + brew install ninja + brew install ccache + - name: ccache + uses: hendrikmuhs/ccache-action@v1.2.12 + with: + key: macos-${{ matrix.disable_aad }} + max-size: 650M + - name: Configure + run: | + cd QuantLib + mkdir build + cd build + cmake -G Ninja -DBOOST_ROOT=/usr \ + -DCMAKE_CXX_STANDARD=${{ matrix.cxx }} \ + -DQLAAD_DISABLE_AAD=${{ matrix.disable_aad }} \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ + -DQL_EXTERNAL_SUBDIRECTORIES="${{ github.workspace }}/xad;${{ github.workspace }}/QuantLibAAD-Cpp" \ + -DQL_EXTRA_LINK_LIBRARIES=QuantLibAAD \ + -DQL_NULL_AS_FUNCTIONS=ON \ + -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/install \ + .. + - name: Compile + run: | + cd QuantLib/build + cmake --build . + - name: Test QuantLib + run: | + cd QuantLib/build + ./test-suite/quantlib-test-suite --log_level=message + - name: Test QuantLibAAD + if: ${{ matrix.disable_aad == 'OFF' }} + run: | + cd QuantLib/build + ./QuantLibAAD-Cpp/test-suite/QuantLibAAD-test-suite --log_level=message + - name: Install + if: ${{ matrix.disable_aad == 'OFF' }} + run: | + cd QuantLib/build + cmake --install . + - name: Test Install + if: ${{ matrix.disable_aad == 'OFF' }} + run: | + mkdir installtest + cp QuantLibAAD-Cpp/Examples/AdjointSwap/AdjointSwapXAD.cpp installtest + cd installtest + echo "cmake_minimum_required(VERSION 3.15.2)" > CMakeLists.txt + echo "project(QlTest LANGUAGES CXX)" >> CMakeLists.txt + echo "find_package(QuantLibAAD REQUIRED)" >> CMakeLists.txt + echo "add_executable(AdjointSwapXAD AdjointSwapXAD.cpp)" >> CMakeLists.txt + echo "target_link_libraries(AdjointSwapXAD PRIVATE QuantLib::QuantLib)" >> CMakeLists.txt + echo "target_compile_features(AdjointSwapXAD PUBLIC cxx_std_17)" >> CMakeLists.txt + mkdir build + cd build + cmake -G Ninja -DBOOST_ROOT=/usr \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_PREFIX_PATH=${{ github.workspace }}/install \ + .. + cmake --build . + ./AdjointSwapXAD - - name: Setup - run: | - brew install boost ninja ccache - - - name: ccache - uses: hendrikmuhs/ccache-action@v1.2.12 - with: - key: macos-xad-${{ matrix.cxx }}-${{ matrix.disable_aad }} - max-size: 650M - - - name: Configure - run: | - cd QuantLib - mkdir build - cd build - cmake -G Ninja \ - -DCMAKE_CXX_STANDARD=${{ matrix.cxx }} \ - -DQLAAD_DISABLE_AAD=${{ matrix.disable_aad }} \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ - -DQL_EXTERNAL_SUBDIRECTORIES="${{ github.workspace }}/xad;${{ github.workspace }}/QuantLibAAD" \ - -DQL_EXTRA_LINK_LIBRARIES=QuantLibAAD \ - -DQL_NULL_AS_FUNCTIONS=ON \ - -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/install \ - .. - - - name: Compile - run: | - cd QuantLib/build - cmake --build . - - - name: Test QuantLib - run: | - cd QuantLib/build - ./test-suite/quantlib-test-suite --log_level=message - - - name: Test QuantLibAAD - if: ${{ matrix.disable_aad == 'OFF' }} - run: | - cd QuantLib/build - ./QuantLibAAD/test-suite/quantlib-aad-test-suite --log_level=message - - - name: Install - if: ${{ matrix.disable_aad == 'OFF' }} - run: | - cd QuantLib/build - cmake --install . - - - name: Test Install - if: ${{ matrix.disable_aad == 'OFF' }} - run: | - mkdir installtest - cp QuantLibAAD/Examples/AdjointSwap/AdjointSwapXAD.cpp installtest - cd installtest - echo "cmake_minimum_required(VERSION 3.15.2)" > CMakeLists.txt - echo "project(QlTest LANGUAGES CXX)" >> CMakeLists.txt - echo "find_package(QuantLibAAD REQUIRED)" >> CMakeLists.txt - echo "add_executable(AdjointSwapXAD AdjointSwapXAD.cpp)" >> CMakeLists.txt - echo "target_link_libraries(AdjointSwapXAD PRIVATE QuantLib::QuantLib)" >> CMakeLists.txt - echo "target_compile_features(AdjointSwapXAD PUBLIC cxx_std_17)" >> CMakeLists.txt - mkdir build - cd build - cmake -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_PREFIX_PATH=${{ github.workspace }}/install \ - .. - cmake --build . - ./AdjointSwapXAD - - ############################################################################## - # Linux - XAD with QL_USE_STD_CLASSES - ############################################################################## xad-linux-std-classes: - runs-on: ubuntu-latest - container: ghcr.io/lballabio/quantlib-devenv:rolling - - steps: - - uses: actions/checkout@v4 - with: - repository: ${{ env.ql_repo }} - ref: ${{ env.ql_branch }} - path: QuantLib - - - uses: actions/checkout@v4 - with: - repository: ${{ env.xad_repo }} - ref: ${{ env.xad_branch }} - path: xad - - - uses: actions/checkout@v4 - with: - path: QuantLibAAD - - - name: Setup - run: | - apt-get update && apt install -y ccache ninja-build - - - name: ccache - uses: hendrikmuhs/ccache-action@v1.2.12 - with: - key: linux-xad-std-classes - max-size: 650M - - - name: Configure - run: | - cd QuantLib - mkdir build - cd build - cmake -G Ninja -DBOOST_ROOT=/usr \ - -DQL_USE_STD_CLASSES=ON \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ - -DQL_EXTERNAL_SUBDIRECTORIES="$(pwd)/../../xad;$(pwd)/../../QuantLibAAD" \ - -DQL_EXTRA_LINK_LIBRARIES=QuantLibAAD \ - -DQL_NULL_AS_FUNCTIONS=ON \ - .. - - - name: Compile - run: | - cd QuantLib/build - cmake --build . - - - name: Test QuantLib - run: | - cd QuantLib/build - ./test-suite/quantlib-test-suite --log_level=message - - - name: Test QuantLibAAD - run: | - cd QuantLib/build - ./QuantLibAAD/test-suite/quantlib-aad-test-suite --log_level=message - - ############################################################################## - # Linux - XAD + Forge JIT - # - # Forge accelerates AAD tape computations via JIT compilation, so these jobs - # only make sense with AAD enabled (QLAAD_DISABLE_AAD=OFF). - # Tests C++17 and C++20 to match original XAD job coverage. - ############################################################################## - forge-linux: strategy: fail-fast: false - matrix: - build_type: ["Release", "Debug"] - cxx: ["17", "20"] runs-on: ubuntu-latest container: ghcr.io/lballabio/quantlib-devenv:rolling - steps: - - name: Checkout QuantLib - uses: actions/checkout@v4 - with: - repository: ${{ env.ql_repo }} - ref: ${{ env.ql_branch }} - path: QuantLib - - - name: Checkout XAD - uses: actions/checkout@v4 - with: - repository: ${{ env.xad_repo }} - ref: ${{ env.xad_branch }} - path: xad - - - name: Checkout Forge - uses: actions/checkout@v4 - with: - repository: ${{ env.forge_repo }} - ref: ${{ env.forge_branch }} - path: forge - - - name: Checkout xad-forge - uses: actions/checkout@v4 - with: - repository: ${{ env.xad_forge_repo }} - ref: ${{ env.xad_forge_branch }} - path: xad-forge - - - name: Checkout QuantLibAAD - uses: actions/checkout@v4 - with: - path: QuantLibAAD - - - name: ccache - uses: hendrikmuhs/ccache-action@v1.2.12 - with: - key: linux-forge-${{ matrix.build_type }} - max-size: 650M - - - name: Setup - run: | - apt-get update && apt-get install -y ninja-build ccache - - - name: Build Forge C API - run: | - cd forge - cmake -B build -S api/c \ - -G Ninja \ - -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ - -DCMAKE_INSTALL_PREFIX=$(pwd)/../install - cmake --build build --config ${{ matrix.build_type }} - cmake --install build --config ${{ matrix.build_type }} - - - name: Configure QuantLib with XAD + Forge - run: | - cd QuantLib - mkdir build - cd build - cmake -G Ninja -DBOOST_ROOT=/usr \ - -DCMAKE_CXX_STANDARD=${{ matrix.cxx }} \ - -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ - -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ - -DXAD_WARNINGS_PARANOID=OFF \ - -DXAD_ENABLE_JIT=ON \ - -DCMAKE_PREFIX_PATH=$(pwd)/../../install \ - -DQL_EXTERNAL_SUBDIRECTORIES="$(pwd)/../../xad;$(pwd)/../../xad-forge;$(pwd)/../../QuantLibAAD" \ - -DQL_EXTRA_LINK_LIBRARIES=QuantLibAAD \ - -DQL_NULL_AS_FUNCTIONS=ON \ - -DQL_BUILD_TEST_SUITE=OFF \ - -DQL_BUILD_EXAMPLES=OFF \ - -DQL_BUILD_BENCHMARK=OFF \ - -DQLAAD_DISABLE_AAD=OFF \ - -DQLAAD_ENABLE_FORGE=ON \ - -DQLAAD_USE_FORGE_CAPI=ON \ - -DXAD_FORGE_USE_CAPI=ON \ - -DQLAAD_BUILD_TEST_SUITE=ON \ - -DQLAAD_ENABLE_FORGE_TESTS=ON \ - .. - - - name: Build - run: | - cd QuantLib/build - cmake --build . - - - name: Test QuantLibAAD - run: | - cd QuantLib/build - ./QuantLibAAD/test-suite/quantlib-aad-test-suite --log_level=message - - ############################################################################## - # Windows - XAD + Forge JIT - # - # Forge accelerates AAD tape computations via JIT compilation, so these jobs - # only make sense with AAD enabled (QLAAD_DISABLE_AAD=OFF). - # Tests C++17 and C++20 to match original XAD job coverage. - ############################################################################## - forge-windows: - strategy: - fail-fast: false - matrix: - build_type: ["Release", "Debug"] - cxx: ["17", "20"] - runs-on: windows-2022 - - env: - VSVARSALL: C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat - - steps: - - name: Checkout QuantLib - uses: actions/checkout@v4 - with: - repository: ${{ env.ql_repo }} - ref: ${{ env.ql_branch }} - path: QuantLib - - - name: Checkout XAD - uses: actions/checkout@v4 - with: - repository: ${{ env.xad_repo }} - ref: ${{ env.xad_branch }} - path: xad - - - name: Checkout Forge - uses: actions/checkout@v4 - with: - repository: ${{ env.forge_repo }} - ref: ${{ env.forge_branch }} - path: forge - - - name: Checkout xad-forge - uses: actions/checkout@v4 - with: - repository: ${{ env.xad_forge_repo }} - ref: ${{ env.xad_forge_branch }} - path: xad-forge - - - name: Checkout QuantLibAAD - uses: actions/checkout@v4 - with: - path: QuantLibAAD - - - name: Setup - run: choco install -y ninja - - - name: Setup Boost - run: | - $Url = "https://downloads.sourceforge.net/project/boost/boost-binaries/1.86.0/boost_1_86_0-msvc-14.3-64.exe" - (New-Object System.Net.WebClient).DownloadFile($Url, "$RUNNER_TEMP\boost.exe") - Start-Process -Wait -FilePath "$RUNNER_TEMP\boost.exe" "/SILENT","/SP-","/SUPPRESSMSGBOXES","/DIR=C:\local\boost" - echo "BOOST_ROOT=C:\local\boost" >> $env:GITHUB_ENV - - - name: Build Forge C API - shell: cmd - run: | - cd forge - call "%VSVARSALL%" amd64 - cmake -B build -S api/c -G Ninja ^ - -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} ^ - -DFORGE_CAPI_BUILD_TESTS=OFF ^ - -DFORGE_CAPI_USE_STATIC_RUNTIME=ON ^ - -DCMAKE_INSTALL_PREFIX="%cd%\..\install" - cmake --build build --config ${{ matrix.build_type }} - cmake --install build --config ${{ matrix.build_type }} - - - name: Configure QuantLib with XAD + Forge - shell: cmd - run: | - cd QuantLib - call "%VSVARSALL%" amd64 - cmake -B build -G Ninja ^ - -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} ^ - -DCMAKE_CXX_STANDARD=${{ matrix.cxx }} ^ - -DXAD_WARNINGS_PARANOID=OFF ^ - -DXAD_ENABLE_JIT=ON ^ - -DXAD_STATIC_MSVC_RUNTIME=ON ^ - -DCMAKE_PREFIX_PATH="%cd%\..\install" ^ - -DQL_EXTERNAL_SUBDIRECTORIES="%cd%\..\xad;%cd%\..\xad-forge;%cd%\..\QuantLibAAD" ^ - -DQL_EXTRA_LINK_LIBRARIES=QuantLibAAD ^ - -DQL_NULL_AS_FUNCTIONS=ON ^ - -DQL_BUILD_TEST_SUITE=OFF ^ - -DQL_BUILD_EXAMPLES=OFF ^ - -DQL_BUILD_BENCHMARK=OFF ^ - -DQLAAD_DISABLE_AAD=OFF ^ - -DQLAAD_ENABLE_FORGE=ON ^ - -DQLAAD_USE_FORGE_CAPI=ON ^ - -DXAD_FORGE_USE_CAPI=ON ^ - -DQLAAD_BUILD_TEST_SUITE=ON ^ - -DQLAAD_ENABLE_FORGE_TESTS=ON - - - name: Build - shell: cmd - run: | - cd QuantLib\build - call "%VSVARSALL%" amd64 - cmake --build . --config ${{ matrix.build_type }} - - - name: Test QuantLibAAD - shell: cmd - run: | - cd QuantLib\build - set PATH=%cd%\..\..\install\bin;%PATH% - QuantLibAAD\test-suite\quantlib-aad-test-suite.exe --log_level=message + - uses: actions/checkout@v4 + with: + repository: ${{ env.ql_repo }} + ref: ${{ env.ql_branch }} + path: QuantLib + - uses: actions/checkout@v4 + with: + repository: ${{ env.xad_repo }} + ref: ${{ env.xad_branch }} + path: xad + - uses: actions/checkout@v4 + with: + path: QuantLibAAD-Cpp + - name: Setup + run: | + apt-get update \ + && apt install -y ccache ninja-build \ + - name: ccache + uses: hendrikmuhs/ccache-action@v1.2.12 + with: + key: linux-std-classes + max-size: 650M + - name: Configure + run: | + cd QuantLib + mkdir build + cd build + cmake -G Ninja -DBOOST_ROOT=/usr \ + -DQL_USE_STD_CLASSES=ON \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ + -DQL_EXTERNAL_SUBDIRECTORIES="$(pwd)/../../xad;$(pwd)/../../QuantLibAAD-Cpp" \ + -DQL_EXTRA_LINK_LIBRARIES=QuantLibAAD \ + -DQL_NULL_AS_FUNCTIONS=ON \ + .. + - name: Compile + run: | + cd QuantLib/build + cmake --build . + - name: Test QuantLib + run: | + cd QuantLib/build + ./test-suite/quantlib-test-suite --log_level=message + - name: Test QuantLibAAD + run: | + cd QuantLib/build + ./QuantLibAAD-Cpp/test-suite/QuantLibAAD-test-suite --log_level=message diff --git a/.github/workflows/ql-benchmarks.yaml b/.github/workflows/ql-benchmarks.yaml deleted file mode 100644 index ed0b179..0000000 --- a/.github/workflows/ql-benchmarks.yaml +++ /dev/null @@ -1,737 +0,0 @@ -############################################################################## -# -# QuantLibAAD Benchmarks -# -# Comprehensive benchmark comparing FD vs XAD vs Forge -# -# Structure: -# - Two jobs: Linux and Windows -# - Each job builds 3 versions on SAME hardware for fair comparison: -# 1. Plain double QuantLib (for FD baseline) -# 2. XAD (JIT disabled) for XAD-Full and XAD (Jacobian splitting) -# 3. XAD + Forge (JIT enabled) for Forge and Forge-AVX -# -# Jobs: -# - Linux, QL + XAD + Forge: Full benchmark suite on Linux -# - Windows, QL + XAD + Forge: Full benchmark suite on Windows -# -# Dependencies: -# - QuantLib (lballabio/QuantLib) -# - XAD (auto-differentiation/xad) -# - QuantLibAAD (this repo) -# - xad-forge (da-roth/xad-forge) [Forge builds] -# - Forge C API (da-roth/forge) [Forge builds] -# -# Copyright (C) 2010-2026 Xcelerit Computing Limited -# SPDX-License-Identifier: AGPL-3.0-or-later -# -############################################################################## - -name: QL Benchmarks - -on: - push: - branches: - - main - - forge - pull_request: - branches: - - main - - forge - workflow_dispatch: - -env: - QL_REPO: lballabio/QuantLib - QL_BRANCH: master - XAD_REPO: auto-differentiation/xad - XAD_BRANCH: main - FORGE_REPO: da-roth/forge - FORGE_BRANCH: main - XAD_FORGE_REPO: da-roth/xad-forge - XAD_FORGE_BRANCH: main - -jobs: - ############################################################################## - # Linux, QL + XAD + Forge - # Comprehensive benchmark: FD (plain double) vs XAD vs Forge JIT - ############################################################################## - linux-benchmark: - name: Linux, QL + XAD + Forge - runs-on: ubuntu-latest - container: - image: ghcr.io/lballabio/quantlib-devenv:rolling - - steps: - - name: Hardware Info - run: | - echo "===== CPU =====" - lscpu | grep -E "^(Model name|CPU\(s\)|Thread|Core|Socket|CPU max MHz)" - echo "===== Memory =====" - free -h - echo "===== SIMD =====" - cat /proc/cpuinfo | grep flags | head -1 | tr ' ' '\n' | grep -E "^(sse|avx)" | sort -u | tr '\n' ' ' - echo "" - - - name: Checkout QuantLib - uses: actions/checkout@v4 - with: - repository: ${{ env.QL_REPO }} - ref: ${{ env.QL_BRANCH }} - path: QuantLib - - - name: Checkout XAD - uses: actions/checkout@v4 - with: - repository: ${{ env.XAD_REPO }} - ref: ${{ env.XAD_BRANCH }} - path: xad - - - name: Checkout Forge - uses: actions/checkout@v4 - with: - repository: ${{ env.FORGE_REPO }} - ref: ${{ env.FORGE_BRANCH }} - path: forge - - - name: Checkout xad-forge - uses: actions/checkout@v4 - with: - repository: ${{ env.XAD_FORGE_REPO }} - ref: ${{ env.XAD_FORGE_BRANCH }} - path: xad-forge - - - name: Checkout QuantLibAAD - uses: actions/checkout@v4 - with: - path: QuantLibAAD - - - name: Setup - run: | - apt-get update - apt-get install -y ninja-build cmake - - - name: ccache - uses: hendrikmuhs/ccache-action@v1.2.12 - with: - key: linux-benchmark - max-size: 1G - - # ========================================================================= - # Build Forge C API (needed for JIT builds) - # ========================================================================= - - name: Build Forge C API - run: | - cd forge - cmake -B build -S api/c -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DFORGE_CAPI_BUILD_TESTS=OFF \ - -DCMAKE_INSTALL_PREFIX=$(pwd)/../install - cmake --build build - cmake --install build - - # ========================================================================= - # Benchmark 1: XAD + Forge (JIT enabled) - Run first (longest build) - # ========================================================================= - - name: Configure QuantLib (XAD + Forge) - run: | - cd QuantLib - cmake -B build-forge -G Ninja -DBOOST_ROOT=/usr \ - -DCMAKE_CXX_STANDARD=17 \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ - -DXAD_WARNINGS_PARANOID=OFF \ - -DXAD_USE_STRONG_INLINE=ON \ - -DXAD_NO_THREADLOCAL=ON \ - -DXAD_SIMD_OPTION=AVX2 \ - -DXAD_ENABLE_JIT=ON \ - -DCMAKE_PREFIX_PATH=$(pwd)/../install \ - -DQL_EXTERNAL_SUBDIRECTORIES="$(pwd)/../xad;$(pwd)/../xad-forge;$(pwd)/../QuantLibAAD" \ - -DQL_EXTRA_LINK_LIBRARIES=QuantLibAAD \ - -DQL_NULL_AS_FUNCTIONS=ON \ - -DQL_BUILD_TEST_SUITE=OFF \ - -DQL_BUILD_EXAMPLES=OFF \ - -DQL_BUILD_BENCHMARK=OFF \ - -DQLAAD_DISABLE_AAD=OFF \ - -DQLAAD_ENABLE_FORGE=ON \ - -DQLAAD_USE_FORGE_CAPI=ON \ - -DXAD_FORGE_USE_CAPI=ON \ - -DQLAAD_BUILD_BENCHMARK_AAD=ON - - - name: Build Forge benchmark (JIT on) - run: | - cd QuantLib/build-forge - cmake --build . --target benchmark_aad - - - name: Run Forge Benchmark (JIT on) - run: | - echo "===== FORGE BENCHMARK (Forge + Forge-AVX) =====" - cd QuantLib/build-forge - export LD_LIBRARY_PATH=$(pwd)/../../install/lib:$LD_LIBRARY_PATH - ./QuantLibAAD/test-suite/benchmark-aad --all 2>&1 | tee forge_results.txt - - - name: Run XAD vs Forge Diagnostic - run: | - echo "===== DIAGNOSTIC: XAD vs Forge Derivative Comparison =====" - cd QuantLib/build-forge - export LD_LIBRARY_PATH=$(pwd)/../../install/lib:$LD_LIBRARY_PATH - ./QuantLibAAD/test-suite/benchmark-aad --diagnose --diagnose-paths=100 2>&1 | tee diagnostic_results.txt - - # ========================================================================= - # Benchmark 2: Plain double QuantLib (for FD benchmark) - # ========================================================================= - - name: Configure QuantLib (plain double) - run: | - cd QuantLib - cmake -B build-double -G Ninja -DBOOST_ROOT=/usr \ - -DCMAKE_CXX_STANDARD=17 \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ - -DQL_BUILD_TEST_SUITE=OFF \ - -DQL_BUILD_EXAMPLES=OFF \ - -DQL_BUILD_BENCHMARK=OFF \ - -DQL_EXTERNAL_SUBDIRECTORIES="$(pwd)/../QuantLibAAD" \ - -DQLAAD_DISABLE_AAD=ON \ - -DQLAAD_BUILD_BENCHMARK_FD=ON - - - name: Build FD benchmark (plain double) - run: | - cd QuantLib/build-double - cmake --build . --target benchmark_fd - - - name: Run FD Benchmark (plain double) - run: | - echo "===== FD BENCHMARK (plain double QuantLib) =====" - cd QuantLib/build-double - ./QuantLibAAD/test-suite/benchmark-fd --all 2>&1 | tee fd_results.txt - - # ========================================================================= - # Benchmark 3: XAD (JIT disabled) for XAD tape AAD - # ========================================================================= - - name: Configure QuantLib (XAD, JIT off) - run: | - cd QuantLib - cmake -B build-xad -G Ninja -DBOOST_ROOT=/usr \ - -DCMAKE_CXX_STANDARD=17 \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ - -DXAD_WARNINGS_PARANOID=OFF \ - -DXAD_USE_STRONG_INLINE=ON \ - -DXAD_NO_THREADLOCAL=ON \ - -DXAD_SIMD_OPTION=AVX2 \ - -DXAD_ENABLE_JIT=OFF \ - -DQL_EXTERNAL_SUBDIRECTORIES="$(pwd)/../xad;$(pwd)/../QuantLibAAD" \ - -DQL_EXTRA_LINK_LIBRARIES=QuantLibAAD \ - -DQL_NULL_AS_FUNCTIONS=ON \ - -DQL_BUILD_TEST_SUITE=OFF \ - -DQL_BUILD_EXAMPLES=OFF \ - -DQL_BUILD_BENCHMARK=OFF \ - -DQLAAD_DISABLE_AAD=OFF \ - -DQLAAD_BUILD_BENCHMARK_AAD=ON - - - name: Build XAD benchmark (JIT off) - run: | - cd QuantLib/build-xad - cmake --build . --target benchmark_aad - - - name: Run XAD Benchmark (JIT off) - run: | - echo "===== XAD BENCHMARK (XAD-Full + XAD) =====" - cd QuantLib/build-xad - ./QuantLibAAD/test-suite/benchmark-aad --all --xad-only 2>&1 | tee xad_results.txt - - # ========================================================================= - # Combine and display results - # ========================================================================= - - name: Generate Combined Report - run: | - echo "================================================================================" - echo " COMBINED BENCHMARK RESULTS" - echo "================================================================================" - echo "" - echo "All benchmarks ran on the same hardware for fair comparison." - echo "" - echo "Raw data (for parsing):" - echo "" - grep -E "^(FD_|VALIDATE_)" QuantLib/build-double/fd_results.txt 2>/dev/null || true - grep -E "^(XADFULL_|XAD_|VALIDATE_)" QuantLib/build-xad/xad_results.txt 2>/dev/null || true - grep -E "^(FORGE_|FORGEAVX_|FORGE_PHASES_|VALIDATE_)" QuantLib/build-forge/forge_results.txt 2>/dev/null || true - echo "" - echo "================================================================================" - echo "" - - # Create a Python script to parse and format results - cat > format_results.py << 'PYEOF' - import sys - import re - from collections import defaultdict - - def parse_line(line): - """Parse a line like 'METHOD_CONFIG:10=1.5,0.1,1[,fixed];100=5.2,0.2,1[,fixed]'""" - match = re.match(r'^(\w+)_(\w+):(.+)$', line.strip()) - if not match: - return None - method, config, data = match.groups() - results = {} - for item in data.split(';'): - if '=' in item: - paths, vals = item.split('=') - parts = vals.split(',') - mean, std, enabled = parts[0], parts[1], parts[2] - fixed = float(parts[3]) if len(parts) > 3 else 0.0 - if enabled == '1' and float(mean) > 0: - results[int(paths)] = {'mean': float(mean), 'fixed': fixed} - return method, config, results - - def parse_phases_line(line): - """Parse FORGE_PHASES_CONFIG:p1,p2,p3""" - match = re.match(r'^FORGE_PHASES_(\w+):(.+)$', line.strip()) - if not match: - return None - config, data = match.groups() - parts = data.split(',') - if len(parts) >= 3: - return config, { - 'phase1': float(parts[0]), - 'phase2': float(parts[1]), - 'phase3': float(parts[2]) - } - return None - - def format_num(n): - """Format number with appropriate precision""" - if n >= 1000: - return f"{n:.0f}" - elif n >= 100: - return f"{n:.1f}" - elif n >= 10: - return f"{n:.1f}" - else: - return f"{n:.2f}" - - def format_paths(p): - """Format path count: 100, 1K, 10K, 100K""" - if p >= 1000: - return f"{p//1000}K" - return str(p) - - # Read all result files - data = defaultdict(lambda: defaultdict(dict)) # data[config][method][paths] = {mean, fixed} - phases = {} # phases[config] = {phase1, phase2, phase3} - - for filepath in sys.argv[1:]: - # Skip XAD from forge_results.txt (use xad_results.txt instead for fair comparison) - is_forge_results = 'forge_results' in filepath - try: - with open(filepath) as f: - for line in f: - result = parse_line(line) - if result: - method, config, results = result - # Use XAD from xad_results.txt, not forge_results.txt - if is_forge_results and method == 'XAD': - continue - for paths, vals in results.items(): - data[config][method][paths] = vals - phase_result = parse_phases_line(line) - if phase_result: - config, phase_data = phase_result - phases[config] = phase_data - except FileNotFoundError: - pass - - # Generate side-by-side summary table (Production vs CVA) - print("SUMMARY TABLE (all times in ms)") - print("") - print(" | Paths | Method | 45 inputs | 90 inputs |") - print(" |--------|-----------|------------|------------|") - - path_counts = [1000, 10000, 100000] - methods_to_show = ['FD', 'XAD', 'FORGEAVX'] - method_display = {'FD': 'FD', 'XAD': 'XAD', 'FORGEAVX': 'Forge-AVX'} - - for i, paths in enumerate(path_counts): - for j, method in enumerate(methods_to_show): - # Get values for PRODUCTION (45 inputs) and CVA (90 inputs) - prod_val = data.get('PRODUCTION', {}).get(method, {}).get(paths, {}) - cva_val = data.get('CVA', {}).get(method, {}).get(paths, {}) - - prod_str = format_num(prod_val['mean']) if prod_val else '-' - cva_str = format_num(cva_val['mean']) if cva_val else '-' - - path_col = format_paths(paths) if j == 0 else '' - print(f" | {path_col:>6} | {method_display[method]:>9} | {prod_str:>10} | {cva_str:>10} |") - - # Separator between path count groups (except after last) - if i < len(path_counts) - 1: - print(" |--------+-----------+------------+------------|") - - print("") - print(" Notes:") - print(" - XAD uses Jacobian splitting for O(1) scaling with inputs") - print(" - Forge uses XAD for curve bootstrap + Jacobian, then JIT compiles the graph") - print(" - Forge-AVX adds AVX2 vectorization (4 paths at a time)") - print(" - FD = Finite Differences baseline (O(n) scaling with inputs)") - - # Print Forge phase breakdown if available - if 'PRODUCTION' in phases: - p = phases['PRODUCTION'] - total = p['phase1'] + p['phase2'] + p['phase3'] - print("") - print(" Forge setup breakdown (one-time cost):") - print(f" Curve bootstrap: {format_num(p['phase1'])} ms") - print(f" Jacobian: {format_num(p['phase2'])} ms") - print(f" JIT compile: {format_num(p['phase3'])} ms") - print(f" Total: {format_num(total)} ms") - PYEOF - - python3 format_results.py \ - QuantLib/build-double/fd_results.txt \ - QuantLib/build-xad/xad_results.txt \ - QuantLib/build-forge/forge_results.txt - - - name: Upload Results - uses: actions/upload-artifact@v4 - with: - name: linux-benchmark-results - path: | - QuantLib/build-double/fd_results.txt - QuantLib/build-xad/xad_results.txt - QuantLib/build-forge/forge_results.txt - - ############################################################################## - # Windows, QL + XAD + Forge - # Comprehensive benchmark: FD (plain double) vs XAD vs Forge JIT - ############################################################################## - windows-benchmark: - name: Windows, QL + XAD + Forge - runs-on: windows-2022 - - env: - VSVARSALL: C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat - - steps: - - name: Checkout QuantLib - uses: actions/checkout@v4 - with: - repository: ${{ env.QL_REPO }} - ref: ${{ env.QL_BRANCH }} - path: QuantLib - - - name: Checkout XAD - uses: actions/checkout@v4 - with: - repository: ${{ env.XAD_REPO }} - ref: ${{ env.XAD_BRANCH }} - path: xad - - - name: Checkout Forge - uses: actions/checkout@v4 - with: - repository: ${{ env.FORGE_REPO }} - ref: ${{ env.FORGE_BRANCH }} - path: forge - - - name: Checkout xad-forge - uses: actions/checkout@v4 - with: - repository: ${{ env.XAD_FORGE_REPO }} - ref: ${{ env.XAD_FORGE_BRANCH }} - path: xad-forge - - - name: Checkout QuantLibAAD - uses: actions/checkout@v4 - with: - path: QuantLibAAD - - - name: Setup - run: choco install -y ninja - - - name: Setup Boost - run: | - $Url = "https://downloads.sourceforge.net/project/boost/boost-binaries/1.86.0/boost_1_86_0-msvc-14.3-64.exe" - (New-Object System.Net.WebClient).DownloadFile($Url, "$RUNNER_TEMP\boost.exe") - Start-Process -Wait -FilePath "$RUNNER_TEMP\boost.exe" "/SILENT","/SP-","/SUPPRESSMSGBOXES","/DIR=C:\local\boost" - echo "BOOST_ROOT=C:\local\boost" >> $env:GITHUB_ENV - - # ========================================================================= - # Build Forge C API - # ========================================================================= - - name: Build Forge C API - shell: cmd - run: | - call "%VSVARSALL%" x64 - cd forge - cmake -B build -S api/c -G Ninja ^ - -DCMAKE_BUILD_TYPE=Release ^ - -DFORGE_CAPI_BUILD_TESTS=OFF ^ - -DFORGE_CAPI_USE_STATIC_RUNTIME=ON ^ - -DCMAKE_INSTALL_PREFIX=%CD%\..\install - cmake --build build - cmake --install build - - # ========================================================================= - # Benchmark 1: XAD + Forge (JIT enabled) - Run first (longest build) - # ========================================================================= - - name: Configure QuantLib (XAD + Forge) - shell: cmd - run: | - call "%VSVARSALL%" x64 - cd QuantLib - cmake -B build-forge -G Ninja ^ - -DCMAKE_CXX_STANDARD=17 ^ - -DCMAKE_BUILD_TYPE=Release ^ - -DXAD_WARNINGS_PARANOID=OFF ^ - -DXAD_USE_STRONG_INLINE=OFF ^ - -DXAD_NO_THREADLOCAL=ON ^ - -DXAD_SIMD_OPTION=AVX2 ^ - -DXAD_ENABLE_JIT=ON ^ - -DXAD_STATIC_MSVC_RUNTIME=ON ^ - -DCMAKE_PREFIX_PATH=%CD%\..\install ^ - -DQL_EXTERNAL_SUBDIRECTORIES="%CD%\..\xad;%CD%\..\xad-forge;%CD%\..\QuantLibAAD" ^ - -DQL_EXTRA_LINK_LIBRARIES=QuantLibAAD ^ - -DQL_NULL_AS_FUNCTIONS=ON ^ - -DQL_BUILD_TEST_SUITE=OFF ^ - -DQL_BUILD_EXAMPLES=OFF ^ - -DQL_BUILD_BENCHMARK=OFF ^ - -DQLAAD_DISABLE_AAD=OFF ^ - -DQLAAD_ENABLE_FORGE=ON ^ - -DQLAAD_USE_FORGE_CAPI=ON ^ - -DXAD_FORGE_USE_CAPI=ON ^ - -DQLAAD_BUILD_BENCHMARK_AAD=ON - - - name: Build Forge benchmark (JIT on) - shell: cmd - run: | - call "%VSVARSALL%" x64 - cd QuantLib\build-forge - cmake --build . --target benchmark_aad -- -j 2 - - - name: Run Forge Benchmark (JIT on) - shell: cmd - run: | - echo ===== FORGE BENCHMARK (Forge + Forge-AVX) ===== - cd QuantLib\build-forge - set PATH=%CD%\..\..\install\bin;%PATH% - QuantLibAAD\test-suite\benchmark-aad.exe --all > forge_results.txt 2>&1 - type forge_results.txt - - - name: Run XAD vs Forge Diagnostic - shell: cmd - run: | - echo ===== DIAGNOSTIC: XAD vs Forge Derivative Comparison ===== - cd QuantLib\build-forge - set PATH=%CD%\..\..\install\bin;%PATH% - QuantLibAAD\test-suite\benchmark-aad.exe --diagnose --diagnose-paths=100 > diagnostic_results.txt 2>&1 - type diagnostic_results.txt - - # ========================================================================= - # Benchmark 2: Plain double QuantLib (for FD benchmark) - # ========================================================================= - - name: Configure QuantLib (plain double) - shell: cmd - run: | - call "%VSVARSALL%" x64 - cd QuantLib - cmake -B build-double -G Ninja ^ - -DCMAKE_CXX_STANDARD=17 ^ - -DCMAKE_BUILD_TYPE=Release ^ - -DQL_BUILD_TEST_SUITE=OFF ^ - -DQL_BUILD_EXAMPLES=OFF ^ - -DQL_BUILD_BENCHMARK=OFF ^ - -DQL_EXTERNAL_SUBDIRECTORIES="%CD%\..\QuantLibAAD" ^ - -DQLAAD_DISABLE_AAD=ON ^ - -DQLAAD_BUILD_BENCHMARK_FD=ON - - - name: Build FD benchmark (plain double) - shell: cmd - run: | - call "%VSVARSALL%" x64 - cd QuantLib\build-double - cmake --build . --target benchmark_fd - - - name: Run FD Benchmark (plain double) - shell: cmd - run: | - echo ===== FD BENCHMARK (plain double QuantLib) ===== - cd QuantLib\build-double - QuantLibAAD\test-suite\benchmark-fd.exe --all > fd_results.txt 2>&1 - type fd_results.txt - - # ========================================================================= - # Benchmark 3: XAD (JIT disabled) for XAD tape AAD - # ========================================================================= - - name: Configure QuantLib (XAD, JIT off) - shell: cmd - run: | - call "%VSVARSALL%" x64 - cd QuantLib - cmake -B build-xad -G Ninja ^ - -DCMAKE_CXX_STANDARD=17 ^ - -DCMAKE_BUILD_TYPE=Release ^ - -DXAD_WARNINGS_PARANOID=OFF ^ - -DXAD_USE_STRONG_INLINE=OFF ^ - -DXAD_NO_THREADLOCAL=ON ^ - -DXAD_SIMD_OPTION=AVX2 ^ - -DXAD_ENABLE_JIT=OFF ^ - -DXAD_STATIC_MSVC_RUNTIME=ON ^ - -DQL_EXTERNAL_SUBDIRECTORIES="%CD%\..\xad;%CD%\..\QuantLibAAD" ^ - -DQL_EXTRA_LINK_LIBRARIES=QuantLibAAD ^ - -DQL_NULL_AS_FUNCTIONS=ON ^ - -DQL_BUILD_TEST_SUITE=OFF ^ - -DQL_BUILD_EXAMPLES=OFF ^ - -DQL_BUILD_BENCHMARK=OFF ^ - -DQLAAD_DISABLE_AAD=OFF ^ - -DQLAAD_BUILD_BENCHMARK_AAD=ON - - - name: Build XAD benchmark (JIT off) - shell: cmd - run: | - call "%VSVARSALL%" x64 - cd QuantLib\build-xad - cmake --build . --target benchmark_aad -- -j 2 - - - name: Run XAD Benchmark (JIT off) - shell: cmd - run: | - echo ===== XAD BENCHMARK (XAD-Full + XAD) ===== - cd QuantLib\build-xad - QuantLibAAD\test-suite\benchmark-aad.exe --all --xad-only > xad_results.txt 2>&1 - type xad_results.txt - - # ========================================================================= - # Combine and display results - # ========================================================================= - - name: Generate Combined Report - shell: pwsh - run: | - Write-Host "================================================================================" - Write-Host " COMBINED BENCHMARK RESULTS (Windows)" - Write-Host "================================================================================" - Write-Host "" - Write-Host "All benchmarks ran on the same hardware for fair comparison." - Write-Host "" - Write-Host "Raw data (for parsing):" - Write-Host "" - Get-Content QuantLib/build-double/fd_results.txt 2>$null | Select-String "^(FD_|VALIDATE_)" - Get-Content QuantLib/build-xad/xad_results.txt 2>$null | Select-String "^(XADFULL_|XAD_|VALIDATE_)" - Get-Content QuantLib/build-forge/forge_results.txt 2>$null | Select-String "^(FORGE_|FORGEAVX_|FORGE_PHASES_|VALIDATE_)" - Write-Host "" - Write-Host "================================================================================" - Write-Host "" - - $script = @' - import sys - import re - from collections import defaultdict - - def parse_line(line): - match = re.match(r'^(\w+)_(\w+):(.+)$', line.strip()) - if not match: - return None - method, config, data = match.groups() - results = {} - for item in data.split(';'): - if '=' in item: - paths, vals = item.split('=') - parts = vals.split(',') - mean, std, enabled = parts[0], parts[1], parts[2] - fixed = float(parts[3]) if len(parts) > 3 else 0.0 - if enabled == '1' and float(mean) > 0: - results[int(paths)] = {'mean': float(mean), 'fixed': fixed} - return method, config, results - - def parse_phases_line(line): - match = re.match(r'^FORGE_PHASES_(\w+):(.+)$', line.strip()) - if not match: - return None - config, data = match.groups() - parts = data.split(',') - if len(parts) >= 3: - return config, {'phase1': float(parts[0]), 'phase2': float(parts[1]), 'phase3': float(parts[2])} - return None - - def format_num(n): - if n >= 1000: return f"{n:.0f}" - elif n >= 100: return f"{n:.1f}" - elif n >= 10: return f"{n:.1f}" - else: return f"{n:.2f}" - - def format_paths(p): - if p >= 1000: return f"{p//1000}K" - return str(p) - - data = defaultdict(lambda: defaultdict(dict)) - phases = {} - for filepath in sys.argv[1:]: - is_forge_results = 'forge_results' in filepath - try: - with open(filepath) as f: - for line in f: - result = parse_line(line) - if result: - method, config, results = result - if is_forge_results and method == 'XAD': - continue - for paths, vals in results.items(): - data[config][method][paths] = vals - phase_result = parse_phases_line(line) - if phase_result: - config, phase_data = phase_result - phases[config] = phase_data - except: pass - - print("SUMMARY TABLE (all times in ms)") - print("") - print(" | Paths | Method | 45 inputs | 90 inputs |") - print(" |--------|-----------|------------|------------|") - - path_counts = [1000, 10000, 100000] - methods_to_show = ['FD', 'XAD', 'FORGEAVX'] - method_display = {'FD': 'FD', 'XAD': 'XAD', 'FORGEAVX': 'Forge-AVX'} - - for i, paths in enumerate(path_counts): - for j, method in enumerate(methods_to_show): - prod_val = data.get('PRODUCTION', {}).get(method, {}).get(paths, {}) - cva_val = data.get('CVA', {}).get(method, {}).get(paths, {}) - - prod_str = format_num(prod_val['mean']) if prod_val else '-' - cva_str = format_num(cva_val['mean']) if cva_val else '-' - - path_col = format_paths(paths) if j == 0 else '' - print(f" | {path_col:>6} | {method_display[method]:>9} | {prod_str:>10} | {cva_str:>10} |") - - if i < len(path_counts) - 1: - print(" |--------+-----------+------------+------------|") - - print("") - print(" Notes:") - print(" - XAD uses Jacobian splitting for O(1) scaling with inputs") - print(" - Forge uses XAD for curve bootstrap + Jacobian, then JIT compiles the graph") - print(" - Forge-AVX adds AVX2 vectorization (4 paths at a time)") - print(" - FD = Finite Differences baseline (O(n) scaling with inputs)") - - if 'PRODUCTION' in phases: - p = phases['PRODUCTION'] - total = p['phase1'] + p['phase2'] + p['phase3'] - print("") - print(" Forge setup breakdown (one-time cost):") - print(f" Curve bootstrap: {format_num(p['phase1'])} ms") - print(f" Jacobian: {format_num(p['phase2'])} ms") - print(f" JIT compile: {format_num(p['phase3'])} ms") - print(f" Total: {format_num(total)} ms") - '@ - - $script | Out-File -FilePath format_results.py -Encoding utf8 - python format_results.py ` - QuantLib/build-double/fd_results.txt ` - QuantLib/build-xad/xad_results.txt ` - QuantLib/build-forge/forge_results.txt - - - name: Upload Results - uses: actions/upload-artifact@v4 - with: - name: windows-benchmark-results - path: | - QuantLib/build-double/fd_results.txt - QuantLib/build-xad/xad_results.txt - QuantLib/build-forge/forge_results.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 090e6ab..d29dd85 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,11 @@ ############################################################################## -# +# # # This file is part of QuantLibAAD, an adaptor module to enable using XAD with # QuantLib. XAD is a fast and comprehensive C++ library for # automatic differentiation. # -# Copyright (C) 2010-2026 Xcelerit Computing Ltd. +# Copyright (C) 2010-2024 Xcelerit Computing Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published @@ -19,87 +19,17 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -# +# ############################################################################## option(QLAAD_DISABLE_AAD "Disable using XAD for QuantLib's Real, allowing to run samples with double" OFF) -option(QLAAD_ENABLE_FORGE "Enable Forge JIT backend via xad-forge" OFF) -option(QLAAD_USE_FORGE_CAPI "Use Forge C API instead of C++ API for binary compatibility" OFF) - -# Backward compatibility for old QLRISKS_* options -foreach(_opt IN ITEMS - DISABLE_AAD - ENABLE_FORGE - USE_FORGE_CAPI -) - if(DEFINED CACHE{QLRISKS_${_opt}}) - set(QLAAD_${_opt} ${QLRISKS_${_opt}} CACHE BOOL "" FORCE) - message(WARNING - "CMake option QLRISKS_${_opt} is deprecated. " - "Please use QLAAD_${_opt} instead." - ) - endif() -endforeach() add_subdirectory(ql) if(MSVC) set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") endif() add_subdirectory(Examples) - -############################################################################## -# QLAAD-Forge integration via xad-forge -# NOTE: This must be defined BEFORE test-suite so tests can link to it -############################################################################## - -if(QLAAD_ENABLE_FORGE) - message(STATUS "QLAAD-Forge: Looking for xad-forge...") - - # Pass through the C API option to xad-forge - set(XAD_FORGE_USE_CAPI ${QLRISKS_USE_FORGE_CAPI} CACHE BOOL "" FORCE) - - # Option 1: Check if xad-forge was added as subdirectory - if(TARGET xad-forge) - message(STATUS "QLAAD-Forge: Found xad-forge target (subdirectory mode)") - set(XAD_FORGE_FOUND TRUE) - endif() - - # Option 2: Try find_package for pre-built xad-forge - if(NOT XAD_FORGE_FOUND) - find_package(xad-forge CONFIG QUIET) - if(xad-forge_FOUND) - message(STATUS "QLAAD-Forge: Found xad-forge package (pre-built mode)") - set(XAD_FORGE_FOUND TRUE) - endif() - endif() - - if(XAD_FORGE_FOUND) - # Create qlaad-forge as an INTERFACE library wrapping xad-forge - add_library(qlaad-forge INTERFACE) - add_library(QLAAD::forge ALIAS qlaad-forge) - - target_link_libraries(qlaad-forge INTERFACE - XADForge::xad-forge - ) - - target_compile_definitions(qlaad-forge INTERFACE QLAAD_HAS_FORGE=1) - - message(STATUS "QLAAD-Forge: Configured with xad-forge") - else() - message(STATUS "QLAAD-Forge: xad-forge not found - ForgeBackend will not be available") - message(STATUS "QLAAD-Forge: To enable, either:") - message(STATUS " 1. Add xad-forge as subdirectory") - message(STATUS " 2. Set CMAKE_PREFIX_PATH to xad-forge installation") - endif() +if(NOT QLAAD_DISABLE_AAD) + # the test suite is not supporting double + add_subdirectory(test-suite) endif() - -############################################################################## -# Test suite (after QLAAD::forge is defined) -############################################################################## - -# Add test-suite if: -# 1. AAD is enabled (for XAD-based tests and benchmarks), OR -# 2. FD benchmark is requested (works with plain double) -if(NOT QLAAD_DISABLE_AAD OR QLAAD_BUILD_BENCHMARK_FD) - add_subdirectory(test-suite) -endif() \ No newline at end of file diff --git a/README.md b/README.md index 1256229..6e0d6af 100644 --- a/README.md +++ b/README.md @@ -18,12 +18,6 @@ This repository contains integration headers, examples, and tests required for this integration. It is not usable stand-alone. -## JIT Compilation Support - -XAD is optimized for computing sensitivities efficiently in a single evaluation pass using adjoint mode. For workflows that require repeated evaluation across many scenarios—such as Monte Carlo simulations, XVA calculations, regulatory stress testing, or scenario-based risk analysis—XAD also supports recording computations into a [`JITGraph`](https://auto-differentiation.github.io/xad/ref/jit/) that can be compiled and re-evaluated efficiently using a JIT backend. The [xad-forge](https://github.com/da-roth/xad-forge) library provides Forge-based backends for this purpose, including vectorized AVX execution. See the [xad-forge README](https://github.com/da-roth/xad-forge#when-to-use-jit) for guidance on when JIT compilation is beneficial. - -The repository includes a [swaption benchmark](.github/workflows/ql-benchmarks.yaml) that demonstrates a hybrid workflow: curve bootstrapping with XAD's tape, followed by Monte Carlo pricing with JIT-compiled evaluation, comparing tape-based, JIT, and JIT-AVX performance. - ## Getting Started For detailed build instructions with [XAD](https://auto-differentiation.github.io) and [QuantLib](https://www.quantlib.org), please refer to the [XAD documentation site](https://auto-differentiation.github.io/quantlib-risks/cxx/). diff --git a/ql/CMakeLists.txt b/ql/CMakeLists.txt index 42f5b14..22dcfd0 100644 --- a/ql/CMakeLists.txt +++ b/ql/CMakeLists.txt @@ -5,7 +5,7 @@ # QuantLib. XAD is a fast and comprehensive C++ library for # automatic differentiation. # -# Copyright (C) 2010-2026 Xcelerit Computing Ltd. +# Copyright (C) 2010-2024 Xcelerit Computing Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published @@ -23,24 +23,23 @@ ############################################################################## set(QLAAD_HEADERS - qlaad.hpp + QLAAD.hpp ) add_library(QuantLibAAD INTERFACE) -add_library(QuantLib-Risks ALIAS QuantLibAAD) # for compatibility target_include_directories(QuantLibAAD INTERFACE $ $ ) target_compile_features(QuantLibAAD INTERFACE cxx_std_14) if(NOT QLAAD_DISABLE_AAD) - target_compile_definitions(QuantLibAAD INTERFACE QL_INCLUDE_FIRST=ql/qlaad.hpp) + target_compile_definitions(QuantLibAAD INTERFACE QL_INCLUDE_FIRST=ql/QLAAD.hpp) else() target_compile_definitions(QuantLibAAD INTERFACE QLAAD_DISABLE_AAD=1) endif() -target_link_libraries(QuantLibAAD INTERFACE XAD::xad) if(MSVC) target_compile_options(QuantLibAAD INTERFACE /bigobj) endif() +target_link_libraries(QuantLibAAD INTERFACE XAD::xad) set_target_properties(QuantLibAAD PROPERTIES EXPORT_NAME QuantLibAAD ) @@ -62,7 +61,7 @@ endforeach() # set(QLAAD_INSTALL_CMAKEDIR "lib/cmake/QuantLibAAD" CACHE STRING - "Installation directory for CMake scripts for QuantLib AAD") + "Installation directory for CMake scripts for QuantLib XAD") include(CMakePackageConfigHelpers) configure_file("../cmake/QuantLibAADConfig.cmake.in" @@ -76,16 +75,3 @@ configure_package_config_file("../cmake/QuantLibAADConfig.cmake.in" install(FILES "${PROJECT_BINARY_DIR}/cmake/QuantLibAADConfig.cmake" DESTINATION "${QLAAD_INSTALL_CMAKEDIR}" ) - -# the following is purely for compatibility - it will be removed in the near future -configure_file("../cmake/QuantLibAADConfig.cmake.in" - "${PROJECT_BINARY_DIR}/cmake/QuantLib-RisksConfig.cmake" - COPYONLY -) -configure_package_config_file("../cmake/QuantLibAADConfig.cmake.in" - "${PROJECT_BINARY_DIR}/cmake/QuantLib-RisksConfig.cmake" - INSTALL_DESTINATION "${QLAAD_INSTALL_CMAKEDIR}" -) -install(FILES "${PROJECT_BINARY_DIR}/cmake/QuantLib-RisksConfig.cmake" - DESTINATION "${QLAAD_INSTALL_CMAKEDIR}" -) diff --git a/test-suite/CMakeLists.txt b/test-suite/CMakeLists.txt index 0edb5b9..34698d3 100644 --- a/test-suite/CMakeLists.txt +++ b/test-suite/CMakeLists.txt @@ -1,6 +1,3 @@ -option(QLAAD_BUILD_TEST_SUITE "Build the QuantLibAAD test suite" OFF) -option(QLAAD_ENABLE_FORGE_TESTS "Enable Forge JIT tests (requires Forge)" OFF) - set(QLAAD_TEST_SOURCES americanoption_xad.cpp barrieroption_xad.cpp @@ -12,104 +9,24 @@ set(QLAAD_TEST_SOURCES forwardrateagreement_xad.cpp hestonmodel_xad.cpp swap_xad.cpp - + utilities_xad.cpp quantlibtestsuite_xad.cpp ) -# Forge JIT tests - require Forge's native code backend -# Tests QuantLib-specific JIT functionality (swaption pricing pipeline) -set(QLAAD_FORGE_TESTS_ENABLED FALSE) -if(QLAAD_ENABLE_FORGE_TESTS AND TARGET QLAAD::forge) - message(STATUS "QLAAD test-suite: Adding Forge JIT tests (native code backend)") - list(APPEND QLAAD_TEST_SOURCES swaption_jit_pipeline_xad.cpp) - set(QLAAD_FORGE_TESTS_ENABLED TRUE) -elseif(QLAAD_ENABLE_FORGE_TESTS) - message(WARNING "QLAAD test-suite: QLAAD_ENABLE_FORGE_TESTS=ON but QLAAD::forge not available") -endif() - set(QLAAD_TEST_HEADERS utilities_xad.hpp) -if(QL_BUILD_TEST_SUITE OR QLAAD_BUILD_TEST_SUITE) +if(QL_BUILD_TEST_SUITE) add_executable(QuantLibAAD_test_suite ${QLAAD_TEST_SOURCES} ${QLAAD_TEST_HEADERS}) - set_target_properties(QuantLibAAD_test_suite PROPERTIES OUTPUT_NAME "quantlib-aad-test-suite") + set_target_properties(QuantLibAAD_test_suite PROPERTIES OUTPUT_NAME "QuantLibAAD-test-suite") if (NOT Boost_USE_STATIC_LIBS) target_compile_definitions(QuantLibAAD_test_suite PRIVATE BOOST_ALL_DYN_LINK) endif() target_link_libraries(QuantLibAAD_test_suite PRIVATE ql_library ${QL_THREAD_LIBRARIES}) - - # ONLY link to Forge if Forge tests are actually enabled - # This is important because linking Forge brings in AVX2-compiled code - # which can cause ODR violations if not properly isolated - if(QLAAD_FORGE_TESTS_ENABLED) - message(STATUS "QLAAD test-suite: Linking QLAAD::forge (Forge tests enabled)") - target_link_libraries(QuantLibAAD_test_suite PRIVATE QLAAD::forge) - target_compile_definitions(QuantLibAAD_test_suite PRIVATE QLAAD_HAS_FORGE=1) - else() - message(STATUS "QLAAD test-suite: NOT linking Forge (Forge tests disabled)") - endif() - if (QL_INSTALL_TEST_SUITE) install(TARGETS QuantLibAAD_test_suite RUNTIME DESTINATION ${QL_INSTALL_BINDIR}) endif() add_test(NAME QuantLibAAD_test_suite COMMAND QuantLibAAD_test_suite --log_level=message) endif() - -# ============================================================================= -# Benchmark - Split FD/AAD executables for fair comparison -# ============================================================================= - -# FD benchmark - Finite Differences using plain double QuantLib -# This is built WITHOUT XAD to ensure fair FD vs AAD comparison -option(QLAAD_BUILD_BENCHMARK_FD "Build FD benchmark (plain double, no XAD)" OFF) -if(QLAAD_BUILD_BENCHMARK_FD) - message(STATUS "QLAAD: Building FD benchmark (plain double)") - add_executable(benchmark_fd - benchmark_fd.cpp - benchmark_common.hpp - benchmark_pricing.hpp - PlatformInfo.hpp - ) - set_target_properties(benchmark_fd PROPERTIES - OUTPUT_NAME "benchmark-fd") - target_link_libraries(benchmark_fd PRIVATE - ql_library - ${QL_THREAD_LIBRARIES}) - - if (QL_INSTALL_TEST_SUITE) - install(TARGETS benchmark_fd RUNTIME DESTINATION ${QL_INSTALL_BINDIR}) - endif() -endif() - -# AAD benchmark - XAD tape-based AAD and optionally Forge JIT -# Built with XAD-enabled QuantLib, optionally links Forge for JIT benchmarks -option(QLAAD_BUILD_BENCHMARK_AAD "Build AAD benchmark (XAD tape, optionally Forge JIT)" OFF) -if(QLAAD_BUILD_BENCHMARK_AAD) - message(STATUS "QLAAD: Building AAD benchmark") - add_executable(benchmark_aad - benchmark_aad.cpp - benchmark_common.hpp - benchmark_pricing.hpp - PlatformInfo.hpp - ) - set_target_properties(benchmark_aad PROPERTIES - OUTPUT_NAME "benchmark-aad") - target_link_libraries(benchmark_aad PRIVATE - ql_library - ${QL_THREAD_LIBRARIES}) - - # Link Forge if available for JIT benchmarks - if(TARGET QLAAD::forge) - message(STATUS "QLAAD AAD benchmark: Linking QLAAD::forge (JIT benchmarks enabled)") - target_link_libraries(benchmark_aad PRIVATE QLAAD::forge) - target_compile_definitions(benchmark_aad PRIVATE QLAAD_HAS_FORGE=1) - else() - message(STATUS "QLAAD AAD benchmark: Building without Forge (XAD tape only)") - endif() - - if (QL_INSTALL_TEST_SUITE) - install(TARGETS benchmark_aad RUNTIME DESTINATION ${QL_INSTALL_BINDIR}) - endif() -endif() \ No newline at end of file diff --git a/test-suite/PlatformInfo.hpp b/test-suite/PlatformInfo.hpp deleted file mode 100644 index 4007d61..0000000 --- a/test-suite/PlatformInfo.hpp +++ /dev/null @@ -1,212 +0,0 @@ -/******************************************************************************* - - Platform Information Utilities - - This header provides cross-platform functions for detecting and reporting - system information such as CPU, memory, OS, compiler, and SIMD capabilities. - - Adapted from XAD's PlatformInfo.hpp for QuantLibAAD benchmarks. - - Copyright (C) 2010-2026 Xcelerit Computing Ltd. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -******************************************************************************/ - -#pragma once - -#include -#include -#include -#include -#include - -#ifdef _WIN32 -#include -#include -#else -#include -#include -#if defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86) -#include -#endif -#endif - -namespace platform_info -{ - -/// Get CPU brand string (e.g., "Intel Core i7-9700K") -inline std::string getCpuInfo() -{ -#if defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86) - char brand[49] = {0}; - unsigned int regs[4]; - -#ifdef _WIN32 - __cpuid(reinterpret_cast(regs), 0x80000000); -#else - __get_cpuid(0x80000000, ®s[0], ®s[1], ®s[2], ®s[3]); -#endif - - if (regs[0] >= 0x80000004) - { - for (unsigned int i = 0; i < 3; ++i) - { -#ifdef _WIN32 - __cpuid(reinterpret_cast(regs), 0x80000002 + i); -#else - __get_cpuid(0x80000002 + i, ®s[0], ®s[1], ®s[2], ®s[3]); -#endif - std::memcpy(brand + i * 16, regs, 16); - } - std::string result(brand); - size_t start = result.find_first_not_of(' '); - if (start != std::string::npos) - result = result.substr(start); - return result; - } -#endif - return "Unknown CPU"; -} - -/// Get OS/platform information (e.g., "Windows 10.0 (Build 19041)" or "Linux 5.4.0") -inline std::string getPlatformInfo() -{ -#ifdef _WIN32 - typedef LONG(WINAPI * RtlGetVersionPtr)(PRTL_OSVERSIONINFOW); - HMODULE hMod = GetModuleHandleW(L"ntdll.dll"); - if (hMod) - { - auto RtlGetVersion = (RtlGetVersionPtr)GetProcAddress(hMod, "RtlGetVersion"); - if (RtlGetVersion) - { - RTL_OSVERSIONINFOW rovi = {0}; - rovi.dwOSVersionInfoSize = sizeof(rovi); - if (RtlGetVersion(&rovi) == 0) - { - std::ostringstream oss; - oss << "Windows " << rovi.dwMajorVersion << "." << rovi.dwMinorVersion << " (Build " - << rovi.dwBuildNumber << ")"; - return oss.str(); - } - } - } - return "Windows"; -#else - struct utsname buf; - if (uname(&buf) == 0) - { - std::ostringstream oss; - oss << buf.sysname << " " << buf.release; - return oss.str(); - } - return "Unknown"; -#endif -} - -/// Get total system memory (e.g., "16 GB") -inline std::string getMemoryInfo() -{ -#ifdef _WIN32 - MEMORYSTATUSEX memInfo; - memInfo.dwLength = sizeof(MEMORYSTATUSEX); - if (GlobalMemoryStatusEx(&memInfo)) - { - double gb = static_cast(memInfo.ullTotalPhys) / (1024.0 * 1024.0 * 1024.0); - std::ostringstream oss; - oss << std::fixed << std::setprecision(0) << gb << " GB"; - return oss.str(); - } -#else - long pages = sysconf(_SC_PHYS_PAGES); - long page_size = sysconf(_SC_PAGE_SIZE); - if (pages > 0 && page_size > 0) - { - double gb = static_cast(pages) * page_size / (1024.0 * 1024.0 * 1024.0); - std::ostringstream oss; - oss << std::fixed << std::setprecision(0) << gb << " GB"; - return oss.str(); - } -#endif - return "Unknown"; -} - -/// Get compiler information (e.g., "GCC 11.2.0" or "MSVC 19.29 (Release)") -inline std::string getCompilerInfo() -{ -#if defined(_MSC_VER) - std::ostringstream oss; - oss << "MSVC " << _MSC_VER / 100 << "." << _MSC_VER % 100; -#if defined(_DEBUG) - oss << " (Debug)"; -#else - oss << " (Release)"; -#endif - return oss.str(); -#elif defined(__clang__) - std::ostringstream oss; - oss << "Clang " << __clang_major__ << "." << __clang_minor__ << "." << __clang_patchlevel__; - return oss.str(); -#elif defined(__GNUC__) - std::ostringstream oss; - oss << "GCC " << __GNUC__ << "." << __GNUC_MINOR__ << "." << __GNUC_PATCHLEVEL__; - return oss.str(); -#else - return "Unknown Compiler"; -#endif -} - -/// Get supported SIMD instruction sets (e.g., "SSE3, SSE4.1, SSE4.2, AVX, AVX2") -inline std::string getSimdInfo() -{ -#if defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86) - unsigned int regs[4]; - std::vector features; - -#ifdef _WIN32 - __cpuid(reinterpret_cast(regs), 1); -#else - __get_cpuid(1, ®s[0], ®s[1], ®s[2], ®s[3]); -#endif - - if (regs[2] & (1 << 0)) features.push_back("SSE3"); - if (regs[2] & (1 << 19)) features.push_back("SSE4.1"); - if (regs[2] & (1 << 20)) features.push_back("SSE4.2"); - if (regs[2] & (1 << 28)) features.push_back("AVX"); - -#ifdef _WIN32 - __cpuidex(reinterpret_cast(regs), 7, 0); -#else - __get_cpuid_count(7, 0, ®s[0], ®s[1], ®s[2], ®s[3]); -#endif - - if (regs[1] & (1 << 5)) features.push_back("AVX2"); - if (regs[1] & (1 << 16)) features.push_back("AVX512F"); - - if (features.empty()) - return "None detected"; - - std::ostringstream oss; - for (size_t i = 0; i < features.size(); ++i) - { - if (i > 0) oss << ", "; - oss << features[i]; - } - return oss.str(); -#else - return "N/A (non-x86)"; -#endif -} - -} // namespace platform_info diff --git a/test-suite/benchmark_aad.cpp b/test-suite/benchmark_aad.cpp deleted file mode 100644 index 152ad0c..0000000 --- a/test-suite/benchmark_aad.cpp +++ /dev/null @@ -1,3232 +0,0 @@ -/******************************************************************************* - * - * QuantLibAAD Swaption Benchmark - AAD Runner - * - * AAD benchmarks using XAD tape, Forge JIT, and Forge JIT-AVX. - * This executable is compiled WITH XAD (and optionally Forge). - * - * Usage: - * ./benchmark_aad [--production|--cva|--all] [--quick] [--xad-only] - * - * Output format is designed to be parsed and combined with FD results. - * - * Copyright (C) 2010-2026 Xcelerit Computing Limited - * SPDX-License-Identifier: AGPL-3.0-or-later - * - ******************************************************************************/ - -#include "benchmark_common.hpp" -#include "benchmark_pricing.hpp" - -// XAD includes -#include - -// Forge JIT backends (conditionally included) -#if defined(QLAAD_HAS_FORGE) -#include -#include -#endif - -#include -#include -#include - -using namespace benchmark; -using Clock = std::chrono::high_resolution_clock; -using DurationMs = std::chrono::duration; - -// Use QuantLib's Real which is xad::AReal via qlaad.hpp -using RealAD = QuantLib::Real; -using tape_type = RealAD::tape_type; - -// ============================================================================ -// XAD Tape-based AAD Benchmark (unified single/dual-curve) -// ============================================================================ - -template -void runXADBenchmarkT(const BenchmarkConfig& config, const LMMSetup& setup, - Size nrTrails, size_t warmup, size_t bench, - double& mean, double& stddev, - ValidationResult* validation = nullptr) -{ - std::vector times; - - for (size_t iter = 0; iter < warmup + bench; ++iter) - { - auto t_start = Clock::now(); - - tape_type tape; - - // Register forecasting curve inputs - std::vector depositRates(config.numDeposits); - std::vector swapRatesAD(config.numSwaps); - for (Size idx = 0; idx < config.numDeposits; ++idx) - depositRates[idx] = config.depoRates[idx]; - for (Size idx = 0; idx < config.numSwaps; ++idx) - swapRatesAD[idx] = config.swapRates[idx]; - - // Register discounting curve inputs (OIS) for dual-curve - std::vector oisDepoRates; - std::vector oisSwapRatesAD; - if constexpr (UseDualCurve) - { - oisDepoRates.resize(config.numOisDeposits); - oisSwapRatesAD.resize(config.numOisSwaps); - for (Size idx = 0; idx < config.numOisDeposits; ++idx) - oisDepoRates[idx] = config.oisDepoRates[idx]; - for (Size idx = 0; idx < config.numOisSwaps; ++idx) - oisSwapRatesAD[idx] = config.oisSwapRates[idx]; - } - - tape.registerInputs(depositRates); - tape.registerInputs(swapRatesAD); - if constexpr (UseDualCurve) - { - tape.registerInputs(oisDepoRates); - tape.registerInputs(oisSwapRatesAD); - } - tape.newRecording(); - - // Price using appropriate function - RealAD price; - if constexpr (UseDualCurve) - price = priceSwaptionDualCurve( - config, setup, depositRates, swapRatesAD, oisDepoRates, oisSwapRatesAD, nrTrails); - else - price = priceSwaption(config, setup, depositRates, swapRatesAD, nrTrails); - - // Compute adjoints - tape.registerOutput(price); - derivative(price) = 1.0; - tape.computeAdjoints(); - - auto t_end = Clock::now(); - - if (iter >= warmup) - { - times.push_back(DurationMs(t_end - t_start).count()); - } - - // Capture validation data on first iteration (before clearing tape) - if (validation && iter == 0) - { - validation->method = "XADFULL"; - validation->pv = value(price); - validation->sensitivities.resize(config.numMarketQuotes()); - Size q = 0; - for (Size idx = 0; idx < config.numDeposits; ++idx) - validation->sensitivities[q++] = derivative(depositRates[idx]); - for (Size idx = 0; idx < config.numSwaps; ++idx) - validation->sensitivities[q++] = derivative(swapRatesAD[idx]); - if constexpr (UseDualCurve) - { - for (Size idx = 0; idx < config.numOisDeposits; ++idx) - validation->sensitivities[q++] = derivative(oisDepoRates[idx]); - for (Size idx = 0; idx < config.numOisSwaps; ++idx) - validation->sensitivities[q++] = derivative(oisSwapRatesAD[idx]); - } - } - - tape.clearAll(); - } - - mean = computeMean(times); - stddev = computeStddev(times); -} - -// Convenience wrappers for backward compatibility -inline void runXADBenchmark(const BenchmarkConfig& config, const LMMSetup& setup, - Size nrTrails, size_t warmup, size_t bench, - double& mean, double& stddev, - ValidationResult* validation = nullptr) -{ - runXADBenchmarkT(config, setup, nrTrails, warmup, bench, mean, stddev, validation); -} - -inline void runXADBenchmarkDualCurve(const BenchmarkConfig& config, const LMMSetup& setup, - Size nrTrails, size_t warmup, size_t bench, - double& mean, double& stddev, - ValidationResult* validation = nullptr) -{ - runXADBenchmarkT(config, setup, nrTrails, warmup, bench, mean, stddev, validation); -} - -// ============================================================================ -// Chain Rule Helper (used by XAD-Split and JIT methods) -// ============================================================================ - -inline void applyChainRule(const double* __restrict jacobian, - const double* __restrict derivatives, - double* __restrict result, - std::size_t numIntermediates, - std::size_t numInputs) -{ - for (std::size_t j = 0; j < numInputs; ++j) - result[j] = 0.0; - - for (std::size_t i = 0; i < numIntermediates; ++i) - { - const double deriv_i = derivatives[i]; - const double* jac_row = jacobian + i * numInputs; - for (std::size_t j = 0; j < numInputs; ++j) - { - result[j] += deriv_i * jac_row[j]; - } - } -} - -// ============================================================================ -// JIT Helper Structures and Functions -// ============================================================================ - -// Result of Phase 1: Curve bootstrap and Jacobian computation -struct CurveSetupResult { - Array initRates; // Forward rates from LMM process - RealAD swapRate; // Fair swap rate - std::vector intermediates; // All intermediates (for tape) - std::vector jacobian; // Jacobian matrix (row-major) - ext::shared_ptr process; // LMM process for path evolution - Size numIntermediates; // Number of intermediates - Size numMarketQuotes; // Number of market inputs - // Additional intermediates for dual-curve (OIS discount factors as doubles) - std::vector oisDiscountFactors; -}; - -// Phase 1 for Single-Curve: Build curve, extract intermediates, compute Jacobian -CurveSetupResult buildSingleCurveAndJacobian( - const BenchmarkConfig& config, - const LMMSetup& setup, - tape_type& tape) -{ - CurveSetupResult result; - result.numMarketQuotes = config.numMarketQuotes(); - result.numIntermediates = config.size + 1; // forward rates + swap rate - - // Register inputs - std::vector depositRates(config.numDeposits); - std::vector swapRatesAD(config.numSwaps); - for (Size idx = 0; idx < config.numDeposits; ++idx) - depositRates[idx] = config.depoRates[idx]; - for (Size idx = 0; idx < config.numSwaps; ++idx) - swapRatesAD[idx] = config.swapRates[idx]; - - tape.registerInputs(depositRates); - tape.registerInputs(swapRatesAD); - tape.newRecording(); - - // Build curve and get intermediates - RelinkableHandle euriborTS; - auto euribor6m = ext::make_shared(euriborTS); - euribor6m->addFixing(Date(2, September, 2005), 0.04); - - std::vector> instruments; - for (Size idx = 0; idx < config.numDeposits; ++idx) - { - auto depoQuote = ext::make_shared(depositRates[idx]); - instruments.push_back(ext::make_shared( - Handle(depoQuote), config.depoTenors[idx], setup.fixingDays, - setup.calendar, ModifiedFollowing, true, setup.dayCounter)); - } - for (Size idx = 0; idx < config.numSwaps; ++idx) - { - auto swapQuote = ext::make_shared(swapRatesAD[idx]); - instruments.push_back(ext::make_shared( - Handle(swapQuote), config.swapTenors[idx], - setup.calendar, Annual, Unadjusted, Thirty360(Thirty360::BondBasis), - euribor6m)); - } - - auto yieldCurve = ext::make_shared>( - setup.settlementDate, instruments, setup.dayCounter); - yieldCurve->enableExtrapolation(); - - // Extract zero rates - std::vector curveDates; - std::vector zeroRates; - curveDates.push_back(setup.settlementDate); - zeroRates.push_back(yieldCurve->zeroRate(setup.settlementDate, setup.dayCounter, Continuous).rate()); - Date endDate = setup.settlementDate + config.curveEndYears * Years; - curveDates.push_back(endDate); - zeroRates.push_back(yieldCurve->zeroRate(endDate, setup.dayCounter, Continuous).rate()); - - std::vector zeroRates_ql; - for (const auto& r : zeroRates) zeroRates_ql.push_back(r); - - // Build LMM process - RelinkableHandle termStructure; - ext::shared_ptr index(new Euribor6M(termStructure)); - index->addFixing(Date(2, September, 2005), 0.04); - termStructure.linkTo(ext::make_shared(curveDates, zeroRates_ql, setup.dayCounter)); - - result.process = ext::make_shared(config.size, index); - result.process->setCovarParam(ext::shared_ptr( - new LfmCovarianceProxy( - ext::make_shared( - result.process->fixingTimes(), 0.291, 1.483, 0.116, 0.00001), - ext::make_shared(config.size, 0.5)))); - - ext::shared_ptr fwdSwap( - new VanillaSwap(Swap::Receiver, 1.0, - setup.schedule, 0.05, setup.dayCounter, - setup.schedule, index, 0.0, index->dayCounter())); - fwdSwap->setPricingEngine(ext::make_shared( - index->forwardingTermStructure())); - result.swapRate = fwdSwap->fairRate(); - - // Extract intermediates (forward rates + swap rate) - result.initRates = result.process->initialValues(); - result.intermediates.resize(result.numIntermediates); - for (Size k = 0; k < config.size; ++k) - result.intermediates[k] = result.initRates[k]; - result.intermediates[config.size] = result.swapRate; - - // Register intermediates as outputs - tape.registerOutputs(result.intermediates); - - // Compute Jacobian: d(intermediates) / d(market inputs) - result.jacobian.resize(result.numIntermediates * result.numMarketQuotes); - for (Size i = 0; i < result.numIntermediates; ++i) - { - tape.clearDerivatives(); - derivative(result.intermediates[i]) = 1.0; - tape.computeAdjoints(); - for (Size j = 0; j < config.numDeposits; ++j) - result.jacobian[i * result.numMarketQuotes + j] = derivative(depositRates[j]); - for (Size j = 0; j < config.numSwaps; ++j) - result.jacobian[i * result.numMarketQuotes + config.numDeposits + j] = derivative(swapRatesAD[j]); - } - - tape.deactivate(); - - return result; -} - -// Phase 1 for Dual-Curve: Build both curves, extract intermediates, compute Jacobian -CurveSetupResult buildDualCurveAndJacobian( - const BenchmarkConfig& config, - const LMMSetup& setup, - tape_type& tape) -{ - CurveSetupResult result; - result.numMarketQuotes = config.numMarketQuotes(); - // Intermediates: forward rates + swap rate + OIS discount factors - result.numIntermediates = config.size + 1 + config.size; // 2*size + 1 - - // Register forecasting curve inputs - std::vector depositRates(config.numDeposits); - std::vector swapRatesAD(config.numSwaps); - for (Size idx = 0; idx < config.numDeposits; ++idx) - depositRates[idx] = config.depoRates[idx]; - for (Size idx = 0; idx < config.numSwaps; ++idx) - swapRatesAD[idx] = config.swapRates[idx]; - - // Register discounting curve inputs (OIS) - std::vector oisDepoRates(config.numOisDeposits); - std::vector oisSwapRatesAD(config.numOisSwaps); - for (Size idx = 0; idx < config.numOisDeposits; ++idx) - oisDepoRates[idx] = config.oisDepoRates[idx]; - for (Size idx = 0; idx < config.numOisSwaps; ++idx) - oisSwapRatesAD[idx] = config.oisSwapRates[idx]; - - tape.registerInputs(depositRates); - tape.registerInputs(swapRatesAD); - tape.registerInputs(oisDepoRates); - tape.registerInputs(oisSwapRatesAD); - tape.newRecording(); - - // Build FORECASTING curve (Euribor deposits + swaps) - RelinkableHandle euriborTS; - auto euribor6m = ext::make_shared(euriborTS); - euribor6m->addFixing(Date(2, September, 2005), 0.04); - - std::vector> forecastingInstruments; - for (Size idx = 0; idx < config.numDeposits; ++idx) - { - auto depoQuote = ext::make_shared(depositRates[idx]); - forecastingInstruments.push_back(ext::make_shared( - Handle(depoQuote), config.depoTenors[idx], setup.fixingDays, - setup.calendar, ModifiedFollowing, true, setup.dayCounter)); - } - for (Size idx = 0; idx < config.numSwaps; ++idx) - { - auto swapQuote = ext::make_shared(swapRatesAD[idx]); - forecastingInstruments.push_back(ext::make_shared( - Handle(swapQuote), config.swapTenors[idx], - setup.calendar, Annual, Unadjusted, Thirty360(Thirty360::BondBasis), - euribor6m)); - } - - auto forecastingCurve = ext::make_shared>( - setup.settlementDate, forecastingInstruments, setup.dayCounter); - forecastingCurve->enableExtrapolation(); - euriborTS.linkTo(forecastingCurve); - - // Build DISCOUNTING curve (OIS deposits + swaps) - RelinkableHandle oisTS; - auto eonia = ext::make_shared(oisTS); - - std::vector> discountingInstruments; - for (Size idx = 0; idx < config.numOisDeposits; ++idx) - { - auto oisDepoQuote = ext::make_shared(oisDepoRates[idx]); - discountingInstruments.push_back(ext::make_shared( - Handle(oisDepoQuote), config.oisDepoTenors[idx], setup.fixingDays, - setup.calendar, ModifiedFollowing, true, Actual360())); - } - for (Size idx = 0; idx < config.numOisSwaps; ++idx) - { - auto oisSwapQuote = ext::make_shared(oisSwapRatesAD[idx]); - discountingInstruments.push_back(ext::make_shared( - 2, config.oisSwapTenors[idx], Handle(oisSwapQuote), eonia)); - } - - auto discountingCurve = ext::make_shared>( - setup.settlementDate, discountingInstruments, setup.dayCounter); - discountingCurve->enableExtrapolation(); - oisTS.linkTo(discountingCurve); - - // Extract zero rates for LMM from forecasting curve - std::vector curveDates; - std::vector zeroRates; - curveDates.push_back(setup.settlementDate); - zeroRates.push_back(forecastingCurve->zeroRate(setup.settlementDate, setup.dayCounter, Continuous).rate()); - Date endDate = setup.settlementDate + config.curveEndYears * Years; - curveDates.push_back(endDate); - zeroRates.push_back(forecastingCurve->zeroRate(endDate, setup.dayCounter, Continuous).rate()); - - std::vector zeroRates_ql; - for (const auto& r : zeroRates) zeroRates_ql.push_back(r); - - // Build LMM process using forecasting curve - RelinkableHandle termStructure; - ext::shared_ptr index(new Euribor6M(termStructure)); - index->addFixing(Date(2, September, 2005), 0.04); - termStructure.linkTo(ext::make_shared(curveDates, zeroRates_ql, setup.dayCounter)); - - result.process = ext::make_shared(config.size, index); - result.process->setCovarParam(ext::shared_ptr( - new LfmCovarianceProxy( - ext::make_shared( - result.process->fixingTimes(), 0.291, 1.483, 0.116, 0.00001), - ext::make_shared(config.size, 0.5)))); - - // Get swap rate (using OIS curve for discounting) - ext::shared_ptr fwdSwap( - new VanillaSwap(Swap::Receiver, 1.0, - setup.schedule, 0.05, setup.dayCounter, - setup.schedule, index, 0.0, index->dayCounter())); - fwdSwap->setPricingEngine(ext::make_shared( - Handle(discountingCurve))); - result.swapRate = fwdSwap->fairRate(); - - // Extract intermediates: - // [0, size-1]: forward rates from LMM process - // [size]: swap rate - // [size+1, 2*size]: OIS discount factors at accrual end times - result.initRates = result.process->initialValues(); - result.intermediates.resize(result.numIntermediates); - - // Forward rates - for (Size k = 0; k < config.size; ++k) - result.intermediates[k] = result.initRates[k]; - - // Swap rate - result.intermediates[config.size] = result.swapRate; - - // OIS discount factors - for (Size k = 0; k < config.size; ++k) - { - Time t = setup.accrualEnd[k]; - result.intermediates[config.size + 1 + k] = discountingCurve->discount(t); - } - - // Register intermediates as outputs - tape.registerOutputs(result.intermediates); - - // Compute Jacobian via XAD adjoints - result.jacobian.resize(result.numIntermediates * result.numMarketQuotes); - - for (Size i = 0; i < result.numIntermediates; ++i) - { - tape.clearDerivatives(); - derivative(result.intermediates[i]) = 1.0; - tape.computeAdjoints(); - - Size col = 0; - // Forecasting inputs - for (Size j = 0; j < config.numDeposits; ++j) - result.jacobian[i * result.numMarketQuotes + col++] = derivative(depositRates[j]); - for (Size j = 0; j < config.numSwaps; ++j) - result.jacobian[i * result.numMarketQuotes + col++] = derivative(swapRatesAD[j]); - // Discounting inputs - for (Size j = 0; j < config.numOisDeposits; ++j) - result.jacobian[i * result.numMarketQuotes + col++] = derivative(oisDepoRates[j]); - for (Size j = 0; j < config.numOisSwaps; ++j) - result.jacobian[i * result.numMarketQuotes + col++] = derivative(oisSwapRatesAD[j]); - } - - tape.deactivate(); - - return result; -} - -// Phase 1 for CVA: Build all curves (forecasting, discounting, credit), extract intermediates, compute Jacobian -// Intermediates: forward rates + swap rate + OIS discounts + counterpartySurvival + ownSurvival -CurveSetupResult buildCVACurveAndJacobian( - const BenchmarkConfig& config, - const LMMSetup& setup, - tape_type& tape) -{ - CurveSetupResult result; - result.numMarketQuotes = config.numMarketQuotes(); - // Intermediates: forward rates + swap rate + OIS discount factors + 2 survival probabilities - // = size + 1 + size + 2 = 2*size + 3 - result.numIntermediates = config.size + 1 + config.size + 2; - - // Register forecasting curve inputs - std::vector depositRates(config.numDeposits); - std::vector swapRatesAD(config.numSwaps); - for (Size idx = 0; idx < config.numDeposits; ++idx) - depositRates[idx] = config.depoRates[idx]; - for (Size idx = 0; idx < config.numSwaps; ++idx) - swapRatesAD[idx] = config.swapRates[idx]; - - // Register discounting curve inputs (OIS) - std::vector oisDepoRates(config.numOisDeposits); - std::vector oisSwapRatesAD(config.numOisSwaps); - for (Size idx = 0; idx < config.numOisDeposits; ++idx) - oisDepoRates[idx] = config.oisDepoRates[idx]; - for (Size idx = 0; idx < config.numOisSwaps; ++idx) - oisSwapRatesAD[idx] = config.oisSwapRates[idx]; - - // Register credit curve inputs (CDS spreads) - std::vector counterpartyCdsSpreadsAD(config.numCounterpartyCds); - std::vector ownCdsSpreadsAD(config.numOwnCds); - for (Size idx = 0; idx < config.numCounterpartyCds; ++idx) - counterpartyCdsSpreadsAD[idx] = config.counterpartyCdsSpreads[idx]; - for (Size idx = 0; idx < config.numOwnCds; ++idx) - ownCdsSpreadsAD[idx] = config.ownCdsSpreads[idx]; - - tape.registerInputs(depositRates); - tape.registerInputs(swapRatesAD); - tape.registerInputs(oisDepoRates); - tape.registerInputs(oisSwapRatesAD); - tape.registerInputs(counterpartyCdsSpreadsAD); - tape.registerInputs(ownCdsSpreadsAD); - tape.newRecording(); - - // Build FORECASTING curve (Euribor deposits + swaps) - RelinkableHandle euriborTS; - auto euribor6m = ext::make_shared(euriborTS); - euribor6m->addFixing(Date(2, September, 2005), 0.04); - - std::vector> forecastingInstruments; - for (Size idx = 0; idx < config.numDeposits; ++idx) - { - auto depoQuote = ext::make_shared(depositRates[idx]); - forecastingInstruments.push_back(ext::make_shared( - Handle(depoQuote), config.depoTenors[idx], setup.fixingDays, - setup.calendar, ModifiedFollowing, true, setup.dayCounter)); - } - for (Size idx = 0; idx < config.numSwaps; ++idx) - { - auto swapQuote = ext::make_shared(swapRatesAD[idx]); - forecastingInstruments.push_back(ext::make_shared( - Handle(swapQuote), config.swapTenors[idx], - setup.calendar, Annual, Unadjusted, Thirty360(Thirty360::BondBasis), - euribor6m)); - } - - auto forecastingCurve = ext::make_shared>( - setup.settlementDate, forecastingInstruments, setup.dayCounter); - forecastingCurve->enableExtrapolation(); - euriborTS.linkTo(forecastingCurve); - - // Build DISCOUNTING curve (OIS deposits + swaps) - RelinkableHandle oisTS; - auto eonia = ext::make_shared(oisTS); - - std::vector> discountingInstruments; - for (Size idx = 0; idx < config.numOisDeposits; ++idx) - { - auto oisDepoQuote = ext::make_shared(oisDepoRates[idx]); - discountingInstruments.push_back(ext::make_shared( - Handle(oisDepoQuote), config.oisDepoTenors[idx], setup.fixingDays, - setup.calendar, ModifiedFollowing, true, Actual360())); - } - for (Size idx = 0; idx < config.numOisSwaps; ++idx) - { - auto oisSwapQuote = ext::make_shared(oisSwapRatesAD[idx]); - discountingInstruments.push_back(ext::make_shared( - 2, config.oisSwapTenors[idx], Handle(oisSwapQuote), eonia)); - } - - auto discountingCurve = ext::make_shared>( - setup.settlementDate, discountingInstruments, setup.dayCounter); - discountingCurve->enableExtrapolation(); - oisTS.linkTo(discountingCurve); - - // Build COUNTERPARTY CREDIT curve (CDS spreads -> hazard rates) - std::vector> counterpartyHelpers; - for (Size idx = 0; idx < config.numCounterpartyCds; ++idx) - { - auto cdsQuote = ext::make_shared(counterpartyCdsSpreadsAD[idx]); - counterpartyHelpers.push_back(ext::make_shared( - Handle(cdsQuote), config.counterpartyCdsTenors[idx], - 0, setup.calendar, Quarterly, ModifiedFollowing, - DateGeneration::CDS2015, setup.dayCounter, - config.counterpartyRecovery, Handle(discountingCurve))); - } - - auto counterpartyCreditCurve = ext::make_shared>( - setup.settlementDate, counterpartyHelpers, setup.dayCounter); - counterpartyCreditCurve->enableExtrapolation(); - - // Build OWN CREDIT curve (CDS spreads -> hazard rates) - std::vector> ownHelpers; - for (Size idx = 0; idx < config.numOwnCds; ++idx) - { - auto cdsQuote = ext::make_shared(ownCdsSpreadsAD[idx]); - ownHelpers.push_back(ext::make_shared( - Handle(cdsQuote), config.ownCdsTenors[idx], - 0, setup.calendar, Quarterly, ModifiedFollowing, - DateGeneration::CDS2015, setup.dayCounter, - config.ownRecovery, Handle(discountingCurve))); - } - - auto ownCreditCurve = ext::make_shared>( - setup.settlementDate, ownHelpers, setup.dayCounter); - ownCreditCurve->enableExtrapolation(); - - // Extract zero rates for LMM from forecasting curve - std::vector curveDates; - std::vector zeroRates; - curveDates.push_back(setup.settlementDate); - zeroRates.push_back(forecastingCurve->zeroRate(setup.settlementDate, setup.dayCounter, Continuous).rate()); - Date endDate = setup.settlementDate + config.curveEndYears * Years; - curveDates.push_back(endDate); - zeroRates.push_back(forecastingCurve->zeroRate(endDate, setup.dayCounter, Continuous).rate()); - - std::vector zeroRates_ql; - for (const auto& r : zeroRates) zeroRates_ql.push_back(r); - - // Build LMM process using forecasting curve - RelinkableHandle termStructure; - ext::shared_ptr index(new Euribor6M(termStructure)); - index->addFixing(Date(2, September, 2005), 0.04); - termStructure.linkTo(ext::make_shared(curveDates, zeroRates_ql, setup.dayCounter)); - - result.process = ext::make_shared(config.size, index); - result.process->setCovarParam(ext::shared_ptr( - new LfmCovarianceProxy( - ext::make_shared( - result.process->fixingTimes(), 0.291, 1.483, 0.116, 0.00001), - ext::make_shared(config.size, 0.5)))); - - // Get swap rate (using OIS curve for discounting) - ext::shared_ptr fwdSwap( - new VanillaSwap(Swap::Receiver, 1.0, - setup.schedule, 0.05, setup.dayCounter, - setup.schedule, index, 0.0, index->dayCounter())); - fwdSwap->setPricingEngine(ext::make_shared( - Handle(discountingCurve))); - result.swapRate = fwdSwap->fairRate(); - - // Extract intermediates: - // [0, size-1]: forward rates from LMM process - // [size]: swap rate - // [size+1, 2*size]: OIS discount factors at accrual end times - // [2*size+1]: counterparty survival probability - // [2*size+2]: own survival probability - result.initRates = result.process->initialValues(); - result.intermediates.resize(result.numIntermediates); - - // Forward rates - for (Size k = 0; k < config.size; ++k) - result.intermediates[k] = result.initRates[k]; - - // Swap rate - result.intermediates[config.size] = result.swapRate; - - // OIS discount factors - for (Size k = 0; k < config.size; ++k) - { - Time t = setup.accrualEnd[k]; - result.intermediates[config.size + 1 + k] = discountingCurve->discount(t); - } - - // Survival probabilities at swaption maturity - Time swaptionMaturity = setup.accrualStart[config.i_opt]; - result.intermediates[2 * config.size + 1] = counterpartyCreditCurve->survivalProbability(swaptionMaturity); - result.intermediates[2 * config.size + 2] = ownCreditCurve->survivalProbability(swaptionMaturity); - - // Register intermediates as outputs - tape.registerOutputs(result.intermediates); - - // Compute Jacobian via XAD adjoints - result.jacobian.resize(result.numIntermediates * result.numMarketQuotes); - - for (Size i = 0; i < result.numIntermediates; ++i) - { - tape.clearDerivatives(); - derivative(result.intermediates[i]) = 1.0; - tape.computeAdjoints(); - - Size col = 0; - // Forecasting inputs - for (Size j = 0; j < config.numDeposits; ++j) - result.jacobian[i * result.numMarketQuotes + col++] = derivative(depositRates[j]); - for (Size j = 0; j < config.numSwaps; ++j) - result.jacobian[i * result.numMarketQuotes + col++] = derivative(swapRatesAD[j]); - // Discounting inputs - for (Size j = 0; j < config.numOisDeposits; ++j) - result.jacobian[i * result.numMarketQuotes + col++] = derivative(oisDepoRates[j]); - for (Size j = 0; j < config.numOisSwaps; ++j) - result.jacobian[i * result.numMarketQuotes + col++] = derivative(oisSwapRatesAD[j]); - // Credit curve inputs - for (Size j = 0; j < config.numCounterpartyCds; ++j) - result.jacobian[i * result.numMarketQuotes + col++] = derivative(counterpartyCdsSpreadsAD[j]); - for (Size j = 0; j < config.numOwnCds; ++j) - result.jacobian[i * result.numMarketQuotes + col++] = derivative(ownCdsSpreadsAD[j]); - } - - tape.deactivate(); - - return result; -} - -// ============================================================================ -// Shared Payoff Recording - used by both JIT and XAD-Split -// ============================================================================ - -// Variables holder for payoff computation (templated on AD type) -template -struct PayoffVariables { - std::vector initRates; - ADType swapRate; - std::vector oisDiscounts; // Empty for single-curve - std::vector randoms; // For JIT only; XAD-Split uses plain doubles - // CVA-specific fields - ADType counterpartySurvival; // Counterparty survival probability at swaption maturity - ADType ownSurvival; // Own survival probability at swaption maturity -}; - -// ============================================================================ -// Pre-computed LMM Step Data (optimization: compute matrices once, not per path) -// ============================================================================ - -struct LMMStepData { - std::vector diff; // Diffusion matrices per step - std::vector covariance; // Covariance matrices per step - std::vector m; // nextIndexReset per step - std::vector dt; // Time step per step - std::vector sdt; // sqrt(dt) per step - std::vector tau; // Accrual periods (constant) - Size numSteps; - Size numFactors; -}; - -// Pre-compute step data for LMM evolution (call once before path loop) -inline LMMStepData precomputeLMMStepData( - const ext::shared_ptr& process, - const LMMSetup& setup, - Size size) -{ - LMMStepData data; - data.numSteps = setup.fullGridSteps; - data.numFactors = setup.numFactors; - - data.diff.resize(data.numSteps); - data.covariance.resize(data.numSteps); - data.m.resize(data.numSteps); - data.dt.resize(data.numSteps); - data.sdt.resize(data.numSteps); - - for (Size step = 1; step <= data.numSteps; ++step) - { - Time t0 = setup.grid[step - 1]; - data.diff[step - 1] = process->covarParam()->diffusion(t0, Array()); - data.covariance[step - 1] = process->covarParam()->covariance(t0, Array()); - data.m[step - 1] = process->nextIndexReset(t0); - data.dt[step - 1] = value(setup.grid.dt(step - 1)); - data.sdt[step - 1] = std::sqrt(data.dt[step - 1]); - } - - // Pre-compute accrual periods - const std::vector