diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml deleted file mode 100644 index 6136a209a..000000000 --- a/.github/workflows/build-and-test.yml +++ /dev/null @@ -1,95 +0,0 @@ -name: Build and Test - -on: - push: - branches: [ master, develop, release ] - pull_request: - branches: [ master, develop, release ] - -env: - OMP_NUM_THREADS: 1 - BUILD_HOME: build - TEST_HOME: nrtests - PACKAGE_NAME: vcpkg-export-20240724-151754.1.0.0 - PKG_NAME: vcpkg-export-20240724-151754 - -jobs: - unit_test: - name: Build and unit test - runs-on: windows-2019 - environment: testing - defaults: - run: - shell: cmd - - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Install boost-test - env: - REMOTE_STORE: "https://nuget.pkg.github.com/USEPA/index.json" - USERNAME: michaeltryby - run: | - nuget sources add -Name github -Source ${{ env.REMOTE_STORE }} -Username ${{ env.USERNAME }} -Password ${{ secrets.ACCESS_TOKEN }} - nuget install ${{env.PKG_NAME}} -Source github - - - name: Build - env: - TOOL_CHAIN_PATH: \scripts\buildsystems\vcpkg.cmake - run: | - cmake -B.\build -DBUILD_TESTS=ON -DCMAKE_TOOLCHAIN_FILE=.\${{env.PACKAGE_NAME}}${{env.TOOL_CHAIN_PATH}} . - cmake --build .\build --config DEBUG - - - name: Unit Test - run: ctest --test-dir .\build -C Debug --output-on-failure - - - reg_test: - name: Build and reg test - runs-on: windows-2019 - defaults: - run: - shell: cmd - working-directory: ci-tools/windows - - steps: - - name: Checkout swmm repo - uses: actions/checkout@v4 - - - name: Checkout ci-tools repo - uses: actions/checkout@v4 - with: - repository: USEPA/swmm-ci-tools - ref: master - path: ci-tools - - - name: Setup python - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - - name: Install requirements - run: | - python -m pip install --upgrade pip - python -m pip install -r requirements-swmm.txt - - - name: Build - run: make.cmd /g "Visual Studio 16 2019" - - - name: Before reg test - env: - NRTESTS_URL: https://github.com/USEPA/swmm-nrtestsuite - BENCHMARK_TAG: v2.5.0-dev - run: before-nrtest.cmd ${{ env.BENCHMARK_TAG }} - - - name: Run reg test - run: run-nrtests.cmd %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% - - - name: Upload artifacts - if: ${{ always() }} - uses: actions/upload-artifact@v4 - with: - name: build-test-artifacts - path: upload/*.* - \ No newline at end of file diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml new file mode 100644 index 000000000..c0ab02d07 --- /dev/null +++ b/.github/workflows/deployment.yml @@ -0,0 +1,215 @@ +name: Deployment + +on: + push: + tags: ["v*.*.*"] + workflow_dispatch: + +env: + VCPKG_ROOT: ${{ github.workspace }}/vcpkg + VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite" + OMP_NUM_THREADS: 1 + +jobs: + # ────────────────────────────────────────────────────────────────────── + # Build release binaries + # ────────────────────────────────────────────────────────────────────── + build: + name: "Build (${{ matrix.alias }})" + permissions: write-all + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + alias: Linux-x64 + cmake_preset: Linux + shell_ext: .sh + vcpkg_triplet: x64-linux + cmake_osx_arch: "" + + - os: macos-latest + alias: macOS-arm64 + cmake_preset: Darwin + shell_ext: .sh + vcpkg_triplet: arm64-osx + cmake_osx_arch: arm64 + + - os: macos-15-intel + alias: macOS-x64 + cmake_preset: Darwin + shell_ext: .sh + vcpkg_triplet: x64-osx + cmake_osx_arch: x86_64 + + - os: windows-latest + alias: Windows-x64 + cmake_preset: Windows + shell_ext: .bat + vcpkg_triplet: x64-windows + cmake_osx_arch: "" + + runs-on: ${{ matrix.os }} + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Checkout vcpkg + uses: actions/checkout@v5 + with: + repository: microsoft/vcpkg + ref: 2025.02.14 + path: vcpkg + + - name: Install OpenMP (macOS) + if: runner.os == 'macOS' + run: brew install libomp + + - name: Install Ninja (Linux) + if: runner.os == 'Linux' + run: sudo apt-get update && sudo apt-get install -y ninja-build + + - name: Bootstrap vcpkg (Windows) + if: runner.os == 'Windows' + working-directory: ${{ env.VCPKG_ROOT }} + run: | + .\bootstrap-vcpkg${{ matrix.shell_ext }} + .\vcpkg.exe integrate install + + - name: Bootstrap vcpkg (Unix) + if: runner.os != 'Windows' + working-directory: ${{ env.VCPKG_ROOT }} + run: | + ./bootstrap-vcpkg${{ matrix.shell_ext }} + chmod +x vcpkg + + - name: Export GitHub Actions cache variables + uses: actions/github-script@v8 + with: + script: | + core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); + core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); + + - name: Configure (Release) + run: > + cmake + --preset=${{ matrix.cmake_preset }} + -B build-${{ matrix.vcpkg_triplet }} + -DCMAKE_OSX_ARCHITECTURES=${{ matrix.cmake_osx_arch }} + + - name: Build and package + run: cmake --build build-${{ matrix.vcpkg_triplet }} --target package --config Release + + - name: Upload release artifacts + uses: actions/upload-artifact@v5 + with: + name: release-${{ matrix.vcpkg_triplet }} + path: | + build-${{ matrix.vcpkg_triplet }}/*.tar.gz + build-${{ matrix.vcpkg_triplet }}/*.zip + + # ────────────────────────────────────────────────────────────────────── + # Python wheels + # ────────────────────────────────────────────────────────────────────── + wheels: + name: "Python Wheels (${{ matrix.alias }})" + needs: build + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + alias: Linux-x64 + vcpkg_triplet: x64-linux + cmake_osx_arch: "" + + - os: macos-latest + alias: macOS-arm64 + cmake_preset: Darwin + shell_ext: .sh + vcpkg_triplet: arm64-osx + cmake_osx_arch: arm64 + + - os: macos-15-intel + alias: macOS-x64 + cmake_preset: Darwin + shell_ext: .sh + vcpkg_triplet: x64-osx + cmake_osx_arch: x86_64 + + - os: windows-latest + alias: Windows-x64 + vcpkg_triplet: x64-windows + cmake_osx_arch: "" + + runs-on: ${{ matrix.os }} + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Install OpenMP and Ninja (macOS) + if: runner.os == 'macOS' + run: brew install libomp ninja + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.13" + + - name: Install Python requirements + working-directory: python + run: | + python -m pip install --upgrade pip + python -m pip install -r requirements.txt + + - name: Clean stale build cache + run: python -c "import shutil, os; shutil.rmtree('python/_skbuild', ignore_errors=True)" + + - name: Build wheels + uses: pypa/cibuildwheel@v2.23.2 + with: + package-dir: ./python + output-dir: ./python/wheelhouse + env: + CMAKE_OSX_ARCHITECTURES: ${{ matrix.cmake_osx_arch }} + CIBW_BUILD_VERBOSITY: 3 + CIBW_BEFORE_BUILD_LINUX: pip install ninja + CIBW_BEFORE_BUILD_MACOS: brew install ninja libomp && pip install delocate + CIBW_ENVIRONMENT_MACOS: MACOSX_DEPLOYMENT_TARGET=15.0 CMAKE_OSX_ARCHITECTURES=${{ matrix.cmake_osx_arch }} + CIBW_BEFORE_BUILD_WINDOWS: pip install delvewheel + CIBW_ENVIRONMENT_WINDOWS: CMAKE_GENERATOR="Visual Studio 17 2022" + CIBW_REPAIR_WHEEL_COMMAND_LINUX: auditwheel repair -w {dest_dir} {wheel} + CIBW_REPAIR_WHEEL_COMMAND_MACOS: delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel} + CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: "" + CIBW_TEST_REQUIRES: pytest numpy + CIBW_TEST_COMMAND: pytest {package}/tests -v --import-mode=importlib + + - name: Upload Python wheels + if: always() + uses: actions/upload-artifact@v5 + with: + name: python-wheels-${{ matrix.vcpkg_triplet }} + path: | + python/wheelhouse/*.whl + python/dist/*.whl + # ────────────────────────────────────────────────────────────────────── + # Create GitHub Release + # ────────────────────────────────────────────────────────────────────── + release: + name: Create GitHub Release + if: startsWith(github.ref, 'refs/tags/v') + needs: [build, wheels] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Download all artifacts + uses: actions/download-artifact@v5 + with: + path: dist + + - name: Create release + uses: softprops/action-gh-release@v2 + with: + generate_release_notes: true + files: dist/**/* diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 000000000..6cc4c59d3 --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,131 @@ +name: Documentation + +on: + push: + # branches: [main, develop, swmm6_rel] + branches: [main, develop] + tags: ["v*.*.*"] + pull_request: + branches: [main] + workflow_dispatch: + +# Allow only one concurrent deployment. Do NOT cancel in-progress runs so +# that production deployments always complete. +concurrency: + group: pages + cancel-in-progress: false + +# ───────────────────────────────────────────────────────────────────────────── +# Job 1 – Build Python (Sphinx) documentation +# ───────────────────────────────────────────────────────────────────────────── +jobs: + build_python_docs: + name: Build Python Bindings Docs (Sphinx) + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Set up Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install Sphinx and theme dependencies + run: | + pip install \ + "sphinx>=7.0" \ + "pydata-sphinx-theme>=0.15.0" \ + "myst-parser>=3.0" \ + "sphinx-autodoc-typehints>=2.0" \ + numpy + + - name: Build Sphinx HTML docs + # conf.py adds python/ to sys.path and installs the Cython stub-file + # finder, so no compiled extensions or package installation is needed. + run: | + sphinx-build -b html python/docs python/docs/_build/html + + - name: Upload Python docs artifact + uses: actions/upload-artifact@v5 + with: + name: python-docs + path: python/docs/_build/html/ + retention-days: 1 + +# ───────────────────────────────────────────────────────────────────────────── +# Job 2 – Build C/C++ documentation (Doxygen) +# Runs after the Python docs build per the documented deployment order. +# ───────────────────────────────────────────────────────────────────────────── + build_doxygen_docs: + name: Build C/C++ Engine Docs (Doxygen) + needs: build_python_docs + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Install Doxygen and Graphviz + run: sudo apt-get install -y doxygen graphviz + + - name: Build Doxygen HTML docs + working-directory: docs + run: doxygen Doxyfile + + - name: Upload Doxygen docs artifact + uses: actions/upload-artifact@v5 + with: + name: doxygen-docs + path: docs/html/ + retention-days: 1 + +# ───────────────────────────────────────────────────────────────────────────── +# Job 3 – Merge both doc sets and deploy to GitHub Pages +# Final site layout: +# / → Doxygen C/C++ engine docs (main entry point) +# /python/ → Sphinx openswmm Python bindings docs +# ───────────────────────────────────────────────────────────────────────────── + deploy: + name: Deploy to GitHub Pages + if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/swmm6_rel') + needs: [build_python_docs, build_doxygen_docs] + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + pages: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + steps: + - name: Download Doxygen docs + uses: actions/download-artifact@v5 + with: + name: doxygen-docs + path: site/ + + - name: Download Python docs + uses: actions/download-artifact@v5 + with: + name: python-docs + path: site/python/ + + - name: Configure GitHub Pages + uses: actions/configure-pages@v5 + + - name: Upload combined site artifact + uses: actions/upload-pages-artifact@v4 + with: + path: site/ + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v5 + diff --git a/.github/workflows/regression_testing.yml b/.github/workflows/regression_testing.yml new file mode 100644 index 000000000..565870602 --- /dev/null +++ b/.github/workflows/regression_testing.yml @@ -0,0 +1,123 @@ +name: Regression Testing + +on: + push: + branches: [main, develop] + workflow_dispatch: + +env: + VCPKG_ROOT: ${{ github.workspace }}/vcpkg + VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite" + OMP_NUM_THREADS: 1 + +jobs: + regression: + name: "Regression (${{ matrix.alias }})" + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + alias: Linux-x64 + cmake_preset: Linux + shell_ext: .sh + vcpkg_triplet: x64-linux + cmake_osx_arch: "" + + - os: windows-latest + alias: Windows-x64 + cmake_preset: Windows + shell_ext: .bat + vcpkg_triplet: x64-windows + cmake_osx_arch: "" + + - os: macos-latest + alias: macOS-arm64 + cmake_preset: Darwin + shell_ext: .sh + vcpkg_triplet: arm64-osx + cmake_osx_arch: arm64 + + - os: macos-15-intel + alias: macOS-x64 + cmake_preset: Darwin + shell_ext: .sh + vcpkg_triplet: x64-osx + cmake_osx_arch: x86_64 + + runs-on: ${{ matrix.os }} + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Checkout vcpkg + uses: actions/checkout@v5 + with: + repository: microsoft/vcpkg + ref: 2025.02.14 + path: vcpkg + + - name: Install Ninja (Linux) + if: runner.os == 'Linux' + run: sudo apt-get update && sudo apt-get install -y ninja-build + + - name: Bootstrap vcpkg (Windows) + if: runner.os == 'Windows' + working-directory: ${{ env.VCPKG_ROOT }} + run: | + .\bootstrap-vcpkg${{ matrix.shell_ext }} + .\vcpkg.exe integrate install + + - name: Bootstrap vcpkg (Unix) + if: runner.os != 'Windows' + working-directory: ${{ env.VCPKG_ROOT }} + run: | + ./bootstrap-vcpkg${{ matrix.shell_ext }} + chmod +x vcpkg + + - name: Export GitHub Actions cache variables + uses: actions/github-script@v8 + with: + script: | + core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); + core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.13" + + - name: Install Python test requirements + run: | + python -m pip install --upgrade pip + python -m pip install pytest numpy + + - name: Configure + run: > + cmake + --preset=${{ matrix.cmake_preset }}-debug + -B build-${{ matrix.vcpkg_triplet }} + -DOPENSWMM_BUILD_TESTS=OFF + -DOPENSWMM_BUILD_UNIT_TESTS=ON + -DOPENSWMM_BUILD_REGRESSION_TESTS=ON + -DCMAKE_OSX_ARCHITECTURES=${{ matrix.cmake_osx_arch }} + + - name: Build + run: cmake --build build-${{ matrix.vcpkg_triplet }} --config Debug + + - name: Unit tests (sanity check) + run: ctest --test-dir build-${{ matrix.vcpkg_triplet }} -C Debug -L unit --output-on-failure + + - name: C++ regression tests + run: ctest --test-dir build-${{ matrix.vcpkg_triplet }} -C Debug -L regression --output-on-failure + + - name: Python regression tests + working-directory: tests/regression_testing + run: python -m pytest -v --tb=short + + - name: Upload artifacts + if: always() + uses: actions/upload-artifact@v5 + with: + name: regression-${{ matrix.vcpkg_triplet }} + path: tests/regression_testing/results/ diff --git a/.github/workflows/unit_testing.yml b/.github/workflows/unit_testing.yml new file mode 100644 index 000000000..5ca5e9b2c --- /dev/null +++ b/.github/workflows/unit_testing.yml @@ -0,0 +1,221 @@ +name: Unit Testing + +on: + push: + branches: [master, main, develop] + pull_request: + branches: [master, main, develop] + +env: + VCPKG_ROOT: ${{ github.workspace }}/vcpkg + VCPKG_EXE: ${{ github.workspace }}/vcpkg/vcpkg + VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite" + OMP_NUM_THREADS: 1 + +jobs: + # ────────────────────────────────────────────────────────────────────── + # C / C++ Engine — build, unit test, and package + # ────────────────────────────────────────────────────────────────────── + engine: + name: "C++ Engine (${{ matrix.alias }})" + permissions: + contents: read + actions: write + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + alias: Linux-x64 + cmake_preset: Linux + shell_ext: .sh + vcpkg_triplet: x64-linux + cmake_osx_arch: "" + + - os: macos-latest + alias: macOS-arm64 + cmake_preset: Darwin + shell_ext: .sh + vcpkg_triplet: arm64-osx + cmake_osx_arch: arm64 + + - os: macos-15-intel + alias: macOS-x64 + cmake_preset: Darwin + shell_ext: .sh + vcpkg_triplet: x64-osx + cmake_osx_arch: x86_64 + + - os: windows-latest + alias: Windows-x64 + cmake_preset: Windows + shell_ext: .bat + vcpkg_triplet: x64-windows + cmake_osx_arch: "" + + runs-on: ${{ matrix.os }} + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Checkout vcpkg + uses: actions/checkout@v5 + with: + repository: microsoft/vcpkg + ref: 2025.02.14 + path: vcpkg + + - name: Install OpenMP (macOS) + if: runner.os == 'macOS' + run: brew install libomp + + - name: Install Ninja (Linux) + if: runner.os == 'Linux' + run: sudo apt-get update && sudo apt-get install -y ninja-build + + - name: Bootstrap vcpkg (Windows) + if: runner.os == 'Windows' + working-directory: ${{ env.VCPKG_ROOT }} + run: | + .\bootstrap-vcpkg${{ matrix.shell_ext }} + .\vcpkg.exe integrate install + + - name: Bootstrap vcpkg (Unix) + if: runner.os != 'Windows' + working-directory: ${{ env.VCPKG_ROOT }} + run: | + ./bootstrap-vcpkg${{ matrix.shell_ext }} + chmod +x vcpkg + + - name: Export GitHub Actions cache variables + uses: actions/github-script@v8 + with: + script: | + core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); + core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); + + - name: Configure + run: > + cmake + --preset=${{ matrix.cmake_preset }}-debug + -B build-${{ matrix.vcpkg_triplet }} + -DOPENSWMM_BUILD_TESTS=OFF + -DOPENSWMM_BUILD_UNIT_TESTS=ON + -DCMAKE_OSX_ARCHITECTURES=${{ matrix.cmake_osx_arch }} + + - name: Build + run: cmake --build build-${{ matrix.vcpkg_triplet }} --config Debug + + - name: Unit tests + run: ctest --test-dir build-${{ matrix.vcpkg_triplet }} -C Debug -L unit --output-on-failure + + - name: Package + run: cmake --build build-${{ matrix.vcpkg_triplet }} --target package + + - name: Upload build artifacts + if: always() + uses: actions/upload-artifact@v5 + with: + name: engine-${{ matrix.vcpkg_triplet }} + path: | + build-${{ matrix.vcpkg_triplet }}/*.tar.gz + build-${{ matrix.vcpkg_triplet }}/*.zip + + # ────────────────────────────────────────────────────────────────────── + # Python bindings — build, test, and wheel packaging + # ────────────────────────────────────────────────────────────────────── + # python: + # name: "Python (${{ matrix.alias }})" + # needs: engine + # strategy: + # fail-fast: false + # matrix: + # include: + # - os: ubuntu-latest + # alias: Linux-x64 + # vcpkg_triplet: x64-linux + # cmake_osx_arch: "" + + # - os: macos-latest + # alias: macOS-arm64 + # vcpkg_triplet: arm64-osx + # cmake_osx_arch: arm64 + + # - os: macos-15-intel + # alias: macOS-x64 + # vcpkg_triplet: x64-osx + # cmake_osx_arch: x86_64 + + # - os: windows-latest + # alias: Windows-x64 + # vcpkg_triplet: x64-windows + # cmake_osx_arch: "" + + # runs-on: ${{ matrix.os }} + # steps: + # - name: Checkout repository + # uses: actions/checkout@v5 + + # - name: Install OpenMP and Ninja (macOS) + # if: runner.os == 'macOS' + # run: brew install libomp ninja + + # - name: Install Ninja (Linux) + # if: runner.os == 'Linux' + # run: sudo apt-get update && sudo apt-get install -y ninja-build + + # - name: Set up Python + # uses: actions/setup-python@v5 + # with: + # python-version: "3.13" + + # - name: Install Python requirements + # working-directory: python + # run: | + # python -m pip install --upgrade pip + # python -m pip install -r requirements.txt + + # - name: Build and install (Windows) + # if: runner.os == 'Windows' + # working-directory: python + # env: + # CMAKE_GENERATOR: "Visual Studio 17 2022" + # run: python -m pip install . + + # - name: Build and install (Unix) + # if: runner.os != 'Windows' + # working-directory: python + # env: + # CMAKE_OSX_ARCHITECTURES: ${{ matrix.cmake_osx_arch }} + # run: python -m pip install . + + # - name: Run Python tests + # env: + # CMAKE_OSX_ARCHITECTURES: ${{ matrix.cmake_osx_arch }} + # working-directory: python + # run: | + # # Temporarily hide source tree so installed package is used + # mv openswmm openswmm_src + # python -m pytest -v tests + # mv openswmm_src openswmm + + # - name: Build wheel (Windows) + # if: runner.os == 'Windows' + # working-directory: python + # env: + # CMAKE_GENERATOR: "Visual Studio 17 2022" + # run: python setup.py bdist_wheel + + # - name: Build wheel (Unix) + # if: runner.os != 'Windows' + # working-directory: python + # env: + # CMAKE_OSX_ARCHITECTURES: ${{ matrix.cmake_osx_arch }} + # run: python setup.py bdist_wheel + + # - name: Upload Python wheel + # if: always() + # uses: actions/upload-artifact@v5 + # with: + # name: python-wheel-${{ matrix.vcpkg_triplet }} + # path: python/dist/*.whl diff --git a/.gitignore b/.gitignore index 7e8e71376..dfc051bcd 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,25 @@ .settings/ build/ +build-*/ nrtests/ upload/ *_export.h +docs/html/ + +# add python ignore files and file types +python/build/ +python/dist/ +python/OpenSWMMCore.egg-info/ +python/_skbuild/ +__pycache__/ +*.pyc +*.pyo +*.pyd +*.so +*.egg +*.egg-info +*.manifest +*.spec + diff --git a/AUTHORS.md b/AUTHORS.md new file mode 100644 index 000000000..fdcaac1f0 --- /dev/null +++ b/AUTHORS.md @@ -0,0 +1,15 @@ +# Authors + +OpenSWMM is built on the EPA SWMM 5.x foundation and extended by the contributors listed below. + +## Project Lead + +- **Caleb Buahin** -- Project lead, architect, and primary developer of the OpenSWMM 6.x engine rewrite (data-oriented SoA architecture, plugin system, GeoPackage I/O, C/C++ API, dynamic wave solver alignment, Python bindings). + +## Contributors + +## AI-Assisted Development + +Portions of the OpenSWMM 6.x codebase were developed with the assistance of: + +- **Claude** (Anthropic) -- Code generation, architecture design, GeoPackage strategy and implementation, test development. diff --git a/Build.md b/Build.md deleted file mode 100644 index b9352e45d..000000000 --- a/Build.md +++ /dev/null @@ -1,32 +0,0 @@ - - - -## Building SWMM Locally on Windows - - -### Dependencies - -Before the project can be built the required dependencies must be installed. - -**Summary of Build Dependencies: Windows** - - - Build - - Build Tools for Visual Studio 2017 - - CMake 3.13 - - -### Build - -SWMM can be built with one simple command. -``` -\> cd swmm -\swmm>tools\make.cmd -``` diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..65cae5baa --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,86 @@ +# Changelog + +All notable changes to the OpenSWMM Engine are documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [6.0.0-alpha.1] — 2026-03-25 + +### Added + +#### New Engine Architecture +- **Data-oriented engine** — Refactored core data structures to Structure of Arrays (SoA) layout for cache efficiency and SIMD-friendly computation. +- **Reentrant design** — All simulation state encapsulated in an opaque `SWMM_Engine` handle, eliminating global state and enabling multiple independent simulations per process. +- **Plugin-based I/O** — Output and report writing abstracted through a plugin interface with a dedicated I/O thread and double-buffered snapshots. +- **Engine lifecycle state machine** — Explicit states: CREATED → OPENED → INITIALIZED → STARTED → RUNNING → ENDED → CLOSED. + +#### Comprehensive C API (19 headers) +- `openswmm_engine.h` — Engine lifecycle, error codes, state machine. +- `openswmm_model.h` — Model building, validation, serialization, options. +- `openswmm_nodes.h` — Junctions, outfalls, storage nodes, dividers. +- `openswmm_links.h` — Conduits, pumps, orifices, weirs, outlets with 20 cross-section shapes. +- `openswmm_subcatchments.h` — Subcatchments, infiltration (Horton/Green-Ampt/Curve Number), landuse coverage. +- `openswmm_gages.h` — Rain gages with timeseries and file data sources. +- `openswmm_pollutants.h` — Pollutant definitions and runtime quality injection. +- `openswmm_tables.h` — Time series, curves, patterns, and cursor-optimized lookups. +- `openswmm_inflows.h` — External inflows, dry weather flow, RDII. +- `openswmm_controls.h` — Control rule expressions and direct link setting/status actions. +- `openswmm_infrastructure.h` — Transects, streets, inlets, LID controls and LID usage. +- `openswmm_spatial.h` — CRS, coordinates, polylines, polygons for all object types. +- `openswmm_quality.h` — Landuse, buildup/washoff functions, treatment expressions. +- `openswmm_massbalance.h` — Continuity errors and cumulative flux totals. +- `openswmm_callbacks.h` — Progress, warning, step-begin/end, plugin state, and hot-start-missing callbacks. +- `openswmm_hotstart.h` — Hot start file save/load/modify/query with workflow examples. +- `openswmm_statistics.h` — Node, link, and subcatchment simulation statistics. +- `openswmm_engine_export.h` — Auto-generated shared library export macros. + +#### Features +- **Hot start API** — Save, open, modify, query, and close hot start files through a transparent C ABI. +- **CRS support** — Coordinate reference system specification via OPTIONS section. +- **User flags** — Custom USER_FLAGS section for user-defined metadata on objects. +- **Plugin SDK** — Header-only development kit for building output/report plugins. +- **HEC-22 inlet analysis** — Street inlet capture with grate, curb, slotted, and custom inlet types (from SWMM 5.2). +- **Variable speed pumps** — Type5 pump curves with speed scaling. +- **New storage shapes** — Conical and pyramidal shapes with elliptical/rectangular bases. +- **Python bindings** — Cython-based bindings with solver context manager, iterative stepping, and output reading. + +#### Testing & CI +- **Google Test migration** — All unit tests converted from Boost.Test to Google Test 1.15.2. +- **Comprehensive test suite** — 73+ legacy engine tests, 41 legacy output tests, and new engine unit tests. +- **Reorganized test structure** — `tests/unit/legacy/{engine,output}` and `tests/unit/{engine,output}`. +- **Multi-platform CI** — GitHub Actions for Windows x64, Linux x64, macOS x64, and macOS ARM64. +- **Performance benchmarks** — Google Benchmark integration for critical-path profiling. + +#### Documentation +- **Doxygen API documentation** — All 19 public C API headers thoroughly documented with `@brief`, `@details`, `@param`, `@returns`, `@see`, and `@note` tags. +- **Technical reference manuals** — Hydrology, Hydraulics, and Water Quality reference manuals updated for OpenSWMM. +- **User manual** — Comprehensive user manual with modeling capabilities, typical applications, and input/output descriptions. +- **Author/license metadata** — All new engine source files annotated with `@author`, `@copyright`, and `@license` Doxygen tags. + +### Changed + +- **Project renamed** from `OpenSWMMCore` to `openswmm` with `openswmm.engine` as the primary library output name. +- **CMake minimum version** raised to 3.21 (from 3.15). +- **C++ standard** set to C++20 (from C++11/14). +- **C standard** set to C17. +- **CMake options** namespaced to `OPENSWMM_*` prefix (legacy `OPENSWMMCORE_*` aliases preserved). +- **Version scheme** updated to SemVer 2.0.0 with pre-release tags. +- **vcpkg** adopted as the dependency manager (replacing NuGet-based Boost distribution). +- **CI/CD pipelines** cleaned up: updated to `actions/checkout@v4`, `actions/setup-python@v5`, `actions/upload-artifact@v4`; removed stale branch triggers; fixed CMake flag from `-DBUILD_TESTS=ON` to `-DOPENSWMM_BUILD_TESTS=ON`. + +### Removed + +- **Boost.Test dependency** — Replaced entirely by Google Test. +- **NuGet package dependency** — Regression testing no longer requires external NuGet-hosted Boost packages. +- **Global state** — Eliminated from the new engine (legacy solver globals preserved in `src/legacy/`). + +### Fixed + +- **CI CMake flag** — Unit testing workflow was passing `-DBUILD_TESTS=ON` which did not match the actual `OPENSWMM_BUILD_TESTS` option, preventing tests from being built in CI. +- **Documentation workflow** — Removed stale `bug_fixes` branch trigger; updated to `actions/checkout@v4`. +- **Export header** — Fixed misplaced `@author`/`@copyright` block that was injected inside a `#define` preprocessor directive in `openswmm_engine_export.h`. + +## [5.2.0] — Legacy + +Last EPA-maintained release. See [docs/SWMM_5.2.0.md](docs/SWMM_5.2.0.md) for details on HEC-22 inlet analysis, new storage shapes, variable speed pumps, and control rule enhancements. diff --git a/CMakeLists.txt b/CMakeLists.txt index 99af0d1cd..fb9c91404 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,69 +1,224 @@ # -# CMakeLists.txt - CMake configuration file for swmm-solver +# CMakeLists.txt - CMake configuration file for OpenSWMM Engine # -# Created: July 11, 2019 -# Modified: Aug 16, 2022 +# Project: openswmm.engine +# Description: Open Storm Water Management Model Engine +# Version: 6.0.0-alpha.1 # -# Author: Michael E. Tryby -# US EPA ORD/CESER +# This is the root CMake file for the openswmm.engine project which contains: +# - openswmm_legacy_engine : Original EPA SWMM 5.x C solver (preserved unmodified) +# - openswmm_legacy_output : Original EPA SWMM 5.x binary output reader +# - openswmm_engine : New refactored C++20 engine with plugin I/O +# - openswmm_plugin_sdk : Header-only plugin development kit +# +# Build system: CMake 3.21+ with vcpkg for dependency management +# Test framework: Google Test + Google Benchmark +# +# See docs/MASTER_IMPLEMENTATION_PLAN.md for the full refactoring roadmap. # +cmake_minimum_required(VERSION 3.21...4.2) -cmake_minimum_required (VERSION 3.13) - -if("${CMAKE_BINARY_DIR}" STREQUAL "${CMAKE_SOURCE_DIR}") - message(FATAL_ERROR "In-source builds are disabled.") +# Prevent in-source builds +if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) + message(FATAL_ERROR "In-source builds not allowed. Please make a new directory (called a build directory) and run CMake from there.") endif() - -project(swmm-solver - VERSION 5.2.4 +# Modern project declaration with metadata +project( + openswmm + VERSION 6.0.0 + DESCRIPTION "Open Storm Water Management Model Engine" + HOMEPAGE_URL "https://hydrocouple.org/projects/openswmm" LANGUAGES C CXX ) -# Append local dir to module search path -list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) +# Pre-release label (e.g. "alpha.1", "beta.1", "rc.1"). Leave empty for a final release. +set(OPENSWMM_PRERELEASE "alpha.1" CACHE STRING "Pre-release tag (e.g. alpha.1, beta.1, rc.1). Empty for a final release.") + +if(OPENSWMM_PRERELEASE) + set(OPENSWMM_FULL_VERSION "${PROJECT_VERSION}-${OPENSWMM_PRERELEASE}") +else() + set(OPENSWMM_FULL_VERSION "${PROJECT_VERSION}") +endif() + +# Backwards compatibility aliases for downstream consumers +set(OPENSWMMCORE_PRERELEASE "${OPENSWMM_PRERELEASE}") +set(OPENSWMMCORE_FULL_VERSION "${OPENSWMM_FULL_VERSION}") + +message(STATUS "OpenSWMM Engine version: ${OPENSWMM_FULL_VERSION}") + +# Modern build type handling +get_property(isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(NOT isMultiConfig) + set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Release" "MinSizeRel" "RelWithDebInfo") +endif() -# Sets the position independent code property for all targets +# Essential global settings +set(CMAKE_C_STANDARD 17) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_POSITION_INDEPENDENT_CODE ON) -# Sets default install prefix when cmakecache is initialized for first time -if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) - set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/install CACHE PATH "..." FORCE) +# Modern feature options with namespacing +option(OPENSWMM_BUILD_TESTS "Build all tests (unit + regression). Convenience flag." ON) +option(OPENSWMM_BUILD_UNIT_TESTS "Build unit tests only" OFF) +option(OPENSWMM_BUILD_REGRESSION_TESTS "Build regression tests only" OFF) +option(OPENSWMM_BUILD_BENCHMARKS "Build Google Benchmark performance tests" OFF) +option(OPENSWMM_INSTALL "Install openswmm libraries" ON) +option(OPENSWMM_BUILD_PYTHON "Build Python bindings" OFF) +option(OPENSWMM_WITH_GEOPACKAGE "Build GeoPackage I/O library (requires sqlite3)" OFF) +option(OPENSWMM_BUILD_2D "Build optional 2D surface routing module (requires SUNDIALS)" OFF) + +# OPENSWMM_BUILD_TESTS is a convenience flag that enables both unit and regression +if(OPENSWMM_BUILD_TESTS) + set(OPENSWMM_BUILD_UNIT_TESTS ON CACHE BOOL "" FORCE) + set(OPENSWMM_BUILD_REGRESSION_TESTS ON CACHE BOOL "" FORCE) endif() +# Include essential modules +include(CMakePackageConfigHelpers) +include(GNUInstallDirs) +include(GenerateExportHeader) + +# Build information (improved) +find_package(Git QUIET) +if(Git_FOUND) + execute_process( + COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE OPENSWMMCORE_GIT_HASH + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + RESULT_VARIABLE GIT_RESULT + ) + if(NOT GIT_RESULT EQUAL 0) + set(OPENSWMMCORE_GIT_HASH "unknown") + endif() +else() + set(OPENSWMMCORE_GIT_HASH "unknown") +endif() -# Define install locations (will be prepended by install prefix) -set(TOOL_DIST "bin") -set(INCLUDE_DIST "include") -set(LIBRARY_DIST "lib") -set(CONFIG_DIST "cmake") +# Create interface library for global headers (backwards compatible alias kept) +add_library(openswmmcore INTERFACE) +add_library(OpenSWMMCore::openswmmcore ALIAS openswmmcore) +add_library(openswmm::common ALIAS openswmmcore) +# Configure global version header +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/include/version.h.in + ${CMAKE_CURRENT_BINARY_DIR}/include/version.h +) -# Define build options -option(BUILD_TESTS "Builds component tests (requires Boost)" OFF) -option(BUILD_DEF "Builds library with def file interface" OFF) +# Set include directories for the common interface +target_include_directories(openswmmcore + INTERFACE + $ + $ + $ +) +# Install generated headers +install( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/include/version.h + DESTINATION + ${CMAKE_INSTALL_INCLUDEDIR}/openswmm +) -# Add project subdirectories -add_subdirectory(src/outfile) -add_subdirectory(src/solver) -add_subdirectory(src/run) +# Install all public headers +install( + DIRECTORY include/openswmm + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + FILES_MATCHING PATTERN "*.h" PATTERN "*.hpp" +) -if(BUILD_TESTS) +# Add testing subdirectories conditionally +if(OPENSWMM_BUILD_UNIT_TESTS OR OPENSWMM_BUILD_REGRESSION_TESTS) enable_testing() + find_package(GTest CONFIG REQUIRED) add_subdirectory(tests) endif() +# Add benchmarks subdirectory conditionally +if(OPENSWMM_BUILD_BENCHMARKS) + find_package(benchmark CONFIG REQUIRED) + add_subdirectory(tests/benchmarks) +endif() -# Create install rules for vcruntime.dll, msvcp.dll, vcomp.dll etc. -set(CMAKE_INSTALL_OPENMP_LIBRARIES TRUE) -include(InstallRequiredSystemLibraries) +# Modern package configuration +if(OPENSWMM_INSTALL OR OPENSWMMCORE_INSTALL) + configure_package_config_file( + cmake/OpenSWMMCoreConfig.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/OpenSWMMCoreConfig.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/OpenSWMMCore + PATH_VARS CMAKE_INSTALL_INCLUDEDIR CMAKE_INSTALL_LIBDIR + ) + + write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/OpenSWMMCoreConfigVersion.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMinorVersion # ✅ More appropriate than AnyNewerVersion + ) + + install( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/OpenSWMMCoreConfig.cmake + ${CMAKE_CURRENT_BINARY_DIR}/OpenSWMMCoreConfigVersion.cmake + DESTINATION + ${CMAKE_INSTALL_LIBDIR}/cmake/OpenSWMMCore + ) + + install( + EXPORT OpenSWMMCoreTargets + FILE OpenSWMMCoreTargets.cmake + NAMESPACE OpenSWMMCore:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/OpenSWMMCore + ) + + install( + TARGETS openswmmcore + EXPORT OpenSWMMCoreTargets + ) +endif() +# Add subdirectories +add_subdirectory(src) + +# Optional Python bindings (Cython extensions compiled via scikit-build). +# Typically invoked indirectly by `pip install ./python`, but can also be +# enabled as part of a top-level CMake build: +# cmake -DOPENSWMM_BUILD_PYTHON=ON .. +if(OPENSWMM_BUILD_PYTHON) + # Discover Python and extension-building dependencies without making them + # hard requirements for a plain C/C++ build. If any of these packages are + # missing, the Python bindings will be skipped with a warning instead of + # causing CMake configuration to fail. + find_package(Python3 QUIET COMPONENTS Interpreter Development.Module) + find_package(PythonExtensions QUIET) + find_package(Cython QUIET) + + if(Python3_FOUND AND PythonExtensions_FOUND AND Cython_FOUND) + add_subdirectory(python/openswmm) + else() + message(WARNING + "OPENSWMM_BUILD_PYTHON is enabled, but required Python build " + "dependencies (Python3, PythonExtensions, Cython) were not found. " + "Skipping build of Python bindings." + ) + endif() +endif() -# Configure CPack driven installer package +# Simplified CPack +set(CPACK_PACKAGE_NAME "openswmm-engine") set(CPACK_GENERATOR "ZIP") -set(CPACK_PACKAGE_VENDOR "US_EPA") -set(CPACK_ARCHIVE_FILE_NAME "swmm") - +set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}") +set(CPACK_PACKAGE_FILE_NAME "openswmm-engine-${OPENSWMM_FULL_VERSION}") +set(CPACK_PACKAGE_VENDOR "https://hydrocouple.org") +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${PROJECT_DESCRIPTION}") include(CPack) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 000000000..dc8a16db3 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,216 @@ +{ + "version": 3, + "configurePresets": [ + { + "name": "default-debug", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/debug", + "toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "default", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build", + "toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", + "cacheVariables": { + } + }, + { + "name": "Windows", + "inherits": "default", + "description": "Windows build using Visual Studio generator", + "generator": "Visual Studio 17 2022", + "binaryDir": "${sourceDir}/build/windows", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_C_FLAGS": "/O2 /GL /fp:precise /W4", + "CMAKE_CXX_FLAGS": "/O2 /GL /fp:precise /W4", + "CMAKE_EXE_LINKER_FLAGS": "/LTCG /OPT:REF /OPT:ICF", + "CMAKE_SHARED_LINKER_FLAGS": "/LTCG /OPT:REF /OPT:ICF", + "CMAKE_MODULE_LINKER_FLAGS": "/LTCG /OPT:REF /OPT:ICF", + "CMAKE_EXPORT_COMPILE_COMMANDS": "YES", + "CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS": "OFF", + "CMAKE_C_FLAGS_RELEASE": "/O2 /GL /fp:precise /W4", + "CMAKE_CXX_FLAGS_RELEASE": "/O2 /GL /fp:precise /W4", + "CMAKE_C_FLAGS_DEBUG": "/Zi /Od /DDEBUG /W4 /fp:precise", + "CMAKE_CXX_FLAGS_DEBUG": "/Zi /Od /DDEBUG /W4 /fp:precise" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + }, + { + "name": "Windows-debug", + "inherits": "default-debug", + "description": "Windows build for debugging using Visual Studio generator", + "generator": "Visual Studio 17 2022", + "binaryDir": "${sourceDir}/build/windows-debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_C_FLAGS": "/Zi /Od /DDEBUG /W4 /fp:precise", + "CMAKE_CXX_FLAGS": "/Zi /Od /DDEBUG /W4 /fp:precise", + "CMAKE_EXE_LINKER_FLAGS": "/DEBUG", + "CMAKE_EXPORT_COMPILE_COMMANDS": "YES", + "CMAKE_C_FLAGS_DEBUG": "/Zi /Od /DDEBUG /W4 /fp:precise", + "CMAKE_CXX_FLAGS_DEBUG": "/Zi /Od /DDEBUG /W4 /fp:precise" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + }, + { + "name": "Linux", + "inherits": "default", + "description": "Linux build using Ninja generator", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/linux", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_C_FLAGS": "-O2 -flto -fdata-sections -ffunction-sections -fipa-icf -fno-fast-math -fexcess-precision=standard -ffloat-store -Wall -Wextra -fvisibility=hidden", + "CMAKE_CXX_FLAGS": "-O2 -flto -fdata-sections -ffunction-sections -fipa-icf -fno-fast-math -fexcess-precision=standard -ffloat-store -Wall -Wextra -fvisibility=hidden", + "CMAKE_EXE_LINKER_FLAGS": "-Wl,--gc-sections -flto", + "CMAKE_SHARED_LINKER_FLAGS": "-Wl,--gc-sections -flto", + "CMAKE_MODULE_LINKER_FLAGS": "-Wl,--gc-sections -flto", + "CMAKE_EXPORT_COMPILE_COMMANDS": "YES", + "CMAKE_C_FLAGS_RELEASE": "-O2 -flto -fdata-sections -ffunction-sections -fipa-icf -fno-fast-math -fexcess-precision=standard -ffloat-store -Wall -Wextra", + "CMAKE_CXX_FLAGS_RELEASE": "-O2 -flto -fdata-sections -ffunction-sections -fipa-icf -fno-fast-math -fexcess-precision=standard -ffloat-store -Wall -Wextra", + "CMAKE_C_FLAGS_DEBUG": "-O0 -g -DDEBUG -Wall -Wextra -fno-fast-math -fexcess-precision=standard -ffloat-store", + "CMAKE_CXX_FLAGS_DEBUG": "-O0 -g -DDEBUG -Wall -Wextra -fno-fast-math -fexcess-precision=standard -ffloat-store" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + } + }, + { + "name": "Linux-debug", + "inherits": "default-debug", + "description": "Linux build for debugging using Ninja generator", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/linux-debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_C_FLAGS": "-O0 -g -DDEBUG -Wall -Wextra -fno-fast-math -fexcess-precision=standard -ffloat-store -fvisibility=hidden", + "CMAKE_CXX_FLAGS": "-O0 -g -DDEBUG -Wall -Wextra -fno-fast-math -fexcess-precision=standard -ffloat-store -fvisibility=hidden", + "CMAKE_EXE_LINKER_FLAGS": "-g", + "CMAKE_EXPORT_COMPILE_COMMANDS": "YES", + "CMAKE_C_FLAGS_DEBUG": "-O0 -g -DDEBUG -Wall -Wextra -fno-fast-math -fexcess-precision=standard -ffloat-store", + "CMAKE_CXX_FLAGS_DEBUG": "-O0 -g -DDEBUG -Wall -Wextra -fno-fast-math -fexcess-precision=standard -ffloat-store" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + } + }, + { + "name": "Darwin", + "inherits": "default", + "description": "macOS build using Ninja generator", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/darwin", + "environment": { + "VCPKG_OSX_ARCHITECTURES": "arm64;x86_64", + "CC": "clang", + "CXX": "clang++" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_C_FLAGS": "-O2 -flto -fdata-sections -ffunction-sections -fno-fast-math -Wall -Wextra -fvisibility=hidden", + "CMAKE_CXX_FLAGS": "-O2 -flto -fdata-sections -ffunction-sections -fno-fast-math -Wall -Wextra -fvisibility=hidden", + "CMAKE_EXE_LINKER_FLAGS": "-Wl, -flto", + "CMAKE_EXPORT_COMPILE_COMMANDS": "YES", + "CMAKE_C_FLAGS_RELEASE": "-O2 -flto -fdata-sections -ffunction-sections -fno-fast-math -Wall -Wextra", + "CMAKE_CXX_FLAGS_RELEASE": "-O2 -flto -fdata-sections -ffunction-sections -fno-fast-math -Wall -Wextra", + "CMAKE_SHARED_LINKER_FLAGS": "-flto -twolevel_namespace", + "CMAKE_MODULE_LINKER_FLAGS": "-flto", + "CMAKE_C_FLAGS_DEBUG": "-O0 -g -DDEBUG -Wall -Wextra -fno-fast-math", + "CMAKE_CXX_FLAGS_DEBUG": "-O0 -g -DDEBUG -Wall -Wextra -fno-fast-math", + "CMAKE_OSX_ARCHITECTURES": "arm64;x86_64" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Darwin" + } + }, + { + "name": "Darwin-debug", + "inherits": "default-debug", + "description": "macOS build for debugging using Ninja generator", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/darwin-debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_C_FLAGS": "-O0 -g -DDEBUG -Wall -Wextra -fno-fast-math -fvisibility=hidden", + "CMAKE_CXX_FLAGS": "-O0 -g -DDEBUG -Wall -Wextra -fno-fast-math -fvisibility=hidden", + "CMAKE_EXE_LINKER_FLAGS": "-g", + "CMAKE_EXPORT_COMPILE_COMMANDS": "YES", + "CMAKE_SHARED_LINKER_FLAGS": "-twolevel_namespace", + "CMAKE_C_FLAGS_DEBUG": "-O0 -g -DDEBUG -Wall -Wextra -fno-fast-math", + "CMAKE_CXX_FLAGS_DEBUG": "-O0 -g -DDEBUG -Wall -Wextra -fno-fast-math" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Darwin" + } + } + ], + "buildPresets": [ + { + "name": "default", + "hidden": true, + "configurePreset": "default" + }, + { + "name": "default-debug", + "hidden": true, + "configurePreset": "default-debug" + }, + { + "name": "Windows", + "configurePreset": "Windows", + "targets": [ + "package" + ] + }, + { + "name": "Windows-debug", + "description": "Build all targets for debugging on Windows", + "configurePreset": "Windows-debug" + }, + { + "name": "Linux", + "configurePreset": "Linux", + "targets": [ + "package" + ] + }, + { + "name": "Linux-debug", + "description": "Build all targets for debugging on Linux", + "configurePreset": "Linux-debug" + }, + { + "name": "Darwin", + "configurePreset": "Darwin", + "targets": [ + "package" + ] + }, + { + "name": "Darwin-debug", + "description": "Build all targets for debugging on macOS", + "configurePreset": "Darwin-debug" + } + ] +} \ No newline at end of file diff --git a/LICENSE b/LICENSE index 16eeb7954..75bc7c916 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,14 @@ MIT License -Copyright (c) 2025 HydroCouple +Copyright 2026 HydroCouple -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +This project contains original material has been released as part of various USEPA SWMM software over the years. These reside in the +public domain and cannot be claimed. This project also contains new material prepared by the United States +Government for which domestic copyright protection is not available under 17 +USC § 105. diff --git a/README.md b/README.md index cc5bcc571..9a0f7e777 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,645 @@ -ORD Stormwater-Management-Model Solver -================================== +# OpenSWMM Engine -Stormwater Management Model (aka "SWMM") solver only +

+ OpenSWMM MCP +

+**Open Storm Water Management Model — Next-Generation Computational Engine** -## Build Status -[![Build and Test](../../actions/workflows/build-and-test.yml/badge.svg)](../../actions/workflows/build-and-test.yml) +[![Unit Testing](https://github.com/HydroCouple/openswmm.engine/actions/workflows/unit_testing.yml/badge.svg)](https://github.com/HydroCouple/openswmm.engine/actions/workflows/unit_testing.yml) + +[![Documentation](https://github.com/HydroCouple/openswmm.engine/actions/workflows/documentation.yml/badge.svg)](https://github.com/HydroCouple/openswmm.engine/actions/workflows/documentation.yml) + +[![Issues](https://img.shields.io/github/issues/HydroCouple/openswmm.engine)](https://github.com/HydroCouple/openswmm.engine/issues) +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) -## Disclaimer -The United States Environmental Protection Agency (EPA) GitHub project code is provided on an "as is" basis and the user assumes responsibility for its use. EPA has relinquished control of the information and no longer has responsibility to protect the integrity, confidentiality, or availability of the information. Any reference to specific commercial products, processes, or services by service mark, trademark, manufacturer, or otherwise, does not constitute or imply their endorsement, recommendation or favoring by EPA. The EPA seal and logo shall not be used in any manner to imply endorsement of any commercial product or activity by EPA or the United States Government. +[![PyPI](https://img.shields.io/pypi/v/openswmm.svg)](https://pypi.org/project/openswmm) +[![Downloads](https://pepy.tech/badge/openswmm)](https://pepy.tech/project/openswmm) +[![Python](https://img.shields.io/pypi/pyversions/openswmm.svg)](https://pypi.org/project/openswmm) +[![Wheel](https://img.shields.io/pypi/wheel/openswmm.svg)](https://pypi.org/project/openswmm) +--- -## Introduction -This is the official SWMM source code repository maintained by US EPA Office of Research and Development, Center For Environmental Solutions & Emergency Response, Water Infrastructure Division located in Cincinnati, Ohio. +## Overview -SWMM is a dynamic hydrology-hydraulic water quality simulation model. It is used for single event or long-term (continuous) simulation of runoff quantity and quality from primarily urban areas. SWMM source code is written in the C Programming Language and released in the Public Domain. +OpenSWMM Engine is a community-driven, open-source continuation of the EPA Storm Water Management Model (SWMM). SWMM is a dynamic hydrology–hydraulic–water quality simulation model used for single-event or long-term (continuous) simulation of runoff quantity and quality from primarily urban areas. +This project preserves and advances the rich legacy of SWMM by developing high-quality, QA/QC'd code while building an active community for sustainable maintenance and improvement. The community is actively working with organizations including ASCE's Environmental and Water Resources Institute (EWRI) and Water Environment Federation (WEF) to ensure the long-term sustainability of the SWMM codebase. -## Find Out More -The source code distributed here is identical to the code found at the official [SWMM Website](http://www.epa.gov/water-research/storm-water-management-model-swmm). +## What's New in v6.0.0 + +OpenSWMM Engine v6.0.0 is a major modernization of the SWMM computational engine. Key improvements include: + +### Architecture & Performance + +- **Data-Oriented Design** — Core data structures refactored to Structure of Arrays (SoA) layout for cache efficiency and SIMD-friendly computation. +- **Reentrant Engine** — Global state eliminated; all simulation state encapsulated in an opaque `SWMM_Engine` handle, enabling multiple independent simulations in the same process. +- **Plugin-Based I/O** — Output and report writing abstracted through a plugin interface. Plugins receive read-only simulation snapshots on a dedicated I/O thread. +- **C++20 Codebase** — New engine written in modern C++20; legacy EPA SWMM 5.x solver preserved unmodified in `src/legacy/`. + +### New C API + +A comprehensive, domain-split C API replaces the monolithic legacy interface: + +| Header | Domain | +|---|---| +| `openswmm_engine.h` | Engine lifecycle, error codes, state machine | +| `openswmm_model.h` | Model building, validation, serialization, options | +| `openswmm_nodes.h` | Junctions, outfalls, storage, dividers | +| `openswmm_links.h` | Conduits, pumps, orifices, weirs, outlets | +| `openswmm_subcatchments.h` | Subcatchments, infiltration, coverage | +| `openswmm_gages.h` | Rain gages (timeseries and file sources) | +| `openswmm_pollutants.h` | Pollutant definitions and runtime injection | +| `openswmm_tables.h` | Time series, curves, and patterns | +| `openswmm_inflows.h` | External inflows, DWF, RDII | +| `openswmm_controls.h` | Control rules and direct link actions | +| `openswmm_infrastructure.h` | Transects, streets, inlets, LID controls | +| `openswmm_spatial.h` | CRS, coordinates, polylines, polygons | +| `openswmm_quality.h` | Landuse, buildup, washoff, treatment | +| `openswmm_massbalance.h` | Continuity errors and cumulative flux totals | +| `openswmm_callbacks.h` | Progress, warning, and step callbacks | +| `openswmm_hotstart.h` | Hot start file save/load/modify | +| `openswmm_statistics.h` | Node, link, and subcatchment statistics | +| `openswmm_geopackage.h` | GeoPackage I/O — observed data, result queries (optional) | + +### Additional Features + +- **Hot Start API** — Save, load, modify, and query hot start files through a transparent C ABI. +- **CRS Support** — Coordinate reference system specification via OPTIONS for spatial data. +- **User Flags** — Custom USER_FLAGS section for user-defined metadata on objects. +- **GeoPackage I/O** — Optional SQLite-based spatial persistence for model definitions, simulation results, observed data, and network topology (see [GeoPackage](#geopackage-io) below). +- **Input Plugin Interface** — `IInputPlugin` allows alternative file formats (GeoPackage, HDF5, databases) to replace the `.inp` text format. Plugins implement `read()` and `write()` and are discovered via `IPluginComponentInfo`. +- **Plugin SDK** — Header-only development kit for building input/output/report plugins. Plugins advertise capabilities via `has_input()`, `has_output()`, `has_report()` booleans and support optional registration. +- **HEC-22 Inlet Analysis** — Street inlet capture, grate and curb inlets (from SWMM 5.2). +- **Variable Speed Pumps** — Type5 pump curves with speed scaling. +- **New Storage Shapes** — Conical and pyramidal shapes with elliptical/rectangular bases. + +### Process Formulation Enhancements + +The following physics-based and numerical improvements are available or being designed for backward-compatible integration. Each addresses a known simplification or performance limitation in the current SWMM formulation. + +#### Implemented + +- **Semi-Implicit Node Continuity** — The legacy SWMM dynamic wave solver uses a two-branch explicit scheme for node depth updates, switching abruptly between free-surface and surcharged formulations. This can cause oscillations at the surcharge transition boundary. The new `SEMI_IMPLICIT` formulation unifies both regimes into a single equation that implicitly accounts for the pressure-flow coupling via the `sumdqdh` term in the denominator. This eliminates the discontinuous branch and improves convergence stability, particularly for rapidly filling/draining systems. Enabled via `NODE_CONTINUITY SEMI_IMPLICIT` in the `[OPTIONS]` section (default). + +- **Anderson Acceleration for Picard Iteration** — The standard Picard fixed-point iteration used in dynamic wave routing converges slowly for stiff surcharge transitions, often requiring 8-12 iterations. Anderson acceleration (depth-2 mixing) uses the residual history from the previous two iterates to compute an optimal linear combination, typically reducing iteration counts by 25-50%. Each saved iteration avoids the full hot path (geometry, momentum, scatter, node update), so the speedup compounds with other performance optimizations. Physical safeguards ensure non-negative depths; nodes that violate bounds fall back to standard Picard automatically. Enabled via `ANDERSON_ACCEL YES` in the `[OPTIONS]` section (default: NO). + +#### In Development + +- **Spatially Explicit Overland Flow & Groundwater** — SWMM currently has one-way feedback between hydrology and hydraulics: flooded nodes pond over a user-specified area and are reintroduced locally. In reality, surcharge flows traverse the terrain and may re-enter the network at a downstream node. The new module couples a 2D overland flow grid with the 1D pipe network, enabling spatially explicit green infrastructure placement, terrain-routed surcharge, and lateral groundwater flow exchanges, etc. + +- **Dynamic Preissmann Slot** — Standard SWMM uses a fixed-width slot for pressurized conduit transitions, which can cause numerical instability at the open-channel/pressure interface. A dynamic slot formulation smooths the transition with a geometry-dependent slot width, improving stability for rapidly filling/draining conduits. + +- **Spatially Explicit Inlets** — Current inlets are attributes of conduits, limiting bypass routing and preventing backflow or series-inlet configurations. The redesign promotes inlets to mode-switching junction nodes that actively capture street flow when the gutter spread exceeds a threshold and revert to passive junctions otherwise. + +- **LID as Storage Nodes** — Current LID units lack hydraulic feedback: no backpressure from the drainage network, no bidirectional flow, and no control-structure integration. The redesign maps LID layers (surface, media, gravel) onto an extended storage node using a reduced-physics kinematic Richards ODE — a simplified 1D formulation that captures gravity-driven percolation and capillary redistribution between discrete layers without the computational cost of a full 3D variably-saturated solver. This provides physically meaningful moisture dynamics while remaining compatible with SWMM's routing timestep, and enables elevation-mapped underdrain links with full network coupling. + +- **Physics-Based Initial Abstraction Recovery** — SWMM's seasonal RTK calibration for RDII confounds infrastructure leakage fraction with soil moisture state, requiring 12 monthly parameter sets. The new formulation tracks initial abstraction capacity as an exponential decay/recovery process. During dry periods, available capacity recovers with additive time-based and temperature-dependent rate components: + + $$IA_{avail}(t+\Delta t) = IA_{max} - \bigl(IA_{max} - IA_{avail}(t)\bigr) \cdot e^{-k_{rec}(T)\,\Delta t}, \qquad k_{rec}(T) = k_0 + k_T \cdot e^{\,\theta\,(T - T_{ref})}$$ + + The base rate $k_0$ captures gravity drainage and capillary redistribution that occur regardless of temperature, while $k_T \cdot e^{\theta(T-T_{ref})}$ captures thermally-driven evapotranspiration. During storms, capacity is depleted: $IA_{avail}(t+\Delta t) = IA_{avail}(t) \cdot e^{-k_{dep}\,\Delta P}$. Recovery is suppressed when $T < T_{freeze}$, producing emergent seasonal variation (high winter RDII, low summer RDII) from a single RTK set per sewershed — no monthly parameter tables required. + +### Input File Extensions + +The new engine extends the standard SWMM `.inp` format with several new sections while remaining fully backward-compatible with existing input files. Below is a complete `.inp` snippet demonstrating all new features: + +```ini +;; EXTENSION OPTIONS — new and custom keys in [OPTIONS] +;; Standard SWMM options work as before. Any unrecognized key is stored +;; in an extension map accessible at runtime via the C/Python API. + +[OPTIONS] +FLOW_UNITS CFS +INFILTRATION HORTON +FLOW_ROUTING DYNWAVE +START_DATE 01/01/2024 +START_TIME 00:00:00 +END_DATE 01/02/2024 +END_TIME 00:00:00 +ROUTING_STEP 00:00:30 +REPORT_STEP 00:05:00 + +;; New built-in options (v6.0.0) +CRS EPSG:4326 +NODE_CONTINUITY SEMI_IMPLICIT +ANDERSON_ACCEL YES + +;; Extension options — stored automatically, readable by plugins +MY_STABILITY_FACTOR 1.05 +PLUGIN_LOG_LEVEL DEBUG + + +;; USER FLAGS — typed custom metadata on any model object +;; Define a schema of flag names with type and default value. +;; Supported types: BOOLEAN, INTEGER, REAL, STRING. + +[USER_FLAGS] +;;Name Type Default Description +INSPECTED BOOLEAN NO 'Has the asset been field-inspected?' +PRIORITY INTEGER 0 'Maintenance priority (0=none, 1=low, 5=critical)' +ROUGHNESS_ADJ REAL 1.0 'Roughness calibration multiplier' +ASSET_ID STRING "" 'External asset management system ID' + +;; Assign flag values to individual objects. +;; ObjectType can be NODE, LINK, SUBCATCHMENT, or GAGE. + +[USER_FLAG_VALUES] +;;ObjectType ObjectName FlagName Value +NODE J1 INSPECTED YES +NODE J1 PRIORITY 3 +NODE J2 ASSET_ID "AM-00412" +LINK C1 ROUGHNESS_ADJ 1.12 +LINK C1 INSPECTED YES +LINK C2 PRIORITY 1 +SUBCATCHMENT S1 INSPECTED NO +SUBCATCHMENT S1 ASSET_ID "AM-00501" + + +;; PLUGINS — load output/report plugins at runtime +;; Each line: [arg1 arg2 ...] +;; The first token is the path to a .so / .dylib / .dll. +;; Remaining tokens are passed to the plugin's initialize() method. + +[PLUGINS] +;; Write time-series results to HDF5 instead of (or alongside) the .out file +./plugins/hdf5_output.so file="results.h5" compress=9 + +;; Write a custom CSV summary report after simulation +./plugins/csv_report.dylib file="summary.csv" delimiter="," + +;; Multiple plugins can be loaded — they run concurrently on the I/O thread +./plugins/netcdf_output.so file="results.nc" variables="depth,flow" +``` + +#### How Each Feature Works + +**Extension Options** — Any keyword the parser does not recognize in `[OPTIONS]` is stored in `options.ext_options` and can be read at runtime: + +```python +# Python +val = solver.get_option("MY_STABILITY_FACTOR") # "1.05" +``` + +```c +/* C API */ +char buf[256]; +swmm_options_get(engine, "MY_STABILITY_FACTOR", buf, sizeof(buf)); +``` + +**User Flags** — Flags attach structured metadata to objects for asset management, calibration tracking, or custom workflows. Plugins can read them during `validate()` or `prepare()` from the `SimulationContext`. + +**Plugins** — Shared libraries loaded from the `[PLUGINS]` section implement one or both of: + +| Interface | Purpose | Thread | +|---|---|---| +| `IOutputPlugin` | Write time-series results at each reporting step | I/O thread | +| `IReportPlugin` | Write summary statistics after simulation ends | Main thread | + +Each plugin library exports a single entry point: + +```cpp +extern "C" openswmm::IPluginComponentInfo* openswmm_plugin_info(void); +``` + +The engine calls the plugin through a managed lifecycle: + +``` +initialize(args) → validate(ctx) → prepare(ctx) → update(snapshot)... → finalize(ctx) + └─ write_summary(ctx) [report plugins] +``` + +Output plugins receive a `SimulationSnapshot` — a read-only deep copy of simulation state safe to consume on the I/O thread while the main simulation advances. + +**Custom Sections** — Plugins and embedders can also register handlers for entirely new `.inp` sections via the C++ `SectionRegistry` API: + +```cpp +engine.registry().register_custom("MY_CUSTOM_DATA", + [](SimulationContext& ctx, const std::vector& lines) { + for (const auto& line : lines) { + // parse and store in ctx or plugin-managed storage + } + }); +``` + +This allows the `.inp` to contain: + +```ini +[MY_CUSTOM_DATA] +;;ID Param1 Param2 +OBJ_1 42.0 enabled +OBJ_2 17.5 disabled +``` + +The two built-in plugins (`DefaultOutputPlugin` for `.out` and `DefaultReportPlugin` for `.rpt`) are always available and require no `[PLUGINS]` entry. The Plugin SDK headers are in `include/openswmm/plugin_sdk/`. + +### GeoPackage I/O + +The optional `openswmm_geopackage` library provides OGC GeoPackage (SQLite + spatial extensions) as an alternative to the text `.inp` and binary `.out` formats. A single `.gpkg` file serves as a complete project container: + +- **Model input** — Nodes (POINT), links (LINESTRING), subcatchments (MULTIPOLYGON), rain gages, options, curves, timeseries, patterns, pollutants, and transects stored as GeoPackage feature and attribute tables with full CRS support. +- **Network topology** — Explicit `node_links` and `subcatch_routing` tables enable SQL-based graph traversal (upstream traces, contributing area, connectivity validation) via recursive CTEs. +- **Multi-run results** — Multiple simulation runs coexist in one file, each keyed by `simulation_id` with engine version recorded. Per-timestep results and summary statistics are stored in a relational timeseries model inspired by the Observations Data Model (ODM). +- **Observed / sensor data** — Independent measured timeseries for calibration workflows. Series can be linked to model objects for sim-vs-observed comparison and goodness-of-fit queries. + +The library is built when `OPENSWMM_WITH_GEOPACKAGE=ON` and requires SQLite3: + +```bash +cmake -B build -DOPENSWMM_WITH_GEOPACKAGE=ON -DOPENSWMM_BUILD_TESTS=ON +cmake --build build +``` + +#### Plugin Architecture + +GeoPackage I/O is implemented as three plugins behind a single `IPluginComponentInfo`: + +| Plugin | Interface | Role | +|--------|-----------|------| +| `GeoPackageInputPlugin` | `IInputPlugin` | Read/write model definitions (`.gpkg` replaces `.inp`) | +| `GeoPackageOutputPlugin` | `IOutputPlugin` | Write per-timestep results during simulation | +| `GeoPackageReportPlugin` | `IReportPlugin` | Write summary statistics and continuity errors | + +The plugin is discoverable via the standard `openswmm_plugin_info()` C export and advertises all three capabilities (`has_input()`, `has_output()`, `has_report()` all return `true`). Registration is supported via `register_plugin()` / `registered()` on the `IPluginComponentInfo` interface. + +#### C API for External Consumers + +The `openswmm_geopackage.h` C API provides read access to models, results, and topology, plus read-write access to observed data with bulk vector operations and transaction support: + +```c +#include + +SWMM_Gpkg gpkg = swmm_gpkg_open("model.gpkg"); + +// Read model metadata +int nodes = swmm_gpkg_node_count(gpkg, "run_1"); + +// Bulk-read simulation results +double times[1000], values[1000]; +int n = swmm_gpkg_read_result_ts(gpkg, "run_1", "NODE", "J1", + "depth", times, values, 1000); + +// Read summary statistics +double max_depth; +swmm_gpkg_read_summary(gpkg, "run_1", "NODE", "J1", "max_depth", &max_depth); + +// Import observed data (bulk write in a transaction for speed) +swmm_gpkg_begin(gpkg); +int sid = swmm_gpkg_create_observed_series(gpkg, "USGS_flow", + "flow", "LINK", "C1", "USGS NWIS", "CMS"); +const char* ts[] = {"2026-01-15T08:00:00Z", "2026-01-15T09:00:00Z"}; +double vals[] = {1.5, 2.3}; +swmm_gpkg_write_observed_values(gpkg, sid, ts, vals, NULL, 2); +swmm_gpkg_commit(gpkg); + +// Read observed data back +double read_vals[100]; +int count = swmm_gpkg_read_observed_values(gpkg, sid, NULL, 0, + read_vals, 100); + +swmm_gpkg_close(gpkg); +``` + + +### Testing & Quality + +- **Google Test** — All unit tests migrated from Boost.Test to Google Test 1.15.2. +- **Comprehensive Test Suite** — Legacy engine (73+ tests), legacy output (41 tests), and new engine unit tests. +- **Multi-Platform CI** — GitHub Actions pipelines for Windows, Linux, and macOS (Intel + ARM64). +- **Doxygen API Docs** — All 19 public C API headers fully documented with Doxygen conventions. + +--- + +## Project Structure + +``` +openswmm.engine/ +├── include/openswmm/ +│ ├── engine/ # New engine public C API headers (19 headers) +│ └── legacy/ # Legacy SWMM 5.x public headers +├── src/ +│ ├── engine/ # New C++20 engine implementation +│ │ └── input/geopackage/ # Optional GeoPackage I/O library +│ ├── legacy/engine/ # Original EPA SWMM 5.x solver (preserved unmodified) +│ ├── legacy/output/ # Original binary output reader +│ ├── plugin_sdk/ # Header-only plugin development kit +│ └── cli/ # Command-line interface +├── tests/ +│ ├── unit/legacy/ # Legacy solver and output tests (Google Test) +│ ├── unit/engine/ # New engine unit tests +│ ├── regression/ # Regression tests (new vs. legacy) +│ └── benchmarks/ # Performance benchmarks (Google Benchmark) +├── python/ # Python bindings (Cython + scikit-build) +├── docs/ # Doxygen config and technical manuals +├── cmake/ # CMake helper modules +└── .github/workflows/ # CI/CD pipelines +``` + +## Prerequisites + +| Requirement | Version | +|---|---| +| CMake | 3.21 or higher | +| C compiler | C17 support (GCC 10+, Clang 12+, MSVC 19.29+) | +| C++ compiler | C++20 support (GCC 10+, Clang 14+, MSVC 19.29+) | +| vcpkg | 2025.02.14 (for test dependencies) | +| Python | 3.9–3.13 (optional, for bindings) | +| Ninja | Recommended for Linux/macOS builds | + +## Build Instructions + +### C/C++ Engine + +```bash +# Clone the repository +git clone https://github.com/HydroCouple/openswmm.engine.git +cd openswmm.engine + +# Bootstrap vcpkg (for test dependencies) +git clone https://github.com/microsoft/vcpkg.git +./vcpkg/bootstrap-vcpkg.sh # or bootstrap-vcpkg.bat on Windows + +# Configure and build using a platform preset +# Available presets: Windows, Windows-debug, Linux, Linux-debug, Darwin, Darwin-debug +export VCPKG_ROOT=$(pwd)/vcpkg + +cmake --preset= -B build +cmake --build build --config Release + +# Build with tests enabled +cmake --preset=-debug -B build-debug -DOPENSWMM_BUILD_TESTS=ON +cmake --build build-debug --config Debug + +# Build with optional GeoPackage I/O support +cmake --preset= -B build -DOPENSWMM_WITH_GEOPACKAGE=ON +cmake --build build --config Release +``` + +### Running Tests + +```bash +# Run all C++ unit tests +ctest --test-dir build-debug -C Debug --output-on-failure + +# Run with verbose output +ctest --test-dir build-debug -C Debug --output-on-failure -V +``` + +### Packaging + +```bash +# Create distributable archives (ZIP + TGZ) +cmake --build build --target package +``` + +### Python Bindings + +```bash +cd python + +# Install requirements +python -m pip install -r requirements.txt + +# Build and install +python -m pip install . + +# Run Python tests +python -m pytest -v tests +``` + +## Python Usage + +The `openswmm` package provides Cython bindings for the full C API. +Domain objects are constructed by passing an active `Solver` instance. + +### Running a Simulation + +```python +from openswmm.engine import Solver, Nodes, Links, Subcatchments, Gages + +# Context manager handles open/initialize/start and end/report/close/destroy +with Solver("model.inp", "model.rpt", "model.out") as s: + nodes = Nodes(s) + links = Links(s) + subcatchments = Subcatchments(s) + gages = Gages(s) + + while s.step(): + # Per-element access by name or integer index + depth = nodes.get_depth("J1") + flow = links.get_flow("C1") + runoff = subcatchments.get_runoff("S1") + + # NumPy bulk access (single memcpy — fast) + all_depths = nodes.get_depths_bulk() # shape (n_nodes,) + all_flows = links.get_flows_bulk() # shape (n_links,) + + # Runtime forcing + nodes.set_lateral_inflow("J2", 0.5) + gages.set_rainfall(0, 25.4) +``` + +### Advanced Forcing with Persistence + +```python +from openswmm.engine import Solver, Nodes, Forcing, ForcingMode, ForcingTarget + +with Solver("model.inp", "model.rpt", "model.out") as s: + nodes = Nodes(s) + forcing = Forcing(s) + + j1 = nodes.get_index("J1") + + # Apply a persistent lateral inflow (held every step until cleared) + forcing.node_lat_inflow(j1, 1.5, ForcingMode.REPLACE, persist=True) + + # Additive forcing (added to model-computed value) + forcing.gage_rainfall(0, 10.0, ForcingMode.ADD, persist=True) + + while s.step(): + pass + + # Clear specific object or all forcing + forcing.clear(ForcingTarget.NODE, j1) + forcing.clear_all() +``` + +### Programmatic Model Building (No .inp File) + +```python +from openswmm.engine import ( + ModelBuilder, Nodes, Links, + NodeType, LinkType, XSectShape, +) + +m = ModelBuilder() + +# Add objects +m.add_node("J1", NodeType.JUNCTION) +m.add_node("J2", NodeType.JUNCTION) +m.add_node("OUT1", NodeType.OUTFALL) +m.add_link("C1", LinkType.CONDUIT) +m.add_link("C2", LinkType.CONDUIT) + +# Set geometry +m.set_node_invert(0, 10.0) +m.set_node_invert(1, 8.0) +m.set_node_invert(2, 5.0) +m.set_link_nodes(0, 0, 1) # C1: J1 -> J2 +m.set_link_nodes(1, 1, 2) # C2: J2 -> OUT1 +m.set_link_length(0, 400.0) +m.set_link_length(1, 400.0) +m.set_link_roughness(0, 0.013) +m.set_link_roughness(1, 0.013) +m.set_link_xsect(0, XSectShape.CIRCULAR, 1.0) +m.set_link_xsect(1, XSectShape.CIRCULAR, 1.0) + +# Set simulation options +m.set_option("FLOW_UNITS", "CFS") +m.set_option("ROUTING_MODEL", "DYNWAVE") +m.set_option("START_DATE", "01/01/2024") +m.set_option("END_DATE", "01/02/2024") + +# Validate, finalize, and simulate +m.validate() +m.finalize() + +solver = m.to_solver() +solver.start() +while solver.step(): + pass +solver.end() + +# Optionally write to .inp for inspection +m.write("generated_model.inp") + +solver.destroy() +``` + +### Reading Binary Output Files + +```python +from openswmm.engine import OutputReader, OutNodeVar, OutLinkVar + +with OutputReader("model.out") as out: + # Query metadata + print(f"Nodes: {out.get_node_count()}") + print(f"Links: {out.get_link_count()}") + print(f"Periods: {out.get_period_count()}") + print(f"Report step: {out.get_report_step()} sec") + + # List object IDs + for i in range(out.get_node_count()): + print(f" Node {i}: {out.get_node_id(i)}") + + # Read all node depths at each reporting period + for t in range(out.get_period_count()): + depths = out.get_node_result(t, OutNodeVar.DEPTH) # float32 array + flows = out.get_link_result(t, OutLinkVar.FLOW) # float32 array + print(f" Period {t}: max depth = {depths.max():.3f}") + + # Time series for a single node + node_depths = out.get_node_series( + node_idx=0, + var=OutNodeVar.DEPTH, + start_period=0, + end_period=out.get_period_count() - 1, + ) +``` + +### Hot Start Save and Restore + +```python +from openswmm.engine import Solver, HotStart + +# Run part of a simulation and save state +with Solver("model.inp", "model.rpt", "model.out") as s: + for _ in range(100): + if not s.step(): + break + HotStart.save(s, "checkpoint.hsf") + +# Later: restore from hot start +hs = HotStart.open("checkpoint.hsf") + +# Optionally modify state before applying +hs.set_node_depth("J1", 2.5) + +with Solver("model.inp", "model.rpt", "model2.out") as s2: + hs.apply(s2) + while s2.step(): + pass + +hs.close() +``` + +### Mass Balance and Statistics + +```python +from openswmm.engine import Solver, MassBalance, Statistics, RunoffTotal + +with Solver("model.inp", "model.rpt", "model.out") as s: + while s.step(): + pass + + mb = MassBalance(s) + stats = Statistics(s) + + # Continuity errors + print(f"Runoff error: {mb.get_runoff_continuity_error():.4f}%") + print(f"Routing error: {mb.get_routing_continuity_error():.4f}%") + + # Cumulative totals + precip = mb.get_runoff_total(RunoffTotal.RAINFALL) + runoff = mb.get_runoff_total(RunoffTotal.RUNOFF) + print(f"Total precip: {precip:.2f}, Total runoff: {runoff:.2f}") + + # Per-object statistics + print(f"Node 0 max depth: {stats.node_max_depth(0):.3f}") + print(f"Link 0 max flow: {stats.link_max_flow(0):.3f}") +``` + +For the full API reference, see the [documentation](https://hydrocouple.github.io/openswmm.engine). + + + +## Libraries Built + +| Target | Description | +|---|---| +| `openswmm_legacy_engine` | Original EPA SWMM 5.x solver (shared library) | +| `openswmm_legacy_output` | Original SWMM binary output reader (shared library) | +| `openswmm_engine` | New refactored C++20 engine (shared library) | +| `openswmm_geopackage` | GeoPackage I/O (static library, optional — requires SQLite3) | +| `openswmm_plugin_sdk` | Header-only plugin SDK (INTERFACE library) | +| `openswmm_cli` | Command-line executable | + +## Documentation + +API documentation is auto-generated with Doxygen and deployed to GitHub Pages: + +**[OpenSWMM Engine API Documentation](https://hydrocouple.github.io/openswmm.engine)** + +The documentation includes: +- Full C API reference with parameter descriptions and usage notes +- Technical reference manuals (Hydrology, Hydraulics, Water Quality) +- User manual with modeling capabilities and examples +- Architecture design decisions and implementation plan + +## Contributing + +Contributions are welcome. Please: + +1. Fork the repository and create a feature branch. +2. Ensure all tests pass (`ctest` for C++, `pytest` for Python). +3. Follow existing code style and naming conventions. +4. Submit a pull request against the `develop` branch. + +## License + +This project is licensed under the **MIT License** — see [LICENSE](LICENSE) for details. + +Copyright 2026 HydroCouple. Original EPA SWMM material is in the public domain under 17 USC § 105. + +## Acknowledgements + +OpenSWMM builds on the foundational work of the EPA Storm Water Management Model, originally developed by Lewis A. Rossman at the U.S. EPA Office of Research and Development. See [docs/authors.md](docs/authors.md) for the complete list of authors and contributors. diff --git a/Readme.txt b/Readme.txt deleted file mode 100644 index 7bbb52e66..000000000 --- a/Readme.txt +++ /dev/null @@ -1,47 +0,0 @@ -CONTENTS OF SWMM522_ENGINE.ZIP -============================== - -The 'src' folder of this archive contains the C source code for -version 5.2.2 of the Storm Water Management Model's computational -engine. Consult the included 'Roadmap.txt' file for an overview of -the various code modules. The code can be compiled into both a shared -object library and a command line executable. Under Windows, the -library file (swmm5.dll) is used to power SWMM's graphical user -interface. - -The 'CMakeLists.txt' file is a script used by CMake (https://cmake.org/) -to build the SWMM 5.2 binaries. CMake is a cross-platform build tool -that generates platform native build systems for many compilers. To -check if the required version is installed on your system, enter - - cmake --version - -from a console window and check that the version is 3.5 or higher. - -To build the SWMM 5.2 engine library and its command line executable -using CMake and the Microsoft Visual Studio C compiler on Windows: - -1. Open a console window and navigate to the directory where this - Readme file resides (which should have 'src' as a sub-directory - underneath it). - -2. Issue the following commands: - mkdir build - cd build - -3. Then enter the following CMake commands: - cmake -G .. -A - cmake --build . --config Release - -where is the name of the Visual Studio compiler being used -in double quotes (e.g., "Visual Studio 15 2017" or "Visual Studio 16 2019") -and is Win32 for a 32-bit build or x64 for a 64-bit build. -The resulting engine DLL (swmm5.dll) and command line executable -(runswmm.exe) will appear in the build\Release directory. - -For other platforms, such as Linux or MacOS, Step 3 can be replaced with: - cmake .. - cmake --build . - -The resulting shared object library (libswmm5.so) and command line executable -(runswmm) will appear in the build directory. \ No newline at end of file diff --git a/bindings/swmm5.def b/bindings/swmm5.def deleted file mode 100644 index 8bcaf6eb4..000000000 --- a/bindings/swmm5.def +++ /dev/null @@ -1,23 +0,0 @@ -LIBRARY SWMM5.DLL - -EXPORTS - swmm_close = _swmm_close@0 - swmm_decodeDate = _swmm_decodeDate@36 - swmm_end = _swmm_end@0 - swmm_getCount = _swmm_getCount@4 - swmm_getError = _swmm_getError@8 - swmm_getIndex = _swmm_getIndex@8 - swmm_getMassBalErr = _swmm_getMassBalErr@12 - swmm_getName = _swmm_getName@16 - swmm_getSavedValue = _swmm_getSavedValue@12 - swmm_getValue = _swmm_getValue@8 - swmm_getVersion = _swmm_getVersion@0 - swmm_getWarnings = _swmm_getWarnings@0 - swmm_open = _swmm_open@12 - swmm_report = _swmm_report@0 - swmm_run = _swmm_run@12 - swmm_setValue = _swmm_setValue@16 - swmm_start = _swmm_start@4 - swmm_step = _swmm_step@4 - swmm_stride = _swmm_stride@8 - swmm_writeLine = _swmm_writeLine@4 diff --git a/cmake/FindOpenMP.cmake b/cmake/FindOpenMP.cmake new file mode 100644 index 000000000..30c39261b --- /dev/null +++ b/cmake/FindOpenMP.cmake @@ -0,0 +1,41 @@ +include_guard(GLOBAL) + +# Apple-specific: try Homebrew locations +if(APPLE AND NOT OpenMP_FOUND) + message(STATUS "Searching for Homebrew OpenMP...") + + # Common Homebrew paths + set(HOMEBREW_PATHS + /opt/homebrew/opt/libomp # Apple Silicon + /usr/local/opt/libomp # Intel + ) + + foreach(HOMEBREW_PATH ${HOMEBREW_PATHS}) + if(EXISTS "${HOMEBREW_PATH}") + message(STATUS "Found Homebrew libomp at ${HOMEBREW_PATH}") + + # Create imported targets + if(NOT TARGET OpenMP::OpenMP_C) + add_library(OpenMP::OpenMP_C INTERFACE IMPORTED) + target_compile_options(OpenMP::OpenMP_C INTERFACE -Xpreprocessor -fopenmp) + target_include_directories(OpenMP::OpenMP_C INTERFACE "${HOMEBREW_PATH}/include") + target_link_libraries(OpenMP::OpenMP_C INTERFACE "${HOMEBREW_PATH}/lib/libomp.dylib") + endif() + + if(NOT TARGET OpenMP::OpenMP_CXX) + add_library(OpenMP::OpenMP_CXX INTERFACE IMPORTED) + target_compile_options(OpenMP::OpenMP_CXX INTERFACE -Xpreprocessor -fopenmp) + target_include_directories(OpenMP::OpenMP_CXX INTERFACE "${HOMEBREW_PATH}/include") + target_link_libraries(OpenMP::OpenMP_CXX INTERFACE "${HOMEBREW_PATH}/lib/libomp.dylib") + endif() + + set(OpenMP_FOUND TRUE PARENT_SCOPE) + set(OpenMP_C_FOUND TRUE PARENT_SCOPE) + set(OpenMP_CXX_FOUND TRUE PARENT_SCOPE) + + return() + endif() + endforeach() + + message(WARNING "OpenMP not found. Install via: brew install libomp") +endif() \ No newline at end of file diff --git a/cmake/OpenSWMMCoreConfig.cmake.in b/cmake/OpenSWMMCoreConfig.cmake.in new file mode 100644 index 000000000..be00eae13 --- /dev/null +++ b/cmake/OpenSWMMCoreConfig.cmake.in @@ -0,0 +1,23 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +# Platform-specific dependencies +if(UNIX AND NOT APPLE) + find_dependency(Threads) +endif() + +# Include the targets file +include("${CMAKE_CURRENT_LIST_DIR}/OpenSWMMCoreTargets.cmake") + +# Provide portable variables for consumers +set_and_check(OpenSWMMCore_INCLUDE_DIR "@PACKAGE_CMAKE_INSTALL_INCLUDEDIR@") +set_and_check(OpenSWMMCore_LIB_DIR "@PACKAGE_CMAKE_INSTALL_LIBDIR@") + +# Set convenience variables +set(OpenSWMMCore_INCLUDE_DIRS "${OpenSWMMCore_INCLUDE_DIR}") +set(OpenSWMMCore_LIBRARY_DIRS "${OpenSWMMCore_LIB_DIR}") +set(OpenSWMMCore_VERSION "@PROJECT_VERSION@") + +# Check required components +check_required_components(OpenSWMMCore) diff --git a/docs/2dModelStrategy.md b/docs/2dModelStrategy.md new file mode 100644 index 000000000..17abf3bc6 --- /dev/null +++ b/docs/2dModelStrategy.md @@ -0,0 +1,1655 @@ +# Two-Dimensional Surface Routing — Implementation Strategy + +## Overview + +This document details the implementation strategy for an optional 2D surface routing module coupled to the OpenSWMM engine. The module implements the **second-order accurate, semi-discrete finite volume formulation** from Kumar, Duffy, and Salvage (2009) — initially limited to the **diffusion-wave surface flow** component. The design follows the same data-oriented, cache-friendly, Structure-of-Arrays (SoA) patterns used throughout the engine. + +**Scope — Phase 1 (this document):** +- 2D diffusion-wave surface routing on a triangular mesh +- Coupling to SWMM nodes/junctions via orifice equation and forcing API +- Rainfall from system rain gages +- Time integration via SUNDIALS CVODE (BDF) with GMRES linear solver + +**Future phases (strategy outlined, not implemented):** +- Subsurface (Richards' equation) coupling +- Infiltration (Green-Ampt / Horton / SCS) +- Evapotranspiration, snowmelt +- Natural neighbour interpolation for rainfall spatial distribution + +--- + +## 1. Input File Sections + +The 2D model is specified via optional sections in the `.inp` file. All section names are prefixed with `2D_` for clarity and to avoid collisions with existing SWMM sections. The sections are registered via `SectionRegistry::register_custom()`. + +### 1.1 Section Names + +| Section | Purpose | +|---------|---------| +| `[2D_OPTIONS]` | Solver options, tolerances, time-stepping parameters | +| `[2D_VERTICES]` | Mesh vertex coordinates | +| `[2D_TRIANGLES]` | Triangle connectivity and surface roughness | +| `[2D_VERTEX_NODE_MAP]` | Vertex-to-SWMM-node coupling | +| `[2D_TRIANGLE_NODE_MAP]` | Triangle-centroid-to-SWMM-node coupling | +| `[2D_BOUNDARY_CONDITIONS]` | *(future)* Boundary types on mesh edges | +| `[2D_INITIAL_CONDITIONS]` | *(future)* Per-cell initial water depth | +| `[2D_INFILTRATION]` | *(future)* Per-cell or per-zone infiltration params | + +### 1.2 `[2D_OPTIONS]` + +``` +;; Solver and timestepping options for the 2D surface routing module +;; +;; Parameter Value +;; ---------------------- ------ +MAX_TIMESTEP 10.0 ;; Maximum CVODE internal step (seconds) +MIN_TIMESTEP 0.001 ;; Minimum CVODE internal step (seconds) +REL_TOLERANCE 1.0e-4 ;; CVODE relative tolerance +ABS_TOLERANCE 1.0e-6 ;; CVODE absolute tolerance (metres of depth) +LINEAR_SOLVER GMRES ;; GMRES | BICGSTAB | TFQMR +PRECONDITIONER NONE ;; NONE | JACOBI | ILU (future) +MAX_KRYLOV_DIM 30 ;; Maximum Krylov subspace dimension +DRY_DEPTH 0.001 ;; Depth below which cell is considered dry (m) +COUPLING_INTERVAL 0 ;; 0 = every SWMM routing step (default) +REPORT_2D YES ;; Write 2D results to output +``` + +### 1.3 `[2D_VERTICES]` + +Each row defines a mesh vertex. Vertices are indexed in order of appearance (0-based). + +``` +;; X Y Z TAG (optional) +100.0 200.0 10.5 +100.5 200.5 10.3 inlet_region +101.0 200.0 10.1 +``` + +- **X, Y** — Horizontal coordinates (same CRS as SWMM coordinates) +- **Z** — Ground surface elevation (project length units) +- **TAG** — Optional string tag for grouping/reference + +### 1.4 `[2D_TRIANGLES]` + +Each row defines a triangle by referencing three vertex indices (0-based) and a Manning's roughness coefficient. + +``` +;; V1 V2 V3 MANNINGS_N TAG (optional) +0 1 2 0.035 +3 4 5 0.025 road_surface +``` + +- **V1, V2, V3** — Vertex indices (0-based) +- **MANNINGS_N** — Manning's roughness coefficient (s/m^{1/3}) +- **TAG** — Optional string tag + +### 1.5 `[2D_VERTEX_NODE_MAP]` + +Maps mesh vertices to SWMM coupling nodes. Flow exchange occurs at these points via an orifice equation. + +``` +;; VERTEX_INDEX_OR_TAG SWMM_NODE_NAME +5 J1 +inlet_region J2 +``` + +### 1.6 `[2D_TRIANGLE_NODE_MAP]` + +Maps triangle centroids to SWMM coupling nodes. Flow exchange is distributed over the triangle area. + +``` +;; TRIANGLE_INDEX_OR_TAG SWMM_NODE_NAME +0 J3 +road_surface J4 +``` + +--- + +## 2. Data Structures (SoA Layout) + +All 2D data structures follow the existing OpenSWMM SoA pattern: parallel `std::vector` arrays, a single `resize()` method, `save_state()` / `reset_state()` lifecycle methods, and flat 2D arrays where needed. + +### 2.1 File: `src/engine/2d/data/MeshData.hpp` + +```cpp +namespace openswmm::twoD { + +struct MeshData { + + // ----------------------------------------------------------------------- + // Vertex arrays — indexed by vertex index [0, n_vertices) + // ----------------------------------------------------------------------- + + std::vector vx; // Vertex X coordinate + std::vector vy; // Vertex Y coordinate + std::vector vz; // Vertex Z (ground elevation) + std::vector vtag; // Optional vertex tag + + // ----------------------------------------------------------------------- + // Triangle static properties — indexed by triangle index [0, n_triangles) + // ----------------------------------------------------------------------- + + // Connectivity (3 vertex indices per triangle) + std::vector tri_v0; // Vertex 0 index + std::vector tri_v1; // Vertex 1 index + std::vector tri_v2; // Vertex 2 index + + // Neighbour connectivity (3 adjacent triangle indices, -1 = boundary) + std::vector tri_nbr0; // Neighbour across edge opposite v0 + std::vector tri_nbr1; // Neighbour across edge opposite v1 + std::vector tri_nbr2; // Neighbour across edge opposite v2 + + // Precomputed geometry + std::vector tri_area; // Planimetric area (m²) + std::vector tri_cx; // Centroid X + std::vector tri_cy; // Centroid Y + std::vector tri_cz; // Centroid Z (avg of vertex elevations) + + // Edge geometry — flat 2D: [tri * 3 + edge] + std::vector edge_length; // Length of each edge + std::vector edge_nx; // Outward normal X component + std::vector edge_ny; // Outward normal Y component + std::vector edge_mx; // Edge midpoint X + std::vector edge_my; // Edge midpoint Y + std::vector edge_mz; // Edge midpoint Z (interpolated) + + // Surface properties + std::vector mannings_n; // Manning's roughness coefficient + std::vector tri_tag; // Optional triangle tag + + // ----------------------------------------------------------------------- + // Vertex reconstruction stencil (pseudo-Laplacian weights) + // ----------------------------------------------------------------------- + // Stored as CSR (compressed sparse row) for variable stencil sizes + std::vector vert_stencil_ptr; // [n_vertices + 1] row pointers + std::vector vert_stencil_idx; // Column indices (triangle indices) + std::vector vert_stencil_wt; // Pseudo-Laplacian weights + + // ----------------------------------------------------------------------- + // Coupling maps + // ----------------------------------------------------------------------- + + // Vertex-to-SWMM-node coupling + std::vector vert_coupled_node; // SWMM node index (-1 = none) + + // Triangle-to-SWMM-node coupling + std::vector tri_coupled_node; // SWMM node index (-1 = none) + + // Deferred resolution names (populated during parsing) + std::vector vert_coupled_node_name; + std::vector tri_coupled_node_name; + + // ----------------------------------------------------------------------- + // Capacity + // ----------------------------------------------------------------------- + + int n_vertices() const noexcept { return static_cast(vx.size()); } + int n_triangles() const noexcept { return static_cast(tri_v0.size()); } + + void resize_vertices(int nv); + void resize_triangles(int nt); + void build_topology(); // Compute neighbours, edges, areas + void build_vertex_stencils(); // Build pseudo-Laplacian weights +}; + +} // namespace openswmm::twoD +``` + +### 2.2 File: `src/engine/2d/data/SurfaceStateData.hpp` + +```cpp +namespace openswmm::twoD { + +struct SurfaceStateData { + + // ----------------------------------------------------------------------- + // State variables — per triangle [0, n_triangles) + // ----------------------------------------------------------------------- + + std::vector depth; // Overland flow depth ψ_o (m) + std::vector head; // Total head h_o = z_s + ψ_o (m) + + // Gradient fields (per triangle) + std::vector grad_hx; // ∂h/∂x (unlimited gradient) + std::vector grad_hy; // ∂h/∂y (unlimited gradient) + std::vector grad_hx_lim; // Limited gradient X + std::vector grad_hy_lim; // Limited gradient Y + + // Reconstructed head at vertices — flat 2D [vertex_idx] + std::vector vert_head; // Head reconstructed at vertices + + // Fluxes — flat 2D: [tri * 3 + edge] + std::vector edge_flux; // Normal flux through each edge + + // Source/sink terms + std::vector rainfall; // Rainfall intensity (m/s) + std::vector coupling_flux; // Exchange with SWMM node (m/s, + = into 2D) + std::vector net_source; // Net source/sink per cell (m/s) + + // ----------------------------------------------------------------------- + // Previous step state + // ----------------------------------------------------------------------- + + std::vector old_depth; + + // ----------------------------------------------------------------------- + // Cumulative statistics + // ----------------------------------------------------------------------- + + std::vector stat_max_depth; + std::vector stat_cum_volume; // Cumulative volume through cell + + // ----------------------------------------------------------------------- + // Lifecycle + // ----------------------------------------------------------------------- + + void resize(int n_triangles); + void save_state() noexcept; + void reset_state() noexcept; +}; + +} // namespace openswmm::twoD +``` + +### 2.3 File: `src/engine/2d/data/SolverOptions2D.hpp` + +```cpp +namespace openswmm::twoD { + +enum class LinearSolverType : int8_t { + GMRES = 0, + BICGSTAB = 1, + TFQMR = 2 +}; + +enum class PreconditionerType : int8_t { + NONE = 0, + JACOBI = 1, + ILU = 2 // future +}; + +struct SolverOptions2D { + double max_timestep = 10.0; + double min_timestep = 0.001; + double rel_tolerance = 1.0e-4; + double abs_tolerance = 1.0e-6; + double dry_depth = 0.001; + int max_krylov_dim = 30; + int coupling_interval = 0; // 0 = every SWMM step + bool report_2d = true; + + LinearSolverType linear_solver = LinearSolverType::GMRES; + PreconditionerType preconditioner = PreconditionerType::NONE; +}; + +} // namespace openswmm::twoD +``` + +--- + +## 3. Mathematical Formulation — Diffusion-Wave Surface Flow + +### 3.1 Governing Equation + +The 2D diffusion-wave approximation of St. Venant's equation (Kumar et al., 2009, Eq. [1]): + +``` +∂ψ_o/∂t = ∇·(ψ_o · K(ψ_o) · ∇h_o) - Q_og + Q_ss +``` + +Where: +- `ψ_o` = overland flow depth (m) +- `h_o = z_s + ψ_o` = total overland flow head (m) +- `z_s` = ground surface elevation (m) +- `K(ψ_o)` = diffusive conductance (m/s) +- `Q_og` = vertical flux exchange with subsurface (m/s) — **zero in Phase 1** +- `Q_ss` = sources/sinks (rainfall, evaporation) (m/s) + +### 3.2 Diffusive Conductance + +``` +K(ψ_o) = (ψ_o^{2/3}) / (n · |∂h_o/∂s|^{1/2}) +``` + +Where `n` is Manning's roughness and `s` is the direction of maximum slope. + +### 3.3 Semi-Discrete Finite Volume Form (Eq. [10]) + +For each triangular cell `i`: + +``` +A_i · dψ_o/dt = Σ_{j=1}^{3} n_j · F_j + Q_ss · V_i +``` + +Where: +- `A_i` = planimetric area of triangle `i` +- `F_j` = lateral flux vector on edge `j` +- `n_j` = outward normal to edge `j` +- The coupling flux `G_k` (vertical flux to subsurface) is zero in Phase 1 + +### 3.4 Lateral Flux Calculation (Eq. [15a]) + +``` +n_j · F_j = UW[ψ_o · K(ψ_o) · ∇h_o]_ξ · ξ_ij +``` + +Where `UW[]` is the upwind function (flux computed at the upstream cell face) and `ξ_ij` is the edge length. + +### 3.5 Second-Order Accuracy Components + +1. **Edge Gradient Calculation** (Eq. [16]–[18]): Green-Gauss theorem on variational triangles +2. **Vertex Reconstruction** (Eq. [19]–[21]): Pseudo-Laplacian weighted interpolation from cell centres to vertices +3. **Linear Reconstruction at Edges** (Eq. [22]): `h_ξ = h_c + r · ∇h_l` +4. **Limited Gradient** (Eq. [23]–[24]): Jawahar-Kamath multidimensional limiter with weights based on L2 norms of unlimited gradients +5. **Unlimited Gradient** (Eq. [25]–[26]): Area-weighted average of edge gradients + +--- + +## 4. Solver Architecture + +### 4.1 File Organization + +``` +src/engine/2d/ +├── CMakeLists.txt +├── data/ +│ ├── MeshData.hpp +│ ├── SurfaceStateData.hpp +│ └── SolverOptions2D.hpp +├── mesh/ +│ ├── MeshBuilder.hpp // Topology, neighbours, edge geometry +│ ├── MeshBuilder.cpp +│ ├── VertexReconstruction.hpp // Pseudo-Laplacian stencil weights +│ └── VertexReconstruction.cpp +├── solver/ +│ ├── SurfaceFluxCalculator.hpp // Edge flux, gradient, limiter +│ ├── SurfaceFluxCalculator.cpp +│ ├── CvodeSurfaceSolver.hpp // CVODE wrapper for surface ODE system +│ ├── CvodeSurfaceSolver.cpp +│ └── DiffusiveConductance.hpp // K(ψ_o) computation +├── coupling/ +│ ├── NodeCoupling.hpp // Orifice-equation exchange with SWMM +│ └── NodeCoupling.cpp +├── input/ +│ ├── SectionHandlers2D.hpp // Input section parsers +│ └── SectionHandlers2D.cpp +├── SurfaceRouter2D.hpp // Top-level orchestrator +└── SurfaceRouter2D.cpp +``` + +### 4.2 CVODE Integration + +**Dependency:** SUNDIALS (via vcpkg: `sundials[cvode]`) + +The surface routing ODE system is: + +``` +dy/dt = f(t, y) +``` + +where `y[i] = ψ_o[i]` (overland flow depth at triangle `i`), and `f(t, y)` computes the right-hand side from the semi-discrete finite volume formulation. + +#### CVODE Setup + +```cpp +class CvodeSurfaceSolver { +public: + /// Initialize CVODE with the ODE system of size n_triangles + void initialize(const MeshData& mesh, const SolverOptions2D& opts); + + /// Advance the solution from t_current to t_target + /// Returns actual time reached + double advance(double t_current, double t_target, + SurfaceStateData& state, const MeshData& mesh); + + /// Clean up CVODE memory + void finalize(); + +private: + void* cvode_mem_ = nullptr; // CVODE memory block + SUNLinearSolver ls_ = nullptr; // GMRES (or alternative) + N_Vector y_ = nullptr; // State vector (wraps state.depth) + SUNContext ctx_ = nullptr; // SUNDIALS context + + /// RHS function: f(t, y, ydot) + /// Registered as CVRhsFn callback + static int rhs_fn(sunrealtype t, N_Vector y, N_Vector ydot, void* user_data); +}; +``` + +#### RHS Function Pseudocode + +``` +rhs_fn(t, y, ydot, user_data): + solver_ctx = (SolverContext*)user_data + mesh = solver_ctx->mesh + state = solver_ctx->state + opts = solver_ctx->opts + + // 1. Copy y into state.depth, compute head = z + depth + for i in 0..n_triangles: + state.depth[i] = max(y[i], 0) + state.head[i] = mesh.tri_cz[i] + state.depth[i] + + // 2. Reconstruct head at vertices (pseudo-Laplacian, Eq. [19]) + reconstruct_vertex_heads(mesh, state) + + // 3. Compute unlimited gradients (Eq. [25]-[26]) + compute_unlimited_gradients(mesh, state) + + // 4. Apply slope limiter (Eq. [23]-[24]) + compute_limited_gradients(mesh, state) + + // 5. Compute edge fluxes (Eq. [15a], [22], [30]) + for i in 0..n_triangles: + for e in 0..3: + compute_edge_flux(i, e, mesh, state, opts) + + // 6. Assemble RHS: A_i * dψ/dt = Σ fluxes + sources + for i in 0..n_triangles: + rhs = 0 + for e in 0..3: + rhs += state.edge_flux[i * 3 + e] + rhs += state.rainfall[i] * mesh.tri_area[i] + rhs += state.coupling_flux[i] * mesh.tri_area[i] + ydot[i] = rhs / mesh.tri_area[i] +``` + +#### Why CVODE with GMRES + +- **CVODE (BDF)** is designed for stiff ODE systems. The nonlinearity of Manning's equation (depth-dependent conductance) and the potentially stiff coupling between wet and dry cells makes this an appropriate choice. +- **GMRES** (Generalized Minimal Residual) is a Krylov iterative solver that avoids forming the full Jacobian matrix. CVODE uses a difference-quotient approximation for Jacobian-vector products, so the Jacobian is never explicitly stored. +- **Alternatives available:** SUNDIALS also provides BiCGStab (`SUNLinearSolver_SPBCGS`) and TFQMR (`SUNLinearSolver_SPTFQMR`) — exposed via `[2D_OPTIONS]`. + +#### vcpkg Integration + +Add to `vcpkg.json`: +```json +{ + "name": "sundials", + "version>=": "7.0.0", + "features": ["cvode"] +} +``` + +CMake: +```cmake +option(OPENSWMM_BUILD_2D "Build optional 2D surface routing module" OFF) + +if(OPENSWMM_BUILD_2D) + find_package(SUNDIALS REQUIRED COMPONENTS cvode) + target_link_libraries(openswmm_engine PRIVATE SUNDIALS::cvode) + target_compile_definitions(openswmm_engine PRIVATE OPENSWMM_HAS_2D=1) +endif() +``` + +The 2D module is **compile-time optional** — when `OPENSWMM_BUILD_2D=OFF`, no SUNDIALS dependency is required and all 2D code is excluded via `#ifdef OPENSWMM_HAS_2D`. + +--- + +## 5. Mesh Processing Pipeline + +### 5.1 Topology Construction (`MeshBuilder`) + +After parsing `[2D_VERTICES]` and `[2D_TRIANGLES]`: + +1. **Build edge-neighbour adjacency** — For each triangle, find the adjacent triangle sharing each edge. Use a hash map keyed by sorted vertex-pair `(min(va, vb), max(va, vb))` → first triangle sets the entry, second triangle completes the pair. + +2. **Compute edge geometry** — For each edge of each triangle: + - Edge length `ξ_ij` + - Outward unit normal `(nx, ny)` — perpendicular to edge, pointing away from cell centre + - Edge midpoint `(mx, my, mz)` + +3. **Compute cell geometry** — For each triangle: + - Planimetric area `A_i` via cross product + - Centroid `(cx, cy, cz)` as average of vertex coordinates + +4. **Identify boundary edges** — Edges with no neighbour (`nbr = -1`) are domain boundaries. Default: zero-flux (wall). Future: configurable via `[2D_BOUNDARY_CONDITIONS]`. + +### 5.2 Vertex Reconstruction Stencils (`VertexReconstruction`) + +For each vertex `b`, build the pseudo-Laplacian reconstruction stencil (Eq. [19]–[21]): + +1. Collect all triangles sharing vertex `b` → stencil cells `{1, ..., M}` +2. Compute moments: `I_xx`, `I_yy`, `I_xy`, `R_x`, `R_y` +3. Compute Lagrange multipliers: `λ_x`, `λ_y` +4. Compute weights: `ω_i = 1 + λ_x(x_i - x_b) + λ_y(y_i - y_b)` +5. Clip extraneous weights at boundaries (Jawahar & Kamath, 2000) +6. Store in CSR format: `vert_stencil_ptr`, `vert_stencil_idx`, `vert_stencil_wt` + +--- + +## 6. Coupling to SWMM + +### 6.1 Coupling Philosophy + +- The 2D module communicates with SWMM **exclusively via the forcing API** (`ForcingData`). +- This keeps the 1D and 2D solvers cleanly separated. +- The 2D module injects lateral inflow into SWMM nodes; SWMM node heads determine backflow. + +### 6.2 Coupling at Nodes — Orifice Equation + +For each coupled vertex/triangle with SWMM node index `j`: + +``` +Q_exchange = C_d · A_orifice · sign(Δh) · sqrt(2g · |Δh|) +``` + +Where: +- `Δh = h_2d - h_swmm` (head difference) +- `h_2d` = 2D surface head at the coupling point +- `h_swmm = nodes.head[j]` = SWMM node head +- `C_d` = discharge coefficient (default 0.65, configurable) +- `A_orifice` = effective exchange area (configurable per coupling point) +- `g` = gravitational acceleration + +**Flow direction:** +- `Δh > 0`: Flow from 2D surface → SWMM node (drainage into pipe network) +- `Δh < 0`: Flow from SWMM node → 2D surface (surcharge/flooding) + +### 6.3 Coupling at Outfalls — Backflow Prevention + +When a coupling node is an OUTFALL (`nodes.type[j] == NodeType::OUTFALL`): + +1. **FREE outfall:** Allow unrestricted drainage. Set `h_swmm` = outfall invert elevation. +2. **FIXED outfall:** Use fixed stage as `h_swmm`. If `h_2d < h_fixed`, no flow (flap gate behaviour). +3. **TIDAL / TIMESERIES outfall:** Use time-varying stage as `h_swmm`. Enforce backflow prevention if `outfall_has_flap_gate[j]` is true. +4. **NORMAL outfall:** Use normal depth computation for `h_swmm`. + +**Flap gate logic:** +``` +if outfall_has_flap_gate[j] and h_swmm > h_2d: + Q_exchange = 0 // No backflow allowed +``` + +### 6.4 Forcing API Integration + +Each coupling step: + +```cpp +// Inject 2D→SWMM flow as lateral inflow +for (auto& cp : coupling_points) { + double Q = compute_orifice_exchange(cp, mesh, state, ctx); + + // Positive Q = flow into SWMM node + ctx.forcing.node_lat_inflow_mode[cp.node_idx] = ForcingMode::ADD; + ctx.forcing.node_lat_inflow_value[cp.node_idx] += Q; + ctx.forcing.node_lat_inflow_persist[cp.node_idx] = ForcingPersist::RESET; + + // Record coupling flux back to 2D cell (negative = drainage out of 2D) + state.coupling_flux[cp.cell_idx] = -Q / mesh.tri_area[cp.cell_idx]; +} +``` + +### 6.5 Coupling Sequence per Timestep + +``` +1. SWMM saves state (nodes.save_state()) +2. SWMM applies forcings (including 2D coupling from previous step) +3. SWMM advances hydraulic routing by dt_swmm +4. Read updated SWMM node heads +5. Compute 2D↔SWMM exchange flows (orifice equation) +6. Inject exchange flows via forcing API (for next SWMM step) +7. Advance 2D solver by dt_swmm using CVODE +8. Update 2D rainfall from system rain gages +``` + +--- + +## 7. Rainfall + +### 7.1 Phase 1: System Rainfall + +Each 2D triangle receives rainfall from the nearest rain gage (or a user-specified gage assignment): + +```cpp +// Simple: use first available gage's current rainfall for all cells +double rain_intensity = ctx.gages.rainfall[0]; // user units (in/hr or mm/hr) +double rain_m_per_s = convert_to_m_per_s(rain_intensity, ctx.options); + +for (int i = 0; i < mesh.n_triangles(); ++i) { + state.rainfall[i] = rain_m_per_s; +} +``` + +### 7.2 Future: Natural Neighbour Interpolation + +For spatially distributed rainfall across multiple gages: + +1. Compute Voronoi diagram of gage locations +2. For each triangle centroid, find its natural neighbour weights among gages +3. Interpolate rainfall intensity using natural neighbour weights + +This provides smooth, data-adaptive spatial interpolation that: +- Reproduces exact values at gage locations +- Provides C1-continuous interpolation between gages +- Adapts automatically to irregular gage spacing + +Implementation will use the gage coordinates from `ctx.spatial.gage_x/y` and rainfall from `ctx.gages.rainfall[]`. + +--- + +## 8. Integration into SimulationContext + +### 8.1 Context Extension + +Add an optional 2D data member to `SimulationContext`: + +```cpp +// In SimulationContext.hpp +#ifdef OPENSWMM_HAS_2D +#include "../2d/data/MeshData.hpp" +#include "../2d/data/SurfaceStateData.hpp" +#include "../2d/data/SolverOptions2D.hpp" + +// Under the "Object data stores" section: +twoD::MeshData mesh_2d; +twoD::SurfaceStateData surface_2d; +twoD::SolverOptions2D options_2d; +bool has_2d = false; // True if [2D_VERTICES] was parsed +#endif +``` + +### 8.2 Section Registration + +In the input reader setup (where built-in sections are registered): + +```cpp +#ifdef OPENSWMM_HAS_2D +registry.register_custom("2D_OPTIONS", twoD::handle_2d_options); +registry.register_custom("2D_VERTICES", twoD::handle_2d_vertices); +registry.register_custom("2D_TRIANGLES", twoD::handle_2d_triangles); +registry.register_custom("2D_VERTEX_NODE_MAP", twoD::handle_2d_vertex_node_map); +registry.register_custom("2D_TRIANGLE_NODE_MAP", twoD::handle_2d_triangle_node_map); +#endif +``` + +### 8.3 Engine Lifecycle Hooks + +``` +open(): + Parse input (including 2D sections if present) + If 2D sections found: + ctx.has_2d = true + Build mesh topology (MeshBuilder) + Build vertex stencils (VertexReconstruction) + Resolve coupling node names → indices (PostParseResolver) + +initialize(): + If ctx.has_2d: + Initialize CVODE solver + Set initial depths (from [2D_INITIAL_CONDITIONS] or zero) + Compute initial heads + +step(): + SWMM routing step (existing) + If ctx.has_2d: + Update coupling exchange flows + Advance 2D surface solver + Apply coupling forcings for next step + +end(): + If ctx.has_2d: + Finalize CVODE + Report 2D statistics +``` + +--- + +## 9. Timestep Synchronization + +The 2D solver and the 1D SWMM solver operate on different timescales and must be carefully synchronized. CVODE internally sub-steps within the SWMM routing interval, while coupling exchange must remain consistent across both solvers. + +### 9.1 Two-Clock Architecture + +``` +SWMM clock: |----dt_swmm----|----dt_swmm----|----dt_swmm----| + t0 t1 t2 t3 + +CVODE clock: |--Δt--|--Δt--|--Δt--|-Δt-|--Δt--|--Δt--|--Δt--| + t0 t1 t2 + + CVODE sub-steps internally to reach each t_swmm boundary +``` + +- **SWMM routing step (`dt_swmm`):** Determined by `TimestepController::compute_next()` as the minimum of CFL, output boundary, control events, and user max step. Typically 1–30 seconds. +- **CVODE internal steps:** Variable-order, variable-step BDF steps taken internally by CVODE. CVODE is called with `CVode(cvode_mem, t_target, ...)` where `t_target = t_current + dt_swmm`. CVODE sub-steps as needed to meet error tolerances, but guarantees arrival at `t_target` exactly. + +### 9.2 Synchronization Modes + +Two modes are supported, controlled by `coupling_interval` in `[2D_OPTIONS]`: + +#### Mode A: Tight Coupling (default, `coupling_interval = 0`) + +The 2D solver advances **every SWMM routing step**. This is the most accurate but most expensive mode. + +``` +for each SWMM routing step dt_swmm: + 1. SWMM computes dt_swmm via TimestepController::compute_next() + 2. SWMM saves state, applies forcings, advances 1D routing by dt_swmm + 3. TimestepController::advance(ctx, dt_swmm) + 4. Read SWMM node heads at t_new + 5. Compute coupling exchange Q via orifice equation + 6. Update 2D rainfall from current gage state + 7. Set coupling_flux[] in 2D state (held constant over CVODE sub-steps) + 8. CVODE advances 2D from t to t + dt_swmm (internal sub-stepping) + 9. Inject exchange Q into forcing API for next SWMM step + 10. If output_due: snapshot includes both 1D and 2D state +``` + +#### Mode B: Subcycled Coupling (`coupling_interval = N`) + +The 2D solver advances every `N` SWMM routing steps. Coupling exchange is computed once per `N` steps and held constant. This reduces computational cost for cases where the 2D domain evolves slowly relative to the pipe network. + +``` +coupling_counter = 0 + +for each SWMM routing step dt_swmm: + 1. SWMM routing step (normal) + 2. coupling_counter++ + 3. if coupling_counter >= N: + a. Compute accumulated dt_2d = sum of last N dt_swmm values + b. Read SWMM node heads + c. Compute coupling exchange + d. CVODE advances 2D from t to t + dt_2d + e. Inject exchange into forcing API + f. coupling_counter = 0 +``` + +### 9.3 Coupling Exchange Timing — Operator Splitting + +The coupling uses **sequential operator splitting** (Lie splitting): + +``` +t_n → t_{n+1}: + Step 1: Advance SWMM 1D: y^{1D}_{n+1} = S_{1D}(dt, y^{1D}_n, Q^{2D→1D}_n) + Step 2: Read h^{1D}_{n+1} + Step 3: Compute Q^{exchange}_{n+1} = orifice(h^{2D}_n, h^{1D}_{n+1}) + Step 4: Advance 2D surface: y^{2D}_{n+1} = S_{2D}(dt, y^{2D}_n, Q^{exchange}_{n+1}) + Step 5: Set Q^{2D→1D}_{n+1} for next SWMM step +``` + +The exchange flow computed at Step 3 uses the **latest SWMM head** (post-routing) but the **previous 2D head** (pre-advance). This is first-order in time for the coupling but avoids implicit coupling iterations. For small `dt_swmm` (as enforced by CFL), the splitting error is small. + +**Alternative (future):** Strang splitting (half-step 1D → full-step 2D → half-step 1D) would give second-order coupling accuracy but requires two 1D half-steps per coupling cycle. + +### 9.4 Interaction with TimestepController + +The 2D solver does **not** modify `TimestepController::compute_next()`. Instead: + +1. **CFL from 2D** can optionally constrain `dt_swmm`. The 2D solver computes a CFL-like stability estimate: + ``` + dt_cfl_2d = min over all cells: (cell_diameter / max_wave_speed) + ``` + This is passed as an additional constraint: + ```cpp + double dt_cfl_1d = dynwave.compute_cfl_step(ctx); + double dt_cfl_2d = ctx.has_2d ? surface_router.compute_cfl_hint(ctx) : 1e30; + double dt_cfl = std::min(dt_cfl_1d, dt_cfl_2d); + double dt_next = TimestepController::compute_next(ctx, dt_cfl); + ``` + Note: Since CVODE handles its own sub-stepping adaptively, this CFL hint is **advisory** — it prevents the coupling interval from being too large, not the internal CVODE steps. + +2. **Output alignment** is unchanged. Both 1D and 2D states are snapshotted when `TimestepController::output_due()` returns true, since the 2D solver has already been advanced to the same time. + +3. **Simulation end** is unchanged. When `TimestepController::simulation_complete()` returns true, the 2D solver finalizes. + +### 9.5 CVODE Internal Stepping Details + +CVODE is configured to: + +```cpp +// Set CVODE to stop exactly at t_target (no overshooting) +CVodeSetStopTime(cvode_mem, t_target); + +// Advance — CVODE takes as many internal steps as needed +int flag = CVode(cvode_mem, t_target, y, &t_reached, CV_NORMAL); +// t_reached == t_target (guaranteed by SetStopTime) +``` + +Key CVODE settings: +- **Method:** BDF (backward differentiation formula) — appropriate for stiff systems +- **Max internal steps:** Configurable, default 500 per call (CVODE's default) +- **Min/max internal step size:** From `[2D_OPTIONS]` `MIN_TIMESTEP` / `MAX_TIMESTEP` +- **Order:** Dynamically adjusted by CVODE between 1 and 5 for optimal efficiency +- **Error control:** Per-cell relative + absolute tolerance + +During each CVODE internal step, the coupling flux and rainfall are **held constant** (they are frozen at the values computed at the start of the coupling interval). This is consistent with the operator-splitting approach. + +### 9.6 Mass Conservation at the Coupling Interface + +To ensure global mass conservation across the 1D↔2D boundary: + +``` +Volume removed from 2D = Volume added to 1D (and vice versa) +``` + +The same `Q_exchange` value (in m³/s) is: +- Subtracted from the 2D cell as `coupling_flux[i] = -Q / A_i` (m/s sink) +- Added to the SWMM node as `node_lat_inflow_value[j] += Q` (m³/s source) + +Both use the same `dt_swmm` interval. The CVODE solver integrates the coupling flux as a constant source/sink over its internal sub-steps, which preserves the total volume exchange. The forcing API's `RESET` persistence ensures the coupling flow is cleared and recomputed each SWMM step. + +### 9.7 Handling Mismatched Timescales + +| Scenario | Behaviour | +|----------|-----------| +| 2D is stiff (small CVODE steps) | CVODE takes many internal sub-steps within `dt_swmm`. No impact on SWMM step. | +| 2D CFL < SWMM CFL | `dt_cfl_2d` constrains `dt_swmm` via `compute_next()`. Both solvers use the smaller step. | +| SWMM step limited by output boundary | 2D solver advances to the same output boundary. Both snapshots are synchronized. | +| SWMM step limited by control rules | 2D solver advances to the control-event time. Coupling is recomputed at the new state. | +| 2D goes dry everywhere | CVODE converges in 1–2 internal steps (trivial RHS). Minimal overhead. | +| Large 2D domain, small pipe network | Use `coupling_interval > 0` to subcycle. 2D advances less frequently. | + +--- + +## 10. Numerical Implementation Details + +### 10.1 Dry Cell Handling + +Cells with `depth < dry_depth` require special treatment to avoid division by zero in Manning's equation: + +```cpp +inline double diffusive_conductance(double depth, double mannings_n, + double grad_h_mag, double dry_depth) { + if (depth < dry_depth) return 0.0; + double denom = mannings_n * std::sqrt(std::max(grad_h_mag, 1e-12)); + return std::pow(depth, 2.0/3.0) / denom; +} +``` + +### 10.2 Upwind Flux Selection + +The upwind function `UW[]` selects the cell from which flow exits through the edge: + +```cpp +// For edge between cell L and cell R: +double h_L = state.head[L]; +double h_R = (R >= 0) ? state.head[R] : h_boundary; + +int upstream = (h_L >= h_R) ? L : R; +// Compute flux using upstream cell's depth and gradient +``` + +### 10.3 Slope Limiter (Jawahar-Kamath) + +The continuously differentiable limiter (Eq. [23]–[24]): + +```cpp +// g1, g2, g3 = squared L2 norms of unlimited gradients in cell and its neighbours +double eps2 = epsilon * epsilon; +double denom = g1*g1 + g2*g2 + g3*g3 + 3.0 * eps2; +double w1 = (g2*g3 + eps2) / denom; +double w2 = (g3*g1 + eps2) / denom; +double w3 = (g1*g2 + eps2) / denom; + +grad_lim_x = w1 * grad_u1_x + w2 * grad_u2_x + w3 * grad_u3_x; +grad_lim_y = w1 * grad_u1_y + w2 * grad_u2_y + w3 * grad_u3_y; +``` + +When all three gradients are equal, the weights reduce to 1/3 each (no limiting). + +### 10.4 C-Property Preservation + +To satisfy the C-property (still water on non-flat bed produces zero flux), reconstruct **total head** at edges, not depth: + +```cpp +double h_edge = h_center + r_dot_grad_h_limited; +double depth_edge = std::max(h_edge - z_edge, 0.0); +``` + +--- + +## 11. Future Extension Strategy + +### 11.1 Subsurface Flow (Richards' Equation) + +Add vertical prismatic layers below each triangle. The subsurface state vector extends the CVODE system: + +``` +y = [ψ_o(1), ..., ψ_o(N), ψ(1,1), ..., ψ(N,M)] +``` + +Where `ψ(i,m)` is the pressure head in triangle `i`, layer `m`. Coupling between surface and subsurface follows Eq. [4] and [14] from the paper. + +**Data structure:** Add `SubsurfaceStateData` with per-layer arrays following the same SoA pattern. + +### 11.2 Infiltration + +Per-cell infiltration models (Green-Ampt, Horton, SCS Curve Number) computed as a source/sink term in the surface ODE: + +``` +Q_infil[i] = infiltration_model(depth[i], soil_params[i], t) +net_source[i] = rainfall[i] - Q_infil[i] +``` + +**Data structure:** Add infiltration parameters to `MeshData` or a new `InfiltrationData2D` SoA struct. + +### 11.3 Evapotranspiration + +Per-cell ET as a sink term, using the system-level ET rate from SWMM options: + +``` +Q_et[i] = min(et_rate, depth[i] / dt) +``` + +### 11.4 Snowmelt + +Per-cell snowpack tracking following SWMM's existing snow model but applied to 2D cells. Would require a `SnowState2D` SoA struct. + +### 11.5 Anisotropic Roughness + +The formulation already supports full-tensor anisotropy (Eq. [28]–[30]). To enable: +- Add `aniso_k1`, `aniso_k2`, `aniso_angle` arrays to `MeshData` +- Modify flux calculation to use Eq. [30] instead of scalar conductance + +--- + +## 12. Testing Strategy + +### 12.1 Unit Tests + +| Test | Validates | +|------|-----------| +| `test_mesh_builder` | Topology, neighbours, edge normals, areas | +| `test_vertex_reconstruction` | Pseudo-Laplacian weights sum to 1, linear exactness | +| `test_gradient_calculation` | Green-Gauss gradients exact for linear fields | +| `test_slope_limiter` | Reduces to 1/3 weights for uniform gradients | +| `test_diffusive_conductance` | Correct K(ψ) values, dry cell handling | +| `test_orifice_coupling` | Correct Q for various head differences | +| `test_backflow_prevention` | Zero flow when flap gate active and h_swmm > h_2d | +| `test_input_parsing` | Correct parse of all 2D sections | +| `test_section_registration` | Custom sections registered and dispatched | + +### 12.2 Verification Tests (from Kumar et al., 2009) + +| Test | Reference | Description | +|------|-----------|-------------| +| Still water (C-property) | — | Zero flux on non-flat bed with uniform head | +| Tilted plane | Analytical | Steady-state flow on constant-slope plane | +| Dam break | Analytical | 1D dam break on flat bed (Ritter solution) | +| Abdul & Gillham (1984) | Lab data | Coupled hillslope surface-subsurface flow (Phase 2) | + +### 12.3 Benchmark Tests + +| Benchmark | Purpose | +|-----------|---------| +| `bench_flux_calculation` | Throughput of edge flux computation | +| `bench_vertex_reconstruction` | Stencil evaluation performance | +| `bench_cvode_advance` | Full solver step timing | + +--- + +## 13. Lateral Exchange — Uncapped Nodes and Surcharge Feedback + +### 13.1 Problem Statement + +When the 1D pipe network surcharges, water rises above the node crown and "caps" at the ground surface in a conventional SWMM simulation (ponding or flooding). With a coupled 2D surface model, **uncapped nodes** must allow bidirectional exchange: surcharge water spills onto the 2D surface, and 2D overland flow can drain back into the pipe network when capacity is available. + +The challenge is ensuring **consistent, mass-conservative, numerically stable feedback** between the 1D node head (which can exceed ground elevation during surcharge) and the 2D surface head at the coupling point. + +### 13.2 Node Classification for 2D Coupling + +Each coupled SWMM node falls into one of these categories: + +| Category | Condition | 2D Exchange Behaviour | +|----------|-----------|----------------------| +| **Sub-surface** | `h_1D < z_ground` | Normal orifice exchange; 2D surface can drain into node | +| **At-grade** | `h_1D ≈ z_ground` | Transition zone; exchange approaches zero as heads equalize | +| **Surcharged (uncapped)** | `h_1D > z_ground` | Surcharge spills onto 2D surface; bidirectional exchange | +| **Flooded (capped, no 2D)** | `h_1D > z_ground`, no 2D coupling | Legacy SWMM flooding/ponding (unchanged) | + +For coupled nodes, the `ponded_area` parameter is effectively replaced by the 2D surface domain — water that would pond in 1D instead flows onto the 2D mesh. + +### 13.3 Uncapped Node Exchange Equation + +The orifice exchange equation from §6.2 is extended for uncapped nodes: + +``` +Q_exchange = C_d · A_eff(h) · sign(Δh) · sqrt(2g · |Δh|) +``` + +Where `A_eff(h)` is a **head-dependent effective area** that transitions smoothly between regimes: + +```cpp +double effective_area(double h_1d, double h_2d, double z_ground, + double z_invert, double A_inlet, double A_manhole) { + double h_max = std::max(h_1d, h_2d); + + if (h_max < z_ground) { + // Sub-surface: flow through inlet grate/opening + return A_inlet; + } else { + // Surcharged: full manhole opening area + // Smooth transition over a small depth range + double d_trans = 0.05; // 5 cm transition depth + double frac = std::min((h_max - z_ground) / d_trans, 1.0); + return A_inlet + frac * (A_manhole - A_inlet); + } +} +``` + +### 13.4 Surcharge Spill Dynamics + +When `h_1D > z_ground + ψ_2D` (1D surcharge head exceeds 2D surface head): + +1. **Spill flow** is computed via the orifice equation with `Δh = h_1D - h_2D` +2. The spill is injected as a **negative coupling flux** into the 2D cell: `coupling_flux[i] = +Q / A_tri` +3. The same flow is removed from the 1D node via `forcing.node_lat_inflow -= Q` + +When `h_2D > h_1D` (2D surface head exceeds 1D node head): + +1. **Return flow** drains from 2D surface back into the pipe network +2. Subject to capacity: if the node is full (`volume >= full_volume`), return flow is throttled +3. Throttling: `Q_return = min(Q_orifice, (full_volume - volume) / dt)` + +### 13.5 Ponding Suppression for Coupled Nodes + +For nodes coupled to the 2D domain, the engine must **suppress the default SWMM ponding/flooding behaviour**: + +```cpp +// In initNodeFlows(), skip overflow computation for 2D-coupled nodes +if (is_2d_coupled[i]) { + nodes.overflow[i] = 0.0; // 2D surface handles the excess + // Do NOT cap depth at full_depth for coupled nodes +} else { + // Standard SWMM overflow logic + if (nodes.volume[ui] > nodes.full_volume[ui] && dt > 0.0) { + nodes.overflow[ui] = (nodes.volume[ui] - nodes.full_volume[ui]) / dt; + } +} +``` + +This is critical: without suppression, the 1D solver would flood water that should instead exchange with the 2D surface, causing double-counting. + +### 13.6 Head Clamping Strategy + +To prevent numerical instability during surcharge, the 1D node head for coupled nodes is allowed to **exceed `z_ground + full_depth`** — the 2D surface acts as the "cap" instead of the ponded area: + +```cpp +// In DWSolver: for 2D-coupled surcharged nodes, do NOT clamp head +if (is_2d_coupled[node_idx]) { + // Head is free to rise — 2D coupling will drain excess + // Still enforce a safety maximum to prevent runaway + double safety_max = z_ground + 2.0 * full_depth; + nodes.head[node_idx] = std::min(nodes.head[node_idx], safety_max); +} +``` + +--- + +## 14. Outfall Boundary Feedback with 2D Surface + +### 14.1 Problem Statement + +Outfall nodes define downstream boundary conditions for the 1D pipe network. When a 2D surface domain is present, **outfalls at the domain boundary** must account for the 2D water level as a dynamic boundary condition rather than using a fixed or tidal stage. + +### 14.2 Outfall Types and 2D Interaction + +| Outfall Type | Without 2D | With 2D Coupling | +|-------------|-----------|-----------------| +| **FREE** | `h = z + min(yNorm, yCrit)` | 2D head at outfall vertex; if 2D head > critical depth, use 2D head as tailwater | +| **NORMAL** | `h = z + yNorm` | Same, but check if 2D head creates backwater exceeding normal depth | +| **FIXED** | `h = z_fixed` | Max of fixed stage and 2D surface head (2D can raise tailwater above fixed stage) | +| **TIDAL** | `h = tidal_curve(t)` | Max of tidal stage and 2D head (tidal flooding propagates through 2D) | +| **TIMESERIES** | `h = ts(t)` | Max of timeseries stage and 2D head | + +### 14.3 Dynamic Tailwater from 2D Surface + +For each outfall coupled to a 2D vertex or triangle: + +```cpp +void setOutfallDepthWith2D(SimulationContext& ctx, int outfall_idx, + const MeshData& mesh, const SurfaceStateData& state) { + auto& nodes = ctx.nodes; + int vert_idx = outfall_2d_vertex[outfall_idx]; // -1 if not coupled + + if (vert_idx < 0) { + // No 2D coupling — use standard outfall logic + outfall::setOutfallDepth(ctx, outfall_idx, ctx.current_date); + return; + } + + // Get 2D surface head at the outfall coupling point + double h_2d = state.vert_head[vert_idx]; + double z_inv = nodes.invert_elev[outfall_idx]; + + // Compute standard outfall depth (without 2D) + double h_standard = computeStandardOutfallHead(ctx, outfall_idx); + + // The effective boundary head is the MAXIMUM of standard and 2D + // This ensures the 2D surface can raise the tailwater (backwater effect) + // but cannot lower it below the standard boundary condition + double h_effective = std::max(h_standard, h_2d); + + nodes.depth[outfall_idx] = std::max(h_effective - z_inv, 0.0); + nodes.head[outfall_idx] = z_inv + nodes.depth[outfall_idx]; +} +``` + +### 14.4 Backflow Prevention at Outfalls + +When a 2D-coupled outfall has a **flap gate**, the gate prevents backflow from the 2D surface into the pipe network: + +```cpp +if (nodes.outfall_has_flap_gate[outfall_idx] && h_2d > h_pipe) { + // Flap gate closed: no backflow from 2D → pipe + // Outfall acts as a wall boundary for the pipe network + // The 2D surface still receives spill from uncapped upstream nodes + Q_exchange = 0.0; + + // Set outfall depth to critical/normal (independent of 2D) + nodes.depth[outfall_idx] = computeStandardOutfallDepth(ctx, outfall_idx); +} +``` + +Without a flap gate, the outfall allows **bidirectional flow**: +- **Pipe → 2D**: Normal pipe discharge enters the 2D surface domain +- **2D → Pipe**: High 2D water levels push water back into the pipe (backwater effect) + +### 14.5 Mass Balance at Outfall Boundaries + +Outfall coupling must preserve mass balance across the 1D↔2D boundary: + +``` +Volume leaving pipe at outfall = Volume entering 2D at outfall vertex/triangle +``` + +The outfall discharge `Q_outfall` (computed by the 1D solver based on boundary head) becomes a **positive source** in the 2D cell containing the outfall coupling point: + +```cpp +// After 1D routing step, transfer outfall discharge to 2D +for (auto& ocp : outfall_coupling_points) { + double Q_pipe = computeOutfallDischarge(ctx, ocp.outfall_idx); + + // Inject pipe outflow as source into 2D cell + state.coupling_flux[ocp.cell_idx] += Q_pipe / mesh.tri_area[ocp.cell_idx]; + + // Track in mass balance + ctx.mass_balance.outfall_to_2d_volume += Q_pipe * dt; +} +``` + +### 14.6 Outfall Coupling Sequence + +``` +Per SWMM routing step: + +1. Read 2D surface heads at all outfall coupling points +2. Compute effective outfall boundary heads: + h_boundary = max(h_standard, h_2d) [unless flap gate blocks backflow] +3. Set outfall depths using effective heads (before 1D routing) +4. Run 1D routing with updated outfall boundaries +5. Compute outfall discharges from 1D solution +6. Inject outfall discharges into 2D coupling cells +7. Advance 2D solver (outfall cells receive pipe discharge as source) +8. Repeat +``` + +This creates a **feedback loop**: 2D surface levels influence outfall boundary conditions → outfall boundaries affect pipe flows → pipe flows discharge into 2D → 2D levels change → next step boundary conditions update. + +--- + +## 15. C API for 2D Module (`openswmm_2d.h`) + +The 2D module exposes a complete C API following the same conventions as the existing engine API. This enables external orchestration of the entire 2D workflow via CFFI/ctypes/Cython without requiring C++ knowledge. + +### 15.1 Design Principles + +1. **Opaque handle pattern** — all 2D state is accessed through the engine handle +2. **Index-based access** — vertices, triangles, and coupling points are accessed by 0-based index +3. **Bulk operations** — array get/set for efficient data transfer across FFI boundary +4. **Lifecycle-aware** — functions check engine state and return error codes +5. **Optional** — all functions return `SWMM_ERR_BADPARAM` if 2D module is not active + +### 15.2 API Header: `include/openswmm/engine/openswmm_2d.h` + +```c +/** + * @file openswmm_2d.h + * @brief Optional 2D surface routing module — C API. + * + * @details Provides query and control of the optional 2D surface routing + * module coupled to the 1D SWMM pipe network. The 2D module is + * active when [2D_VERTICES] and [2D_TRIANGLES] sections are present + * in the input file and the engine was compiled with OPENSWMM_BUILD_2D. + * + * All functions require the engine to be in SWMM_STATE_RUNNING + * unless otherwise noted. Functions return SWMM_ERR_BADPARAM if + * the 2D module is not active. + * + * @defgroup engine_2d 2D Surface Routing API + * @ingroup engine_api + */ + +#ifndef OPENSWMM_2D_H +#define OPENSWMM_2D_H + +#include "openswmm_callbacks.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* ========================================================================= + * 2D Module Status + * ========================================================================= */ + +/** @brief Check whether the 2D module is active for this simulation. + * @param engine Engine handle. + * @param active Output: 1 if 2D is active, 0 otherwise. + * @returns SWMM_OK or error code. + * @note Valid after SWMM_STATE_INITIALIZED. */ +SWMM_ENGINE_API int swmm_2d_is_active(SWMM_Engine engine, int* active); + +/* ========================================================================= + * Mesh Geometry — Query (read-only after initialization) + * ========================================================================= */ + +/** @brief Get the number of mesh vertices. */ +SWMM_ENGINE_API int swmm_2d_vertex_count(SWMM_Engine engine, int* count); + +/** @brief Get the number of mesh triangles. */ +SWMM_ENGINE_API int swmm_2d_triangle_count(SWMM_Engine engine, int* count); + +/** @brief Get vertex coordinates. + * @param idx Vertex index (0-based). + * @param x,y,z Output coordinates. */ +SWMM_ENGINE_API int swmm_2d_vertex_get_xyz(SWMM_Engine engine, int idx, + double* x, double* y, double* z); + +/** @brief Bulk get vertex coordinates. + * @param x,y,z Output arrays (must be pre-allocated to vertex_count). */ +SWMM_ENGINE_API int swmm_2d_vertex_get_xyz_bulk(SWMM_Engine engine, + double* x, double* y, double* z); + +/** @brief Get triangle connectivity (3 vertex indices). + * @param idx Triangle index (0-based). + * @param v0,v1,v2 Output vertex indices. */ +SWMM_ENGINE_API int swmm_2d_triangle_get_vertices(SWMM_Engine engine, int idx, + int* v0, int* v1, int* v2); + +/** @brief Get triangle area. + * @param idx Triangle index. + * @param area Output area (m² or ft²). */ +SWMM_ENGINE_API int swmm_2d_triangle_get_area(SWMM_Engine engine, int idx, + double* area); + +/** @brief Get triangle centroid coordinates. */ +SWMM_ENGINE_API int swmm_2d_triangle_get_centroid(SWMM_Engine engine, int idx, + double* cx, double* cy, double* cz); + +/** @brief Get triangle Manning's roughness. */ +SWMM_ENGINE_API int swmm_2d_triangle_get_mannings(SWMM_Engine engine, int idx, + double* n); + +/** @brief Get triangle neighbour indices (-1 = boundary edge). + * @param n0,n1,n2 Adjacent triangle indices across edges opposite v0,v1,v2. */ +SWMM_ENGINE_API int swmm_2d_triangle_get_neighbours(SWMM_Engine engine, int idx, + int* n0, int* n1, int* n2); + +/* ========================================================================= + * Coupling Map — Query + * ========================================================================= */ + +/** @brief Get the number of vertex-to-node coupling points. */ +SWMM_ENGINE_API int swmm_2d_vertex_coupling_count(SWMM_Engine engine, int* count); + +/** @brief Get the number of triangle-to-node coupling points. */ +SWMM_ENGINE_API int swmm_2d_triangle_coupling_count(SWMM_Engine engine, int* count); + +/** @brief Get vertex coupling: which SWMM node is coupled to this vertex. + * @param vertex_idx Vertex index. + * @param node_idx Output: SWMM node index, or -1 if uncoupled. */ +SWMM_ENGINE_API int swmm_2d_vertex_get_coupled_node(SWMM_Engine engine, + int vertex_idx, int* node_idx); + +/** @brief Get triangle coupling: which SWMM node is coupled to this triangle. + * @param tri_idx Triangle index. + * @param node_idx Output: SWMM node index, or -1 if uncoupled. */ +SWMM_ENGINE_API int swmm_2d_triangle_get_coupled_node(SWMM_Engine engine, + int tri_idx, int* node_idx); + +/* ========================================================================= + * 2D State — Per-Triangle (read during RUNNING) + * ========================================================================= */ + +/** @brief Get water depth at a triangle. + * @param idx Triangle index. + * @param depth Output depth (m or ft). */ +SWMM_ENGINE_API int swmm_2d_get_depth(SWMM_Engine engine, int idx, double* depth); + +/** @brief Get total head at a triangle (z + depth). */ +SWMM_ENGINE_API int swmm_2d_get_head(SWMM_Engine engine, int idx, double* head); + +/** @brief Get coupling exchange flux at a triangle (m/s, + = into 2D). */ +SWMM_ENGINE_API int swmm_2d_get_coupling_flux(SWMM_Engine engine, int idx, + double* flux); + +/** @brief Get rainfall intensity at a triangle (m/s). */ +SWMM_ENGINE_API int swmm_2d_get_rainfall(SWMM_Engine engine, int idx, + double* rainfall); + +/** @brief Get net source/sink rate at a triangle (m/s). */ +SWMM_ENGINE_API int swmm_2d_get_net_source(SWMM_Engine engine, int idx, + double* net_source); + +/** @brief Bulk get depths for all triangles. + * @param depths Output array (pre-allocated to triangle_count). */ +SWMM_ENGINE_API int swmm_2d_get_depths_bulk(SWMM_Engine engine, double* depths); + +/** @brief Bulk get heads for all triangles. */ +SWMM_ENGINE_API int swmm_2d_get_heads_bulk(SWMM_Engine engine, double* heads); + +/** @brief Bulk get coupling fluxes for all triangles. */ +SWMM_ENGINE_API int swmm_2d_get_coupling_fluxes_bulk(SWMM_Engine engine, + double* fluxes); + +/* ========================================================================= + * 2D State — Per-Vertex (reconstructed heads) + * ========================================================================= */ + +/** @brief Get reconstructed head at a vertex. */ +SWMM_ENGINE_API int swmm_2d_vertex_get_head(SWMM_Engine engine, int idx, + double* head); + +/** @brief Bulk get reconstructed heads at all vertices. */ +SWMM_ENGINE_API int swmm_2d_vertex_get_heads_bulk(SWMM_Engine engine, + double* heads); + +/* ========================================================================= + * 2D Solver Statistics + * ========================================================================= */ + +/** @brief Get the maximum depth across all triangles. */ +SWMM_ENGINE_API int swmm_2d_get_max_depth(SWMM_Engine engine, double* max_depth); + +/** @brief Get total 2D surface volume (sum of depth * area). */ +SWMM_ENGINE_API int swmm_2d_get_total_volume(SWMM_Engine engine, double* volume); + +/** @brief Get total exchange flow rate (sum of all coupling flows, m³/s). + * Positive = net flow from 2D into 1D network. */ +SWMM_ENGINE_API int swmm_2d_get_total_exchange_flow(SWMM_Engine engine, + double* flow); + +/** @brief Get number of CVODE internal steps taken in the last advance. */ +SWMM_ENGINE_API int swmm_2d_get_cvode_steps(SWMM_Engine engine, long* steps); + +/** @brief Get CVODE last internal step size. */ +SWMM_ENGINE_API int swmm_2d_get_cvode_last_step(SWMM_Engine engine, double* h_last); + +/** @brief Get per-triangle max depth statistics (cumulative). + * @param max_depths Output array (pre-allocated to triangle_count). */ +SWMM_ENGINE_API int swmm_2d_get_stat_max_depths(SWMM_Engine engine, + double* max_depths); + +/* ========================================================================= + * 2D Forcing — Override rainfall or coupling for external control + * ========================================================================= */ + +/** @brief Force rainfall on a specific triangle. + * @param idx Triangle index. + * @param value Rainfall rate (m/s). + * @param mode SWMM_FORCING_OVERRIDE or SWMM_FORCING_ADD. + * @param persist SWMM_FORCING_RESET or SWMM_FORCING_PERSIST. */ +SWMM_ENGINE_API int swmm_2d_force_rainfall(SWMM_Engine engine, int idx, + double value, int mode, int persist); + +/** @brief Force rainfall on all triangles (uniform). */ +SWMM_ENGINE_API int swmm_2d_force_rainfall_uniform(SWMM_Engine engine, + double value, int mode, + int persist); + +/** @brief Force coupling flux on a specific triangle (override computed exchange). + * @param value Flux rate (m/s, + = into 2D). */ +SWMM_ENGINE_API int swmm_2d_force_coupling_flux(SWMM_Engine engine, int idx, + double value, int mode, + int persist); + +/** @brief Clear all 2D forcings. */ +SWMM_ENGINE_API int swmm_2d_force_clear_all(SWMM_Engine engine); + +/* ========================================================================= + * 2D Solver Options — Query/Modify (valid after INITIALIZED) + * ========================================================================= */ + +/** @brief Get the dry depth threshold (m). */ +SWMM_ENGINE_API int swmm_2d_get_dry_depth(SWMM_Engine engine, double* dry_depth); + +/** @brief Set the dry depth threshold (m). */ +SWMM_ENGINE_API int swmm_2d_set_dry_depth(SWMM_Engine engine, double dry_depth); + +/** @brief Get CVODE relative tolerance. */ +SWMM_ENGINE_API int swmm_2d_get_rel_tolerance(SWMM_Engine engine, double* rtol); + +/** @brief Set CVODE relative tolerance. */ +SWMM_ENGINE_API int swmm_2d_set_rel_tolerance(SWMM_Engine engine, double rtol); + +/** @brief Get CVODE absolute tolerance. */ +SWMM_ENGINE_API int swmm_2d_get_abs_tolerance(SWMM_Engine engine, double* atol); + +/** @brief Set CVODE absolute tolerance. */ +SWMM_ENGINE_API int swmm_2d_set_abs_tolerance(SWMM_Engine engine, double atol); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* OPENSWMM_2D_H */ +``` + +### 15.3 Cython Declarations (`python/openswmm/engine/_2d.pxd`) + +```cython +cdef extern from "openswmm_2d.h": + # Status + int swmm_2d_is_active(void* engine, int* active) + + # Mesh geometry + int swmm_2d_vertex_count(void* engine, int* count) + int swmm_2d_triangle_count(void* engine, int* count) + int swmm_2d_vertex_get_xyz(void* engine, int idx, + double* x, double* y, double* z) + int swmm_2d_vertex_get_xyz_bulk(void* engine, + double* x, double* y, double* z) + int swmm_2d_triangle_get_vertices(void* engine, int idx, + int* v0, int* v1, int* v2) + int swmm_2d_triangle_get_area(void* engine, int idx, double* area) + int swmm_2d_triangle_get_centroid(void* engine, int idx, + double* cx, double* cy, double* cz) + int swmm_2d_triangle_get_mannings(void* engine, int idx, double* n) + int swmm_2d_triangle_get_neighbours(void* engine, int idx, + int* n0, int* n1, int* n2) + + # Coupling + int swmm_2d_vertex_coupling_count(void* engine, int* count) + int swmm_2d_triangle_coupling_count(void* engine, int* count) + int swmm_2d_vertex_get_coupled_node(void* engine, int vidx, int* nidx) + int swmm_2d_triangle_get_coupled_node(void* engine, int tidx, int* nidx) + + # State + int swmm_2d_get_depth(void* engine, int idx, double* depth) + int swmm_2d_get_head(void* engine, int idx, double* head) + int swmm_2d_get_coupling_flux(void* engine, int idx, double* flux) + int swmm_2d_get_rainfall(void* engine, int idx, double* rainfall) + int swmm_2d_get_depths_bulk(void* engine, double* depths) + int swmm_2d_get_heads_bulk(void* engine, double* heads) + int swmm_2d_get_coupling_fluxes_bulk(void* engine, double* fluxes) + + # Vertex state + int swmm_2d_vertex_get_head(void* engine, int idx, double* head) + int swmm_2d_vertex_get_heads_bulk(void* engine, double* heads) + + # Statistics + int swmm_2d_get_max_depth(void* engine, double* max_depth) + int swmm_2d_get_total_volume(void* engine, double* volume) + int swmm_2d_get_total_exchange_flow(void* engine, double* flow) + int swmm_2d_get_cvode_steps(void* engine, long* steps) + int swmm_2d_get_cvode_last_step(void* engine, double* h_last) + + # Forcing + int swmm_2d_force_rainfall(void* engine, int idx, + double value, int mode, int persist) + int swmm_2d_force_rainfall_uniform(void* engine, + double value, int mode, int persist) + int swmm_2d_force_coupling_flux(void* engine, int idx, + double value, int mode, int persist) + int swmm_2d_force_clear_all(void* engine) + + # Options + int swmm_2d_get_dry_depth(void* engine, double* dry_depth) + int swmm_2d_set_dry_depth(void* engine, double dry_depth) + int swmm_2d_get_rel_tolerance(void* engine, double* rtol) + int swmm_2d_set_rel_tolerance(void* engine, double rtol) + int swmm_2d_get_abs_tolerance(void* engine, double* atol) + int swmm_2d_set_abs_tolerance(void* engine, double atol) +``` + +### 15.4 Python Wrapper (`python/openswmm/engine/_2d.pyx`) + +```python +# High-level Python class wrapping the 2D C API +cimport numpy as np +import numpy as np + +cdef class Surface2D: + """Read-only view of the 2D surface routing state.""" + + cdef void* _engine + + def __init__(self, engine_handle): + self._engine = engine_handle + + @property + def is_active(self): + cdef int active = 0 + _check(swmm_2d_is_active(self._engine, &active)) + return bool(active) + + @property + def n_vertices(self): + cdef int count = 0 + _check(swmm_2d_vertex_count(self._engine, &count)) + return count + + @property + def n_triangles(self): + cdef int count = 0 + _check(swmm_2d_triangle_count(self._engine, &count)) + return count + + def get_depths(self): + """Return depths for all triangles as a numpy array.""" + cdef int n = self.n_triangles + cdef np.ndarray[double, ndim=1] arr = np.empty(n, dtype=np.float64) + _check(swmm_2d_get_depths_bulk(self._engine, &arr[0])) + return arr + + def get_heads(self): + """Return total heads for all triangles as a numpy array.""" + cdef int n = self.n_triangles + cdef np.ndarray[double, ndim=1] arr = np.empty(n, dtype=np.float64) + _check(swmm_2d_get_heads_bulk(self._engine, &arr[0])) + return arr + + def get_vertex_coords(self): + """Return (x, y, z) arrays for all vertices.""" + cdef int n = self.n_vertices + cdef np.ndarray[double, ndim=1] x = np.empty(n, dtype=np.float64) + cdef np.ndarray[double, ndim=1] y = np.empty(n, dtype=np.float64) + cdef np.ndarray[double, ndim=1] z = np.empty(n, dtype=np.float64) + _check(swmm_2d_vertex_get_xyz_bulk(self._engine, &x[0], &y[0], &z[0])) + return x, y, z + + @property + def total_volume(self): + cdef double vol = 0.0 + _check(swmm_2d_get_total_volume(self._engine, &vol)) + return vol + + @property + def total_exchange_flow(self): + cdef double flow = 0.0 + _check(swmm_2d_get_total_exchange_flow(self._engine, &flow)) + return flow +``` + +### 15.5 End-to-End CFFI Workflow Example + +The C API enables a complete external orchestration workflow: + +```c +/* Example: External Python/C driver controlling the full 2D workflow */ + +SWMM_Engine e = swmm_engine_create(); +swmm_engine_open(e, "model_with_2d.inp", "model.rpt", "model.out", NULL); +swmm_engine_initialize(e); + +/* Check if 2D is active */ +int has_2d = 0; +swmm_2d_is_active(e, &has_2d); + +int n_tri = 0, n_vert = 0; +if (has_2d) { + swmm_2d_triangle_count(e, &n_tri); + swmm_2d_vertex_count(e, &n_vert); +} + +swmm_engine_start(e, 1); + +double elapsed = 0.0; +double* depths = malloc(n_tri * sizeof(double)); + +while (swmm_engine_step(e, &elapsed) == SWMM_OK && elapsed > 0.0) { + if (has_2d) { + /* Read 2D state after each step */ + swmm_2d_get_depths_bulk(e, depths); + + /* Optionally override rainfall for scenario testing */ + swmm_2d_force_rainfall_uniform(e, 0.001, /* 1 mm/s */ + SWMM_FORCING_OVERRIDE, SWMM_FORCING_RESET); + + /* Query coupling statistics */ + double total_exchange = 0.0; + swmm_2d_get_total_exchange_flow(e, &total_exchange); + + /* Query solver diagnostics */ + long cvode_steps = 0; + swmm_2d_get_cvode_steps(e, &cvode_steps); + } +} + +free(depths); +swmm_engine_end(e); +swmm_engine_report(e); +swmm_engine_close(e); +swmm_engine_destroy(e); +``` + +--- + +## 16. Summary of Key Design Decisions + +| Decision | Choice | Rationale | +|----------|--------|-----------| +| Mesh type | Constrained Delaunay triangles | Boundary-fitting, adaptive resolution, matches paper | +| Solver | CVODE (BDF) + GMRES | Handles stiffness from Manning's nonlinearity, Jacobian-free | +| Accuracy | Second-order (linear reconstruction + limiter) | Paper formulation, avoids first-order numerical diffusion | +| Coupling mechanism | Forcing API | Clean separation, no modifications to core SWMM solver | +| Exchange equation | Orifice | Standard for manhole/inlet exchange, handles bidirectional flow | +| Uncapped nodes | Suppress ponding, allow head overshoot | 2D surface replaces ponded area for coupled nodes | +| Outfall feedback | max(h_standard, h_2d) | Dynamic tailwater from 2D raises boundary without lowering it | +| C API | `openswmm_2d.h` with opaque handles | Consistent with engine API, enables CFFI/ctypes/Cython | +| Dependency management | vcpkg (SUNDIALS) | Consistent with existing project infrastructure | +| Compile-time optional | `OPENSWMM_BUILD_2D` CMake flag | No penalty for users who don't need 2D | +| Data layout | SoA (parallel vectors) | Matches existing engine pattern, cache-friendly | +| Section naming | `[2D_*]` prefix | Clear, avoids collisions, extensible | + +--- + +## References + +- Kumar, M., Duffy, C.J., and Salvage, K.M. (2009). "A Second-Order Accurate, Finite Volume–Based, Integrated Hydrologic Modeling (FIHM) Framework for Simulation of Surface and Subsurface Flow." *Vadose Zone Journal*, doi:10.2136/vzj2009.0014. +- Jawahar, P. and Kamath, H. (2000). "A high-resolution procedure for Euler and Navier-Stokes computations on unstructured grids." *J. Comput. Phys.*, 164:165–203. +- Abdul, A.S. and Gillham, R.W. (1984). "Laboratory studies of the effects of the capillary fringe on streamflow generation." *Water Resour. Res.*, 20:691–698. +- Cohen, S.D. and Hindmarsh, A.C. (1994). "CVODE user guide." Technical Rep. UCRL-MA-118618. Lawrence Livermore National Lab. diff --git a/docs/Doxyfile b/docs/Doxyfile new file mode 100644 index 000000000..4c6dcca25 --- /dev/null +++ b/docs/Doxyfile @@ -0,0 +1,2997 @@ +# Doxyfile 1.13.2 + +# This file describes the settings to be used by the documentation system +# Doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). +# +# Note: +# +# Use Doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x [configFile] +# Use Doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables or CMake type +# replacement variables: +# doxygen -x_noenv [configFile] + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "OpenSWMM Engine" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = 6.0.0-alpha.1 + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewers a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = "Data-oriented, plugin-extensible SWMM Engine (6.0.0-alpha.1)" + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = ./Images/hydrocouple_logo.png + +# With the PROJECT_ICON tag one can specify an icon that is included in the tabs +# when the HTML document is shown. Doxygen will copy the logo to the output +# directory. + +PROJECT_ICON = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where Doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = . + +# If the CREATE_SUBDIRS tag is set to YES then Doxygen will create up to 4096 +# sub-directories (in 2 levels) under the output directory of each output format +# and will distribute the generated files over these directories. Enabling this +# option can be useful when feeding Doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise cause +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. +# The default value is: NO. + +CREATE_SUBDIRS = YES + +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# number of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + +# If the ALLOW_UNICODE_NAMES tag is set to YES, Doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by Doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with +# English messages), Korean, Korean-en (Korean with English messages), Latvian, +# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, +# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, +# Swedish, Turkish, Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, Doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, Doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, Doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, Doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which Doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where Doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, Doxygen will generate much shorter (but +# less readable) file names. This can be useful if your file system doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen will interpret the +# first line (until the first dot, question mark or exclamation mark) of a +# Javadoc-style comment as the brief description. If set to NO, the Javadoc- +# style will behave just like regular Qt-style comments (thus requiring an +# explicit @brief command for a brief description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then Doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by Doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will interpret the first +# line (until the first dot, question mark or exclamation mark) of a Qt-style +# comment as the brief description. If set to NO, the Qt-style will behave just +# like regular Qt-style comments (thus requiring an explicit \brief command for +# a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# By default Python docstrings are displayed as preformatted text and Doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# Doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as Doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then Doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:^^" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) + +ALIASES = "license=\par License\n" \ + "description=\par Discription\n" \ + "mod=\par Modified\n" \ + "moditem{3}=\b \date\1 \author\2 \3\n" + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by Doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make Doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by Doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then Doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by Doxygen, so you can +# mix Doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 6. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + +# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to +# generate identifiers for the Markdown headings. Note: Every identifier is +# unique. +# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a +# sequence number starting at 0 and GITHUB use the lower case version of title +# with any whitespace replaced by '-' and punctuation characters removed. +# The default value is: DOXYGEN. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +MARKDOWN_ID_STYLE = DOXYGEN + +# When enabled Doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. Words listed in the +# AUTOLINK_IGNORE_WORDS tag are excluded from automatic linking. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# This tag specifies a list of words that, when matching the start of a word in +# the documentation, will suppress auto links generation, if it is enabled via +# AUTOLINK_SUPPORT. This list does not affect affect links explicitly created +# using \# or the \link or commands. +# This tag requires that the tag AUTOLINK_SUPPORT is set to YES. + +AUTOLINK_IGNORE_WORDS = + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let Doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also makes the inheritance and +# collaboration diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software) sources only. Doxygen will parse +# them like normal C++ but will assume all classes use public instead of private +# inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# Doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then Doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, Doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# Doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run Doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +# The NUM_PROC_THREADS specifies the number of threads Doxygen is allowed to use +# during processing. When set to 0 Doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which effectively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +# If the TIMESTAMP tag is set different from NO then each generated page will +# contain the date or date and time when the page was generated. Setting this to +# NO can help when comparing the output of multiple runs. +# Possible values are: YES, NO, DATETIME and DATE. +# The default value is: NO. + +TIMESTAMP = NO + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, Doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# will also hide undocumented C++ concepts if enabled. This option has no effect +# if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_UNDOC_NAMESPACES tag is set to YES, Doxygen will hide all +# undocumented namespaces that are normally visible in the namespace hierarchy. +# If set to NO, these namespaces will be included in the various overviews. This +# option has no effect if EXTRACT_ALL is enabled. +# The default value is: YES. + +HIDE_UNDOC_NAMESPACES = YES + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# With the correct setting of option CASE_SENSE_NAMES Doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and macOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# Possible values are: SYSTEM, NO and YES. +# The default value is: SYSTEM. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO then Doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then Doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES then Doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then Doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then Doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then Doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then Doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and Doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING Doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# Doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by Doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by Doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents Doxygen's defaults, run Doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. +# +# Note that if you run Doxygen from a directory containing a file called +# DoxygenLayout.xml, Doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = DoxygenLayout.xml + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +# The EXTERNAL_TOOL_PATH tag can be used to extend the search path (PATH +# environment variable) so that external tools such as latex and gs can be +# found. +# Note: Directories specified with EXTERNAL_TOOL_PATH are added in front of the +# path already specified by the PATH variable, and are added in the order +# specified. +# Note: This option is particularly useful for macOS version 14 (Sonoma) and +# higher, when running Doxygen from Doxywizard, because in this case any user- +# defined changes to the PATH are ignored. A typical example on macOS is to set +# EXTERNAL_TOOL_PATH = /Library/TeX/texbin /usr/local/bin +# together with the standard path, the full search path used by doxygen when +# launching external tools will then become +# PATH=/Library/TeX/texbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin + +EXTERNAL_TOOL_PATH = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by Doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by Doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then Doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, Doxygen will generate warnings for +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# If WARN_IF_INCOMPLETE_DOC is set to YES, Doxygen will warn about incomplete +# function parameter documentation. If set to NO, Doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, Doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, Doxygen will warn about +# undocumented enumeration values. If set to NO, Doxygen will accept +# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: NO. + +WARN_IF_UNDOC_ENUM_VAL = NO + +# If WARN_LAYOUT_FILE option is set to YES, Doxygen will warn about issues found +# while parsing the user defined layout file, such as missing or wrong elements. +# See also LAYOUT_FILE for details. If set to NO, problems with the layout file +# will be suppressed. +# The default value is: YES. + +WARN_LAYOUT_FILE = YES + +# If the WARN_AS_ERROR tag is set to YES then Doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then Doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the Doxygen process Doxygen will return with a non-zero status. +# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then Doxygen behaves +# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined Doxygen will not +# write the warning messages in between other messages but write them at the end +# of a run, in case a WARN_LOGFILE is defined the warning messages will be +# besides being in the defined file also be shown at the end of a run, unless +# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case +# the behavior will remain as with the setting FAIL_ON_WARNINGS. +# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that Doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# See also: WARN_LINE_FORMAT +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of Doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = ../README.md \ + ../src/legacy/engine \ + ../src/legacy/output \ + ../src/engine \ + ../src/solver \ + ../src/output \ + ../include \ + ./images/*.png \ + ./manuals/user/figures/*.png \ + ./Updates.md \ + ./authors.md \ + ./manuals/manuals.md \ + ./manuals/user \ + ./manuals/reference \ + ./manuals/application + +# This tag can be used to specify the character encoding of the source files +# that Doxygen parses. Internally Doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# See also: INPUT_FILE_ENCODING +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# This tag can be used to specify the character encoding of the source files +# that Doxygen parses. The INPUT_FILE_ENCODING tag can be used to specify +# character encoding on a per file pattern basis. Doxygen will compare the file +# name with each pattern and apply the encoding instead of the default +# INPUT_ENCODING if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). +# See also: INPUT_ENCODING for further information on supported encodings. + +INPUT_FILE_ENCODING = + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by Doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm, +# *.cpp, *.cppm, *.ccm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, +# *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, +# *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to +# be provided as Doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f \ + *.for \ + *.tcl \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf \ + *.as \ + *.js + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which Doxygen is +# run. + +EXCLUDE = ./manuals/reference/hydrology/sections \ + ./manuals/reference/hydraulics/sections \ + ./manuals/reference/quality/sections \ + ./manuals/user/manual + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = manuals/user/manual/* \ + */sections/* \ + */SWMM* \ + manuals/user/figures/* \ + manuals/reference/*/media/* \ + manuals/reference/*/figures/* \ + manuals/application/figures/* \ + implementation-plan.md.bak + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# ANamespace::AClass, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = ./images \ + ./manuals/user/figures \ + ./manuals/reference/hydrology/media/media \ + ./manuals/reference/hydraulics/media/media \ + ./manuals/reference/quality/media/media + +# The INPUT_FILTER tag can be used to specify a program that Doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that Doxygen will use the data processed and written to standard output +# for further processing, therefore nothing else, like debug statements or used +# commands (so in case of a Windows batch file always use @echo OFF), should be +# written to standard output. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by Doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by Doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the Doxygen output. + +USE_MDFILE_AS_MAINPAGE = ../README.md + +# If the IMPLICIT_DIR_DOCS tag is set to YES, any README.md file found in sub- +# directories of the project's root, is used as the documentation for that sub- +# directory, except when the README.md starts with a \dir, \page or \mainpage +# command. If set to NO, the README.md file needs to start with an explicit \dir +# command in order to be used as directory documentation. +# The default value is: YES. + +IMPLICIT_DIR_DOCS = YES + +# The Fortran standard specifies that for fixed formatted Fortran code all +# characters from position 72 are to be considered as comment. A common +# extension is to allow longer lines before the automatic comment starts. The +# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can +# be processed before the automatic comment starts. +# Minimum value: 7, maximum value: 10000, default value: 72. + +FORTRAN_COMMENT_AFTER = 72 + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# multi-line macros, enums or list initialized variables directly into the +# documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct Doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of Doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by Doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then Doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +# If the CLANG_ASSISTED_PARSING tag is set to YES then Doxygen will use the +# clang parser (see: +# http://clang.llvm.org/) for more accurate parsing at the cost of reduced +# performance. This can be particularly helpful with template rich C++ code for +# which Doxygen's built-in parser lacks the necessary type information. +# Note: The availability of this option depends on whether or not Doxygen was +# generated with the -Duse_libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS +# tag is set to YES then Doxygen will add the directory of each input to the +# include path. +# The default value is: YES. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_ADD_INC_PATHS = YES + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by Doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +# If clang assisted parsing is enabled you can provide the clang parser with the +# path to the directory containing a file called compile_commands.json. This +# file is the compilation database (see: +# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the +# options used when the source files were built. This is equivalent to +# specifying the -p option to a clang tool, such as clang-check. These options +# will then be passed to the parser. Any options specified with CLANG_OPTIONS +# will be added as well. +# Note: The availability of this option depends on whether or not Doxygen was +# generated with the -Duse_libclang=ON option for CMake. + +CLANG_DATABASE_PATH = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) +# that should be ignored while generating the index headers. The IGNORE_PREFIX +# tag works for classes, function and member names. The entity will be placed in +# the alphabetical list under the first letter of the entity name that remains +# after removing the prefix. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = I + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, Doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank Doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that Doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that Doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of Doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = ./custom/html/header.html + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank Doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that Doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank Doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that Doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by Doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# Note: Since the styling of scrollbars can currently not be overruled in +# Webkit/Chromium, the styling will be left out of the default doxygen.css if +# one or more extra stylesheets have been specified. So if scrollbar +# customization is desired it has to be added explicitly. For an example see the +# documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = ./custom/css/doxygen-awesome.css + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = custom/js/doxygen-awesome-darkmode-toggle.js \ + custom/js/doxygen-awesome-fragment-copy-button.js \ + custom/js/doxygen-awesome-interactive-toc.js \ + custom/js/doxygen-awesome-paragraph-link.js \ + custom/js/doxygen-awesome-tabs.js + +# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output +# should be rendered with a dark or light theme. +# Possible values are: LIGHT always generates light mode output, DARK always +# generates dark mode output, AUTO_LIGHT automatically sets the mode according +# to the user preference, uses light mode if no preference is set (the default), +# AUTO_DARK automatically sets the mode according to the user preference, uses +# dark mode if no preference is set and TOGGLE allows a user to switch between +# light and dark mode via a button. +# The default value is: AUTO_LIGHT. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE = LIGHT + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a color-wheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use gray-scales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 174 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 84 + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be +# dynamically folded and expanded in the generated HTML source code. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_CODE_FOLDING = YES + +# If the HTML_COPY_CLIPBOARD tag is set to YES then Doxygen will show an icon in +# the top right corner of code and text fragments that allows the user to copy +# its content to the clipboard. Note this only works if supported by the browser +# and the web page is served via a secure context (see: +# https://www.w3.org/TR/secure-contexts/), i.e. using the https: or file: +# protocol. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COPY_CLIPBOARD = YES + +# Doxygen stores a couple of settings persistently in the browser (via e.g. +# cookies). By default these settings apply to all HTML pages generated by +# Doxygen across all projects. The HTML_PROJECT_COOKIE tag can be used to store +# the settings under a project specific key, such that the user preferences will +# be stored separately. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_PROJECT_COOKIE = + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, Doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag determines the URL of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then Doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline (the HTML help workshop was already many +# years in maintenance mode). You can download the HTML help workshop from the +# web archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by Doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# Doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# The SITEMAP_URL tag is used to specify the full URL of the place where the +# generated documentation will be placed on the server by the user during the +# deployment of the documentation. The generated sitemap is called sitemap.xml +# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL +# is specified no sitemap is generated. For information about the sitemap +# protocol see https://www.sitemaps.org +# This tag requires that the tag GENERATE_HTML is set to YES. + +SITEMAP_URL = + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty Doxygen will try to +# run qhelpgenerator on the generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine tune the look of the index (see "Fine-tuning the output"). As an +# example, the default style sheet generated by Doxygen has an example that +# shows how to put an image at the root of the tree instead of the PROJECT_NAME. +# Since the tree basically has the same information as the tab index, you could +# consider setting DISABLE_INDEX to YES when enabling this option. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = YES + +# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the +# FULL_SIDEBAR option determines if the side bar is limited to only the treeview +# area (value NO) or if it should extend to the full height of the window (value +# YES). Setting this to YES gives a layout similar to +# https://docs.readthedocs.io with more room for contents, but less room for the +# project logo, title, and description. If either GENERATE_TREEVIEW or +# DISABLE_INDEX is set to NO, this option has no effect. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FULL_SIDEBAR = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# Doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 1 + +# When the SHOW_ENUM_VALUES tag is set doxygen will show the specified +# enumeration values besides the enumeration mnemonics. +# The default value is: NO. + +SHOW_ENUM_VALUES = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, Doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the OBFUSCATE_EMAILS tag is set to YES, Doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + +# If the HTML_FORMULA_FORMAT option is set to svg, Doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# Doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = YES + +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_3 + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for MathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = chtml + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = https://cdn.jsdelivr.net/npm/mathjax@3/es5/ + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = ams + +# The MATHJAX_CODEFILE tag can be used to specify a file with JavaScript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = ./custom/mathjax-config.js + +# When the SEARCHENGINE tag is enabled Doxygen will generate a search box for +# the HTML output. The underlying search engine uses JavaScript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the JavaScript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /