diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index c7438596acc..00000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,149 +0,0 @@ -version: 2.1 - -install-python-components: &install-python-components - name: Install FEniCS Python components - command: | - git clone https://github.com/FEniCS/basix.git --branch main --single-branch - cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -B build-dir -S ./basix/cpp - cmake --build build-dir --parallel 3 - cmake --install build-dir - pip3 install ./basix/python - pip3 install git+https://github.com/FEniCS/ufl.git - pip3 install git+https://github.com/FEniCS/ffcx.git - -configure-cpp: &configure-cpp - name: Configure (C++) - command: mkdir -p build && cd build && cmake -G Ninja -DCMAKE_BUILD_TYPE=Developer ../cpp/ - -build-install-cpp: &build-install-cpp - name: Build and install (C++) - command: cd build && ninja -j3 install - -unit-tests-cpp: &unit-tests-cpp - name: Build and run C++ unit tests (serial and MPI) - command: | - cmake -G Ninja -DCMAKE_BUILD_TYPE=Developer -B build/test/ -S cpp/test/ - cmake --build build/test - cd build/test - ctest --output-on-failure -R unittests - mpirun -np 3 ctest --output-on-failure -R unittests - -regression-tests-cpp: ®ression-tests-cpp - name: Build and run C++ regressions tests (serial) - command: | - cmake -G Ninja -DCMAKE_BUILD_TYPE=Developer -B build/demo/ -S cpp/demo/ - cmake --build build/demo - cd build/demo - ctest -j3 -R demo -R serial - -regression-tests-cpp-mpi: ®ression-tests-cpp-mpi - name: Run C++ regression tests (MPI) - command: | - cd build/demo - ctest --verbose -R demo -R mpi_3 - -build-python-interface: &build-python-interface - name: Build Python/nanobind interface - command: | - export CMAKE_BUILD_PARALLEL_LEVEL=2 - cd python - pip3 install -r build-requirements.txt - pip3 -v install --config-setting cmake.build-type=Debug --no-build-isolation --user . - -demos-python: &demos-python - name: Run demos (Python, serial) - command: | - mkdir -p ~/junit - cd python/demo - python3 -m pytest -n=4 -v -m serial --durations=10 --junitxml=~/junit/demo-results.xml test.py - -demos-python-mpi: &demos-python-mpi - name: Run demos (Python, MPI) - command: | - cd python/demo - python3 -m pytest -n=2 -v -m mpi test.py --num-proc=3 - -set-jit-defaults: &set-jit-defaults - name: Set default DOLFINx JIT options - command: | - mkdir -p ~/.config/dolfinx - echo '{ "cffi_extra_compile_args" : ["-g0", "-O0" ] }' > ~/.config/dolfinx/dolfinx_jit_options.json - -unit-tests-python: &unit-tests-python - name: Run unit tests (Python, serial) - command: | - mkdir -p ~/junit - cd python/test - python3 -m pytest -n=4 --durations=50 --junitxml=~/junit/test-results.xml unit/ - -unit-tests-python-mpi: &unit-tests-python-mpi - name: Run unit tests (Python, MPI) - command: | - cd python/test - mpirun -np 3 python3 -m pytest unit/ - -jobs: - build-real: - docker: - - image: ghcr.io/fenics/test-env:current-mpich - environment: - DEBIAN_FRONTEND: "noninteractive" - PETSC_ARCH: "linux-gnu-real64-32" - CMAKE_BUILD_PARALLEL_LEVEL: "3" - steps: - - checkout - - run: *install-python-components - - run: *configure-cpp - - run: *build-install-cpp - - - run: *unit-tests-cpp - - run: *regression-tests-cpp - - run: *regression-tests-cpp-mpi - - - run: *build-python-interface - - - run: *demos-python - - run: *demos-python-mpi - - run: *set-jit-defaults - - run: *unit-tests-python - - run: *unit-tests-python-mpi - - store_test_results: - path: ~/junit - - store_artifacts: - path: ~/junit - - build-complex: - docker: - - image: ghcr.io/fenics/test-env:current-mpich - environment: - DEBIAN_FRONTEND: "noninteractive" - PETSC_ARCH: "linux-gnu-complex128-32" - CMAKE_BUILD_PARALLEL_LEVEL: "3" - steps: - - checkout - - run: *install-python-components - - run: *configure-cpp - - run: *build-install-cpp - - - run: *unit-tests-cpp - - run: *regression-tests-cpp - - run: *regression-tests-cpp-mpi - - - run: *build-python-interface - - - run: *demos-python - - run: *demos-python-mpi - - run: *set-jit-defaults - - run: *unit-tests-python - - run: *unit-tests-python-mpi - - store_test_results: - path: ~/junit - - store_artifacts: - path: ~/junit - -workflows: - version: 2 - build: - jobs: - - build-real - - build-complex diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000000..0d08e261a2a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "github-actions" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index ecffbb2f3b3..8dd638008ec 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -72,7 +72,7 @@ jobs: CIBW_ENVIRONMENT: PIP_EXTRA_INDEX_URL=file:///project/simple PETSC_DIR=/usr/local MAKEFLAGS=-j2 steps: - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v5 - name: Install Python dependencies run: python -m pip install cibuildwheel simple503 wheel diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 3427a18fe5d..88add093759 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -30,9 +30,16 @@ on: jobs: lint: runs-on: ubuntu-latest - container: ghcr.io/fenics/test-env:current-openmpi + name: Lint steps: - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Install linting tools + run: pip install clang-format cmake-format[YAML] mypy ruff - name: ruff .py files in C++ code run: | cd cpp/ @@ -54,7 +61,7 @@ jobs: run: | cd cpp clang-format --version - find . -type f \( -name "*.cpp" -o -name "*.h" \) ! -name "loguru.cpp" | xargs clang-format --dry-run --Werror + find . -type f \( -name "*.cpp" -o -name "*.h" \) | xargs clang-format --dry-run --Werror - name: clang-format Python binding checks (non-blocking) continue-on-error: true run: | @@ -69,33 +76,47 @@ jobs: build: runs-on: ubuntu-latest needs: lint - container: ghcr.io/fenics/test-env:current-openmpi env: - SCOTCH_DIR: /usr/local/petsc/linux-gnu-real64-32 OMPI_ALLOW_RUN_AS_ROOT: 1 OMPI_ALLOW_RUN_AS_ROOT_CONFIRM: 1 + PRTE_MCA_rmaps_default_mapping_policy: :oversubscribe # Newer OpenMPI + OMPI_MCA_rmaps_base_oversubscribe: true # Older OpenMPI + name: Build and test steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install catch2 cmake g++ libboost-dev libboost-timer-dev libhdf5-mpi-dev libparmetis-dev libpugixml-dev libspdlog-dev mpi-default-dev ninja-build pkg-config + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install Python build dependencies + run: | + pip install --upgrade -r python/build-requirements.txt + - name: Install FEniCS Python components (default branches/tags) if: github.event_name != 'workflow_dispatch' run: | - python3 -m pip install git+https://github.com/FEniCS/ufl.git - python3 -m pip install git+https://github.com/FEniCS/basix.git - python3 -m pip install git+https://github.com/FEniCS/ffcx.git + pip install git+https://github.com/FEniCS/ufl.git + pip install git+https://github.com/FEniCS/basix.git + pip install git+https://github.com/FEniCS/ffcx.git - name: Install FEniCS Python components if: github.event_name == 'workflow_dispatch' run: | - python3 -m pip install git+https://github.com/FEniCS/ufl.git@${{ github.event.inputs.ufl_ref }} - python3 -m pip install git+https://github.com/FEniCS/basix.git@${{ github.event.inputs.basix_ref }} - python3 -m pip install git+https://github.com/FEniCS/ffcx.git@${{ github.event.inputs.ffcx_ref }} + pip install git+https://github.com/FEniCS/ufl.git@${{ github.event.inputs.ufl_ref }} + pip install git+https://github.com/FEniCS/basix.git@${{ github.event.inputs.basix_ref }} + pip install git+https://github.com/FEniCS/ffcx.git@${{ github.event.inputs.ffcx_ref }} - - uses: actions/checkout@v4 - - name: Configure C++ - run: cmake -G Ninja -DDOLFINX_ENABLE_PETSC=false -DCMAKE_BUILD_TYPE=Developer -B build -S cpp/ - - name: Build and install C++ library + - name: Configure and install C++ run: | + cmake -G Ninja -DDOLFINX_ENABLE_PETSC=false -DCMAKE_BUILD_TYPE=Developer -B build -S cpp/ cmake --build build - cmake --install build + sudo cmake --install build - name: Build C++ unit tests run: | cmake -G Ninja -DCMAKE_BUILD_TYPE=Developer -B build/test/ -S cpp/test/ @@ -107,58 +128,67 @@ jobs: - name: Run C++ unit tests (MPI) run: | cd build/test - mpiexec -np 2 ctest -V --output-on-failure -R unittests + mpirun -n 3 ctest -V --output-on-failure -R unittests - name: Build Python interface - run: python3 -m pip -v install --check-build-dependencies --no-build-isolation --config-settings=cmake.build-type="Debug" python/ - - name: Test Python import - run: python3 -c "import dolfinx; print(dolfinx.__version__)" + run: | + pip install --check-build-dependencies --no-build-isolation --config-settings=cmake.build-type="Debug" 'python/[test]' + python -c "from mpi4py import MPI; import dolfinx; assert not dolfinx.has_petsc4py" + + - name: Run demos (Python, serial) + run: | + pip install pytest-xdist + python -m pytest -n auto -m serial --durations=10 python/demo/test.py + - name: Run demos (Python, MPI (np=3)) + run: python -m pytest -m mpi --num-proc=3 python/demo/test.py + - name: Run unit tests + run: python -m pytest -n auto -m "not petsc4py and not adios2" python/test/unit + - name: Run unit tests (MPI, np=3) + run: mpirun -np 3 python -m pytest -m "not petsc4py and not adios2" python/test/unit build-with-petsc: runs-on: ubuntu-latest needs: lint - container: ghcr.io/fenics/test-env:current-openmpi + strategy: + matrix: + petsc_arch: [linux-gnu-real32-32, linux-gnu-real64-32, linux-gnu-complex64-32, linux-gnu-complex128-32, linux-gnu-real64-64, linux-gnu-complex128-64] + docker_image: ["ghcr.io/fenics/test-env:current-openmpi"] + include: + - docker_image: "ghcr.io/fenics/test-env:current-mpich" + petsc_arch: linux-gnu-real64-32 + - docker_image: "ghcr.io/fenics/test-env:current-mpich" + petsc_arch: linux-gnu-complex128-32 + container: ${{ matrix.docker_image }} env: PETSC_ARCH: ${{ matrix.petsc_arch }} OMPI_ALLOW_RUN_AS_ROOT: 1 OMPI_ALLOW_RUN_AS_ROOT_CONFIRM: 1 - strategy: - matrix: - petsc_arch: [linux-gnu-real32-32, linux-gnu-real64-32, linux-gnu-complex64-32, linux-gnu-complex128-32, linux-gnu-real64-64, linux-gnu-complex128-64] + PRTE_MCA_rmaps_default_mapping_policy: :oversubscribe - name: Build and test (${{ matrix.petsc_arch }}) + name: Build and test (${{ matrix.petsc_arch }}, ${{ matrix.docker_image }}) steps: - uses: actions/checkout@v4 - name: Install FEniCS Python components (default branches/tags) if: github.event_name != 'workflow_dispatch' run: | - python3 -m pip install git+https://github.com/FEniCS/ufl.git - python3 -m pip install git+https://github.com/FEniCS/basix.git - python3 -m pip install git+https://github.com/FEniCS/ffcx.git + pip install git+https://github.com/FEniCS/ufl.git + pip install git+https://github.com/FEniCS/basix.git + pip install git+https://github.com/FEniCS/ffcx.git + - name: Install FEniCS Python components if: github.event_name == 'workflow_dispatch' run: | - python3 -m pip install git+https://github.com/FEniCS/ufl.git@${{ github.event.inputs.ufl_ref }} - python3 -m pip install git+https://github.com/FEniCS/basix.git@${{ github.event.inputs.basix_ref }} - python3 -m pip install git+https://github.com/FEniCS/ffcx.git@${{ github.event.inputs.ffcx_ref }} - - - name: Configure C++ - run: cmake -G Ninja -DCMAKE_BUILD_TYPE=Developer -B build -S cpp/ + pip install git+https://github.com/FEniCS/ufl.git@${{ github.event.inputs.ufl_ref }} + pip install git+https://github.com/FEniCS/basix.git@${{ github.event.inputs.basix_ref }} + pip install git+https://github.com/FEniCS/ffcx.git@${{ github.event.inputs.ffcx_ref }} - - name: Build and install C++ library + - name: Configure, build and install C++ library run: | + cmake -G Ninja -DCMAKE_BUILD_TYPE=Developer -DDOLFINX_ENABLE_ADIOS2=true -DDOLFINX_ENABLE_KAHIP=true -DDOLFINX_ENABLE_PARMETIS=false -DDOLFINX_ENABLE_PETSC=true -DDOLFINX_ENABLE_SCOTCH=true -DDOLFINX_ENABLE_SLEPC=true -B build -S cpp/ cmake --build build cmake --install build - - name: Build C++ interface documentation - run: | - export DOLFINX_VERSION=`cmake -L build | grep DOXYGEN_DOLFINX_VERSION | cut -f2 -d "="` - echo $DOLFINX_VERSION - cd cpp/doc - doxygen Doxyfile - make html - - name: Build C++ unit tests run: | cmake -G Ninja -DCMAKE_BUILD_TYPE=Developer -B build/test/ -S cpp/test/ @@ -170,7 +200,7 @@ jobs: - name: Run C++ unit tests (MPI) run: | cd build/test - mpiexec -np 2 ctest -V --output-on-failure -R unittests + mpirun -n 3 ctest -V --output-on-failure -R unittests - name: Build and run C++ regression tests (serial and MPI (np=2)) run: | cmake -G Ninja -DCMAKE_BUILD_TYPE=Developer -B build/demo/ -S cpp/demo/ @@ -179,13 +209,13 @@ jobs: ctest -V -R demo -R serial ctest -V -R demo -R mpi_2 + - name: Install Python build dependencies + run: pip install -r python/build-requirements.txt + - name: Build Python interface run: | - python3 -m pip -v install --check-build-dependencies --no-build-isolation --config-settings=cmake.build-type="Debug" python/ - - name: Build Python interface documentation - run: | - cd python/doc - python3 -m sphinx -W -b html source/ build/html/ + pip install --check-build-dependencies --no-build-isolation --config-settings=cmake.build-type="Debug" 'python/[test]' + python -c "from mpi4py import MPI; import dolfinx; assert dolfinx.has_adios2; assert dolfinx.has_kahip; assert not dolfinx.has_parmetis; assert dolfinx.has_petsc; assert dolfinx.has_petsc4py; assert dolfinx.has_ptscotch; assert dolfinx.has_slepc; assert dolfinx.has_complex_ufcx_kernels" - name: Set default DOLFINx JIT options run: | @@ -193,47 +223,98 @@ jobs: echo '{ "cffi_extra_compile_args": ["-g0", "-O0" ] }' > ~/.config/dolfinx/dolfinx_jit_options.json - name: Run demos (Python, serial) - run: python3 -m pytest -n=2 -m serial --durations=10 python/demo/test.py - - name: Run demos (Python, MPI (np=2)) - run: python3 -m pytest -m mpi --num-proc=2 python/demo/test.py + run: | + pip install pytest-xdist + python -m pytest -n auto -m serial --durations=10 python/demo/test.py + - name: Run demos (Python, MPI (np=3)) + run: python -m pytest -m mpi --num-proc=3 python/demo/test.py - name: Run Python unit tests (serial) - run: python3 -m pytest -n=auto --durations=50 python/test/unit/ - - name: Run Python unit tests (MPI, np=2) - run: mpirun -np 2 python3 -m pytest python/test/unit/ + run: python -m pytest -m "petsc4py or adios2" -n=auto --durations=50 python/test/unit/ + - name: Run Python unit tests (MPI, np=3) + run: mpirun -np 3 python -m pytest -m "petsc4py or adios2" python/test/unit/ - - name: Upload C++ documentation artifact + build-and-publish-docs: + runs-on: ubuntu-latest + container: "ghcr.io/fenics/test-env:current-openmpi" + env: + PETSC_ARCH: linux-gnu-real64-32 + OMPI_ALLOW_RUN_AS_ROOT: 1 + OMPI_ALLOW_RUN_AS_ROOT_CONFIRM: 1 + PRTE_MCA_rmaps_default_mapping_policy: :oversubscribe + + name: Build and publish docs + steps: + - uses: actions/checkout@v4 + + - name: Install FEniCS Python components (default branches/tags) + if: github.event_name != 'workflow_dispatch' + run: | + pip install git+https://github.com/FEniCS/ufl.git + pip install git+https://github.com/FEniCS/basix.git + pip install git+https://github.com/FEniCS/ffcx.git + - name: Install FEniCS Python components + if: github.event_name == 'workflow_dispatch' + run: | + pip install git+https://github.com/FEniCS/ufl.git@${{ github.event.inputs.ufl_ref }} + pip install git+https://github.com/FEniCS/basix.git@${{ github.event.inputs.basix_ref }} + pip install git+https://github.com/FEniCS/ffcx.git@${{ github.event.inputs.ffcx_ref }} + + - name: Configure C++ + run: | + cmake -G Ninja -DCMAKE_BUILD_TYPE=Developer -B build -S cpp/ + cmake --build build + cmake --install build + + - name: Build Python interface + run: | + pip install -r python/build-requirements.txt + pip install --check-build-dependencies --no-build-isolation --config-settings=cmake.build-type="Debug" 'python/[docs]' + + - name: Build C++ interface documentation + run: | + export DOLFINX_VERSION=`cmake -L build | grep DOXYGEN_DOLFINX_VERSION | cut -f2 -d "="` + echo $DOLFINX_VERSION + cd cpp/doc + doxygen Doxyfile + make html + - name: Upload C++ Doxygen documentation artifact uses: actions/upload-artifact@v4 with: - name: doc-cpp-${{ matrix.petsc_arch }} - path: | - cpp/doc/html/ - cpp/doc/build/ - retention-days: 2 - if-no-files-found: error - + name: docs-cpp-doxygen + path: cpp/doc/html + retention-days: 2 + - name: Upload C++ Sphinx documentation artifact + uses: actions/upload-artifact@v4 + with: + name: docs-cpp-sphinx + path: cpp/doc/build/html + retention-days: 2 + + - name: Build Python interface documentation + run: | + cd python/doc + python -m sphinx -W -b html source/ build/html/ - name: Upload Python documentation artifact uses: actions/upload-artifact@v4 with: - name: doc-python-${{ matrix.petsc_arch }} - path: | - python/doc/build/html/ - retention-days: 2 - if-no-files-found: error + name: docs-python + path: python/doc/build/html + retention-days: 2 - name: Checkout FEniCS/docs - if: ${{ github.repository == 'FEniCS/dolfinx' && ( github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') ) && runner.os == 'Linux' }} + if: ${{ github.repository == 'FEniCS/dolfinx' && ( github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') ) }} uses: actions/checkout@v4 with: repository: "FEniCS/docs" path: "docs" ssh-key: "${{ secrets.SSH_GITHUB_DOCS_PRIVATE_KEY }}" - name: Set version name - if: ${{ github.repository == 'FEniCS/dolfinx' && ( github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') ) && runner.os == 'Linux' }} + if: ${{ github.repository == 'FEniCS/dolfinx' && ( github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') ) }} run: | echo "VERSION_NAME=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV - name: Copy documentation into repository - if: ${{ github.repository == 'FEniCS/dolfinx' && ( github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') ) && runner.os == 'Linux' }} + if: ${{ github.repository == 'FEniCS/dolfinx' && ( github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') ) }} run: | cd docs git rm -r --ignore-unmatch dolfinx/${{ env.VERSION_NAME }}/cpp @@ -245,7 +326,7 @@ jobs: cp -r ../cpp/doc/html/* dolfinx/${{ env.VERSION_NAME }}/cpp/doxygen cp -r ../python/doc/build/html/* dolfinx/${{ env.VERSION_NAME }}/python - name: Commit and push documentation to FEniCS/docs - if: ${{ github.repository == 'FEniCS/dolfinx' && ( github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') ) && runner.os == 'Linux' && matrix.petsc_arch == 'linux-gnu-real64-32' }} + if: ${{ github.repository == 'FEniCS/dolfinx' && ( github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') ) }} run: | cd docs git config --global user.email "fenics@github.com" diff --git a/.github/workflows/conda.yml b/.github/workflows/conda.yml index 82a0cf51d12..40d57adc68b 100644 --- a/.github/workflows/conda.yml +++ b/.github/workflows/conda.yml @@ -16,7 +16,6 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-13, macos-14] - fail-fast: false runs-on: ${{ matrix.os }} timeout-minutes: 30 diff --git a/.github/workflows/docker-dev-test-env.yml b/.github/workflows/docker-dev-test-env.yml index 7af7b899bc7..58a1b8bf497 100644 --- a/.github/workflows/docker-dev-test-env.yml +++ b/.github/workflows/docker-dev-test-env.yml @@ -84,7 +84,7 @@ jobs: echo "PETSC_SLEPC_DEBUGGING=no" >> $GITHUB_ENV - name: Build and push - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: build-args: | MPI=${{ matrix.mpi }} diff --git a/.github/workflows/docker-end-user.yml b/.github/workflows/docker-end-user.yml index da4062589ed..eee7fa51a55 100644 --- a/.github/workflows/docker-end-user.yml +++ b/.github/workflows/docker-end-user.yml @@ -130,7 +130,7 @@ jobs: uses: docker/setup-buildx-action@v3 - name: Build dolfinx-onbuild - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: build-args: | DOLFINX_CMAKE_BUILD_TYPE=RelWithDebInfo @@ -142,7 +142,7 @@ jobs: tags: docker.io/dolfinx/dolfinx-onbuild:${{ env.TAG }} - name: Build intermediate - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: build-args: | DOLFINX_CMAKE_BUILD_TYPE=RelWithDebInfo @@ -152,7 +152,7 @@ jobs: target: intermediate - name: Build dolfinx - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: build-args: | BASEIMAGE=${{ env.BASEIMAGE }} @@ -163,7 +163,7 @@ jobs: tags: docker.io/dolfinx/dolfinx:${{ env.TAG }} - name: Build lab - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: build-args: | BASEIMAGE=${{ env.BASEIMAGE }} @@ -177,12 +177,12 @@ jobs: if: ${{ !contains(github.event.inputs.dockerfile, 'dummy') }} run: | docker run --rm docker.io/dolfinx/dolfinx:${{ env.TAG }} \ - python3 -c "import dolfinx; from dolfinx.fem import functionspace; from dolfinx.mesh import create_unit_square; from mpi4py import MPI; mesh = create_unit_square(MPI.COMM_WORLD, 10, 10); V = functionspace(mesh, ('Lagrange', 1));" + python3 -c "import dolfinx; from dolfinx.fem import functionspace; from dolfinx.mesh import create_unit_square; from mpi4py import MPI; mesh = create_unit_square(MPI.COMM_WORLD, 10, 10); V = functionspace(mesh, ('Lagrange', 1));" docker run --rm docker.io/dolfinx/dolfinx:${{ env.TAG }} \ - /bin/bash -c "source /usr/local/bin/dolfinx-complex-mode && python3 -c $'import dolfinx; from dolfinx.fem import functionspace; from dolfinx.mesh import create_unit_square; from mpi4py import MPI; mesh = create_unit_square(MPI.COMM_WORLD, 10, 10); V = functionspace(mesh, (\"Lagrange\", 1));'" + /bin/bash -c "source /usr/local/bin/dolfinx-complex-mode && python3 -c $'import dolfinx; from dolfinx.fem import functionspace; from dolfinx.mesh import create_unit_square; from mpi4py import MPI; mesh = create_unit_square(MPI.COMM_WORLD, 10, 10); V = functionspace(mesh, (\"Lagrange\", 1));'" - name: Login to Docker Hub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -212,13 +212,13 @@ jobs: echo "TAG=${USER_INPUT:-nightly}" >> $GITHUB_ENV - name: Login to Docker Hub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GitHub Container Registry - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} diff --git a/.github/workflows/docker-oneapi-test-env.yml b/.github/workflows/docker-oneapi-test-env.yml deleted file mode 100644 index fb3868a1c3f..00000000000 --- a/.github/workflows/docker-oneapi-test-env.yml +++ /dev/null @@ -1,61 +0,0 @@ ---- -name: Docker Intel oneAPI test image - -# Builds Intel OneAPI test environment image (no FEniCS components). -# -# Must be triggered manually via GitHub interface. - -on: - workflow_dispatch: - inputs: - tag_prefix: - description: "tag prefix for docker images" - default: "current" - type: string - required: true - -jobs: - create_build_images: - name: Create build env images - if: ${{ github.repository == 'FEniCS/dolfinx' }} - - runs-on: ubuntu-latest - steps: - - name: Checkout DOLFINx - uses: actions/checkout@v4 - - - name: Reduce disk usage - run: | - # Workaround to provide additional free space for builds. - # https://github.com/actions/virtual-environments/issues/2840 - sudo apt-get remove -y '^dotnet-.*' - sudo apt-get remove -y 'php.*' - sudo apt-get remove -y azure-cli google-chrome-stable firefox powershell mono-devel - sudo apt-get autoremove -y - sudo apt-get clean - sudo rm -rf /usr/share/dotnet - sudo rm -rf "/usr/local/share/boost" - sudo rm -rf "$AGENT_TOOLSDIRECTORY" - - - name: Create image name and tag - run: | - USER_INPUT=${{ github.event.inputs.tag_prefix }} - echo "TAG=docker.io/fenicsproject/test-env:${USER_INPUT:-current}-oneapi" >> $GITHUB_ENV - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Build and push - uses: docker/build-push-action@v5 - with: - context: docker/ - cache-to: type=inline - file: docker/Dockerfile.oneapi - push: true - tags: ${{ env.TAG }} diff --git a/.github/workflows/docker-redhat-test-env.yml b/.github/workflows/docker-redhat-test-env.yml index c8b68c97d6f..a0dc888f57e 100644 --- a/.github/workflows/docker-redhat-test-env.yml +++ b/.github/workflows/docker-redhat-test-env.yml @@ -39,7 +39,7 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: docker/ cache-to: type=inline diff --git a/.github/workflows/docker-update-stable.yml b/.github/workflows/docker-update-stable.yml index 7cf4009d14b..586a1ac2358 100644 --- a/.github/workflows/docker-update-stable.yml +++ b/.github/workflows/docker-update-stable.yml @@ -26,13 +26,13 @@ jobs: uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GitHub Container Registry - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 7e67aaa73fc..6ff1ceda5d2 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -1,9 +1,17 @@ name: macOS build on: - schedule: - # '*' is a special character in YAML, so string must be quoted - - cron: "0 4 * * WED" + pull_request: + branches: + - main + push: + branches: + - "main" + tags: + - "v*" + merge_group: + branches: + - main workflow_dispatch: jobs: @@ -22,18 +30,17 @@ jobs: - name: Install Homebrew dependencies run: | - brew install adios2 boost cmake hdf5-mpi make ninja open-mpi pkg-config pugixml # FEniCS - brew install bison flex # PETSc + brew install adios2 boost cmake hdf5-mpi make ninja open-mpi pkg-config pugixml spdlog # FEniCS + brew install bison flex gfortran # PETSc - - name: Install Python dependencies + - name: Install Python dependencies (petsc4py) run: | - python -m pip install --upgrade pip setuptools wheel - python -m pip install cffi cppimport cython numba numpy pytest pytest-xdist scipy - python -m pip install --no-build-isolation mpi4py + python -m pip install mpi4py numpy + python -m pip install cython setuptools wheel - name: Install minimal PETSc and petsc4py run: | - export PATH="$(brew --prefix bison)/bin:$PATH" + export PATH="$(brew --prefix gfortran)/bin:$(brew --prefix bison)/bin:$PATH" git clone -b release https://gitlab.com/petsc/petsc.git petsc cd petsc python ./configure \ @@ -46,10 +53,12 @@ jobs: --download-metis \ --download-parmetis \ --download-ptscotch \ - --download-superlu_dist + --download-scalapack \ + --download-mumps \ + --download-mumps-avoid-mpi-in-place make all cd src/binding/petsc4py - arch -arm64 python -m pip install --no-build-isolation --no-cache-dir -v . + arch -arm64 python -m pip -v install --no-build-isolation --no-cache-dir . - name: Install FEniCSx dependencies run: | @@ -89,7 +98,7 @@ jobs: working-directory: dolfinx run: | python -m pip install -r python/build-requirements.txt - python -m pip install --check-build-dependencies --no-build-isolation python/ + python -m pip install --check-build-dependencies --no-build-isolation 'python/[test]' - name: Basic test run: | @@ -98,7 +107,10 @@ jobs: - name: Run Python unit tests (serial) working-directory: dolfinx - run: python3 -m pytest -n=auto --durations=50 python/test/unit/ + run: | + python -m pip install pytest-xdist + python3 -m pytest -n=auto --durations=50 python/test/unit/ - name: Run Python unit tests (MPI, np=3) working-directory: dolfinx - run: mpirun -np 3 python3 -m pytest python/test/unit/ + run: | + mpirun -np 3 python3 -m pytest python/test/unit/ diff --git a/.github/workflows/oneapi.yml b/.github/workflows/oneapi.yml index be36441b955..09b96b25359 100644 --- a/.github/workflows/oneapi.yml +++ b/.github/workflows/oneapi.yml @@ -17,24 +17,42 @@ on: jobs: build: runs-on: ubuntu-latest - container: fenicsproject/test-env:current-oneapi + container: ubuntu:24.04 env: - PETSC_ARCH: ${{ matrix.petsc_arch }} + CC: icx + CXX: icpx + MPICH_CC: icx + MPICH_CXX: icpx + I_MPI_OFI_LIBRARY_INTERNAL: 0 + OMP_NUM_THREADS: 1 + OPENBLAS_NUM_THREADS: 1 - strategy: - matrix: - petsc_arch: [linux-gnu-real32-32, linux-gnu-real64-32, linux-gnu-complex128-32] + name: oneAPI build and test - name: oneAPI build and test (${{ matrix.petsc_arch }}) + defaults: + run: + shell: bash -el {0} steps: + - name: Install compiler dependencies + run: | + apt-get -y update + apt-get -y install binutils libstdc++-14-dev + - uses: actions/checkout@v4 + - uses: conda-incubator/setup-miniconda@v3 + with: + activate-environment: oneapi-test-env + environment-file: python/conda-oneapi-test-env.yml + auto-activate-base: false + - run: | + conda info + conda list + - name: Install Basix - run: | - . /opt/intel/oneapi/setvars.sh - pip install git+https://github.com/FEniCS/basix.git + run: pip install --no-build-isolation git+https://github.com/FEniCS/basix.git - name: Clone FFCx uses: actions/checkout@v4 @@ -44,31 +62,26 @@ jobs: ref: main - name: Install FFCx C interface run: | - . /opt/intel/oneapi/setvars.sh - cmake -B ufcx-build-dir -S ffcx/cmake/ + cmake -G Ninja -B ufcx-build-dir -S ffcx/cmake/ cmake --build ufcx-build-dir cmake --install ufcx-build-dir - name: Configure DOLFINx C++ run: | - . /opt/intel/oneapi/setvars.sh cmake -G Ninja -DCMAKE_BUILD_TYPE=Developer -DDOLFINX_ENABLE_SCOTCH=on -DDOLFINX_ENABLE_KAHIP=on -DDOLFINX_UFCX_PYTHON=off -B build -S cpp/ - name: Build and install DOLFINx C++ library run: | - . /opt/intel/oneapi/setvars.sh cmake --build build cmake --install build - name: Install UFL and FFCx modules run: | - . /opt/intel/oneapi/setvars.sh - pip install git+https://github.com/FEniCS/ufl.git - pip install ffcx/ + pip install --no-build-isolation git+https://github.com/FEniCS/ufl.git + pip install --no-build-isolation ffcx/ - name: Build and run DOLFINx C++ unit tests (serial and MPI) run: | - . /opt/intel/oneapi/setvars.sh cmake -G Ninja -DCMAKE_BUILD_TYPE=Developer -B build/test/ -S cpp/test/ cmake --build build/test cd build/test @@ -77,7 +90,6 @@ jobs: - name: Build and run DOLFINx C++ regression tests (serial and MPI (np=2)) run: | - . /opt/intel/oneapi/setvars.sh cmake -G Ninja -DCMAKE_BUILD_TYPE=Developer -B build/demo/ -S cpp/demo/ cmake --build build/demo cd build/demo @@ -85,26 +97,13 @@ jobs: ctest -R demo -R mpi_2 - name: Build DOLFINx Python interface - run: | - . /opt/intel/oneapi/setvars.sh - pip -v install --check-build-dependencies --no-build-isolation python/ - - name: Set default DOLFINx JIT options - run: | - mkdir -p ~/.config/dolfinx - echo '{ "cffi_extra_compile_args": ["-g0", "-O0" ] }' > ~/.config/dolfinx/dolfinx_jit_options.json + run: pip -v install --check-build-dependencies --no-build-isolation --config-settings=cmake.build-type="Developer" python/ - name: Run DOLFINx demos (Python, serial) - run: | - . /opt/intel/oneapi/setvars.sh - pytest -v -n=2 -m serial --durations=10 python/demo/test.py + run: python -m pytest -v -n=2 -m serial --durations=10 python/demo/test.py - name: Run DOLFINx demos (Python, MPI (np=2)) - run: | - . /opt/intel/oneapi/setvars.sh - pytest -m mpi --num-proc=2 python/demo/test.py + run: pytest -m mpi --num-proc=2 python/demo/test.py + - name: Run DOLFINx Python unit tests (serial) - run: | - . /opt/intel/oneapi/setvars.sh - pytest -n=auto --durations=50 python/test/unit + run: python -m pytest -m "not adios2" -n=auto --durations=50 python/test/unit - name: Run DOLFINx Python unit tests (MPI, np=2) - run: | - . /opt/intel/oneapi/setvars.sh - mpiexec -n 2 pytest python/test/unit + run: mpiexec -n 2 python -m pytest -m "not adios2" python/test/unit diff --git a/.github/workflows/pyvista.yml b/.github/workflows/pyvista.yml index f82d117c632..6263f0ae29a 100644 --- a/.github/workflows/pyvista.yml +++ b/.github/workflows/pyvista.yml @@ -20,8 +20,9 @@ jobs: # For pyvista/pyvistaqt DISPLAY: ":99.0" PYVISTA_OFF_SCREEN: true - PYVISTA_QT_VERSION: 0.11.0 - PYVISTA_VERSION: 0.40.0 + PYVISTA_QT_VERSION: 0.11.1 + PYVISTA_VERSION: 0.44.1 + QT_DEBUG_PLUGINS: 1 PETSC_ARCH: ${{ matrix.petsc_arch }} OMPI_ALLOW_RUN_AS_ROOT: 1 @@ -37,15 +38,15 @@ jobs: - name: Install FEniCS Python components run: | - python3 -m pip install git+https://github.com/FEniCS/ufl.git - python3 -m pip install git+https://github.com/FEniCS/basix.git - python3 -m pip install git+https://github.com/FEniCS/ffcx.git + pip install git+https://github.com/FEniCS/ufl.git + pip install git+https://github.com/FEniCS/basix.git + pip install git+https://github.com/FEniCS/ffcx.git apt-get update apt-get install -y --no-install-recommends libgl1-mesa-dev xvfb # pyvista - apt-get install -y --no-install-recommends python3-pyqt5 libgl1-mesa-glx # pyvistaqt - python3 -m pip install pyvista==${PYVISTA_VERSION} - python3 -m pip install pyvistaqt==${PYVISTA_QT_VERSION} - python3 -m pip install matplotlib ipython + apt-get install -y --no-install-recommends libqt5gui5t64 libgl1 # pyvistaqt + pip install pyvista==${PYVISTA_VERSION} + pip install pyqt5 pyvistaqt==${PYVISTA_QT_VERSION} + pip install --no-build-isolation -r python/build-requirements.txt - name: Configure C++ run: cmake -G Ninja -DCMAKE_BUILD_TYPE=Developer -B build -S cpp/ @@ -55,10 +56,12 @@ jobs: cmake --install build - name: Build Python interface - run: pip -v install --check-build-dependencies --config-settings=build-dir="build" --config-settings=cmake.build-type="Debug" --no-build-isolation python/ + run: pip -v install --check-build-dependencies --config-settings=build-dir="build" --config-settings=cmake.build-type="Debug" --no-build-isolation 'python/[test]' - name: Run pyvista demos (Python, serial) - run: python3 -m pytest -v -n=2 -m serial --durations=10 python/demo/test.py + run: | + pip install pytest-xdist + python3 -m pytest -v -n 2 -m serial --durations=10 python/demo/test.py - name: Run pyvista demos (Python, MPI (np=2)) run: python3 -m pytest -v -m mpi --num-proc=2 python/demo/test.py diff --git a/.github/workflows/redhat.yml b/.github/workflows/redhat.yml index 4182f6d6fc1..76aaadca04e 100644 --- a/.github/workflows/redhat.yml +++ b/.github/workflows/redhat.yml @@ -24,6 +24,10 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Install Python build dependencies + run: | + python3 -m pip install --no-cache-dir --upgrade pip setuptools wheel + - name: Install FEniCS Python components run: | python3 -m pip install git+https://github.com/FEniCS/ufl.git @@ -32,7 +36,6 @@ jobs: - name: Configure C++ run: cmake -G Ninja -DCMAKE_BUILD_TYPE=Developer -B build -S cpp/ - - name: Build and install C++ library run: | cmake --build build @@ -58,9 +61,12 @@ jobs: ctest -V -R demo -R serial ctest -V -R demo -R mpi_2 + - name: Install Python dependencies + run: | - name: Build Python interface (editable install) run: | - python3 -m pip -v install --check-build-dependencies --no-build-isolation --config-settings=cmake.build-type=Debug --config-settings=build-dir="build" -e python/ + python3 -m pip install --upgrade -r python/build-requirements.txt + python3 -m pip install --check-build-dependencies --no-build-isolation --config-settings=cmake.build-type=Debug --config-settings=build-dir="build" -e 'python/[test]' - name: Set default DOLFINx JIT options run: | @@ -68,11 +74,13 @@ jobs: echo '{ "cffi_extra_compile_args": ["-g0", "-O0" ] }' > ~/.config/dolfinx/dolfinx_jit_options.json - name: Run demos (Python, serial) - run: python3 -m pytest -n=2 -m serial --durations=10 python/demo/test.py + run: | + python3 -m pip install pytest-xdist + python3 -m pytest -n auto -m serial --durations=10 python/demo/test.py - name: Run demos (Python, MPI (np=2)) run: python3 -m pytest -m mpi --num-proc=2 python/demo/test.py - name: Run Python unit tests (serial) - run: python3 -m pytest -n=auto --durations=50 python/test/unit/ + run: python3 -m pytest -n auto -m "not adios2" --durations=50 python/test/unit/ - name: Run Python unit tests (MPI, np=2) - run: mpirun -np 2 python3 -m pytest python/test/unit/ + run: mpirun -np 2 python3 -m pytest -m "not adios2" python/test/unit/ diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 376a75fe472..092e2eb84b6 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -17,7 +17,7 @@ jobs: container: fenicsproject/test-env:current-mpich env: SONAR_SCANNER_VERSION: - 5.0.1.3006 # Find the latest version at: + 6.1.0.4477 # Find the latest version at: # https://github.com/SonarSource/sonar-scanner-cli/tags SONAR_SERVER_URL: "https://sonarcloud.io" BUILD_WRAPPER_OUT_DIR: build_wrapper_output_directory # Directory where build-wrapper output will be placed @@ -26,29 +26,29 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - - name: Install zip + - name: Install zip amd spdlog run: | apt-get -y update - apt-get install unzip + apt-get -y install libspdlog-dev unzip - name: Set up JDK 17 uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: 17 - name: Cache SonarCloud packages - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.sonar/cache key: ${{ runner.os }}-sonar restore-keys: ${{ runner.os }}-sonar - name: Download and set up sonar-scanner env: - SONAR_SCANNER_DOWNLOAD_URL: https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${{ env.SONAR_SCANNER_VERSION }}-linux.zip + SONAR_SCANNER_DOWNLOAD_URL: https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${{ env.SONAR_SCANNER_VERSION }}-linux-x64.zip run: | mkdir -p $HOME/.sonar wget -O $HOME/.sonar/sonar-scanner.zip ${{ env.SONAR_SCANNER_DOWNLOAD_URL }} unzip -o $HOME/.sonar/sonar-scanner.zip -d $HOME/.sonar/ - echo "$HOME/.sonar/sonar-scanner-${{ env.SONAR_SCANNER_VERSION }}-linux/bin" >> $GITHUB_PATH + echo "$HOME/.sonar/sonar-scanner-${{ env.SONAR_SCANNER_VERSION }}-linux-x64/bin" >> $GITHUB_PATH - name: Download and set up build-wrapper env: BUILD_WRAPPER_DOWNLOAD_URL: ${{ env.SONAR_SERVER_URL }}/static/cpp/build-wrapper-linux-x86.zip @@ -71,4 +71,4 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: | - sonar-scanner --define sonar.host.url="${{ env.SONAR_SERVER_URL }}" --define sonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}" + sonar-scanner --define sonar.host.url="${{ env.SONAR_SERVER_URL }}" --define sonar.cfamily.compile-commands="${{ env.BUILD_WRAPPER_OUT_DIR }}/compile_commands.json" diff --git a/.github/workflows/spack.yml b/.github/workflows/spack.yml index cc93bf4fe48..ca3f9a3f317 100644 --- a/.github/workflows/spack.yml +++ b/.github/workflows/spack.yml @@ -18,15 +18,23 @@ on: description: "Spack repository branch/tag to test" default: "develop" type: string + dolfinx_version: + description: "DOLFINx release branch/tag to test" + default: "0.8.0" + type: string jobs: build: runs-on: ubuntu-latest - container: ubuntu:latest + container: ubuntu:24.04 env: OMPI_ALLOW_RUN_AS_ROOT: 1 OMPI_ALLOW_RUN_AS_ROOT_CONFIRM: 1 + PRTE_MCA_rmaps_default_mapping_policy: :oversubscribe # Newer OpenMPI + OMPI_MCA_rmaps_base_oversubscribe: true # Older OpenMPI + + DOLFINX_RELEASE_VERSION: "${{ github.event_name != 'workflow_dispatch' && '0.8.0' || github.event.inputs.dolfinx_version }}" steps: - name: Get Spack @@ -46,7 +54,7 @@ jobs: - name: Install Spack requirements run: | apt-get -y update - apt-get install -y bzip2 curl file git gzip make patch python3-minimal tar xz-utils + apt-get install -y bzip2 curl file git gzip make patch python3-minimal tar unzip xz-utils apt-get install -y g++ gfortran # compilers - name: Build DOLFINx (C++) development version via Spack @@ -71,35 +79,34 @@ jobs: cd demo/poisson cmake . export VERBOSE=1 - make -j2 + make -j 4 mpirun -np 2 ./demo_poisson - - name: Build DOLFINx (C++) release version via Spack run: | . ./spack/share/spack/setup-env.sh spack env create cpp-release spack env activate cpp-release - spack add fenics-dolfinx+adios2 + spack add fenics-dolfinx@${DOLFINX_RELEASE_VERSION}+adios2 spack install - name: Get DOLFINx release code (to access test files) uses: actions/checkout@v4 with: - ref: v0.7.2 - # ref: v${{ github.event.inputs.spack_branch }} + ref: v${{ env.DOLFINX_RELEASE_VERSION }} path: ./dolfinx-release - name: Run a C++ test (release version) run: | . ./spack/share/spack/setup-env.sh spack env create cpp-release-test spack env activate cpp-release-test - spack add fenics-dolfinx+adios2 cmake py-fenics-ffcx + spack add fenics-dolfinx@${DOLFINX_RELEASE_VERSION}+adios2 cmake py-fenics-ffcx + spack add py-nanobind@1.9 spack install cd dolfinx-release/cpp/ cd demo/poisson cmake . export VERBOSE=1 - make -j2 + make -j 4 mpirun -np 2 ./demo_poisson - name: Build DOLFINx (Python, development) @@ -115,16 +122,15 @@ jobs: spack env activate py-main mpirun -np 2 python3 ./dolfinx-main/python/demo/demo_elasticity.py - # Disabled due to capicty issues on Actions and LTO with pybind11 - # - name: Build DOLFINx (Python, release version) - # run: | - # . ./spack/share/spack/setup-env.sh - # spack env create py-release - # spack env activate py-release - # spack add py-fenics-dolfinx - # spack install -j 2 - # - name: Run DOLFINx (Python, release) test - # run: | - # . ./spack/share/spack/setup-env.sh - # spack env activate py-release - # mpirun -np 2 python3 ./dolfinx-release/python/demo/demo_elasticity.py + - name: Build DOLFINx (Python, release version) + run: | + . ./spack/share/spack/setup-env.sh + spack env create py-release + spack env activate py-release + spack add py-fenics-dolfinx@${DOLFINX_RELEASE_VERSION} + spack install -j 4 + - name: Run DOLFINx (Python, release) test + run: | + . ./spack/share/spack/setup-env.sh + spack env activate py-release + mpirun -np 2 python3 ./dolfinx-release/python/demo/demo_elasticity.py diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml new file mode 100644 index 00000000000..5a200a081a1 --- /dev/null +++ b/.github/workflows/windows.yml @@ -0,0 +1,160 @@ +name: Windows build + +on: + # push: + # branches: + # - main + # tags: + # - "v*" + # pull_request: + # branches: + # - main + # merge_group: + # branches: + # - main + workflow_dispatch: + +jobs: + windows-build: + name: Windows vcpkg build + runs-on: windows-latest + env: + VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite" + + steps: + - name: Export GitHub Actions cache environment variables + uses: actions/github-script@v7 + 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.12" + + - name: Install UFL + run: pip install git+https://github.com/FEniCS/ufl.git + + - name: Checkout Basix + uses: actions/checkout@v4 + with: + repository: "fenics/basix" + path: basix + + - name: Insert add_dll_directory calls into Basix + working-directory: basix/python/basix + run: | + (Get-Content __init__.py).Replace('# WINDOWSDLL', 'import os; os.add_dll_directory("D:/a/dolfinx/basix-install/bin")') | Set-Content __init__.py + Get-Content __init__.py + + - name: Install Basix (C++) + working-directory: basix + run: | + cd cpp + cmake -DINSTALL_RUNTIME_DEPENDENCIES=ON -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -B build-dir -S . + cmake --build build-dir --config Release + cmake --install build-dir --config Release --prefix D:/a/dolfinx/basix-install + echo "D:/a/dolfinx/basix-install/bin" | Out-File -Append -FilePath $env:GITHUB_PATH -Encoding utf8 + + - name: Install build dependencies (workaround) + run: | + python -m pip install git+https://github.com/jhale/nanobind.git@jhale/msvc2022-workaround + python -m pip install scikit-build-core[pyproject] setuptools wheel + - name: Install Basix (Python) + working-directory: basix + run: | + cd python + python -m pip -v install --check-build-dependencies --no-build-isolation --no-cache-dir .[ci] --config-settings=cmake.args=-DBasix_DIR=D:/a/basix/install/lib/cmake/basix + cd ../ + + - name: Checkout FFCx + uses: actions/checkout@v4 + with: + repository: "fenics/ffcx" + path: ffcx + + - name: Install UFCx header + working-directory: ffcx + run: | + cmake -B build-dir -S cmake/ + cmake --build build-dir --config Release + cmake --install build-dir --config Release --prefix D:/a/dolfinx/ufcx-install + + - name: Install FFCx + working-directory: ffcx + run: | + pip install . + + - name: Checkout DOLFINx + uses: actions/checkout@v4 + with: + path: dolfinx + + - name: Insert add_dll_directory calls into DOLFINx + working-directory: dolfinx/python/dolfinx + run: | + (Get-Content __init__.py).Replace('# WINDOWSDLL', 'import os; os.add_dll_directory("D:/a/dolfinx/dolfinx-install/bin"); os.add_dll_directory("C:/Program Files (x86)/Intel/oneAPI/mpi/2021.12/opt/mpi/libfabric/bin")') | Set-Content __init__.py + Get-Content __init__.py + + - uses: mpi4py/setup-mpi@v1.2.2 + with: + mpi: "intelmpi" + + - name: Install DOLFINx (C++) + working-directory: dolfinx + run: | + cmake -DINSTALL_RUNTIME_DEPENDENCIES=ON -DDOLFINX_BASIX_PYTHON=OFF -DBasix_DIR=D:/a/dolfinx/basix-install/share/basix -DDOLFINX_UFCX_PYTHON=OFF -Dufcx_DIR=D:/a/dolfinx/ufcx-install/share/ufcx/cmake -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_OVERLAY_PORTS="cpp/.vcpkg-overlay" -B build-dir -S cpp + cmake --build build-dir --config Release + cmake --install build-dir --config Release --prefix D:/a/dolfinx/dolfinx-install + echo "D:/a/dolfinx/dolfinx-install/bin" | Out-File -Append -FilePath $env:GITHUB_PATH -Encoding utf8 + + - name: Build unit tests (C++) + working-directory: dolfinx + run: | + cmake -DBasix_DIR=D:/a/dolfinx/basix-install/share/basix -Dufcx_DIR=D:/a/dolfinx/ufcx-install/share/ufcx/cmake -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_OVERLAY_PORTS="cpp/.vcpkg-overlay" -B build/test/ -S cpp/test/ + cmake --build build/test --config Release --parallel 3 + + - name: Run unit tests (C++, MPI, np=1) + working-directory: dolfinx + run: | + cd build/test + mpiexec -n 1 ctest -V --output-on-failure -R unittests + - name: Run C++ tests (C++, MPI, np=3) + working-directory: dolfinx + run: | + cd build/test + mpiexec -n 3 ctest -V --output-on-failure -R unittests + + - name: Install build dependencies + working-directory: dolfinx + run: | + pip -v install --no-binary mpi4py -r python/build-requirements.txt + + - name: Install DOLFINx (Python) + working-directory: dolfinx + run: | + cd python + pip -v install --check-build-dependencies --no-build-isolation .[test] --config-settings=cmake.args=-DBasix_DIR=D:/a/dolfinx/basix-install/lib/cmake/basix --config-settings=cmake.args=-Dufcx_DIR=D:/a/dolfinx/ufcx-install/share/ufcx/cmake --config-settings=cmake.args=-DDOLFINX_DIR=D:/a/dolfinx/dolfinx-install/lib/cmake/dolfinx --config-settings=cmake.args=-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake --config-settings=cmake.args=-DVCPKG_OVERLAY_PORTS="../cpp/.vcpkg-overlay" + + - name: Run units tests (Python, serial) + working-directory: dolfinx + run: | + pip install pytest-xdist + python -m pytest -n auto -m "not petsc4py and not adios2" python/test/unit + - name: Run units tests (Python, MPI, np=3) + working-directory: dolfinx + run: | + mpiexec -n 3 python -m pytest -m "not petsc4py and not adios2" python/test/unit + + - name: Run Python demos (serial) + working-directory: dolfinx + run: | + cd python/demo + python3 -m pytest -n auto -m serial --durations=10 test.py + - name: Run Python demos (MPI, np=3) + working-directory: dolfinx + run: | + cd python/demo + python3 -m pytest -m mpi --num-proc=3 test.py diff --git a/.mailmap b/.mailmap deleted file mode 100644 index e26736207db..00000000000 --- a/.mailmap +++ /dev/null @@ -1,132 +0,0 @@ -Anders Logg -Anders Logg -Anders Logg -Anders Logg -Anders Logg logg -Anders Logg fenics -Anders Logg hg -Anders Logg root -Anders Logg -Anders Logg -Anders Logg -Anders Logg -Anders Logg -Anders Logg anders -Anders Logg logg -Anders Logg logg -Anders Logg logg -Anders Logg logg -Andy R. Terrel -Andy R. Terrel -Andy R. Terrel aterrel -Andy R. Terrel -Benjamin Kehlet -Benjamin Kehlet -Benjamin Kehlet -Benjamin Kehlet -Benjamin Kehlet -Chris Richardson -Chris Richardson chris -Chris Richardson Chris Richardson -Chris Richardson root -Corrado Maurini -Corrado Maurini -Dag Lindbo -Dag Lindbo dag -Dag Lindbo dag -David Ham -David Ham david.ham@imperial.ac.uk <> -Evan Lezar -Evan Lezar -Evan Lezar elezar -Evan Lezar elezar -Fredrik Valdmanis -Fredrik Valdmanis Fredrik Valdmanis -Garth N. Wells -Garth N. Wells root -Garth N. Wells garth -Garth N. Wells -Garth N. Wells -Garth N. Wells -Garth N. Wells -Garth N. Wells -Garth N. Wells -Garth N. Wells -Garth N. Wells -Garth N. Wells -Garth N. Wells -Garth N. Wells gnw20@cam.ac.uk <> -gideonsimpson -Gustav Magnus Vikström -Gustav Magnus Vikström -Gustav Magnus Vikström -Gustav Magnus Vikström -Harish Narayanan -Harish Narayanan Harish Narayanan -Johan Hoffman -Johan Hoffman hoffman -Johan Hoffman -Johan Hoffman -Ilmar Wilbers -Ilmar Wilbers -Ilmar Wilbers -Jack S. Hale -Jack S. Hale -Johan Hake -Johan Hake -Johan Hake -Johan Jansson -Johan Jansson johan -Johan Jansson johan -Johan Jansson johanjan -Johan Jansson johanjan -Johan Jansson Johan Jansson -Johannes Ring -Johannes Ring -Kent-Andre Mardal -Kent-Andre Mardal -Kristian B. Ølgaard -Kristian B. Ølgaard -Kristian B. Ølgaard -Magnus Vikstrøm -Marco Morandini -Marco Morandini -Marie E. Rognes -Marie E. Rognes -Marie E. Rognes -Marie E. Rognes Marie E. Rognes (meg@simula.no) -Marie E. Rognes meg@simula.no <> -Martin Sandve Alnæs -Martin Sandve Alnæs -Martin Sandve Alnæs -Michele Zaffalon -Mikael Mortensen -Mikael Mortensen -Nate Sime -Nate Sime -Nuno Lopes -Nuno Lopes N.Lopes -Patrick Farrell -Patrick Farrell -Patrick Farrell -Quang Ha -Kristoffer Selim -Kristoffer Selim -Simon Funke -Simon Funke -Simon Funke -Solveig Bruvoll -Solveig Masvie -Steven Vandekerckhove -Steven Vandekerckhove -stockli -stockli -Tianyi Li -Steffen Müthing -Steffen Müthing Steffen Müthing steffen.muething@ipvs.uni-stuttgart.de <> -Miklós Homolya -Åsmund Ødegård -Åsmund Ødegård -Ola Skavhaug -Andre Massing -Andrew McRae diff --git a/README.md b/README.md index 8099bfb0310..d044e65427c 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # DOLFINx [![DOLFINx CI](https://github.com/FEniCS/dolfinx/actions/workflows/ccpp.yml/badge.svg)](https://github.com/FEniCS/dolfinx/actions/workflows/ccpp.yml) -[![CircleCI](https://circleci.com/gh/FEniCS/dolfinx.svg?style=shield)](https://circleci.com/gh/FEniCS/dolfinx) [![Actions Docker images](https://github.com/FEniCS/dolfinx/actions/workflows/docker-end-user.yml/badge.svg)](https://github.com/FEniCS/dolfinx/actions/workflows/docker-end-user.yml) [![Actions Spack build](https://github.com/FEniCS/dolfinx/actions/workflows/spack.yml/badge.svg)](https://github.com/FEniCS/dolfinx/actions/workflows/spack.yml) [![Actions Conda install](https://github.com/FEniCS/dolfinx/actions/workflows/conda.yml/badge.svg)](https://github.com/FEniCS/dolfinx/actions/workflows/conda.yml) [![Actions macOS/Homebrew install](https://github.com/FEniCS/dolfinx/actions/workflows/macos.yml/badge.svg)](https://github.com/FEniCS/dolfinx/actions/workflows/macos.yml) +[![Actions Windows/vcpkg install](https://github.com/FEniCS/dolfinx/actions/workflows/windows.yml/badge.svg)](https://github.com/FEniCS/dolfinx/actions/workflows/windows.yml) DOLFINx is the computational environment of [FEniCSx](https://fenicsproject.org) and implements the FEniCS Problem @@ -72,7 +72,7 @@ comprehensive instructions. [docker](#docker-images) or [conda](#conda). See also [Spack](#spack). - Windows: [docker](#docker-images), or install [WSL2](https://docs.microsoft.com/en-us/windows/wsl/install) and use - [Ubuntu](#ubuntu-packages). + [Ubuntu](#ubuntu-packages). [conda](#conda) packages in beta testing. - High performance computers: [Spack](#spack) or [from source](#from-source), both using system-provided MPI. @@ -87,6 +87,15 @@ conda activate fenicsx-env conda install -c conda-forge fenics-dolfinx mpich pyvista ``` +Windows conda packages are currently in beta testing and can be installed using: +```shell +conda create -n fenicsx-env +conda activate fenicsx-env +conda install -c minrk/label/fenics-windows -c conda-forge fenics-dolfinx=0.9.0.dev +``` +Because FEniCS uses just-in-time compilation it also necessary to install +[Microsoft Visual Studio](https://visualstudio.microsoft.com/downloads/). + conda is distributed with [Anaconda](https://www.anaconda.com/) and [Miniconda](https://docs.conda.io/en/latest/miniconda.html). The recipe is hosted on @@ -96,10 +105,6 @@ is hosted on | --- | --- | --- | --- | | [![Conda Recipe](https://img.shields.io/badge/recipe-fenics--dolfinx-green.svg)](https://anaconda.org/conda-forge/fenics-dolfinx) | [![Conda Downloads](https://img.shields.io/conda/dn/conda-forge/fenics-dolfinx.svg)](https://anaconda.org/conda-forge/fenics-dolfinx) | [![Conda Version](https://img.shields.io/conda/vn/conda-forge/fenics-dolfinx.svg)](https://anaconda.org/conda-forge/fenics-dolfinx) | [![Conda Platforms](https://img.shields.io/conda/pn/conda-forge/fenics-dolfinx.svg)](https://anaconda.org/conda-forge/fenics-dolfinx) | -> **Note** -> Windows packages are not available. This is due to some DOLFINx -> dependencies not supporting Windows. - #### Ubuntu packages The [Ubuntu diff --git a/cpp/.vcpkg-overlay/README.md b/cpp/.vcpkg-overlay/README.md new file mode 100644 index 00000000000..e8e98953a3b --- /dev/null +++ b/cpp/.vcpkg-overlay/README.md @@ -0,0 +1,14 @@ +# vcpkg overlay port for Intel MPI + +This vcpkg overlay port contains scripts for installing Intel MPI on Windows +(only). MSMPI, which is used by default with vcpkg, does not support the MPI3 +standard. Using this port requires that Intel OneAPI binaries are already +installed. On Unix systems the built-in OpenMPI or MPICH ports can be used. + +From the root of this repository it can be activated by e.g.: + + cmake -DCMAKE_TOOLCHAIN_FILE=%VCPKG_ROOT%/scripts/buildsystems/vcpkg.cmake -DVCPKG_OVERLAY_PORTS="cpp/.vcpkg-overlay" -B build-dir -S cpp/ + +This overlay port was adapted from the original at: + +https://github.com/arcaneframework/framework-ci diff --git a/cpp/.vcpkg-overlay/intel-mpi/mpi-wrapper.cmake b/cpp/.vcpkg-overlay/intel-mpi/mpi-wrapper.cmake new file mode 100644 index 00000000000..4dedbc73693 --- /dev/null +++ b/cpp/.vcpkg-overlay/intel-mpi/mpi-wrapper.cmake @@ -0,0 +1,45 @@ +get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_FILE}" PATH) +get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) +get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) + +# Needed to find 'mpiexec' +set(ENV{I_MPI_ROOT} "${_IMPORT_PREFIX}/tools/intel-mpi") + +set(MPI_C_ADDITIONAL_INCLUDE_DIRS + "${_IMPORT_PREFIX}/include" + CACHE STRING "MPI C additional include directories" FORCE +) +set(MPI_CXX_ADDITIONAL_INCLUDE_DIRS + "${_IMPORT_PREFIX}/include" + CACHE STRING "MPI CXX additional include directories" FORCE +) + +set(MPI_C_LIB_NAMES + "IMPI" + CACHE STRING "MPI C lib name" FORCE +) +set(MPI_CXX_LIB_NAMES + "IMPI" + CACHE STRING "MPI CXX lib name" FORCE +) +set(MPI_IMPI_LIBRARY + "${_IMPORT_PREFIX}/lib/impi.lib" + CACHE STRING "MPI C/CXX libraries" FORCE +) +set(MPI_ASSUME_NO_BUILTIN_MPI + TRUE + CACHE BOOL "" FORCE +) + +set(MPI_C_COMPILER + "${_IMPORT_PREFIX}/tools/intel-mpi/mpicc.bat" + CACHE STRING "MPI C Compiler" FORCE +) +set(MPI_CXX_COMPILER + "${_IMPORT_PREFIX}/tools/intel-mpi/mpicxx.bat" + CACHE STRING "MPI C Compiler" FORCE +) + +unset(_IMPORT_PREFIX) + +_find_package(${ARGS}) diff --git a/cpp/.vcpkg-overlay/intel-mpi/portfile.cmake b/cpp/.vcpkg-overlay/intel-mpi/portfile.cmake new file mode 100644 index 00000000000..6d746de99f6 --- /dev/null +++ b/cpp/.vcpkg-overlay/intel-mpi/portfile.cmake @@ -0,0 +1,78 @@ +set(INTELMPI_VERSION "2021.12") +set(SOURCE_PATH "${CURRENT_BUILDTREES_DIR}/src/intel-mpi-${INTELMPI_VERSION}") + +cmake_path(SET SDK_SOURCE_DIR "C:/Program Files (x86)/Intel/oneAPI") + +message(STATUS "Using Intel MPI source SDK at ${SDK_SOURCE_DIR}") + +set(SDK_SOURCE_MPI_DIR "${SDK_SOURCE_DIR}/mpi/${INTELMPI_VERSION}") + +set(SOURCE_INCLUDE_PATH "${SDK_SOURCE_MPI_DIR}/include") +set(SOURCE_LIB_PATH "${SDK_SOURCE_MPI_DIR}/lib") +set(SOURCE_DEBUG_LIB_PATH "${SDK_SOURCE_MPI_DIR}/lib/mpi/debug") +set(SOURCE_BIN_PATH "${SDK_SOURCE_MPI_DIR}/bin") +set(SOURCE_DEBUG_BIN_PATH "${SDK_SOURCE_MPI_DIR}/bin/mpi/debug") +set(SOURCE_TOOLS_PATH "${SDK_SOURCE_MPI_DIR}/bin") +set(SOURCE_LIBFABRIC_PATH "${SDK_SOURCE_MPI_DIR}/opt/mpi/libfabric/bin") + +# Get files in include directory +file( + GLOB_RECURSE SOURCE_INCLUDE_FILES + LIST_DIRECTORIES TRUE + "${SOURCE_INCLUDE_PATH}/*" +) + +# Get files in bin directory +file(GLOB TOOLS_FILES "${SOURCE_TOOLS_PATH}/*.exe" "${SOURCE_TOOLS_PATH}/*.dll" + "${SOURCE_TOOLS_PATH}/*.bat" +) + +# Install tools files +file(INSTALL ${TOOLS_FILES} DESTINATION "${CURRENT_PACKAGES_DIR}/tools/${PORT}") + +# Also install include files in the tools directory because the compiler +# wrappers (mpicc.bat for example) needs them +file(INSTALL ${SOURCE_INCLUDE_FILES} + DESTINATION "${CURRENT_PACKAGES_DIR}/tools/${PORT}/include" +) + +# Install include files +file(INSTALL ${SOURCE_INCLUDE_FILES} + DESTINATION "${CURRENT_PACKAGES_DIR}/include" +) + +# Install release library files +file(INSTALL "${SOURCE_LIB_PATH}/impi.lib" "${SOURCE_LIB_PATH}/impicxx.lib" + DESTINATION "${CURRENT_PACKAGES_DIR}/lib" +) + +# Install debug library files +file(INSTALL "${SOURCE_DEBUG_LIB_PATH}/impi.lib" + "${SOURCE_DEBUG_LIB_PATH}/impicxx.lib" + DESTINATION "${CURRENT_PACKAGES_DIR}/debug/lib" +) + +# 'libfabric.dll' is not needed for the compilation but it is needed for the +# runtime and should be in the PATH for 'mpiexec' to work +file(INSTALL "${SOURCE_LIBFABRIC_PATH}/libfabric.dll" + "${SOURCE_BIN_PATH}/impi.dll" "${SOURCE_BIN_PATH}/impi.pdb" + DESTINATION "${CURRENT_PACKAGES_DIR}/bin" +) + +file(INSTALL "${SOURCE_LIBFABRIC_PATH}/libfabric.dll" + "${SOURCE_DEBUG_BIN_PATH}/impi.dll" "${SOURCE_DEBUG_BIN_PATH}/impi.pdb" + DESTINATION "${CURRENT_PACKAGES_DIR}/debug/bin" +) + +file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/mpi-wrapper.cmake" + DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" +) + +# Handle copyright +file( + COPY "${SDK_SOURCE_DIR}/licensing/2024.1/licensing/2024.1/license.htm" + DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" +) +file(WRITE "${CURRENT_PACKAGES_DIR}/share/${PORT}/copyright" + "See the licence.htm file in this directory." +) diff --git a/cpp/.vcpkg-overlay/intel-mpi/vcpkg.json b/cpp/.vcpkg-overlay/intel-mpi/vcpkg.json new file mode 100644 index 00000000000..6cb2516b47f --- /dev/null +++ b/cpp/.vcpkg-overlay/intel-mpi/vcpkg.json @@ -0,0 +1,8 @@ +{ + "name": "intel-mpi", + "version": "2021.12.1", + "port-version": 2, + "description": "Intel MPI is a Intel implementation of the Message Passing Interface standard for developing and running parallel applications.", + "homepage": "https://www.intel.com/content/www/us/en/developer/tools/oneapi/mpi-library.html", + "supports": "windows & !uwp" +} diff --git a/cpp/.vcpkg-overlay/mpi/portfile.cmake b/cpp/.vcpkg-overlay/mpi/portfile.cmake new file mode 100644 index 00000000000..12cefbc7251 --- /dev/null +++ b/cpp/.vcpkg-overlay/mpi/portfile.cmake @@ -0,0 +1,9 @@ +set(VCPKG_POLICY_EMPTY_PACKAGE enabled) + +if(VCPKG_TARGET_IS_WINDOWS) + file( + INSTALL "${CURRENT_INSTALLED_DIR}/share/intel-mpi/mpi-wrapper.cmake" + DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" + RENAME vcpkg-cmake-wrapper.cmake + ) +endif() diff --git a/cpp/.vcpkg-overlay/mpi/vcpkg.json b/cpp/.vcpkg-overlay/mpi/vcpkg.json new file mode 100644 index 00000000000..ce8206160d6 --- /dev/null +++ b/cpp/.vcpkg-overlay/mpi/vcpkg.json @@ -0,0 +1,18 @@ +{ + "name": "mpi", + "version-string": "1", + "port-version": 3, + "description": "Message Passing Interface (MPI) is a standardized and portable message-passing standard designed by a group of researchers from academia and industry to function on a wide variety of parallel computing architectures. The standard defines the syntax and semantics of a core of library routines useful to a wide range of users writing portable message-passing programs in C, C++, and Fortran. There are several well-tested and efficient implementations of MPI, many of which are open-source or in the public domain.", + "license": null, + "supports": "!uwp", + "dependencies": [ + { + "name": "intel-mpi", + "platform": "windows" + }, + { + "name": "openmpi", + "platform": "!windows" + } + ] +} diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 31d27d1e906..c9dd25e8d2e 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -1,10 +1,14 @@ # ------------------------------------------------------------------------------ # Top level CMakeLists.txt file for DOLFINx -cmake_minimum_required(VERSION 3.19) +cmake_minimum_required(VERSION 3.21) + +if(POLICY CMP0167) + cmake_policy(SET CMP0167 NEW) # Boost CONFIG mode +endif() # ------------------------------------------------------------------------------ # Set project name and version number -project(DOLFINX VERSION "0.8.0") +project(DOLFINX VERSION "0.9.0.0") set(DOXYGEN_DOLFINX_VERSION ${DOLFINX_VERSION} @@ -12,14 +16,11 @@ set(DOXYGEN_DOLFINX_VERSION ) # ------------------------------------------------------------------------------ -# Use C++20 -set(CMAKE_CXX_STANDARD 20) - -# Require C++20 -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -# Do not enable compler-specific extensions -set(CMAKE_CXX_EXTENSIONS OFF) +if(WIN32) + # Windows requires all symbols to be manually exported. This flag exports all + # symbols automatically, as in Unix. + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE) +endif() # ------------------------------------------------------------------------------ # Get GIT changeset, if available @@ -55,6 +56,17 @@ add_feature_info( BUILD_SHARED_LIBS BUILD_SHARED_LIBS "Build DOLFINx with shared libraries." ) +# If libdolfinx links to a symbol contained in an external dll, that dll will be +# installed alongside libdolfinx. This excludes system dlls included with +# Windows. +option(INSTALL_RUNTIME_DEPENDENCIES + "Include runtime dependencies in install (Windows-only)" OFF +) +add_feature_info( + INSTALL_RUNTIME_DEPENDENCIES INSTALL_RUNTIME_DEPENDENCIES + "Include runtime dependencies in install (Windows-only)" +) + option(DOLFINX_SKIP_BUILD_TESTS "Skip build tests for testing usability of dependency packages." OFF ) @@ -72,16 +84,28 @@ add_feature_info( "Add paths to linker search and installed rpath." ) +# Control Basix discovery +option( + DOLFINX_BASIX_PYTHON + "Ask Python basix module for hint where to find Basix C++ install using CONFIG mode." + ON +) +add_feature_info( + DOLFINX_BASIX_PYTHON + DOLFINX_BASIX_PYTHON + "Ask Python basix module for hint where to find Basix C++ install using CONFIG mode." +) + # Control UFCx discovery option( DOLFINX_UFCX_PYTHON - "Enable UFCx discovery using Python. Disable if UFCx should be found using CMake." + "Ask Python FFCx module where to find ufcx.h header using MODULE mode. Otherwise use CONFIG mode." ON ) add_feature_info( DOLFINX_UFCX_PYTHON DOLFINX_UFCX_PYTHON - "Enable UFCx discovery using Python. Disable if UFCx should be found using a CMake config file." + "Ask Python FFCx module where to find ufcx.h header using MODULE mode. Otherwise use CONFIG mode." ) # ------------------------------------------------------------------------------ @@ -200,7 +224,7 @@ else() CACHE BOOL "Is KaHIP REQUIRED?" ) endif() -option(DOLFINX_ENABLE_KAHIP "Compile with support for KaHIP." OFF) +option(DOLFINX_ENABLE_KAHIP "Compile with support for KaHIP." ON) set_package_properties( KaHIP PROPERTIES TYPE OPTIONAL @@ -213,6 +237,7 @@ set_package_properties( # Check for MPI find_package(MPI 3 REQUIRED) +find_package(spdlog REQUIRED) # ------------------------------------------------------------------------------ # Compiler flags @@ -256,13 +281,22 @@ if(HAVE_O2_OPTIMISATION) list(APPEND DOLFINX_CXX_DEVELOPER_FLAGS -O2) endif() +# Turn off some checks in gcc12 and gcc13 due to false positives with the fmt +# library +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" + AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER "11.4" + AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "14.0") + list(APPEND DOLFINX_CXX_DEVELOPER_FLAGS + -Wno-array-bounds;-Wno-stringop-overflow) +endif() + # ------------------------------------------------------------------------------ # Find required packages # pugixml find_package(pugixml REQUIRED) -# Note: When updating Boost version, also update DOLFINXCongif.cmake.in +# Note: When updating Boost version, also update DOLFINXConfig.cmake.in if(DEFINED ENV{BOOST_ROOT} OR DEFINED BOOST_ROOT) set(Boost_NO_SYSTEM_PATHS on) endif() @@ -277,33 +311,41 @@ set_package_properties( URL "http://www.boost.org" ) -# Use Python for detecting UFCx and Basix -find_package( - Python3 - COMPONENTS Interpreter - QUIET -) +# Basix C++ files can be installed as a standalone C++ library, or in the Basix +# Python module tree. -# Check for Basix Note: Basix may be installed as a standalone C++ library, or -# in the Basix Python module tree -if(Python3_Interpreter_FOUND) - message(STATUS "Checking for basix hints with ${Python3_EXECUTABLE}") - execute_process( - COMMAND - ${Python3_EXECUTABLE} -c - "import basix, os, sys; sys.stdout.write(os.path.dirname(basix.__file__))" - OUTPUT_VARIABLE BASIX_PY_DIR - RESULT_VARIABLE BASIX_PY_COMMAND_RESULT - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE +# If requested (default), ask the Python interpreter for hints on where to find +# Basix C++ library. +if(DOLFINX_BASIX_PYTHON) + find_package( + Python3 + COMPONENTS Interpreter + QUIET ) + if(Python3_Interpreter_FOUND) + message(STATUS "Checking for Basix hints with ${Python3_EXECUTABLE}") + execute_process( + COMMAND + ${Python3_EXECUTABLE} -c + "import basix, os, sys; sys.stdout.write(os.path.dirname(basix.__file__))" + OUTPUT_VARIABLE BASIX_PY_DIR + RESULT_VARIABLE BASIX_PY_COMMAND_RESULT + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE + ) + endif() + if(BASIX_PY_DIR) + # Converts os native to cmake native path + cmake_path(SET BASIX_PY_DIR "${BASIX_PY_DIR}") message(STATUS "Adding ${BASIX_PY_DIR} to Basix search hints") - # Basix installed from manylinux wheel + # Basix installed from manylinux wheel requires rpath set. if(IS_DIRECTORY ${BASIX_PY_DIR}/../fenics_basix.libs) set(CMAKE_INSTALL_RPATH ${BASIX_PY_DIR}/../fenics_basix.libs) endif() + else() + message(STATUS "No Basix hint was found.") endif() endif() @@ -335,18 +377,18 @@ set_package_properties( ) # Check for UFC Note: we use the case (ufcx vs UFCx) elsewhere to determine by -# which method UFCx was found +# which method UFCx was found. if(NOT DOLFINX_UFCX_PYTHON) # Check in CONFIG mode, i.e. look for installed ufcxConfig.cmake - find_package(ufcx 0.8 REQUIRED CONFIG) + find_package(ufcx 0.9 REQUIRED CONFIG) else() - # Check in MODULE mode (using FindUFCX.cmake) + # Check in MODULE mode (using FindUFCX.cmake) using Python intepreter. find_package( Python3 COMPONENTS Interpreter REQUIRED ) - find_package(UFCx 0.8 REQUIRED MODULE) + find_package(UFCx 0.9 REQUIRED MODULE) endif() set_package_properties( @@ -425,10 +467,22 @@ elseif(_REQUIRE_SLEPC AND NOT PETSC_FOUND) ) endif() -if(DOLFINX_ENABLE_SCOTCH AND _REQUIRE_SCOTCH) - find_package(SCOTCH REQUIRED) -elseif(DOLFINX_ENABLE_SCOTCH) - find_package(SCOTCH) +if(DOLFINX_ENABLE_SCOTCH) + find_package(SCOTCH CONFIG) # Attempt to find in CONFIG mode + if(NOT SCOTCH_FOUND) + if(_REQUIRE_SCOTCH) # If not found in CONFIG mode, try MODULE mode + find_package(SCOTCH REQUIRED) + else() + find_package(SCOTCH) + endif() + endif() + + if(TARGET SCOTCH::scotch AND NOT TARGET SCOTCH::ptscotch) + message( + STATUS "SCOTCH found, but not PT-SCOTCH (parallel). Not enabling SCOTCH." + ) + set(SCOTCH_FOUND FALSE) + endif() endif() if(DOLFINX_ENABLE_PARMETIS AND _REQUIRE_PARMETIS) diff --git a/cpp/cmake/modules/FindParMETIS.cmake b/cpp/cmake/modules/FindParMETIS.cmake index 8363138541d..ae5b6c5b142 100644 --- a/cpp/cmake/modules/FindParMETIS.cmake +++ b/cpp/cmake/modules/FindParMETIS.cmake @@ -65,10 +65,26 @@ if(MPI_CXX_FOUND) METIS_LIBRARY metis DOC "Directory where the METIS library is located" ) + # Newer METIS and ParMETIS build against separate GKLib + find_library( + GKLIB_LIBRARY gklib + HINTS ${PARMETIS_ROOT}/lib $ENV{PARMETIS_ROOT}/lib ${PETSC_LIBRARY_DIRS} + NO_DEFAULT_PATH + DOC "Directory where the gklib library is located" + ) + find_library( + GKLIB_LIBRARY gklib DOC "Directory where the GKLib library is located" + ) + set(PARMETIS_LIBRARIES ${PARMETIS_LIBRARY}) if(METIS_LIBRARY) set(PARMETIS_LIBRARIES ${PARMETIS_LIBRARIES} ${METIS_LIBRARY}) endif() + if(GKLIB_LIBRARY) + set(PARMETIS_LIBRARIES ${PARMETIS_LIBRARIES} ${METIS_LIBRARY} + ${GKLIB_LIBRARY} + ) + endif() # Try compiling and running test program if(DOLFINX_SKIP_BUILD_TESTS) diff --git a/cpp/cmake/modules/FindSCOTCH.cmake b/cpp/cmake/modules/FindSCOTCH.cmake index 855dad09e44..40737fd6332 100644 --- a/cpp/cmake/modules/FindSCOTCH.cmake +++ b/cpp/cmake/modules/FindSCOTCH.cmake @@ -383,3 +383,10 @@ find_package_handle_standard_args( SCOTCH "SCOTCH could not be found. Be sure to set SCOTCH_DIR." SCOTCH_LIBRARIES SCOTCH_INCLUDE_DIRS SCOTCH_TEST_RUNS ) + +if(SCOTCH_FOUND AND NOT TARGET SCOTCH::ptscotch) + add_library(SCOTCH::ptscotch INTERFACE IMPORTED) + set_property(TARGET SCOTCH::ptscotch PROPERTY INTERFACE_LINK_LIBRARIES "${SCOTCH_LIBRARIES}") + set_property(TARGET SCOTCH::ptscotch PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${SCOTCH_INCLUDE_DIRS}") +endif() + diff --git a/cpp/cmake/modules/FindUFCx.cmake b/cpp/cmake/modules/FindUFCx.cmake index a64e186a92c..7aa039afbb8 100644 --- a/cpp/cmake/modules/FindUFCx.cmake +++ b/cpp/cmake/modules/FindUFCx.cmake @@ -36,9 +36,15 @@ # POSSIBILITY OF SUCH DAMAGE. #============================================================================= +find_package( + Python3 + COMPONENTS Interpreter + REQUIRED +) + message( STATUS - "Asking Python module FFCx for location of UFC... (Python executable: ${Python3_EXECUTABLE})" + "Asking Python module FFCx for location of ufcx.h..." ) # Get include path @@ -48,6 +54,8 @@ execute_process( "import ffcx.codegeneration, sys; sys.stdout.write(ffcx.codegeneration.get_include_path())" OUTPUT_VARIABLE UFCX_INCLUDE_DIR ) +# Converts os native to cmake native path type +cmake_path(SET UFCX_INCLUDE_DIR "${UFCX_INCLUDE_DIR}") # Get ufcx.h version if(UFCX_INCLUDE_DIR) diff --git a/cpp/cmake/templates/DOLFINXConfig.cmake.in b/cpp/cmake/templates/DOLFINXConfig.cmake.in index 3b2c16b4b51..ac88d977ba3 100644 --- a/cpp/cmake/templates/DOLFINXConfig.cmake.in +++ b/cpp/cmake/templates/DOLFINXConfig.cmake.in @@ -9,7 +9,12 @@ include(CMakeFindDependencyMacro) find_dependency(MPI REQUIRED) -find_dependency(pugixml) +find_dependency(spdlog REQUIRED) +find_dependency(pugixml REQUIRED) + +if(POLICY CMP0167) + cmake_policy(SET CMP0167 NEW) # Boost CONFIG mode +endif() # Check for Boost if(DEFINED ENV{BOOST_ROOT} OR DEFINED BOOST_ROOT) @@ -17,28 +22,42 @@ if(DEFINED ENV{BOOST_ROOT} OR DEFINED BOOST_ROOT) endif() set(Boost_USE_MULTITHREADED $ENV{BOOST_USE_MULTITHREADED}) set(Boost_VERBOSE TRUE) -find_dependency(Boost 1.70 REQUIRED COMPONENTS timer filesystem) +find_package(Boost 1.70 REQUIRED timer) if(@ufcx_FOUND@) find_dependency(ufcx) endif() # Basix -find_package(Python3 COMPONENTS Interpreter) -if(Python3_Interpreter_FOUND) - execute_process( - COMMAND - ${Python3_EXECUTABLE} -c - "import basix, os, sys; sys.stdout.write(os.path.dirname(basix.__file__))" - OUTPUT_VARIABLE BASIX_PY_DIR - RESULT_VARIABLE BASIX_PY_COMMAND_RESULT - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE +if(@DOLFINX_BASIX_PYTHON@) + find_package( + Python3 + COMPONENTS Interpreter + QUIET ) + + if(Python3_Interpreter_FOUND) + message(STATUS "Checking for Basix hints with ${Python3_EXECUTABLE}") + execute_process( + COMMAND + ${Python3_EXECUTABLE} -c + "import basix, os, sys; sys.stdout.write(os.path.dirname(basix.__file__))" + OUTPUT_VARIABLE BASIX_PY_DIR + RESULT_VARIABLE BASIX_PY_COMMAND_RESULT + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE + ) + endif() + + if(BASIX_PY_DIR) + # TODO: CMake 3.20 has more modern cmake_path. + file(TO_CMAKE_PATH "${BASIX_PY_DIR}" BASIX_PY_DIR) + message(STATUS "Adding ${BASIX_PY_DIR} to Basix search hints") + else() + message(STATUS "No Basix hint was found.") + endif() endif() -if(BASIX_PY_DIR) - message(STATUS "Adding ${BASIX_PY_DIR} to Basix search hints") -endif() -find_dependency(Basix CONFIG HINTS ${BASIX_PY_DIR}) + +find_dependency(Basix REQUIRED CONFIG HINTS ${BASIX_PY_DIR}) # HDF5 if(NOT TARGET hdf5::hdf5) diff --git a/cpp/demo/CMakeLists.txt b/cpp/demo/CMakeLists.txt index be4d0a305c0..25c5200e6eb 100644 --- a/cpp/demo/CMakeLists.txt +++ b/cpp/demo/CMakeLists.txt @@ -16,10 +16,11 @@ macro(add_demo_subdirectory subdir) endmacro(add_demo_subdirectory) # Add demos +add_demo_subdirectory(biharmonic) +add_demo_subdirectory(codim_0_assembly) add_demo_subdirectory(custom_kernel) add_demo_subdirectory(poisson) add_demo_subdirectory(poisson_matrix_free) add_demo_subdirectory(hyperelasticity) add_demo_subdirectory(interpolation-io) add_demo_subdirectory(interpolation_different_meshes) -add_demo_subdirectory(biharmonic) diff --git a/cpp/demo/biharmonic/CMakeLists.txt b/cpp/demo/biharmonic/CMakeLists.txt index c373efed2ef..aff7feda83a 100644 --- a/cpp/demo/biharmonic/CMakeLists.txt +++ b/cpp/demo/biharmonic/CMakeLists.txt @@ -45,9 +45,7 @@ add_custom_command( set(CMAKE_INCLUDE_CURRENT_DIR ON) -add_executable( - ${PROJECT_NAME} main.cpp ${CMAKE_CURRENT_BINARY_DIR}/biharmonic.c -) +add_executable(${PROJECT_NAME} main.cpp ${CMAKE_CURRENT_BINARY_DIR}/biharmonic.c) target_link_libraries(${PROJECT_NAME} dolfinx) # Do not throw error for 'multi-line comments' (these are typical in rst which diff --git a/cpp/demo/biharmonic/main.cpp b/cpp/demo/biharmonic/main.cpp index 04b2477c6a4..4dec92293bc 100644 --- a/cpp/demo/biharmonic/main.cpp +++ b/cpp/demo/biharmonic/main.cpp @@ -128,6 +128,7 @@ // convenience we also include the DOLFINx namespace. #include "biharmonic.h" +#include #include #include #include @@ -162,9 +163,14 @@ int main(int argc, char* argv[]) // A function space object, which is defined in the generated code, // is created: + auto element = basix::create_element( + basix::element::family::P, basix::cell::type::triangle, 2, + basix::element::lagrange_variant::unset, + basix::element::dpc_variant::unset, false); + // Create function space auto V = std::make_shared>( - fem::create_functionspace(functionspace_form_biharmonic_a, "u", mesh)); + fem::create_functionspace(mesh, element)); // The source function $f$ and the penalty term $\alpha$ are // declared: @@ -185,9 +191,9 @@ int main(int argc, char* argv[]) // Define variational forms auto a = std::make_shared>(fem::create_form( - *form_biharmonic_a, {V, V}, {}, {{"alpha", alpha}}, {})); + *form_biharmonic_a, {V, V}, {}, {{"alpha", alpha}}, {}, {})); auto L = std::make_shared>( - fem::create_form(*form_biharmonic_L, {V}, {{"f", f}}, {}, {})); + fem::create_form(*form_biharmonic_L, {V}, {{"f", f}}, {}, {}, {})); // Now, the Dirichlet boundary condition ($u = 0$) can be // created using the class {cpp:class}`DirichletBC`. A @@ -201,23 +207,7 @@ int main(int argc, char* argv[]) // follows: // Define boundary condition - auto facets = mesh::locate_entities_boundary( - *mesh, 1, - [](auto x) - { - constexpr U eps = 1.0e-6; - std::vector marker(x.extent(1), false); - for (std::size_t p = 0; p < x.extent(1); ++p) - { - U x0 = x(0, p); - U x1 = x(1, p); - if (std::abs(x0) < eps or std::abs(x0 - 1) < eps) - marker[p] = true; - if (std::abs(x1) < eps or std::abs(x1 - 1) < eps) - marker[p] = true; - } - return marker; - }); + auto facets = mesh::exterior_facet_indices(*mesh->topology()); const auto bdofs = fem::locate_dofs_topological( *V->mesh()->topology_mutable(), *V->dofmap(), 1, facets); auto bc = std::make_shared>(0.0, bdofs, V); @@ -249,7 +239,7 @@ int main(int argc, char* argv[]) fem::assemble_vector(b.mutable_array(), *L); fem::apply_lifting(b.mutable_array(), {a}, {{bc}}, {}, T(1.0)); b.scatter_rev(std::plus()); - fem::set_bc(b.mutable_array(), {bc}); + bc->set(b.mutable_array(), std::nullopt); la::petsc::KrylovSolver lu(MPI_COMM_WORLD); la::petsc::options::set("ksp_type", "preonly"); diff --git a/cpp/demo/codim_0_assembly/CMakeLists.txt b/cpp/demo/codim_0_assembly/CMakeLists.txt new file mode 100644 index 00000000000..70e9c2b8a7e --- /dev/null +++ b/cpp/demo/codim_0_assembly/CMakeLists.txt @@ -0,0 +1,67 @@ +# This file was generated by running +# +# python cmake/scripts/generate-cmakefiles from dolfinx/cpp +# +cmake_minimum_required(VERSION 3.19) + +set(PROJECT_NAME demo_codim_0_assembly) +project(${PROJECT_NAME} LANGUAGES C CXX) + +# Set C++20 standard +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +if(NOT TARGET dolfinx) + find_package(DOLFINX REQUIRED) +endif() + +include(CheckSymbolExists) +set(CMAKE_REQUIRED_INCLUDES ${PETSC_INCLUDE_DIRS}) +check_symbol_exists(PETSC_USE_COMPLEX petscsystypes.h PETSC_SCALAR_COMPLEX) +check_symbol_exists(PETSC_USE_REAL_DOUBLE petscsystypes.h PETSC_REAL_DOUBLE) + +# Add target to compile UFL files +if(PETSC_SCALAR_COMPLEX EQUAL 1) + if(PETSC_REAL_DOUBLE EQUAL 1) + set(SCALAR_TYPE "--scalar_type=complex128") + else() + set(SCALAR_TYPE "--scalar_type=complex64") + endif() +else() + if(PETSC_REAL_DOUBLE EQUAL 1) + set(SCALAR_TYPE "--scalar_type=float64") + else() + set(SCALAR_TYPE "--scalar_type=float32") + endif() +endif() +add_custom_command( + OUTPUT mixed_codim0.c + COMMAND ffcx ${CMAKE_CURRENT_SOURCE_DIR}/mixed_codim0.py ${SCALAR_TYPE} + VERBATIM + DEPENDS mixed_codim0.py + COMMENT "Compile mixed_codim0.py using FFCx" +) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +add_executable(${PROJECT_NAME} main.cpp ${CMAKE_CURRENT_BINARY_DIR}/mixed_codim0.c) +target_link_libraries(${PROJECT_NAME} dolfinx) + +# Do not throw error for 'multi-line comments' (these are typical in rst which +# includes LaTeX) +include(CheckCXXCompilerFlag) +check_cxx_compiler_flag("-Wno-comment" HAVE_NO_MULTLINE) +set_source_files_properties( + main.cpp + PROPERTIES + COMPILE_FLAGS + "$<$:-Wno-comment -Wall -Wextra -pedantic -Werror>" +) + +# Test targets (used by DOLFINx testing system) +set(TEST_PARAMETERS2 -np 2 ${MPIEXEC_PARAMS} "./${PROJECT_NAME}") +set(TEST_PARAMETERS3 -np 3 ${MPIEXEC_PARAMS} "./${PROJECT_NAME}") +add_test(NAME ${PROJECT_NAME}_mpi_2 COMMAND "mpirun" ${TEST_PARAMETERS2}) +add_test(NAME ${PROJECT_NAME}_mpi_3 COMMAND "mpirun" ${TEST_PARAMETERS3}) +add_test(NAME ${PROJECT_NAME}_serial COMMAND ${PROJECT_NAME}) diff --git a/cpp/demo/codim_0_assembly/main.cpp b/cpp/demo/codim_0_assembly/main.cpp new file mode 100644 index 00000000000..95537e79b29 --- /dev/null +++ b/cpp/demo/codim_0_assembly/main.cpp @@ -0,0 +1,173 @@ +// # Mixed assembly with a function mesh on a subset of cells +// +// This demo illustrates how to: +// +// * Create a submesh of co-dimension 0 +// * Assemble a mixed formulation with function spaces defined on the sub mesh +// and parent mesh + +#include "mixed_codim0.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace dolfinx; +using T = PetscScalar; +using U = typename dolfinx::scalar_value_type_t; + +int main(int argc, char* argv[]) +{ + dolfinx::init_logging(argc, argv); + PetscInitialize(&argc, &argv, nullptr, nullptr); + + { + // Create mesh and function space + auto part = mesh::create_cell_partitioner(mesh::GhostMode::shared_facet); + auto mesh = std::make_shared>( + mesh::create_rectangle(MPI_COMM_WORLD, {{{0.0, 0.0}, {2.0, 1.0}}}, + {1, 4}, mesh::CellType::quadrilateral, part)); + + auto element = basix::create_element( + basix::element::family::P, basix::cell::type::quadrilateral, 1, + basix::element::lagrange_variant::unset, + basix::element::dpc_variant::unset, false); + + auto V = std::make_shared>( + fem::create_functionspace(mesh, element, {})); + + // Next we find all cells of the mesh with y<0.5 + const int tdim = mesh->topology()->dim(); + auto marked_cells = mesh::locate_entities( + *mesh, tdim, + [](auto x) + { + using U = typename decltype(x)::value_type; + constexpr U eps = 1.0e-8; + std::vector marker(x.extent(1), false); + for (std::size_t p = 0; p < x.extent(1); ++p) + { + auto y = x(1, p); + if (std::abs(y) <= 0.5 + eps) + marker[p] = true; + } + return marker; + }); + // We create a MeshTags object where we mark these cells with 2, and any + // other cell with 1 + auto cell_map = mesh->topology()->index_map(tdim); + std::size_t num_cells_local + = mesh->topology()->index_map(tdim)->size_local() + + mesh->topology()->index_map(tdim)->num_ghosts(); + std::vector cells(num_cells_local); + std::iota(cells.begin(), cells.end(), 0); + std::vector values(num_cells_local, 1); + std::for_each(marked_cells.begin(), marked_cells.end(), + [&values](auto& c) { values[c] = 2; }); + dolfinx::mesh::MeshTags cell_marker(mesh->topology(), tdim, + cells, values); + + std::shared_ptr> submesh; + std::vector submesh_to_mesh; + { + auto [_submesh, _submesh_to_mesh, v_map, g_map] + = mesh::create_submesh(*mesh, tdim, cell_marker.find(2)); + submesh = std::make_shared>(std::move(_submesh)); + submesh_to_mesh = std::move(_submesh_to_mesh); + } + + // We create the function space used for the trial space + auto W = std::make_shared>( + fem::create_functionspace(submesh, element, {})); + + // A mixed-domain form has functions defined over different meshes. The mesh + // associated with the measure (dx, ds, etc.) is called the integration + // domain. To assemble mixed-domain forms, maps must be provided taking + // entities in the integration domain to entities on each mesh in the form. + // Since one of our forms has a measure defined over `mesh` and involves a + // function defined over `submesh`, we must provide a map from entities in + // `mesh` to entities in `submesh`. This is simply the "inverse" of + // `submesh_to_mesh`. + std::vector mesh_to_submesh(num_cells_local, -1); + for (std::size_t i = 0; i < submesh_to_mesh.size(); ++i) + mesh_to_submesh[submesh_to_mesh[i]] = i; + + std::shared_ptr> const_ptr = submesh; + std::map>, + std::span> + entity_maps + = {{const_ptr, std::span(mesh_to_submesh.data(), + mesh_to_submesh.size())}}; + + // Next we compute the integration entities on the integration domain `mesh` + std::map< + fem::IntegralType, + std::vector>>> + subdomain_map = {}; + auto integration_entities = fem::compute_integration_domains( + fem::IntegralType::cell, *mesh->topology(), cell_marker.find(2), tdim); + + subdomain_map[fem::IntegralType::cell].push_back( + {3, + std::span(integration_entities.data(), integration_entities.size())}); + + // We can now create the bi-linear form + auto a_mixed = std::make_shared>( + fem::create_form(*form_mixed_codim0_a_mixed, {V, W}, {}, {}, + subdomain_map, entity_maps, V->mesh())); + + la::SparsityPattern sp_mixed = fem::create_sparsity_pattern(*a_mixed); + sp_mixed.finalize(); + la::MatrixCSR A_mixed(sp_mixed); + fem::assemble_matrix(A_mixed.mat_add_values(), *a_mixed, {}); + A_mixed.scatter_rev(); + + auto a = std::make_shared>( + fem::create_form(*form_mixed_codim0_a, {W, W}, {}, {}, {}, {})); + la::SparsityPattern sp = fem::create_sparsity_pattern(*a); + sp.finalize(); + + la::MatrixCSR A(sp); + fem::assemble_matrix(A.mat_add_values(), *a, {}); + A.scatter_rev(); + + std::vector A_mixed_flattened = A_mixed.to_dense(); + std::stringstream cc; + cc.precision(3); + cc << "A_mixed:" << std::endl; + + std::size_t num_owned_rows = V->dofmap()->index_map->size_local(); + std::size_t num_sub_cols = W->dofmap()->index_map->size_local() + + W->dofmap()->index_map->num_ghosts(); + for (std::size_t i = 0; i < num_owned_rows; i++) + { + for (std::size_t j = 0; j < num_sub_cols; j++) + { + cc << A_mixed_flattened[i * num_sub_cols + j] << " "; + } + cc << std::endl; + } + + std::size_t num_owned_sub_rows = W->dofmap()->index_map->size_local(); + std::vector A_flattened = A.to_dense(); + cc << "A" << std::endl; + for (std::size_t i = 0; i < num_owned_sub_rows; i++) + { + for (std::size_t j = 0; j < num_sub_cols; j++) + { + cc << A_flattened[i * num_sub_cols + j] << " "; + } + cc << std::endl; + } + std::cout << cc.str() << std::endl; + } + + PetscFinalize(); + + return 0; +} diff --git a/cpp/demo/codim_0_assembly/mixed_codim0.py b/cpp/demo/codim_0_assembly/mixed_codim0.py new file mode 100644 index 00000000000..24a73493dc8 --- /dev/null +++ b/cpp/demo/codim_0_assembly/mixed_codim0.py @@ -0,0 +1,36 @@ +# This demo aims to illustrate how to assemble a matrix with a trial function +# defined on a submesh of co-dimension 0, and a test function defined on the parent mesh +from basix.ufl import element +from ufl import ( + FunctionSpace, + Mesh, + TestFunction, + TrialFunction, + dx, + inner, +) + +cell = "quadrilateral" +coord_element = element("Lagrange", cell, 1, shape=(2,)) +mesh = Mesh(coord_element) + +# We define the function space and test function on the full mesh +e = element("Lagrange", cell, 1) +V = FunctionSpace(mesh, e) +v = TestFunction(V) + +# Next we define the sub-mesh +submesh = Mesh(coord_element) +W = FunctionSpace(submesh, e) +p = TrialFunction(W) + +# And finally we define a "mass matrix" on the submesh, with the test function +# of the parent mesh. The integration domain is the parent mesh, but we restrict integration +# to all cells marked with subdomain_id=3, which will indicate what cells of our mesh is part +# of the submesh +a_mixed = inner(p, v) * dx(domain=mesh, subdomain_id=3) + +q = TestFunction(W) +a = inner(p, q) * dx(domain=submesh) + +forms = [a_mixed, a] diff --git a/cpp/demo/custom_kernel/main.cpp b/cpp/demo/custom_kernel/main.cpp index 77c92f11a35..b1b4070fef9 100644 --- a/cpp/demo/custom_kernel/main.cpp +++ b/cpp/demo/custom_kernel/main.cpp @@ -76,7 +76,8 @@ double assemble_matrix0(std::shared_ptr> V, auto kernel, std::span cells) { // Kernel data (ID, kernel function, cell indices to execute over) - std::vector kernel_data{fem::integral_data(-1, kernel, cells)}; + std::vector kernel_data{ + fem::integral_data(-1, kernel, cells, std::vector{})}; // Associate kernel with cells (as opposed to facets, etc) std::map integrals{std::pair{fem::IntegralType::cell, kernel_data}}; @@ -107,7 +108,8 @@ double assemble_vector0(std::shared_ptr> V, auto kernel, std::span cells) { auto mesh = V->mesh(); - std::vector kernal_data{fem::integral_data(-1, kernel, cells)}; + std::vector kernal_data{ + fem::integral_data(-1, kernel, cells, std::vector{})}; std::map integrals{std::pair{fem::IntegralType::cell, kernal_data}}; fem::Form L({V}, integrals, {}, {}, false, {}, mesh); auto dofmap = V->dofmap(); diff --git a/cpp/demo/hyperelasticity/CMakeLists.txt b/cpp/demo/hyperelasticity/CMakeLists.txt index ce398bbe127..3b2a0104817 100644 --- a/cpp/demo/hyperelasticity/CMakeLists.txt +++ b/cpp/demo/hyperelasticity/CMakeLists.txt @@ -40,9 +40,7 @@ else() set(CMAKE_INCLUDE_CURRENT_DIR ON) - add_executable( - ${PROJECT_NAME} main.cpp ${CMAKE_CURRENT_BINARY_DIR}/hyperelasticity.c - ) + add_executable(${PROJECT_NAME} main.cpp ${CMAKE_CURRENT_BINARY_DIR}/hyperelasticity.c) target_link_libraries(${PROJECT_NAME} dolfinx) # Do not throw error for 'multi-line comments' (these are typical in rst which diff --git a/cpp/demo/hyperelasticity/main.cpp b/cpp/demo/hyperelasticity/main.cpp index 35da5a18895..01790d7408e 100644 --- a/cpp/demo/hyperelasticity/main.cpp +++ b/cpp/demo/hyperelasticity/main.cpp @@ -15,6 +15,7 @@ // ## C++ program #include "hyperelasticity.h" +#include #include #include #include @@ -24,9 +25,14 @@ #include #include #include +#include #include #include #include +#include +#include +#include +#include using namespace dolfinx; using T = PetscScalar; @@ -80,8 +86,8 @@ class HyperElasticProblem return [&](const Vec x, Vec) { // Assemble b and update ghosts - std::span b(_b.mutable_array()); - std::fill(b.begin(), b.end(), 0.0); + std::span b(_b.mutable_array()); + std::ranges::fill(b, 0); fem::assemble_vector(b, *_l); VecGhostUpdateBegin(_b_petsc, ADD_VALUES, SCATTER_REVERSE); VecGhostUpdateEnd(_b_petsc, ADD_VALUES, SCATTER_REVERSE); @@ -91,10 +97,11 @@ class HyperElasticProblem VecGhostGetLocalForm(x, &x_local); PetscInt n = 0; VecGetSize(x_local, &n); - const T* array = nullptr; - VecGetArrayRead(x_local, &array); - fem::set_bc(b, _bcs, std::span(array, n), -1.0); - VecRestoreArrayRead(x, &array); + const T* _x = nullptr; + VecGetArrayRead(x_local, &_x); + std::ranges::for_each(_bcs, [b, x = std::span(_x, n)](auto& bc) + { bc->set(b, x, -1); }); + VecRestoreArrayRead(x_local, &_x); }; } @@ -137,8 +144,9 @@ int main(int argc, char* argv[]) // Set the logging thread name to show the process rank int mpi_rank; MPI_Comm_rank(MPI_COMM_WORLD, &mpi_rank); - std::string thread_name = "RANK " + std::to_string(mpi_rank); - loguru::set_thread_name(thread_name.c_str()); + std::string fmt = "[%Y-%m-%d %H:%M:%S.%e] [RANK " + std::to_string(mpi_rank) + + "] [%l] %v"; + spdlog::set_pattern(fmt); { // Inside the `main` function, we begin by defining a tetrahedral // mesh of the domain and the function space on this mesh. Here, we @@ -153,8 +161,13 @@ int main(int argc, char* argv[]) mesh::CellType::tetrahedron, mesh::create_cell_partitioner(mesh::GhostMode::none))); - auto V = std::make_shared>(fem::create_functionspace( - functionspace_form_hyperelasticity_F_form, "u", mesh)); + auto element = basix::create_element( + basix::element::family::P, basix::cell::type::tetrahedron, 1, + basix::element::lagrange_variant::unset, + basix::element::dpc_variant::unset, false); + + auto V = std::make_shared>( + fem::create_functionspace(mesh, element, {3})); auto B = std::make_shared>(std::vector{0, 0, 0}); auto traction = std::make_shared>(std::vector{0, 0, 0}); @@ -163,10 +176,10 @@ int main(int argc, char* argv[]) auto u = std::make_shared>(V); auto a = std::make_shared>( fem::create_form(*form_hyperelasticity_J_form, {V, V}, {{"u", u}}, - {{"B", B}, {"T", traction}}, {})); + {{"B", B}, {"T", traction}}, {}, {})); auto L = std::make_shared>( fem::create_form(*form_hyperelasticity_F_form, {V}, {{"u", u}}, - {{"B", B}, {"T", traction}}, {})); + {{"B", B}, {"T", traction}}, {}, {})); auto u_rotation = std::make_shared>(V); u_rotation->interpolate( @@ -264,7 +277,7 @@ int main(int argc, char* argv[]) auto sigma = fem::Function(S); sigma.name = "cauchy_stress"; - sigma.interpolate(sigma_expression, *mesh); + sigma.interpolate(sigma_expression); // Save solution in VTK format io::VTKFile file_u(mesh->comm(), "u.pvd", "w"); diff --git a/cpp/demo/interpolation-io/main.cpp b/cpp/demo/interpolation-io/main.cpp index d6c2cf7c6ce..a4904cac9e7 100644 --- a/cpp/demo/interpolation-io/main.cpp +++ b/cpp/demo/interpolation-io/main.cpp @@ -29,8 +29,6 @@ using namespace dolfinx; /// and outputs the finite element function to a VTX file for /// visualisation. /// -/// Also shows how to create a finite element using Basix. -/// /// @tparam T Scalar type of the finite element function. /// @tparam U Float type for the finite element basis and the mesh. /// @param mesh Mesh. @@ -149,8 +147,7 @@ void interpolate_nedelec(std::shared_ptr> mesh, { std::vector f(2 * x.extent(1), 0.0); std::copy_n(x.data_handle(), f.size(), f.begin()); - std::transform(f.cbegin(), f.cend(), f.begin(), - [](auto x) { return x + T(1); }); + std::ranges::transform(f, f.begin(), [](auto x) { return x + T(1); }); return {f, {2, x.extent(1)}}; }, cells1); @@ -191,8 +188,8 @@ void interpolate_nedelec(std::shared_ptr> mesh, #endif } -/// @brief This program shows how to create finite element spaces without FFCx -/// generated code. +/// @brief This program shows how to interpolate functions into different types +/// of finite element spaces and output the result to file for visualisation. int main(int argc, char* argv[]) { dolfinx::init_logging(argc, argv); diff --git a/cpp/demo/interpolation_different_meshes/main.cpp b/cpp/demo/interpolation_different_meshes/main.cpp index 09651b6da4f..c83cd0852fc 100644 --- a/cpp/demo/interpolation_different_meshes/main.cpp +++ b/cpp/demo/interpolation_different_meshes/main.cpp @@ -68,14 +68,18 @@ int main(int argc, char* argv[]) u_tet->interpolate(fun); // Interpolate from u_tet to u_hex - constexpr T padding = 1e-8; - auto nmm_interpolation_data - = fem::create_nonmatching_meshes_interpolation_data( - *u_hex->function_space()->mesh(), + auto cell_map + = mesh_hex->topology()->index_map(mesh_hex->topology()->dim()); + assert(cell_map); + std::vector cells( + cell_map->size_local() + cell_map->num_ghosts(), 0); + std::iota(cells.begin(), cells.end(), 0); + geometry::PointOwnershipData interpolation_data + = fem::create_interpolation_data( + u_hex->function_space()->mesh()->geometry(), *u_hex->function_space()->element(), - *u_tet->function_space()->mesh(), padding); - constexpr std::span cell_map; - u_hex->interpolate(*u_tet, cell_map, nmm_interpolation_data); + *u_tet->function_space()->mesh(), std::span(cells), 1e-8); + u_hex->interpolate(*u_tet, cells, interpolation_data); #ifdef HAS_ADIOS2 io::VTXWriter write_tet(mesh_tet->comm(), "u_tet.bp", {u_tet}); diff --git a/cpp/demo/mixed_topology/main.cpp b/cpp/demo/mixed_topology/main.cpp deleted file mode 100644 index 0ec8af0be11..00000000000 --- a/cpp/demo/mixed_topology/main.cpp +++ /dev/null @@ -1,150 +0,0 @@ -// # Mixed topology -// -// Experimental demo. - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace dolfinx; - -// Note: this demo is not currently intended to provide a fully -// functional example of using a mixed-topology mesh, but shows only the -// basic construction. Experimental. - -int main(int argc, char* argv[]) -{ - dolfinx::init_logging(argc, argv); - MPI_Init(&argc, &argv); - - // Number of square cell in x-direction - constexpr int nx_s = 2; - // Number of triangle cells in x-direction - constexpr int nx_t = 2; - // Number of cells in y-direction - constexpr int ny = 4; - - constexpr int num_s = nx_s * ny; - constexpr int num_t = 2 * nx_t * ny; - - std::vector x; - for (int i = 0; i < nx_s + nx_t + 1; ++i) - { - for (int j = 0; j < ny + 1; ++j) - { - x.push_back(i); - x.push_back(j); - } - } - - std::vector cells; - std::vector offsets{0}; - for (int i = 0; i < nx_s + nx_t; ++i) - { - for (int j = 0; j < ny; ++j) - { - const int v_0 = j + i * (ny + 1); - const int v_1 = v_0 + 1; - const int v_2 = v_0 + ny + 1; - const int v_3 = v_0 + ny + 2; - if (i < nx_s) - { - cells.push_back(v_0); - cells.push_back(v_1); - cells.push_back(v_3); - cells.push_back(v_2); - offsets.push_back(cells.size()); - } - else - { - cells.push_back(v_0); - cells.push_back(v_1); - cells.push_back(v_2); - offsets.push_back(cells.size()); - - cells.push_back(v_1); - cells.push_back(v_2); - cells.push_back(v_3); - offsets.push_back(cells.size()); - } - } - } - - graph::AdjacencyList cells_list(cells, offsets); - std::vector original_global_index(num_s + num_t); - std::iota(original_global_index.begin(), original_global_index.end(), 0); - std::vector ghost_owners; - std::vector cell_group_offsets{0, num_s, num_s + num_t, - num_s + num_t, num_s + num_t}; - std::vector boundary_vertices; - for (int j = 0; j < ny + 1; ++j) - { - boundary_vertices.push_back(j); - boundary_vertices.push_back(j + (nx_s + nx_t + 1) * ny); - } - for (int i = 0; i < nx_s + nx_t + 1; ++i) - { - boundary_vertices.push_back((ny + 1) * i); - boundary_vertices.push_back(ny + (ny + 1) * i); - } - - std::sort(boundary_vertices.begin(), boundary_vertices.end()); - boundary_vertices.erase( - std::unique(boundary_vertices.begin(), boundary_vertices.end()), - boundary_vertices.end()); - - std::vector cell_types{mesh::CellType::quadrilateral, - mesh::CellType::triangle}; - std::vector> elements; - for (auto ct : cell_types) - elements.push_back(fem::CoordinateElement(ct, 1)); - - { - auto topo = std::make_shared(mesh::create_topology( - MPI_COMM_WORLD, cells_list, original_global_index, ghost_owners, - cell_types, cell_group_offsets, boundary_vertices)); - - auto topo_cells = topo->connectivity(2, 0); - - std::cout << "cells\n------\n"; - for (int i = 0; i < topo_cells->num_nodes(); ++i) - { - std::cout << i << " ["; - for (auto q : topo_cells->links(i)) - std::cout << q << " "; - std::cout << "]\n"; - } - - topo->create_connectivity(1, 0); - - std::cout << "facets\n------\n"; - auto topo_facets = topo->connectivity(1, 0); - for (int i = 0; i < topo_facets->num_nodes(); ++i) - { - std::cout << i << " ["; - for (auto q : topo_facets->links(i)) - std::cout << q << " "; - std::cout << "]\n"; - } - - mesh::Geometry geom = mesh::create_geometry(MPI_COMM_WORLD, *topo, elements, - cells_list, x, 2); - - mesh::Mesh mesh(MPI_COMM_WORLD, topo, geom); - std::cout << "num cells = " << mesh.topology()->index_map(2)->size_local() - << "\n"; - for (auto q : mesh.topology()->entity_group_offsets(2)) - std::cout << q << " "; - std::cout << "\n"; - } - - MPI_Finalize(); - - return 0; -} diff --git a/cpp/demo/poisson/main.cpp b/cpp/demo/poisson/main.cpp index 0a296adb73f..6d5d6624d4e 100644 --- a/cpp/demo/poisson/main.cpp +++ b/cpp/demo/poisson/main.cpp @@ -80,10 +80,15 @@ // namespace. #include "poisson.h" +#include #include #include #include #include +#include +#include +#include +#include #include #include @@ -114,8 +119,13 @@ int main(int argc, char* argv[]) mesh::create_rectangle(MPI_COMM_WORLD, {{{0.0, 0.0}, {2.0, 1.0}}}, {32, 16}, mesh::CellType::triangle, part)); + auto element = basix::create_element( + basix::element::family::P, basix::cell::type::triangle, 1, + basix::element::lagrange_variant::unset, + basix::element::dpc_variant::unset, false); + auto V = std::make_shared>( - fem::create_functionspace(functionspace_form_poisson_a, "u", mesh)); + fem::create_functionspace(mesh, element, {})); // Next, we define the variational formulation by initializing the // bilinear and linear forms ($a$, $L$) using the previously @@ -130,9 +140,9 @@ int main(int argc, char* argv[]) // Define variational forms auto a = std::make_shared>(fem::create_form( - *form_poisson_a, {V, V}, {}, {{"kappa", kappa}}, {})); + *form_poisson_a, {V, V}, {}, {{"kappa", kappa}}, {}, {})); auto L = std::make_shared>(fem::create_form( - *form_poisson_L, {V}, {{"f", f}, {"g", g}}, {}, {})); + *form_poisson_L, {V}, {{"f", f}, {"g", g}}, {}, {}, {})); // Now, the Dirichlet boundary condition ($u = 0$) can be created // using the class {cpp:class}`DirichletBC`. A @@ -215,7 +225,7 @@ int main(int argc, char* argv[]) fem::assemble_vector(b.mutable_array(), *L); fem::apply_lifting(b.mutable_array(), {a}, {{bc}}, {}, T(1)); b.scatter_rev(std::plus()); - fem::set_bc(b.mutable_array(), {bc}); + bc->set(b.mutable_array(), std::nullopt); la::petsc::KrylovSolver lu(MPI_COMM_WORLD); la::petsc::options::set("ksp_type", "preonly"); diff --git a/cpp/demo/poisson_matrix_free/main.cpp b/cpp/demo/poisson_matrix_free/main.cpp index 3281b664ce4..df7bb19aa41 100644 --- a/cpp/demo/poisson_matrix_free/main.cpp +++ b/cpp/demo/poisson_matrix_free/main.cpp @@ -42,6 +42,8 @@ // ## C++ program #include "poisson.h" +#include +#include #include #include #include @@ -49,6 +51,7 @@ #include #include #include +#include using namespace dolfinx; @@ -61,9 +64,8 @@ namespace linalg /// @param[in] y void axpy(auto&& r, auto alpha, auto&& x, auto&& y) { - std::transform(x.array().begin(), x.array().end(), y.array().begin(), - r.mutable_array().begin(), - [alpha](auto x, auto y) { return alpha * x + y; }); + std::ranges::transform(x.array(), y.array(), r.mutable_array().begin(), + [alpha](auto x, auto y) { return alpha * x + y; }); } /// @brief Solve problem A.x = b using the conjugate gradient (CG) @@ -137,20 +139,24 @@ void solver(MPI_Comm comm) auto mesh = std::make_shared>(mesh::create_rectangle( comm, {{{0.0, 0.0}, {1.0, 1.0}}}, {10, 10}, mesh::CellType::triangle, mesh::create_cell_partitioner(mesh::GhostMode::none))); + auto element = basix::create_element( + basix::element::family::P, basix::cell::type::triangle, 2, + basix::element::lagrange_variant::unset, + basix::element::dpc_variant::unset, false); auto V = std::make_shared>( - fem::create_functionspace(functionspace_form_poisson_M, "ui", mesh)); + fem::create_functionspace(mesh, element, {})); // Prepare and set Constants for the bilinear form auto f = std::make_shared>(-6.0); // Define variational forms auto L = std::make_shared>( - fem::create_form(*form_poisson_L, {V}, {}, {{"f", f}}, {})); + fem::create_form(*form_poisson_L, {V}, {}, {{"f", f}}, {}, {})); // Action of the bilinear form "a" on a function ui auto ui = std::make_shared>(V); auto M = std::make_shared>( - fem::create_form(*form_poisson_M, {V}, {{"ui", ui}}, {{}}, {})); + fem::create_form(*form_poisson_M, {V}, {{"ui", ui}}, {{}}, {}, {})); // Define boundary condition auto u_D = std::make_shared>(V); @@ -176,14 +182,14 @@ void solver(MPI_Comm comm) // Apply lifting to account for Dirichlet boundary condition // b <- b - A * x_bc - fem::set_bc(ui->x()->mutable_array(), {bc}, T(-1)); + bc->set(ui->x()->mutable_array(), std::nullopt, T(-1)); fem::assemble_vector(b.mutable_array(), *M); // Communicate ghost values b.scatter_rev(std::plus()); // Set BC dofs to zero (effectively zeroes columns of A) - fem::set_bc(b.mutable_array(), {bc}, T(0)); + bc->set(b.mutable_array(), std::nullopt, T(0)); b.scatter_fwd(); @@ -198,8 +204,7 @@ void solver(MPI_Comm comm) y.set(0.0); // Update coefficient ui (just copy data from x to ui) - std::copy(x.array().begin(), x.array().end(), - ui->x()->mutable_array().begin()); + std::ranges::copy(x.array(), ui->x()->mutable_array().begin()); // Compute action of A on x fem::pack_coefficients(*M, coeff); @@ -207,7 +212,7 @@ void solver(MPI_Comm comm) fem::make_coefficients_span(coeff)); // Set BC dofs to zero (effectively zeroes rows of A) - fem::set_bc(y.mutable_array(), {bc}, T(0)); + bc->set(y.mutable_array(), std::nullopt, T(0)); // Accumulate ghost values y.scatter_rev(std::plus()); @@ -221,12 +226,12 @@ void solver(MPI_Comm comm) int num_it = linalg::cg(*u->x(), b, action, 200, 1e-6); // Set BC values in the solution vectors - fem::set_bc(u->x()->mutable_array(), {bc}, T(1)); + bc->set(u->x()->mutable_array(), std::nullopt, T(1)); // Compute L2 error (squared) of the solution vector e = (u - u_d, u // - u_d)*dx auto E = std::make_shared>(fem::create_form( - *form_poisson_E, {}, {{"uexact", u_D}, {"usol", u}}, {}, {}, mesh)); + *form_poisson_E, {}, {{"uexact", u_D}, {"usol", u}}, {}, {}, {}, mesh)); T error = fem::assemble_scalar(*E); if (dolfinx::MPI::rank(comm) == 0) { diff --git a/cpp/doc/source/conf.py b/cpp/doc/source/conf.py index 187a74457dd..8dcb543d2f7 100644 --- a/cpp/doc/source/conf.py +++ b/cpp/doc/source/conf.py @@ -47,12 +47,13 @@ # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] +source_suffix = {".rst": "restructuredtext", ".md": "markdown"} + # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = [] - # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for diff --git a/cpp/doc/source/fem.rst b/cpp/doc/source/fem.rst index 80e2c2563f8..cdb41bbaf8c 100644 --- a/cpp/doc/source/fem.rst +++ b/cpp/doc/source/fem.rst @@ -79,18 +79,11 @@ Interpolation .. doxygenfunction:: dolfinx::fem::interpolation_coords :project: DOLFINx -.. doxygenfunction:: dolfinx::fem::interpolate(Function &u, const Function &v, std::span cells) - :project: DOLFINx - - -.. doxygenfunction:: dolfinx::fem::interpolate(Function& u, std::span f, std::array fshape, std::span cells) - :project: DOLFINx - Sparsity pattern construction ----------------------------- -.. doxygenfunction:: dolfinx::fem::create_sparsity_pattern(const Form&) +.. doxygenfunction:: dolfinx::fem::create_sparsity_pattern(const Form&) :project: DOLFINx diff --git a/cpp/dolfinx/CMakeLists.txt b/cpp/dolfinx/CMakeLists.txt index e8cec2ed85c..63d941fef51 100644 --- a/cpp/dolfinx/CMakeLists.txt +++ b/cpp/dolfinx/CMakeLists.txt @@ -2,8 +2,9 @@ include(GNUInstallDirs) # ------------------------------------------------------------------------------ -# Declare the library (target) +# Declare the library (target) and set C++ standard add_library(dolfinx) +target_compile_features(dolfinx PUBLIC cxx_std_20) # ------------------------------------------------------------------------------ # Add source files to the target @@ -65,6 +66,14 @@ target_compile_options( # Add version to definitions (public) target_compile_definitions(dolfinx PUBLIC DOLFINX_VERSION="${DOLFINX_VERSION}") +# MSVC does not support the optional C99 _Complex type. Consequently, ufcx.h +# does not contain tabulate_tensor_complex* functions when built with MSVC. On +# MSVC this DOLFINX macro is set and this removes all calls to +# tabulate_tensor_complex*. +if(MSVC) + target_compile_definitions(dolfinx PUBLIC DOLFINX_NO_STDC_COMPLEX_KERNELS) +endif() + # ------------------------------------------------------------------------------ # Add include directories and libraries of required packages @@ -92,6 +101,8 @@ target_link_libraries(dolfinx PUBLIC Boost::timer) # MPI target_link_libraries(dolfinx PUBLIC MPI::MPI_CXX) +target_link_libraries(dolfinx PUBLIC spdlog::spdlog) + # HDF5 target_link_libraries(dolfinx PUBLIC hdf5::hdf5) @@ -119,8 +130,10 @@ endif() # SCOTCH if(DOLFINX_ENABLE_SCOTCH AND SCOTCH_FOUND) target_compile_definitions(dolfinx PUBLIC HAS_PTSCOTCH) - target_link_libraries(dolfinx PRIVATE ${SCOTCH_LIBRARIES}) - target_include_directories(dolfinx SYSTEM PRIVATE ${SCOTCH_INCLUDE_DIRS}) + target_link_libraries(dolfinx PRIVATE SCOTCH::ptscotch) + if(TARGET SCOTCH::scotcherr) + target_link_libraries(dolfinx PRIVATE SCOTCH::scotcherr) + endif() endif() # ParMETIS @@ -139,13 +152,41 @@ endif() # ------------------------------------------------------------------------------ # Install dolfinx library and header files -install( - TARGETS dolfinx - EXPORT DOLFINXTargets - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT RuntimeExecutables - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT RuntimeLibraries - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Development -) +if(WIN32) + install( + TARGETS dolfinx + EXPORT DOLFINXTargets + RUNTIME_DEPENDENCY_SET dependencies + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT RuntimeExecutables + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT RuntimeLibraries + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Development + ) + + if(INSTALL_RUNTIME_DEPENDENCIES) + list(APPEND PRE_EXCLUDE_REGEXES "api-ms-.*") + list(APPEND PRE_EXCLUDE_REGEXES "ext-ms-.*") + install( + RUNTIME_DEPENDENCY_SET + dependencies + DESTINATION + ${CMAKE_INSTALL_BINDIR} + PRE_EXCLUDE_REGEXES + ${PRE_EXCLUDE_REGEXES} + POST_EXCLUDE_REGEXES + ".*system32/.*\\.dll" + DIRECTORIES + $ + ) + endif() +else() + install( + TARGETS dolfinx + EXPORT DOLFINXTargets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT RuntimeExecutables + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT RuntimeLibraries + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Development + ) +endif() # Generate DOLFINTargets.cmake install(EXPORT DOLFINXTargets DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/dolfinx) @@ -235,7 +276,8 @@ foreach(_target ${PKGCONFIG_DOLFINX_TARGET_LINK_LIBRARIES}) list(APPEND PKGCONFIG_DOLFINX_LIBS ${_target}) endif() - # Special handling for compiled Boost imported targets + # Special handling to extract data for compiled Boost imported + # targets to use in generate pkg-config file if(("${_target}" MATCHES "^.*Boost::.*$") AND NOT "${_target}" STREQUAL "Boost::headers" ) diff --git a/cpp/dolfinx/common/CMakeLists.txt b/cpp/dolfinx/common/CMakeLists.txt index 7ab6428d6a1..91425be408a 100644 --- a/cpp/dolfinx/common/CMakeLists.txt +++ b/cpp/dolfinx/common/CMakeLists.txt @@ -4,7 +4,6 @@ set(HEADERS_common ${CMAKE_CURRENT_SOURCE_DIR}/dolfinx_doc.h ${CMAKE_CURRENT_SOURCE_DIR}/IndexMap.h ${CMAKE_CURRENT_SOURCE_DIR}/log.h - ${CMAKE_CURRENT_SOURCE_DIR}/loguru.hpp ${CMAKE_CURRENT_SOURCE_DIR}/sort.h ${CMAKE_CURRENT_SOURCE_DIR}/types.h ${CMAKE_CURRENT_SOURCE_DIR}/math.h diff --git a/cpp/dolfinx/common/IndexMap.cpp b/cpp/dolfinx/common/IndexMap.cpp index 30021177267..dd3ae3a7145 100644 --- a/cpp/dolfinx/common/IndexMap.cpp +++ b/cpp/dolfinx/common/IndexMap.cpp @@ -1,4 +1,5 @@ -// Copyright (C) 2015-2022 Chris Richardson, Garth N. Wells and Igor Baratta +// Copyright (C) 2015-2024 Chris Richardson, Garth N. Wells, Igor Baratta, +// Joseph P. Dean and Jørgen S. Dokken // // This file is part of DOLFINx (https://www.fenicsproject.org) // @@ -19,7 +20,6 @@ using namespace dolfinx::common; namespace { - /// @brief Given source ranks (ranks that own indices ghosted by the /// calling rank), compute ranks that ghost indices owned by the calling /// rank. @@ -36,11 +36,12 @@ std::array, 2> build_src_dest(MPI_Comm comm, } std::vector src(owners.begin(), owners.end()); - std::sort(src.begin(), src.end()); - src.erase(std::unique(src.begin(), src.end()), src.end()); + std::ranges::sort(src); + auto [unique_end, range_end] = std::ranges::unique(src); + src.erase(unique_end, range_end); src.shrink_to_fit(); std::vector dest = dolfinx::MPI::compute_graph_edges_nbx(comm, src); - std::sort(dest.begin(), dest.end()); + std::ranges::sort(dest); return {std::move(src), std::move(dest)}; } @@ -96,25 +97,18 @@ communicate_ghosts_to_owners(MPI_Comm comm, std::span src, std::vector> pos_to_ghost(src.size()); for (std::size_t i = 0; i < ghosts.size(); ++i) { - auto it = std::lower_bound(src.begin(), src.end(), owners[i]); + auto it = std::ranges::lower_bound(src, owners[i]); assert(it != src.end() and *it == owners[i]); - std::size_t r = std::distance(src.begin(), it); - if (include_ghost[i]) + if (std::size_t r = std::distance(src.begin(), it); include_ghost[i]) { send_data[r].push_back(ghosts[i]); pos_to_ghost[r].push_back(i); } - else - { - send_data[r].push_back(-1); - pos_to_ghost[r].push_back(-1); - } } // Count number of ghosts per dest - std::transform(send_data.begin(), send_data.end(), - std::back_inserter(send_sizes), - [](auto& d) { return d.size(); }); + std::ranges::transform(send_data, std::back_inserter(send_sizes), + [](auto& d) { return d.size(); }); // Send how many indices I ghost to each owner, and receive how many // of my indices other ranks ghost @@ -161,6 +155,7 @@ communicate_ghosts_to_owners(MPI_Comm comm, std::span src, /// Given an index map and a subset of local indices (can be owned or /// ghost but must be unique and sorted), compute the owned, ghost and /// ghost owners in the submap. +/// /// @param[in] imap An index map. /// @param[in] indices List of entity indices (indices local to the /// process). @@ -180,14 +175,11 @@ compute_submap_indices(const IndexMap& imap, std::span indices, IndexMapOrder order, bool allow_owner_change) { - std::span src = imap.src(); - std::span dest = imap.dest(); - // Create lookup array to determine if an index is in the sub-map std::vector is_in_submap(imap.size_local() + imap.num_ghosts(), 0); - std::for_each(indices.begin(), indices.end(), - [&is_in_submap](auto i) { is_in_submap[i] = 1; }); + std::ranges::for_each(indices, + [&is_in_submap](auto i) { is_in_submap[i] = 1; }); // --- Step 1 ---: Send ghost indices in `indices` to their owners // and receive indices owned by this process that are in `indices` @@ -199,46 +191,52 @@ compute_submap_indices(const IndexMap& imap, std::span(is_in_submap.cbegin() + imap.size_local(), is_in_submap.cend())); - // --- Step 2 ---: Create a map from the indices in `recv_indices` (i.e. - // indices owned by this process that are in `indices` on other processes) to - // their owner in the submap. This is required since not all indices in - // `recv_indices` will necessarily be in `indices` on this process, and thus - // other processes must own them in the submap. - + // --- Step 2 ---: Create a map from the indices in `recv_indices` + // (i.e. indices owned by this process that are in `indices` on other + // processes) to their owner in the submap. This is required since not + // all indices in `recv_indices` will necessarily be in `indices` on + // this process, and thus other processes must own them in the submap. + // If ownership of received index doesn't change, then this process + // has the receiving rank as a destination std::vector recv_owners(send_disp.back()); + std::vector submap_dest; + submap_dest.reserve(1); const int rank = dolfinx::MPI::rank(imap.comm()); { - // Flag to track if the owner of any indices have changed in the submap + // Flag to track if the owner of any indices have changed in the + // submap bool owners_changed = false; // Create a map from (global) indices in `recv_indices` to a list of // processes that can own them in the submap. std::vector> global_idx_to_possible_owner; const std::array local_range = imap.local_range(); + // Loop through the received indices + std::span dest = imap.dest(); for (std::size_t i = 0; i < recv_disp.size() - 1; ++i) { for (int j = recv_disp[i]; j < recv_disp[i + 1]; ++j) { - // Check if the index is included in the submap by process dest[i]. - // If so, it must be assigned a submap owner - if (std::int64_t idx = recv_indices[j]; idx != -1) + // Compute the local index + std::int64_t idx = recv_indices[j]; + assert(idx >= 0); + std::int32_t idx_local = idx - local_range[0]; + assert(idx_local >= 0); + assert(idx_local < local_range[1]); + + // Check if index is included in the submap on this process. If + // so, this process remains its owner in the submap. Otherwise, + // add the process that sent it to the list of possible owners. + if (is_in_submap[idx_local]) { - // Compute the local index - std::int32_t idx_local = idx - local_range[0]; - assert(idx_local >= 0); - assert(idx_local < local_range[1]); - - // Check if index is included in the submap on this process. If so, - // this process remains its owner in the submap. Otherwise, - // add the process that sent it to the list of possible owners. - if (is_in_submap[idx_local]) - global_idx_to_possible_owner.push_back({idx, rank}); - else - { - owners_changed = true; - global_idx_to_possible_owner.push_back({idx, dest[i]}); - } + global_idx_to_possible_owner.push_back({idx, rank}); + submap_dest.push_back(dest[i]); + } + else + { + owners_changed = true; + global_idx_to_possible_owner.push_back({idx, dest[i]}); } } @@ -246,35 +244,78 @@ compute_submap_indices(const IndexMap& imap, throw std::runtime_error("Index owner change detected!"); } - std::sort(global_idx_to_possible_owner.begin(), - global_idx_to_possible_owner.end()); + std::ranges::sort(global_idx_to_possible_owner); - // Choose the submap owner for each index in `recv_indices` + // Choose the submap owner for each index in `recv_indices` and pack + // destination ranks for each process that has received new indices. + // During ownership determination, we know what other processes + // requires this index, and add them to the destination set. std::vector send_owners; - send_owners.reserve(recv_indices.size()); - for (auto idx : recv_indices) + send_owners.reserve(1); + std::vector new_owner_dest_ranks; + new_owner_dest_ranks.reserve(1); + std::vector new_owner_dest_ranks_offsets(recv_sizes.size() + 1, 0); + std::vector new_owner_dest_ranks_sizes(recv_sizes.size()); + new_owner_dest_ranks_sizes.reserve(1); + for (std::size_t i = 0; i < recv_sizes.size(); ++i) { - // Check if the index is in the submap. If so, choose its owner in the - // submap, otherwise send -1 - if (idx != -1) + for (int j = recv_disp[i]; j < recv_disp[i + 1]; ++j) { + std::int64_t idx = recv_indices[j]; + // NOTE: Could choose new owner in a way that is is better for // load balancing, though the impact is probably only very small - auto it = std::lower_bound(global_idx_to_possible_owner.begin(), - global_idx_to_possible_owner.end(), idx, - [](auto a, auto b) { return a.first < b; }); + auto it = std::ranges::lower_bound(global_idx_to_possible_owner, idx, + std::ranges::less(), + [](auto e) { return e.first; }); assert(it != global_idx_to_possible_owner.end() and it->first == idx); send_owners.push_back(it->second); + + // If rank that sent this ghost is the submap owner, send all + // other ranks + if (it->second == dest[i]) + { + // Find upper limit of recv index and pack all ranks from + // ownership determination (except new owner) as dest ranks + auto it_upper = std::ranges::upper_bound( + it, global_idx_to_possible_owner.end(), idx, std::ranges::less(), + [](auto e) { return e.first; }); + std::transform(std::next(it), it_upper, + std::back_inserter(new_owner_dest_ranks), + [](auto e) { return e.second; }); + } + } + + // Remove duplicate new dest ranks from recv process. The new + // owning process can have taken ownership of multiple indices + // from the same rank. + if (auto dest_begin = std::next(new_owner_dest_ranks.begin(), + new_owner_dest_ranks_offsets[i]); + dest_begin != new_owner_dest_ranks.end()) + { + std::ranges::sort(dest_begin, new_owner_dest_ranks.end()); + auto [unique_end, range_end] + = std::ranges::unique(dest_begin, new_owner_dest_ranks.end()); + new_owner_dest_ranks.erase(unique_end, range_end); + + std::size_t num_unique_dest_ranks + = std::distance(dest_begin, unique_end); + new_owner_dest_ranks_sizes[i] = num_unique_dest_ranks; + new_owner_dest_ranks_offsets[i + 1] + = new_owner_dest_ranks_offsets[i] + num_unique_dest_ranks; } else - send_owners.push_back(-1); + { + new_owner_dest_ranks_sizes[i] = 0; + new_owner_dest_ranks_offsets[i + 1] = new_owner_dest_ranks_offsets[i]; + } } // Create neighbourhood comm (owner -> ghost) MPI_Comm comm1; int ierr = MPI_Dist_graph_create_adjacent( - imap.comm(), src.size(), src.data(), MPI_UNWEIGHTED, dest.size(), - dest.data(), MPI_UNWEIGHTED, MPI_INFO_NULL, false, &comm1); + imap.comm(), imap.src().size(), imap.src().data(), MPI_UNWEIGHTED, + dest.size(), dest.data(), MPI_UNWEIGHTED, MPI_INFO_NULL, false, &comm1); dolfinx::MPI::check_error(imap.comm(), ierr); // Send the data @@ -284,6 +325,36 @@ compute_submap_indices(const IndexMap& imap, comm1); dolfinx::MPI::check_error(imap.comm(), ierr); + // Communicate number of received dest_ranks from each process + std::vector recv_dest_ranks_sizes(imap.src().size()); + recv_dest_ranks_sizes.reserve(1); + ierr = MPI_Neighbor_alltoall(new_owner_dest_ranks_sizes.data(), 1, MPI_INT, + recv_dest_ranks_sizes.data(), 1, MPI_INT, + comm1); + dolfinx::MPI::check_error(imap.comm(), ierr); + + // Communicate new dest ranks + std::vector recv_dest_rank_disp(imap.src().size() + 1, 0); + std::partial_sum(recv_dest_ranks_sizes.begin(), recv_dest_ranks_sizes.end(), + std::next(recv_dest_rank_disp.begin())); + std::vector recv_dest_ranks(recv_dest_rank_disp.back()); + recv_dest_ranks.reserve(1); + ierr = MPI_Neighbor_alltoallv( + new_owner_dest_ranks.data(), new_owner_dest_ranks_sizes.data(), + new_owner_dest_ranks_offsets.data(), MPI_INT, recv_dest_ranks.data(), + recv_dest_ranks_sizes.data(), recv_dest_rank_disp.data(), MPI_INT, + comm1); + dolfinx::MPI::check_error(imap.comm(), ierr); + + // Append new submap dest ranks and remove duplicates + std::ranges::copy(recv_dest_ranks, std::back_inserter(submap_dest)); + std::ranges::sort(submap_dest); + { + auto [unique_end, range_end] = std::ranges::unique(submap_dest); + submap_dest.erase(unique_end, range_end); + submap_dest.shrink_to_fit(); + } + // Free the communicator ierr = MPI_Comm_free(&comm1); dolfinx::MPI::check_error(imap.comm(), ierr); @@ -295,6 +366,7 @@ compute_submap_indices(const IndexMap& imap, // Local indices (w.r.t. original map) owned by this process in the // submap std::vector submap_owned; + submap_owned.reserve(indices.size()); // Local indices (w.r.t. original map) ghosted by this process in the // submap @@ -306,9 +378,9 @@ compute_submap_indices(const IndexMap& imap, { // Add owned indices to submap_owned - for (std::int32_t v : indices) - if (v < imap.size_local()) - submap_owned.push_back(v); + std::copy_if( + indices.begin(), indices.end(), std::back_inserter(submap_owned), + [local_size = imap.size_local()](auto i) { return i < local_size; }); // FIXME: Could just create when making send_indices std::vector send_indices_local(send_indices.size()); @@ -318,30 +390,26 @@ compute_submap_indices(const IndexMap& imap, // submap_owned if the owning process has decided this process to be // the submap owner. Else, add the index and its (possibly new) // owner to submap_ghost and submap_ghost_owners respectively. - for (std::size_t i = 0; i < send_indices.size(); ++i) + for (std::size_t i = 0; i < send_indices_local.size(); ++i) { - // Check if index is in the submap - if (std::int64_t global_idx = send_indices[i]; global_idx >= 0) + std::int32_t local_idx = send_indices_local[i]; + if (int owner = recv_owners[i]; owner == rank) + submap_owned.push_back(local_idx); + else { - std::int32_t local_idx = send_indices_local[i]; - if (int owner = recv_owners[i]; owner == rank) - submap_owned.push_back(local_idx); - else - { - submap_ghost.push_back(local_idx); - submap_ghost_owners.push_back(owner); - } + submap_ghost.push_back(local_idx); + submap_ghost_owners.push_back(owner); } } - std::sort(submap_owned.begin(), submap_owned.end()); + std::ranges::sort(submap_owned); } // Get submap source ranks std::vector submap_src(submap_ghost_owners.begin(), submap_ghost_owners.end()); - std::sort(submap_src.begin(), submap_src.end()); - submap_src.erase(std::unique(submap_src.begin(), submap_src.end()), - submap_src.end()); + std::ranges::sort(submap_src); + auto [unique_end, range_end] = std::ranges::unique(submap_src); + submap_src.erase(unique_end, range_end); submap_src.shrink_to_fit(); // If required, preserve the order of the ghost indices @@ -352,7 +420,7 @@ compute_submap_indices(const IndexMap& imap, pos.reserve(submap_ghost.size()); for (std::int32_t idx : submap_ghost) pos.emplace_back(idx, pos.size()); - std::sort(pos.begin(), pos.end()); + std::ranges::sort(pos); // Order ghosts in the sub-map by their position in the parent map std::vector submap_ghost_owners1; @@ -369,12 +437,6 @@ compute_submap_indices(const IndexMap& imap, submap_ghost = std::move(submap_ghost1); } - // Compute submap destination ranks - // FIXME Remove call to NBX - std::vector submap_dest - = dolfinx::MPI::compute_graph_edges_nbx(imap.comm(), submap_src); - std::sort(submap_dest.begin(), submap_dest.end()); - return {std::move(submap_owned), std::move(submap_ghost), std::move(submap_ghost_owners), std::move(submap_src), std::move(submap_dest)}; @@ -399,7 +461,7 @@ compute_submap_ghost_indices(std::span submap_src, std::span submap_owned, std::span submap_ghosts_global, std::span submap_ghost_owners, - int submap_offset, const IndexMap& imap) + std::int64_t submap_offset, const IndexMap& imap) { // --- Step 1 ---: Send global ghost indices (w.r.t. original imap) to // owning rank @@ -417,25 +479,26 @@ compute_submap_ghost_indices(std::span submap_src, std::vector send_gidx; { send_gidx.reserve(recv_indices.size()); - // NOTE: Received indices are owned by this process in the submap, but not - // necessarily in the original imap, so we must use global_to_local to - // convert rather than subtracting local_range[0] - // TODO Convert recv_indices or submap_owned? - std::vector recv_indices_local(recv_indices.size()); + // NOTE: Received indices are owned by this process in the submap, + // but not necessarily in the original imap, so we must use + // global_to_local to convert rather than subtracting local_range[0] + // TODO: Convert recv_indices or submap_owned? + std::vector recv_indices_local(recv_indices.size()); imap.global_to_local(recv_indices, recv_indices_local); // Compute submap global index - for (auto idx : recv_indices_local) + for (std::int32_t idx : recv_indices_local) { // Could avoid search by creating look-up array - auto it = std::lower_bound(submap_owned.begin(), submap_owned.end(), idx); + auto it = std::ranges::lower_bound(submap_owned, idx); assert(it != submap_owned.end() and *it == idx); std::size_t idx_local_submap = std::distance(submap_owned.begin(), it); send_gidx.push_back(idx_local_submap + submap_offset); } } - // --- Step 3 ---: Send submap global indices to process that ghost them + // --- Step 3 ---: Send submap global indices to process that ghost + // them std::vector recv_gidx(send_disp.back()); { @@ -474,15 +537,14 @@ common::compute_owned_indices(std::span indices, const IndexMap& map) { // Assume that indices are sorted and unique - assert(std::is_sorted(indices.begin(), indices.end())); + assert(std::ranges::is_sorted(indices)); std::span ghosts = map.ghosts(); std::vector owners(map.owners().begin(), map.owners().end()); // Find first index that is not owned by this rank std::int32_t size_local = map.size_local(); - const auto it_owned_end - = std::lower_bound(indices.begin(), indices.end(), size_local); + const auto it_owned_end = std::ranges::lower_bound(indices, size_local); // Get global indices and owners for ghost indices std::size_t first_ghost_index = std::distance(indices.begin(), it_owned_end); @@ -498,8 +560,8 @@ common::compute_owned_indices(std::span indices, } // Sort indices and owners - std::sort(global_indices.begin(), global_indices.end()); - std::sort(ghost_owners.begin(), ghost_owners.end()); + std::ranges::sort(global_indices); + std::ranges::sort(ghost_owners); std::span dest = map.dest(); std::span src = map.src(); @@ -554,26 +616,29 @@ common::compute_owned_indices(std::span indices, dolfinx::MPI::check_error(map.comm(), ierr); // Remove duplicates from received indices - std::sort(recv_buffer.begin(), recv_buffer.end()); - recv_buffer.erase(std::unique(recv_buffer.begin(), recv_buffer.end()), - recv_buffer.end()); + { + std::ranges::sort(recv_buffer); + auto [unique_end, range_end] = std::ranges::unique(recv_buffer); + recv_buffer.erase(unique_end, range_end); + } // Copy owned and ghost indices into return array std::vector owned; owned.reserve(num_ghost_indices + recv_buffer.size()); std::copy(indices.begin(), it_owned_end, std::back_inserter(owned)); - std::transform(recv_buffer.begin(), recv_buffer.end(), - std::back_inserter(owned), - [range = map.local_range()](auto idx) - { - assert(idx >= range[0]); - assert(idx < range[1]); - return idx - range[0]; - }); - - std::sort(owned.begin(), owned.end()); - owned.erase(std::unique(owned.begin(), owned.end()), owned.end()); + std::ranges::transform(recv_buffer, std::back_inserter(owned), + [range = map.local_range()](auto idx) + { + assert(idx >= range[0]); + assert(idx < range[1]); + return idx - range[0]; + }); + { + std::ranges::sort(owned); + auto [unique_end, range_end] = std::ranges::unique(owned); + owned.erase(unique_end, range_end); + } return owned; } //----------------------------------------------------------------------------- @@ -592,9 +657,8 @@ common::stack_index_maps( // Get local offset (into new map) for each index map std::vector local_sizes; - std::transform(maps.begin(), maps.end(), std::back_inserter(local_sizes), - [](auto map) - { return map.second * map.first.get().size_local(); }); + std::ranges::transform(maps, std::back_inserter(local_sizes), [](auto& map) + { return map.second * map.first.get().size_local(); }); std::vector local_offset(local_sizes.size() + 1, 0); std::partial_sum(local_sizes.begin(), local_sizes.end(), std::next(local_offset.begin())); @@ -651,7 +715,7 @@ common::stack_index_maps( std::vector> pos_to_ghost(src.size()); for (std::size_t i = 0; i < ghosts.size(); ++i) { - auto it = std::lower_bound(src.begin(), src.end(), owners[i]); + auto it = std::ranges::lower_bound(src, owners[i]); assert(it != src.end() and *it == owners[i]); int r = std::distance(src.begin(), it); ghost_by_rank[r].push_back(ghosts[i]); @@ -659,9 +723,8 @@ common::stack_index_maps( } // Count number of ghosts per dest - std::transform(ghost_by_rank.begin(), ghost_by_rank.end(), - std::back_inserter(send_sizes), - [](auto& g) { return g.size(); }); + std::ranges::transform(ghost_by_rank, std::back_inserter(send_sizes), + [](auto& g) { return g.size(); }); // Send buffer and ghost position to send buffer position for (auto& g : ghost_by_rank) @@ -831,8 +894,8 @@ IndexMap::IndexMap(MPI_Comm comm, std::int32_t local_size, _dest(src_dest[1]) { assert(ghosts.size() == owners.size()); - assert(std::is_sorted(src_dest[0].begin(), src_dest[0].end())); - assert(std::is_sorted(src_dest[1].begin(), src_dest[1].end())); + assert(std::ranges::is_sorted(src_dest[0])); + assert(std::ranges::is_sorted(src_dest[1])); // Get global offset (index), using partial exclusive reduction std::int64_t offset = 0; @@ -882,8 +945,8 @@ void IndexMap::local_to_global(std::span local, { assert(local.size() <= global.size()); const std::int32_t local_size = _local_range[1] - _local_range[0]; - std::transform( - local.begin(), local.end(), global.begin(), + std::ranges::transform( + local, global.begin(), [local_size, local_range = _local_range[0], &ghosts = _ghosts](auto local) { if (local < local_size) @@ -905,23 +968,24 @@ void IndexMap::global_to_local(std::span global, for (std::size_t i = 0; i < _ghosts.size(); ++i) global_to_local[i] = {_ghosts[i], i + local_size}; - std::sort(global_to_local.begin(), global_to_local.end()); - std::transform(global.begin(), global.end(), local.begin(), - [range = _local_range, - &global_to_local](std::int64_t index) -> std::int32_t - { - if (index >= range[0] and index < range[1]) - return index - range[0]; - else - { - auto it = std::lower_bound( - global_to_local.begin(), global_to_local.end(), index, - [](auto a, auto b) { return a.first < b; }); - return (it != global_to_local.end() and it->first == index) - ? it->second - : -1; - } - }); + std::ranges::sort(global_to_local); + std::ranges::transform( + global, local.begin(), + [range = _local_range, + &global_to_local](std::int64_t index) -> std::int32_t + { + if (index >= range[0] and index < range[1]) + return index - range[0]; + else + { + auto it = std::ranges::lower_bound(global_to_local, index, + std::ranges::less(), + [](auto e) { return e.first; }); + return (it != global_to_local.end() and it->first == index) + ? it->second + : -1; + } + }); } //----------------------------------------------------------------------------- std::vector IndexMap::global_indices() const @@ -932,8 +996,7 @@ std::vector IndexMap::global_indices() const std::vector global(local_size + num_ghosts); std::iota(global.begin(), std::next(global.begin(), local_size), global_offset); - std::copy(_ghosts.cbegin(), _ghosts.cend(), - std::next(global.begin(), local_size)); + std::ranges::copy(_ghosts, std::next(global.begin(), local_size)); return global; } //----------------------------------------------------------------------------- @@ -945,10 +1008,11 @@ graph::AdjacencyList IndexMap::index_to_dest_ranks() const // Build lists of src and dest ranks std::vector src = _owners; - std::sort(src.begin(), src.end()); - src.erase(std::unique(src.begin(), src.end()), src.end()); + std::ranges::sort(src); + auto [unique_end, range_end] = std::ranges::unique(src); + src.erase(unique_end, range_end); auto dest = dolfinx::MPI::compute_graph_edges_nbx(_comm.comm(), src); - std::sort(dest.begin(), dest.end()); + std::ranges::sort(dest); // Array (local idx, ghosting rank) pairs for owned indices std::vector> idx_to_rank; @@ -960,19 +1024,17 @@ graph::AdjacencyList IndexMap::index_to_dest_ranks() const { // Build list of (owner rank, index) pairs for each ghost index, and sort std::vector> owner_to_ghost; - std::transform(_ghosts.begin(), _ghosts.end(), _owners.begin(), - std::back_inserter(owner_to_ghost), - [](auto idx, auto r) -> std::pair - { return {r, idx}; }); - std::sort(owner_to_ghost.begin(), owner_to_ghost.end()); + std::ranges::transform(_ghosts, _owners, std::back_inserter(owner_to_ghost), + [](auto idx, auto r) -> std::pair + { return {r, idx}; }); + std::ranges::sort(owner_to_ghost); // Build send buffer (the second component of each pair in // owner_to_ghost) to send to rank that owns the index std::vector send_buffer; send_buffer.reserve(owner_to_ghost.size()); - std::transform(owner_to_ghost.begin(), owner_to_ghost.end(), - std::back_inserter(send_buffer), - [](auto x) { return x.second; }); + std::ranges::transform(owner_to_ghost, std::back_inserter(send_buffer), + [](auto x) { return x.second; }); // Compute send sizes and displacements std::vector send_sizes, send_disp{0}; @@ -1020,14 +1082,14 @@ graph::AdjacencyList IndexMap::index_to_dest_ranks() const for (std::size_t r = 0; r < recv_disp.size() - 1; ++r) for (int j = recv_disp[r]; j < recv_disp[r + 1]; ++j) idx_to_rank.push_back({recv_buffer[j] - offset, r}); - std::sort(idx_to_rank.begin(), idx_to_rank.end()); + std::ranges::sort(idx_to_rank); // -- Send to ranks that ghost my indices all the sharing ranks // Build adjacency list data for (owned index) -> (ghosting ranks) data.reserve(idx_to_rank.size()); - std::transform(idx_to_rank.begin(), idx_to_rank.end(), - std::back_inserter(data), [](auto x) { return x.second; }); + std::ranges::transform(idx_to_rank, std::back_inserter(data), + [](auto x) { return x.second; }); offsets.reserve(this->size_local() + this->num_ghosts() + 1); { auto it = idx_to_rank.begin(); @@ -1077,9 +1139,8 @@ graph::AdjacencyList IndexMap::index_to_dest_ranks() const } // Count number of ghosts per destination and build send buffer - std::transform(dest_idx_to_rank.begin(), dest_idx_to_rank.end(), - std::back_inserter(send_sizes), - [](auto& x) { return x.size(); }); + std::ranges::transform(dest_idx_to_rank, std::back_inserter(send_sizes), + [](auto& x) { return x.size(); }); for (auto& d : dest_idx_to_rank) send_buffer.insert(send_buffer.end(), d.begin(), d.end()); @@ -1100,8 +1161,8 @@ graph::AdjacencyList IndexMap::index_to_dest_ranks() const dolfinx::MPI::check_error(_comm.comm(), ierr); // Prepare displacement vectors - std::vector send_disp(dest.size() + 1, 0), - recv_disp(src.size() + 1, 0); + std::vector send_disp(dest.size() + 1, 0); + std::vector recv_disp(src.size() + 1, 0); std::partial_sum(send_sizes.begin(), send_sizes.end(), std::next(send_disp.begin())); std::partial_sum(recv_sizes.begin(), recv_sizes.end(), @@ -1121,7 +1182,7 @@ graph::AdjacencyList IndexMap::index_to_dest_ranks() const std::vector> idx_to_pos; for (auto idx : _ghosts) idx_to_pos.push_back({idx, idx_to_pos.size()}); - std::sort(idx_to_pos.begin(), idx_to_pos.end()); + std::ranges::sort(idx_to_pos); // Build list of (local ghost position, sharing rank) pairs from // the received data, and sort @@ -1129,21 +1190,20 @@ graph::AdjacencyList IndexMap::index_to_dest_ranks() const for (std::size_t i = 0; i < recv_indices.size(); i += 2) { std::int64_t idx = recv_indices[i]; - auto it = std::lower_bound( - idx_to_pos.begin(), idx_to_pos.end(), - std::pair{idx, 0}, + auto it = std::ranges::lower_bound( + idx_to_pos, std::pair{idx, 0}, [](auto a, auto b) { return a.first < b.first; }); assert(it != idx_to_pos.end() and it->first == idx); int rank = recv_indices[i + 1]; idxpos_to_rank.push_back({it->second, rank}); } - std::sort(idxpos_to_rank.begin(), idxpos_to_rank.end()); + std::ranges::sort(idxpos_to_rank); // Add processed received data to adjacency list data array, and // extend offset array - std::transform(idxpos_to_rank.begin(), idxpos_to_rank.end(), - std::back_inserter(data), [](auto x) { return x.second; }); + std::ranges::transform(idxpos_to_rank, std::back_inserter(data), + [](auto x) { return x.second; }); auto it = idxpos_to_rank.begin(); for (std::size_t i = 0; i < _ghosts.size(); ++i) { @@ -1157,8 +1217,8 @@ graph::AdjacencyList IndexMap::index_to_dest_ranks() const } // Convert ranks for owned indices from neighbour to global ranks - std::transform(idx_to_rank.begin(), idx_to_rank.end(), data.begin(), - [&dest](auto x) { return dest[x.second]; }); + std::ranges::transform(idx_to_rank, data.begin(), + [&dest](auto x) { return dest[x.second]; }); return graph::AdjacencyList(std::move(data), std::move(offsets)); } @@ -1168,10 +1228,10 @@ std::vector IndexMap::shared_indices() const // Each process gets a chunk of consecutive indices (global indices) // Sorting the ghosts groups them by owner std::vector send_buffer(_ghosts); - std::sort(send_buffer.begin(), send_buffer.end()); + std::ranges::sort(send_buffer); std::vector owners(_owners); - std::sort(owners.begin(), owners.end()); + std::ranges::sort(owners); std::vector send_sizes, send_disp{0}; // Count number of ghost per destination @@ -1218,18 +1278,18 @@ std::vector IndexMap::shared_indices() const std::vector shared; shared.reserve(recv_buffer.size()); - std::transform(recv_buffer.begin(), recv_buffer.end(), - std::back_inserter(shared), - [range = _local_range](auto idx) - { - assert(idx >= range[0]); - assert(idx < range[1]); - return idx - range[0]; - }); + std::ranges::transform(recv_buffer, std::back_inserter(shared), + [range = _local_range](auto idx) + { + assert(idx >= range[0]); + assert(idx < range[1]); + return idx - range[0]; + }); // Sort and remove duplicates - std::sort(shared.begin(), shared.end()); - shared.erase(std::unique(shared.begin(), shared.end()), shared.end()); + std::ranges::sort(shared); + auto [unique_end, range_end] = std::ranges::unique(shared); + shared.erase(unique_end, range_end); return shared; } diff --git a/cpp/dolfinx/common/MPI.cpp b/cpp/dolfinx/common/MPI.cpp index 163d35331cb..d2a90f75819 100644 --- a/cpp/dolfinx/common/MPI.cpp +++ b/cpp/dolfinx/common/MPI.cpp @@ -94,10 +94,10 @@ void dolfinx::MPI::check_error(MPI_Comm comm, int code) std::vector dolfinx::MPI::compute_graph_edges_pcx(MPI_Comm comm, std::span edges) { - LOG(INFO) - << "Computing communication graph edges (using PCX algorithm). Number " - "of input edges: " - << edges.size(); + spdlog::info( + "Computing communication graph edges (using PCX algorithm). Number " + "of input edges: {}", + static_cast(edges.size())); // Build array with '0' for no outedge and '1' for an outedge for each // rank @@ -151,9 +151,9 @@ dolfinx::MPI::compute_graph_edges_pcx(MPI_Comm comm, std::span edges) } } - LOG(INFO) << "Finished graph edge discovery using PCX algorithm. Number " - "of discovered edges " - << other_ranks.size(); + spdlog::info("Finished graph edge discovery using PCX algorithm. Number " + "of discovered edges {}", + static_cast(other_ranks.size())); return other_ranks; } @@ -161,10 +161,10 @@ dolfinx::MPI::compute_graph_edges_pcx(MPI_Comm comm, std::span edges) std::vector dolfinx::MPI::compute_graph_edges_nbx(MPI_Comm comm, std::span edges) { - LOG(INFO) - << "Computing communication graph edges (using NBX algorithm). Number " - "of input edges: " - << edges.size(); + spdlog::info( + "Computing communication graph edges (using NBX algorithm). Number " + "of input edges: {}", + static_cast(edges.size())); // Start non-blocking synchronised send std::vector send_requests(edges.size()); @@ -232,9 +232,9 @@ dolfinx::MPI::compute_graph_edges_nbx(MPI_Comm comm, std::span edges) } } - LOG(INFO) << "Finished graph edge discovery using NBX algorithm. Number " - "of discovered edges " - << other_ranks.size(); + spdlog::info("Finished graph edge discovery using NBX algorithm. Number " + "of discovered edges {}", + static_cast(other_ranks.size())); return other_ranks; } diff --git a/cpp/dolfinx/common/MPI.h b/cpp/dolfinx/common/MPI.h index d54c6a483e7..0c32ba144a3 100644 --- a/cpp/dolfinx/common/MPI.h +++ b/cpp/dolfinx/common/MPI.h @@ -6,14 +6,14 @@ #pragma once +#include "Timer.h" +#include "log.h" +#include "types.h" #include #include #include #include #include -#include -#include -#include #include #include #include @@ -319,7 +319,7 @@ distribute_to_postoffice(MPI_Comm comm, const U& x, assert(x.size() % shape[1] == 0); const std::int32_t shape0_local = x.size() / shape[1]; - LOG(2) << "Sending data to post offices (distribute_to_postoffice)"; + spdlog::debug("Sending data to post offices (distribute_to_postoffice)"); // Post office ranks will receive data from this rank std::vector row_to_dest(shape0_local); @@ -339,7 +339,7 @@ distribute_to_postoffice(MPI_Comm comm, const U& x, if (int dest = MPI::index_owner(size, idx, shape[0]); dest != rank) dest_to_index.push_back({dest, i}); } - std::sort(dest_to_index.begin(), dest_to_index.end()); + std::ranges::sort(dest_to_index); // Build list of neighbour src ranks and count number of items (rows // of x) to receive from each src post office (by neighbourhood rank) @@ -374,9 +374,9 @@ distribute_to_postoffice(MPI_Comm comm, const U& x, // Determine source ranks const std::vector src = MPI::compute_graph_edges_nbx(comm, dest); - LOG(INFO) - << "Number of neighbourhood source ranks in distribute_to_postoffice: " - << src.size(); + spdlog::info( + "Number of neighbourhood source ranks in distribute_to_postoffice: {}", + static_cast(src.size())); // Create neighbourhood communicator for sending data to post offices MPI_Comm neigh_comm; @@ -445,13 +445,13 @@ distribute_to_postoffice(MPI_Comm comm, const U& x, err = MPI_Comm_free(&neigh_comm); dolfinx::MPI::check_error(comm, err); - LOG(2) << "Completed send data to post offices."; + spdlog::debug("Completed send data to post offices."); // Convert to local indices const std::int64_t r0 = MPI::local_range(rank, shape[0], size)[0]; std::vector index_local(recv_buffer_index.size()); - std::transform(recv_buffer_index.cbegin(), recv_buffer_index.cend(), - index_local.begin(), [r0](auto idx) { return idx - r0; }); + std::ranges::transform(recv_buffer_index, index_local.begin(), + [r0](auto idx) { return idx - r0; }); return {index_local, recv_buffer_data}; } @@ -492,7 +492,7 @@ distribute_from_postoffice(MPI_Comm comm, std::span indices, if (int src = dolfinx::MPI::index_owner(size, idx, shape[0]); src != rank) src_to_index.push_back({src, idx, i}); } - std::sort(src_to_index.begin(), src_to_index.end()); + std::ranges::sort(src_to_index); // Build list is neighbour src ranks and count number of items (rows // of x) to receive from each src post office (by neighbourhood rank) @@ -515,10 +515,11 @@ distribute_from_postoffice(MPI_Comm comm, std::span indices, // me) const std::vector dest = dolfinx::MPI::compute_graph_edges_nbx(comm, src); - LOG(INFO) << "Neighbourhood destination ranks from post office in " - "distribute_data (rank, num dests, num dests/mpi_size): " - << rank << ", " << dest.size() << ", " - << static_cast(dest.size()) / size; + spdlog::info( + "Neighbourhood destination ranks from post office in " + "distribute_data (rank, num dests, num dests/mpi_size): {}, {}, {}", + rank, static_cast(dest.size()), + static_cast(dest.size()) / size); // Create neighbourhood communicator for sending data to post offices // (src), and receiving data form my send my post office @@ -548,9 +549,8 @@ distribute_from_postoffice(MPI_Comm comm, std::span indices, // post offices assert(send_disp.back() == (int)src_to_index.size()); std::vector send_buffer_index(src_to_index.size()); - std::transform(src_to_index.cbegin(), src_to_index.cend(), - send_buffer_index.begin(), - [](auto& x) { return std::get<1>(x); }); + std::ranges::transform(src_to_index, send_buffer_index.begin(), + [](auto x) { return std::get<1>(x); }); // Prepare the receive buffer std::vector recv_buffer_index(recv_disp.back()); diff --git a/cpp/dolfinx/common/Scatterer.h b/cpp/dolfinx/common/Scatterer.h index 4ba77b1d584..f3d297388bc 100644 --- a/cpp/dolfinx/common/Scatterer.h +++ b/cpp/dolfinx/common/Scatterer.h @@ -57,8 +57,8 @@ class Scatterer return; // Check that src and dest ranks are unique and sorted - assert(std::is_sorted(_src.begin(), _src.end())); - assert(std::is_sorted(_dest.begin(), _dest.end())); + assert(std::ranges::is_sorted(_src)); + assert(std::ranges::is_sorted(_dest)); // Create communicators with directed edges: // (0) owner -> ghost, @@ -79,17 +79,17 @@ class Scatterer std::span owners = map.owners(); std::vector perm(owners.size()); std::iota(perm.begin(), perm.end(), 0); - dolfinx::argsort_radix(owners, perm); + dolfinx::radix_sort(perm, [&owners](auto index) { return owners[index]; }); // Sort (i) ghost indices and (ii) ghost index owners by rank // (using perm array) std::span ghosts = map.ghosts(); std::vector owners_sorted(owners.size()); std::vector ghosts_sorted(owners.size()); - std::transform(perm.begin(), perm.end(), owners_sorted.begin(), - [&owners](auto idx) { return owners[idx]; }); - std::transform(perm.begin(), perm.end(), ghosts_sorted.begin(), - [&ghosts](auto idx) { return ghosts[idx]; }); + std::ranges::transform(perm, owners_sorted.begin(), + [&owners](auto idx) { return owners[idx]; }); + std::ranges::transform(perm, ghosts_sorted.begin(), + [&ghosts](auto idx) { return ghosts[idx]; }); // For data associated with ghost indices, packed by owning // (neighbourhood) rank, compute sizes and displacements. I.e., @@ -139,16 +139,14 @@ class Scatterer const std::array range = map.local_range(); #ifndef NDEBUG // Check that all received indice are within the owned range - std::for_each(recv_buffer.begin(), recv_buffer.end(), [range](auto idx) - { assert(idx >= range[0] and idx < range[1]); }); + std::ranges::for_each(recv_buffer, [range](auto idx) + { assert(idx >= range[0] and idx < range[1]); }); #endif // Scale sizes and displacements by block size { - auto rescale = [](auto& x, int bs) - { - std::transform(x.begin(), x.end(), x.begin(), - [bs](auto e) { return e *= bs; }); + auto rescale = [](auto& x, int bs) { + std::ranges::transform(x, x.begin(), [bs](auto e) { return e *= bs; }); }; rescale(_sizes_local, bs); rescale(_displs_local, bs); @@ -525,7 +523,7 @@ class Scatterer assert(local_buffer.size() == _local_inds.size()); if (!_local_inds.empty()) { - assert(*std::max_element(_local_inds.begin(), _local_inds.end()) + assert(*std::ranges::max_element(_local_inds) < std::int32_t(local_data.size())); } scatter_rev_end(request); diff --git a/cpp/dolfinx/common/Table.cpp b/cpp/dolfinx/common/Table.cpp index dbaf7f5e70c..814cc43d14e 100644 --- a/cpp/dolfinx/common/Table.cpp +++ b/cpp/dolfinx/common/Table.cpp @@ -5,8 +5,8 @@ // SPDX-License-Identifier: LGPL-3.0-or-later #include "Table.h" +#include "MPI.h" #include -#include #include #include #include diff --git a/cpp/dolfinx/common/Table.h b/cpp/dolfinx/common/Table.h index 67bde23f3df..4e39d728811 100644 --- a/cpp/dolfinx/common/Table.h +++ b/cpp/dolfinx/common/Table.h @@ -15,7 +15,8 @@ namespace dolfinx { -/// This class provides storage and pretty-printing for tables. +/// @brief This class provides storage and pretty-printing for tables. +/// /// Example usage: /// /// Table table("Timings"); @@ -23,7 +24,6 @@ namespace dolfinx /// table.set("Foo", "Solve", 0.020); /// table.set("Bar", "Assemble", 0.011); /// table.set("Bar", "Solve", 0.019); - class Table { public: diff --git a/cpp/dolfinx/common/TimeLogger.cpp b/cpp/dolfinx/common/TimeLogger.cpp index 6e0d48fa8fd..dfc60cf6177 100644 --- a/cpp/dolfinx/common/TimeLogger.cpp +++ b/cpp/dolfinx/common/TimeLogger.cpp @@ -5,8 +5,8 @@ // SPDX-License-Identifier: LGPL-3.0-or-later #include "TimeLogger.h" -#include -#include +#include "MPI.h" +#include "log.h" #include #include @@ -25,7 +25,7 @@ void TimeLogger::register_timing(std::string task, double wall, double user, std::string line = "Elapsed wall, usr, sys time: " + std::to_string(wall) + ", " + std::to_string(user) + ", " + std::to_string(system) + " (" + task + ")"; - DLOG(INFO) << line; + spdlog::debug(line.c_str()); // Store values for summary if (auto it = _timings.find(task); it != _timings.end()) diff --git a/cpp/dolfinx/common/TimeLogger.h b/cpp/dolfinx/common/TimeLogger.h index bd0981918aa..ef264d56822 100644 --- a/cpp/dolfinx/common/TimeLogger.h +++ b/cpp/dolfinx/common/TimeLogger.h @@ -6,8 +6,8 @@ #pragma once -#include -#include +#include "Table.h" +#include "timing.h" #include #include #include diff --git a/cpp/dolfinx/common/Timer.cpp b/cpp/dolfinx/common/Timer.cpp index 3b2e9d53c4e..7b3db92c94e 100644 --- a/cpp/dolfinx/common/Timer.cpp +++ b/cpp/dolfinx/common/Timer.cpp @@ -7,21 +7,14 @@ #include "Timer.h" #include "TimeLogManager.h" #include "TimeLogger.h" +#include #include using namespace dolfinx; using namespace dolfinx::common; //----------------------------------------------------------------------------- -Timer::Timer() : Timer::Timer("") -{ - // Do nothing -} -//----------------------------------------------------------------------------- -Timer::Timer(const std::string& task) : _task(task) -{ - // Do nothing -} +Timer::Timer(std::optional task) : _task(task) {} //----------------------------------------------------------------------------- Timer::~Timer() { @@ -33,7 +26,7 @@ void Timer::start() { _timer.start(); } //----------------------------------------------------------------------------- void Timer::resume() { - if (!_task.empty()) + if (_task.has_value()) { throw std::runtime_error( "Resuming is not well-defined for logging timer. Only " @@ -46,8 +39,8 @@ double Timer::stop() { _timer.stop(); const auto [wall, user, system] = this->elapsed(); - if (!_task.empty()) - TimeLogManager::logger().register_timing(_task, wall, user, system); + if (_task.has_value()) + TimeLogManager::logger().register_timing(_task.value(), wall, user, system); return wall; } //----------------------------------------------------------------------------- diff --git a/cpp/dolfinx/common/Timer.h b/cpp/dolfinx/common/Timer.h index db1d44d690c..8b966f2b8cb 100644 --- a/cpp/dolfinx/common/Timer.h +++ b/cpp/dolfinx/common/Timer.h @@ -8,6 +8,7 @@ #include #include +#include #include namespace dolfinx::common @@ -30,11 +31,11 @@ namespace dolfinx::common class Timer { public: - /// Create timer without logging - Timer(); - - /// Create timer with logging - Timer(const std::string& task); + /// Create timer + /// + /// If a task name is provided this enables logging to logger, otherwise (i.e. + /// no task provided) nothing gets logged. + Timer(std::optional task = std::nullopt); /// Destructor ~Timer(); @@ -54,7 +55,7 @@ class Timer private: // Name of task - std::string _task; + std::optional _task; // Implementation of timer boost::timer::cpu_timer _timer; diff --git a/cpp/dolfinx/common/defines.cpp b/cpp/dolfinx/common/defines.cpp index e58b167f808..8cb19caecd6 100644 --- a/cpp/dolfinx/common/defines.cpp +++ b/cpp/dolfinx/common/defines.cpp @@ -15,58 +15,4 @@ std::string dolfinx::git_commit_hash() { return std::string(DOLFINX_GIT_COMMIT_HASH); } -//------------------------------------------------------------------------- -bool dolfinx::has_debug() -{ -#ifndef NDEBUG - return true; -#else - return false; -#endif -} -//------------------------------------------------------------------------- -bool dolfinx::has_petsc() -{ -#ifdef HAS_PETSC - return true; -#else - return false; -#endif -} -//------------------------------------------------------------------------- -bool dolfinx::has_slepc() -{ -#ifdef HAS_SLEPC - return true; -#else - return false; -#endif -} -//------------------------------------------------------------------------- -bool dolfinx::has_parmetis() -{ -#ifdef HAS_PARMETIS - return true; -#else - return false; -#endif -} -//------------------------------------------------------------------------- -bool dolfinx::has_kahip() -{ -#ifdef HAS_KAHIP - return true; -#else - return false; -#endif -} -//------------------------------------------------------------------------- -bool dolfinx::has_adios2() -{ -#ifdef HAS_ADIOS2 - return true; -#else - return false; -#endif -} -//------------------------------------------------------------------------- +//------------------------------------------------------------------------- \ No newline at end of file diff --git a/cpp/dolfinx/common/defines.h b/cpp/dolfinx/common/defines.h index 76005cf5c4a..11a8bac754e 100644 --- a/cpp/dolfinx/common/defines.h +++ b/cpp/dolfinx/common/defines.h @@ -23,24 +23,86 @@ std::string git_commit_hash(); /// Return true if DOLFINx is compiled in debugging mode, /// i.e., with assertions on -bool has_debug(); +consteval bool has_debug() +{ +#ifndef NDEBUG + return true; +#else + return false; +#endif +} /// Return true if DOLFINx is compiled with PETSc -bool has_petsc(); +consteval bool has_petsc() +{ +#ifdef HAS_PETSC + return true; +#else + return false; +#endif +} /// Return true if DOLFINx is compiled with SLEPc -bool has_slepc(); - -/// Return true if DOLFINx is compiled with Scotch -bool has_scotch(); +consteval bool has_slepc() +{ +#ifdef HAS_SLEPC + return true; +#else + return false; +#endif +} /// Return true if DOLFINx is compiled with ParMETIS -bool has_parmetis(); +consteval bool has_parmetis() +{ +#ifdef HAS_PARMETIS + return true; +#else + return false; +#endif +} /// Return true if DOLFINx is compiled with KaHIP -bool has_kahip(); +consteval bool has_kahip() +{ +#ifdef HAS_KAHIP + return true; +#else + return false; +#endif +} /// Return true if DOLFINX is compiled with ADIOS2 -bool has_adios2(); +consteval bool has_adios2() +{ +#ifdef HAS_ADIOS2 + return true; +#else + return false; +#endif +} + +/// Return true if DOLFINX is compiled with PT-SCOTCH +consteval bool has_ptscotch() +{ +#ifdef HAS_PTSCOTCH + return true; +#else + return false; +#endif +} + +/// Return true if DOLFINx supports UFCx kernels with arguments of type C99 +/// _Complex. When DOLFINx was built with MSVC this returns false. This +/// returning false does not preclude using DOLFINx with kernels accepting +/// std::complex. +consteval bool has_complex_ufcx_kernels() +{ +#ifdef DOLFINX_NO_STDC_COMPLEX_KERNELS + return false; +#else + return true; +#endif +} } // namespace dolfinx diff --git a/cpp/dolfinx/common/log.cpp b/cpp/dolfinx/common/log.cpp index 58e2623a24d..6f38401c00b 100644 --- a/cpp/dolfinx/common/log.cpp +++ b/cpp/dolfinx/common/log.cpp @@ -5,26 +5,14 @@ // SPDX-License-Identifier: LGPL-3.0-or-later #include "log.h" -#include "loguru.cpp" +#include #include //----------------------------------------------------------------------------- void dolfinx::init_logging(int argc, char* argv[]) { - loguru::g_stderr_verbosity = loguru::Verbosity_WARNING; - -#ifdef NDEBUG - loguru::SignalOptions signals = loguru::SignalOptions::none(); -#else - loguru::SignalOptions signals; -#endif - - loguru::Options options = {"-dolfinx_loglevel", "main", signals}; - - // Make a copy of argv, as loguru may modify it - std::vector argv_copy(argv, argv + argc); - argv_copy.push_back(nullptr); - - loguru::init(argc, argv_copy.data(), options); + // Initialise to level::warn, can be overridden later + spdlog::set_level(spdlog::level::warn); + spdlog::cfg::load_argv_levels(argc, argv); } //----------------------------------------------------------------------------- diff --git a/cpp/dolfinx/common/log.h b/cpp/dolfinx/common/log.h index d5604862406..1cecaf90a55 100644 --- a/cpp/dolfinx/common/log.h +++ b/cpp/dolfinx/common/log.h @@ -6,10 +6,7 @@ #pragma once -#define LOGURU_WITH_STREAMS 1 -#define LOGURU_REPLACE_GLOG 1 - -#include "loguru.hpp" +#include namespace dolfinx { @@ -17,16 +14,12 @@ namespace dolfinx /// @brief Optional initialisation of the logging backend. /// /// The log verbosity can be controlled from the command line using -/// `-dolfinx_loglevel `, where `` is an integer. -/// Increasing values increase verbosity. +/// `SPDLOG_LEVEL=`, where `` is info, warn, debug, etc. /// -/// The full `loguru` API can be used in applications to control the log -/// system. See https://emilk.github.io/loguru/ for the loguru +/// The full `spdlog` API can be used in applications to control the log +/// system. See https://github.com/gabime/spdlog for the spdlog /// documentation. /// -/// @note The logging backend is loguru -/// (https://github.com/emilk/loguru). -/// /// @param[in] argc Number of command line arguments. /// @param[in] argv Command line argument vector. void init_logging(int argc, char* argv[]); diff --git a/cpp/dolfinx/common/loguru.cpp b/cpp/dolfinx/common/loguru.cpp deleted file mode 100644 index 71261f3a4ac..00000000000 --- a/cpp/dolfinx/common/loguru.cpp +++ /dev/null @@ -1,1961 +0,0 @@ -#ifndef _WIN32 -// Disable all warnings from gcc/clang: -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wpragmas" - -#pragma GCC diagnostic ignored "-Wc++98-compat" -#pragma GCC diagnostic ignored "-Wc++98-compat-pedantic" -#pragma GCC diagnostic ignored "-Wexit-time-destructors" -#pragma GCC diagnostic ignored "-Wformat-nonliteral" -#pragma GCC diagnostic ignored "-Wglobal-constructors" -#pragma GCC diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" -#pragma GCC diagnostic ignored "-Wmissing-prototypes" -#pragma GCC diagnostic ignored "-Wpadded" -#pragma GCC diagnostic ignored "-Wsign-compare" -#pragma GCC diagnostic ignored "-Wsign-conversion" -#pragma GCC diagnostic ignored "-Wunknown-pragmas" -#pragma GCC diagnostic ignored "-Wunused-macros" -#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant" -#else -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable:4018) -#endif // _MSC_VER -#endif - -#include "loguru.hpp" - -#ifndef LOGURU_HAS_BEEN_IMPLEMENTED -#define LOGURU_HAS_BEEN_IMPLEMENTED - -#define LOGURU_PREAMBLE_WIDTH (53 + LOGURU_THREADNAME_WIDTH + LOGURU_FILENAME_WIDTH) - -#undef min -#undef max - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if LOGURU_SYSLOG -#include -#else -#define LOG_USER 0 -#endif - -#ifdef _WIN32 - #include - - #define localtime_r(a, b) localtime_s(b, a) // No localtime_r with MSVC, but arguments are swapped for localtime_s -#else - #include - #include // mkdir - #include // STDERR_FILENO -#endif - -#ifdef __linux__ - #include // PATH_MAX -#elif !defined(_WIN32) - #include // PATH_MAX -#endif - -#ifndef PATH_MAX - #define PATH_MAX 1024 -#endif - -#ifdef __APPLE__ - #include "TargetConditionals.h" -#endif - -// TODO: use defined(_POSIX_VERSION) for some of these things? - -#if defined(_WIN32) || defined(__CYGWIN__) - #define LOGURU_PTHREADS 0 - #define LOGURU_WINTHREADS 1 - #ifndef LOGURU_STACKTRACES - #define LOGURU_STACKTRACES 0 - #endif -#elif defined(__rtems__) || defined(__ANDROID__) || defined(__FreeBSD__) - #define LOGURU_PTHREADS 1 - #define LOGURU_WINTHREADS 0 - #ifndef LOGURU_STACKTRACES - #define LOGURU_STACKTRACES 0 - #endif -#else - #define LOGURU_PTHREADS 1 - #define LOGURU_WINTHREADS 0 - #ifndef LOGURU_STACKTRACES - #define LOGURU_STACKTRACES 1 - #endif -#endif - -#if LOGURU_STACKTRACES - #include // for __cxa_demangle - #include // for dladdr - #include // for backtrace -#endif // LOGURU_STACKTRACES - -#if LOGURU_PTHREADS - #include - #if defined(__FreeBSD__) - #include - #include - #elif defined(__OpenBSD__) - #include - #endif - - #ifdef __linux__ - /* On Linux, the default thread name is the same as the name of the binary. - Additionally, all new threads inherit the name of the thread it got forked from. - For this reason, Loguru use the pthread Thread Local Storage - for storing thread names on Linux. */ - #ifndef LOGURU_PTLS_NAMES - #define LOGURU_PTLS_NAMES 1 - #endif - #endif -#endif - -#if LOGURU_WINTHREADS - #ifndef _WIN32_WINNT - #define _WIN32_WINNT 0x0502 - #endif - #define WIN32_LEAN_AND_MEAN - #define NOMINMAX - #include -#endif - -#ifndef LOGURU_PTLS_NAMES - #define LOGURU_PTLS_NAMES 0 -#endif - - -namespace loguru -{ - using namespace std::chrono; - -#if LOGURU_WITH_FILEABS - struct FileAbs - { - char path[PATH_MAX]; - char mode_str[4]; - Verbosity verbosity; - struct stat st; - FILE* fp; - bool is_reopening = false; // to prevent recursive call in file_reopen. - decltype(steady_clock::now()) last_check_time = steady_clock::now(); - }; -#else - typedef FILE* FileAbs; -#endif - - struct Callback - { - std::string id; - log_handler_t callback; - void* user_data; - Verbosity verbosity; // Does not change! - close_handler_t close; - flush_handler_t flush; - unsigned indentation; - }; - - using CallbackVec = std::vector; - - using StringPair = std::pair; - using StringPairList = std::vector; - - const auto s_start_time = steady_clock::now(); - - Verbosity g_stderr_verbosity = Verbosity_0; - bool g_colorlogtostderr = true; - unsigned g_flush_interval_ms = 0; - bool g_preamble_header = true; - bool g_preamble = true; - - Verbosity g_internal_verbosity = Verbosity_0; - - // Preamble details - bool g_preamble_date = true; - bool g_preamble_time = true; - bool g_preamble_uptime = true; - bool g_preamble_thread = true; - bool g_preamble_file = true; - bool g_preamble_verbose = true; - bool g_preamble_pipe = true; - - static std::recursive_mutex s_mutex; - static Verbosity s_max_out_verbosity = Verbosity_OFF; - static std::string s_argv0_filename; - static std::string s_arguments; - static char s_current_dir[PATH_MAX]; - static CallbackVec s_callbacks; - static fatal_handler_t s_fatal_handler = nullptr; - static verbosity_to_name_t s_verbosity_to_name_callback = nullptr; - static name_to_verbosity_t s_name_to_verbosity_callback = nullptr; - static StringPairList s_user_stack_cleanups; - static bool s_strip_file_path = true; - static std::atomic s_stderr_indentation { 0 }; - - // For periodic flushing: - static std::thread* s_flush_thread = nullptr; - static bool s_needs_flushing = false; - - static SignalOptions s_signal_options = SignalOptions::none(); - - static const bool s_terminal_has_color = [](){ - #ifdef _WIN32 - #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING - #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 - #endif - - HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); - if (hOut != INVALID_HANDLE_VALUE) { - DWORD dwMode = 0; - GetConsoleMode(hOut, &dwMode); - dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; - return SetConsoleMode(hOut, dwMode) != 0; - } - return false; - #else - if (!isatty(STDERR_FILENO)) { - return false; - } - if (const char* term = getenv("TERM")) { - return 0 == strcmp(term, "cygwin") - || 0 == strcmp(term, "linux") - || 0 == strcmp(term, "rxvt-unicode-256color") - || 0 == strcmp(term, "screen") - || 0 == strcmp(term, "screen-256color") - || 0 == strcmp(term, "screen.xterm-256color") - || 0 == strcmp(term, "tmux-256color") - || 0 == strcmp(term, "xterm") - || 0 == strcmp(term, "xterm-256color") - || 0 == strcmp(term, "xterm-termite") - || 0 == strcmp(term, "xterm-color"); - } else { - return false; - } - #endif - }(); - - static void print_preamble_header(char* out_buff, size_t out_buff_size); - - // ------------------------------------------------------------------------------ - // Colors - - bool terminal_has_color() { return s_terminal_has_color; } - - // Colors - -#ifdef _WIN32 -#define VTSEQ(ID) ("\x1b[1;" #ID "m") -#else -#define VTSEQ(ID) ("\x1b[" #ID "m") -#endif - - const char* terminal_black() { return s_terminal_has_color ? VTSEQ(30) : ""; } - const char* terminal_red() { return s_terminal_has_color ? VTSEQ(31) : ""; } - const char* terminal_green() { return s_terminal_has_color ? VTSEQ(32) : ""; } - const char* terminal_yellow() { return s_terminal_has_color ? VTSEQ(33) : ""; } - const char* terminal_blue() { return s_terminal_has_color ? VTSEQ(34) : ""; } - const char* terminal_purple() { return s_terminal_has_color ? VTSEQ(35) : ""; } - const char* terminal_cyan() { return s_terminal_has_color ? VTSEQ(36) : ""; } - const char* terminal_light_gray() { return s_terminal_has_color ? VTSEQ(37) : ""; } - const char* terminal_white() { return s_terminal_has_color ? VTSEQ(37) : ""; } - const char* terminal_light_red() { return s_terminal_has_color ? VTSEQ(91) : ""; } - const char* terminal_dim() { return s_terminal_has_color ? VTSEQ(2) : ""; } - - // Formatting - const char* terminal_bold() { return s_terminal_has_color ? VTSEQ(1) : ""; } - const char* terminal_underline() { return s_terminal_has_color ? VTSEQ(4) : ""; } - - // You should end each line with this! - const char* terminal_reset() { return s_terminal_has_color ? VTSEQ(0) : ""; } - - // ------------------------------------------------------------------------------ -#if LOGURU_WITH_FILEABS - void file_reopen(void* user_data); - inline FILE* to_file(void* user_data) { return reinterpret_cast(user_data)->fp; } -#else - inline FILE* to_file(void* user_data) { return reinterpret_cast(user_data); } -#endif - - void file_log(void* user_data, const Message& message) - { -#if LOGURU_WITH_FILEABS - FileAbs* file_abs = reinterpret_cast(user_data); - if (file_abs->is_reopening) { - return; - } - // It is better checking file change every minute/hour/day, - // instead of doing this every time we log. - // Here check_interval is set to zero to enable checking every time; - const auto check_interval = seconds(0); - if (duration_cast(steady_clock::now() - file_abs->last_check_time) > check_interval) { - file_abs->last_check_time = steady_clock::now(); - file_reopen(user_data); - } - FILE* file = to_file(user_data); - if (!file) { - return; - } -#else - FILE* file = to_file(user_data); -#endif - fprintf(file, "%s%s%s%s\n", - message.preamble, message.indentation, message.prefix, message.message); - if (g_flush_interval_ms == 0) { - fflush(file); - } - } - - void file_close(void* user_data) - { - FILE* file = to_file(user_data); - if (file) { - fclose(file); - } -#if LOGURU_WITH_FILEABS - delete reinterpret_cast(user_data); -#endif - } - - void file_flush(void* user_data) - { - FILE* file = to_file(user_data); - fflush(file); - } - -#if LOGURU_WITH_FILEABS - void file_reopen(void* user_data) - { - FileAbs * file_abs = reinterpret_cast(user_data); - struct stat st; - int ret; - if (!file_abs->fp || (ret = stat(file_abs->path, &st)) == -1 || (st.st_ino != file_abs->st.st_ino)) { - file_abs->is_reopening = true; - if (file_abs->fp) { - fclose(file_abs->fp); - } - if (!file_abs->fp) { - VLOG_F(g_internal_verbosity, "Reopening file '" LOGURU_FMT(s) "' due to previous error", file_abs->path); - } - else if (ret < 0) { - const auto why = errno_as_text(); - VLOG_F(g_internal_verbosity, "Reopening file '" LOGURU_FMT(s) "' due to '" LOGURU_FMT(s) "'", file_abs->path, why.c_str()); - } else { - VLOG_F(g_internal_verbosity, "Reopening file '" LOGURU_FMT(s) "' due to file changed", file_abs->path); - } - // try reopen current file. - if (!create_directories(file_abs->path)) { - LOG_F(ERROR, "Failed to create directories to '" LOGURU_FMT(s) "'", file_abs->path); - } - file_abs->fp = fopen(file_abs->path, file_abs->mode_str); - if (!file_abs->fp) { - LOG_F(ERROR, "Failed to open '" LOGURU_FMT(s) "'", file_abs->path); - } else { - stat(file_abs->path, &file_abs->st); - } - file_abs->is_reopening = false; - } - } -#endif - // ------------------------------------------------------------------------------ - // ------------------------------------------------------------------------------ -#if LOGURU_SYSLOG - void syslog_log(void* /*user_data*/, const Message& message) - { - /* - Level 0: Is reserved for kernel panic type situations. - Level 1: Is for Major resource failure. - Level 2->7 Application level failures - */ - int level; - if (message.verbosity < Verbosity_FATAL) { - level = 1; // System Alert - } else { - switch(message.verbosity) { - case Verbosity_FATAL: level = 2; break; // System Critical - case Verbosity_ERROR: level = 3; break; // System Error - case Verbosity_WARNING: level = 4; break; // System Warning - case Verbosity_INFO: level = 5; break; // System Notice - case Verbosity_1: level = 6; break; // System Info - default: level = 7; break; // System Debug - } - } - - // Note: We don't add the time info. - // This is done automatically by the syslog daemon. - // Otherwise log all information that the file log does. - syslog(level, "%s%s%s", message.indentation, message.prefix, message.message); - } - - void syslog_close(void* /*user_data*/) - { - closelog(); - } - - void syslog_flush(void* /*user_data*/) - {} -#endif -// ------------------------------------------------------------------------------ - // Helpers: - - Text::~Text() { free(_str); } - -#if LOGURU_USE_FMTLIB - Text vtextprintf(const char* format, fmt::format_args args) - { - return Text(STRDUP(fmt::vformat(format, args).c_str())); - } -#else - LOGURU_PRINTF_LIKE(1, 0) - static Text vtextprintf(const char* format, va_list vlist) - { -#ifdef _WIN32 - int bytes_needed = _vscprintf(format, vlist); - CHECK_F(bytes_needed >= 0, "Bad string format: '%s'", format); - char* buff = (char*)malloc(bytes_needed+1); - vsnprintf(buff, bytes_needed+1, format, vlist); - return Text(buff); -#else - char* buff = nullptr; - int result = vasprintf(&buff, format, vlist); - CHECK_F(result >= 0, "Bad string format: '" LOGURU_FMT(s) "'", format); - return Text(buff); -#endif - } - - Text textprintf(const char* format, ...) - { - va_list vlist; - va_start(vlist, format); - auto result = vtextprintf(format, vlist); - va_end(vlist); - return result; - } -#endif - - // Overloaded for variadic template matching. - Text textprintf() - { - return Text(static_cast(calloc(1, 1))); - } - - static const char* indentation(unsigned depth) - { - static const char buff[] = - ". . . . . . . . . . " ". . . . . . . . . . " - ". . . . . . . . . . " ". . . . . . . . . . " - ". . . . . . . . . . " ". . . . . . . . . . " - ". . . . . . . . . . " ". . . . . . . . . . " - ". . . . . . . . . . " ". . . . . . . . . . "; - static const size_t INDENTATION_WIDTH = 4; - static const size_t NUM_INDENTATIONS = (sizeof(buff) - 1) / INDENTATION_WIDTH; - depth = std::min(depth, NUM_INDENTATIONS); - return buff + INDENTATION_WIDTH * (NUM_INDENTATIONS - depth); - } - - static void parse_args(int& argc, char* argv[], const char* verbosity_flag) - { - int arg_dest = 1; - int out_argc = argc; - - for (int arg_it = 1; arg_it < argc; ++arg_it) { - auto cmd = argv[arg_it]; - auto arg_len = strlen(verbosity_flag); - if (strncmp(cmd, verbosity_flag, arg_len) == 0 && !std::isalpha(cmd[arg_len], std::locale(""))) { - out_argc -= 1; - auto value_str = cmd + arg_len; - if (value_str[0] == '\0') { - // Value in separate argument - arg_it += 1; - CHECK_LT_F(arg_it, argc, "Missing verbosiy level after " LOGURU_FMT(s) "", verbosity_flag); - value_str = argv[arg_it]; - out_argc -= 1; - } - if (*value_str == '=') { value_str += 1; } - - auto req_verbosity = get_verbosity_from_name(value_str); - if (req_verbosity != Verbosity_INVALID) { - g_stderr_verbosity = req_verbosity; - } else { - char* end = 0; - g_stderr_verbosity = static_cast(strtol(value_str, &end, 10)); - CHECK_F(end && *end == '\0', - "Invalid verbosity. Expected integer, INFO, WARNING, ERROR or OFF, got '" LOGURU_FMT(s) "'", value_str); - } - } else { - argv[arg_dest++] = argv[arg_it]; - } - } - - argc = out_argc; - argv[argc] = nullptr; - } - - static long long now_ns() - { - return duration_cast(high_resolution_clock::now().time_since_epoch()).count(); - } - - // Returns the part of the path after the last / or \ (if any). - const char* filename(const char* path) - { - for (auto ptr = path; *ptr; ++ptr) { - if (*ptr == '/' || *ptr == '\\') { - path = ptr + 1; - } - } - return path; - } - - // ------------------------------------------------------------------------------ - - static void on_atexit() - { - VLOG_F(g_internal_verbosity, "atexit"); - flush(); - } - - static void install_signal_handlers(const SignalOptions& signal_options); - - static void write_hex_digit(std::string& out, unsigned num) - { - DCHECK_LT_F(num, 16u); - if (num < 10u) { out.push_back(char('0' + num)); } - else { out.push_back(char('A' + num - 10)); } - } - - static void write_hex_byte(std::string& out, uint8_t n) - { - write_hex_digit(out, n >> 4u); - write_hex_digit(out, n & 0x0f); - } - - static void escape(std::string& out, const std::string& str) - { - for (signed char c : str) { - /**/ if (c == '\a') { out += "\\a"; } - else if (c == '\b') { out += "\\b"; } - else if (c == '\f') { out += "\\f"; } - else if (c == '\n') { out += "\\n"; } - else if (c == '\r') { out += "\\r"; } - else if (c == '\t') { out += "\\t"; } - else if (c == '\v') { out += "\\v"; } - else if (c == '\\') { out += "\\\\"; } - else if (c == '\'') { out += "\\\'"; } - else if (c == '\"') { out += "\\\""; } - else if (c == ' ') { out += "\\ "; } - else if (0 <= c && c < 0x20) { // ASCII control character: - // else if (c < 0x20 || c != (c & 127)) { // ASCII control character or UTF-8: - out += "\\x"; - write_hex_byte(out, static_cast(c)); - } else { out += c; } - } - } - - Text errno_as_text() - { - char buff[256]; - #if defined(__GLIBC__) && defined(_GNU_SOURCE) - // GNU Version - return Text(STRDUP(strerror_r(errno, buff, sizeof(buff)))); - #elif defined(__APPLE__) || _POSIX_C_SOURCE >= 200112L - // XSI Version - strerror_r(errno, buff, sizeof(buff)); - return Text(strdup(buff)); - #elif defined(_WIN32) - strerror_s(buff, sizeof(buff), errno); - return Text(STRDUP(buff)); - #else - // Not thread-safe. - return Text(STRDUP(strerror(errno))); - #endif - } - - void init(int& argc, char* argv[], const Options& options) - { - CHECK_GT_F(argc, 0, "Expected proper argc/argv"); - CHECK_EQ_F(argv[argc], nullptr, "Expected proper argc/argv"); - - s_argv0_filename = filename(argv[0]); - - #ifdef _WIN32 - #define getcwd _getcwd - #endif - - if (!getcwd(s_current_dir, sizeof(s_current_dir))) { - const auto error_text = errno_as_text(); - LOG_F(WARNING, "Failed to get current working directory: " LOGURU_FMT(s) "", error_text.c_str()); - } - - s_arguments = ""; - for (int i = 0; i < argc; ++i) { - escape(s_arguments, argv[i]); - if (i + 1 < argc) { - s_arguments += " "; - } - } - - if (options.verbosity_flag) { - parse_args(argc, argv, options.verbosity_flag); - } - - if (const auto main_thread_name = options.main_thread_name) { - #if LOGURU_PTLS_NAMES || LOGURU_WINTHREADS - set_thread_name(main_thread_name); - #elif LOGURU_PTHREADS - char old_thread_name[16] = {0}; - auto this_thread = pthread_self(); - #if defined(__APPLE__) || defined(__linux__) || defined(__sun) - pthread_getname_np(this_thread, old_thread_name, sizeof(old_thread_name)); - #endif - if (old_thread_name[0] == 0) { - #ifdef __APPLE__ - pthread_setname_np(main_thread_name); - #elif defined(__FreeBSD__) || defined(__OpenBSD__) - pthread_set_name_np(this_thread, main_thread_name); - #elif defined(__linux__) || defined(__sun) - pthread_setname_np(this_thread, main_thread_name); - #endif - } - #endif // LOGURU_PTHREADS - } - - if (g_stderr_verbosity >= Verbosity_INFO) { - if (g_preamble_header) { - char preamble_explain[LOGURU_PREAMBLE_WIDTH]; - print_preamble_header(preamble_explain, sizeof(preamble_explain)); - if (g_colorlogtostderr && s_terminal_has_color) { - fprintf(stderr, "%s%s%s\n", terminal_reset(), terminal_dim(), preamble_explain); - } else { - fprintf(stderr, "%s\n", preamble_explain); - } - } - fflush(stderr); - } - VLOG_F(g_internal_verbosity, "arguments: " LOGURU_FMT(s) "", s_arguments.c_str()); - if (strlen(s_current_dir) != 0) - { - VLOG_F(g_internal_verbosity, "Current dir: " LOGURU_FMT(s) "", s_current_dir); - } - VLOG_F(g_internal_verbosity, "stderr verbosity: " LOGURU_FMT(d) "", g_stderr_verbosity); - VLOG_F(g_internal_verbosity, "-----------------------------------"); - - install_signal_handlers(options.signals); - - atexit(on_atexit); - } - - void shutdown() - { - VLOG_F(g_internal_verbosity, "loguru::shutdown()"); - remove_all_callbacks(); - set_fatal_handler(nullptr); - set_verbosity_to_name_callback(nullptr); - set_name_to_verbosity_callback(nullptr); - } - - void write_date_time(char* buff, size_t buff_size) - { - auto now = system_clock::now(); - long long ms_since_epoch = duration_cast(now.time_since_epoch()).count(); - time_t sec_since_epoch = time_t(ms_since_epoch / 1000); - tm time_info; - localtime_r(&sec_since_epoch, &time_info); - snprintf(buff, buff_size, "%04d%02d%02d_%02d%02d%02d.%03lld", - 1900 + time_info.tm_year, 1 + time_info.tm_mon, time_info.tm_mday, - time_info.tm_hour, time_info.tm_min, time_info.tm_sec, ms_since_epoch % 1000); - } - - const char* argv0_filename() - { - return s_argv0_filename.c_str(); - } - - const char* arguments() - { - return s_arguments.c_str(); - } - - const char* current_dir() - { - return s_current_dir; - } - - const char* home_dir() - { - #ifdef _WIN32 - char* user_profile; - size_t len; - errno_t err = _dupenv_s(&user_profile, &len, "USERPROFILE"); - CHECK_F(err != 0, "Missing USERPROFILE"); - return user_profile; - #else // _WIN32 - auto home = getenv("HOME"); - CHECK_F(home != nullptr, "Missing HOME"); - return home; - #endif // _WIN32 - } - - void suggest_log_path(const char* prefix, char* buff, unsigned buff_size) - { - if (prefix[0] == '~') { - snprintf(buff, buff_size - 1, "%s%s", home_dir(), prefix + 1); - } else { - snprintf(buff, buff_size - 1, "%s", prefix); - } - - // Check for terminating / - size_t n = strlen(buff); - if (n != 0) { - if (buff[n - 1] != '/') { - CHECK_F(n + 2 < buff_size, "Filename buffer too small"); - buff[n] = '/'; - buff[n + 1] = '\0'; - } - } - - #ifdef _WIN32 - strncat_s(buff, buff_size - strlen(buff) - 1, s_argv0_filename.c_str(), buff_size - strlen(buff) - 1); - strncat_s(buff, buff_size - strlen(buff) - 1, "/", buff_size - strlen(buff) - 1); - write_date_time(buff + strlen(buff), buff_size - strlen(buff)); - strncat_s(buff, buff_size - strlen(buff) - 1, ".log", buff_size - strlen(buff) - 1); - #else - strncat(buff, s_argv0_filename.c_str(), buff_size - strlen(buff) - 1); - strncat(buff, "/", buff_size - strlen(buff) - 1); - write_date_time(buff + strlen(buff), buff_size - strlen(buff)); - strncat(buff, ".log", buff_size - strlen(buff) - 1); - #endif - } - - bool create_directories(const char* file_path_const) - { - CHECK_F(file_path_const && *file_path_const); - char* file_path = STRDUP(file_path_const); - for (char* p = strchr(file_path + 1, '/'); p; p = strchr(p + 1, '/')) { - *p = '\0'; - - #ifdef _WIN32 - if (_mkdir(file_path) == -1) { - #else - if (mkdir(file_path, 0755) == -1) { - #endif - if (errno != EEXIST) { - LOG_F(ERROR, "Failed to create directory '" LOGURU_FMT(s) "'", file_path); - LOG_IF_F(ERROR, errno == EACCES, "EACCES"); - LOG_IF_F(ERROR, errno == ENAMETOOLONG, "ENAMETOOLONG"); - LOG_IF_F(ERROR, errno == ENOENT, "ENOENT"); - LOG_IF_F(ERROR, errno == ENOTDIR, "ENOTDIR"); - LOG_IF_F(ERROR, errno == ELOOP, "ELOOP"); - - *p = '/'; - free(file_path); - return false; - } - } - *p = '/'; - } - free(file_path); - return true; - } - bool add_file(const char* path_in, FileMode mode, Verbosity verbosity) - { - char path[PATH_MAX]; - if (path_in[0] == '~') { - snprintf(path, sizeof(path) - 1, "%s%s", home_dir(), path_in + 1); - } else { - snprintf(path, sizeof(path) - 1, "%s", path_in); - } - - if (!create_directories(path)) { - LOG_F(ERROR, "Failed to create directories to '" LOGURU_FMT(s) "'", path); - } - - const char* mode_str = (mode == FileMode::Truncate ? "w" : "a"); - FILE* file; - #ifdef _WIN32 - errno_t file_error = fopen_s(&file, path, mode_str); - if (file_error) { - #else - file = fopen(path, mode_str); - if (!file) { - #endif - LOG_F(ERROR, "Failed to open '" LOGURU_FMT(s) "'", path); - return false; - } -#if LOGURU_WITH_FILEABS - FileAbs* file_abs = new FileAbs(); // this is deleted in file_close; - snprintf(file_abs->path, sizeof(file_abs->path) - 1, "%s", path); - snprintf(file_abs->mode_str, sizeof(file_abs->mode_str) - 1, "%s", mode_str); - stat(file_abs->path, &file_abs->st); - file_abs->fp = file; - file_abs->verbosity = verbosity; - add_callback(path_in, file_log, file_abs, verbosity, file_close, file_flush); -#else - add_callback(path_in, file_log, file, verbosity, file_close, file_flush); -#endif - - if (mode == FileMode::Append) { - fprintf(file, "\n\n\n\n\n"); - } - if (!s_arguments.empty()) { - fprintf(file, "arguments: %s\n", s_arguments.c_str()); - } - if (strlen(s_current_dir) != 0) { - fprintf(file, "Current dir: %s\n", s_current_dir); - } - fprintf(file, "File verbosity level: %d\n", verbosity); - if (g_preamble_header) { - char preamble_explain[LOGURU_PREAMBLE_WIDTH]; - print_preamble_header(preamble_explain, sizeof(preamble_explain)); - fprintf(file, "%s\n", preamble_explain); - } - fflush(file); - - VLOG_F(g_internal_verbosity, "Logging to '" LOGURU_FMT(s) "', mode: '" LOGURU_FMT(s) "', verbosity: " LOGURU_FMT(d) "", path, mode_str, verbosity); - return true; - } - - /* - Will add syslog as a standard sink for log messages - Any logging message with a verbosity lower or equal to - the given verbosity will be included. - - This works for Unix like systems (i.e. Linux/Mac) - There is no current implementation for Windows (as I don't know the - equivalent calls or have a way to test them). If you know please - add and send a pull request. - - The code should still compile under windows but will only generate - a warning message that syslog is unavailable. - - Search for LOGURU_SYSLOG to find and fix. - */ - bool add_syslog(const char* app_name, Verbosity verbosity) - { - return add_syslog(app_name, verbosity, LOG_USER); - } - bool add_syslog(const char* app_name, Verbosity verbosity, int facility) - { -#if LOGURU_SYSLOG - if (app_name == nullptr) { - app_name = argv0_filename(); - } - openlog(app_name, 0, facility); - add_callback("'syslog'", syslog_log, nullptr, verbosity, syslog_close, syslog_flush); - - VLOG_F(g_internal_verbosity, "Logging to 'syslog' , verbosity: " LOGURU_FMT(d) "", verbosity); - return true; -#else - (void)app_name; - (void)verbosity; - (void)facility; - VLOG_F(g_internal_verbosity, "syslog not implemented on this system. Request to install syslog logging ignored."); - return false; -#endif - } - // Will be called right before abort(). - void set_fatal_handler(fatal_handler_t handler) - { - s_fatal_handler = handler; - } - - fatal_handler_t get_fatal_handler() - { - return s_fatal_handler; - } - - void set_verbosity_to_name_callback(verbosity_to_name_t callback) - { - s_verbosity_to_name_callback = callback; - } - - void set_name_to_verbosity_callback(name_to_verbosity_t callback) - { - s_name_to_verbosity_callback = callback; - } - - void add_stack_cleanup(const char* find_this, const char* replace_with_this) - { - if (strlen(find_this) <= strlen(replace_with_this)) { - LOG_F(WARNING, "add_stack_cleanup: the replacement should be shorter than the pattern!"); - return; - } - - s_user_stack_cleanups.push_back(StringPair(find_this, replace_with_this)); - } - - static void on_callback_change() - { - s_max_out_verbosity = Verbosity_OFF; - for (const auto& callback : s_callbacks) { - s_max_out_verbosity = std::max(s_max_out_verbosity, callback.verbosity); - } - } - - void add_callback( - const char* id, - log_handler_t callback, - void* user_data, - Verbosity verbosity, - close_handler_t on_close, - flush_handler_t on_flush) - { - std::lock_guard lock(s_mutex); - s_callbacks.push_back(Callback{id, callback, user_data, verbosity, on_close, on_flush, 0}); - on_callback_change(); - } - - // Returns a custom verbosity name if one is available, or nullptr. - // See also set_verbosity_to_name_callback. - const char* get_verbosity_name(Verbosity verbosity) - { - auto name = s_verbosity_to_name_callback - ? (*s_verbosity_to_name_callback)(verbosity) - : nullptr; - - // Use standard replacements if callback fails: - if (!name) - { - if (verbosity <= Verbosity_FATAL) { - name = "FATL"; - } else if (verbosity == Verbosity_ERROR) { - name = "ERR"; - } else if (verbosity == Verbosity_WARNING) { - name = "WARN"; - } else if (verbosity == Verbosity_INFO) { - name = "INFO"; - } - } - - return name; - } - - // Returns Verbosity_INVALID if the name is not found. - // See also set_name_to_verbosity_callback. - Verbosity get_verbosity_from_name(const char* name) - { - auto verbosity = s_name_to_verbosity_callback - ? (*s_name_to_verbosity_callback)(name) - : Verbosity_INVALID; - - // Use standard replacements if callback fails: - if (verbosity == Verbosity_INVALID) { - if (strcmp(name, "OFF") == 0) { - verbosity = Verbosity_OFF; - } else if (strcmp(name, "INFO") == 0) { - verbosity = Verbosity_INFO; - } else if (strcmp(name, "WARNING") == 0) { - verbosity = Verbosity_WARNING; - } else if (strcmp(name, "ERROR") == 0) { - verbosity = Verbosity_ERROR; - } else if (strcmp(name, "FATAL") == 0) { - verbosity = Verbosity_FATAL; - } - } - - return verbosity; - } - - bool remove_callback(const char* id) - { - std::lock_guard lock(s_mutex); - auto it = std::find_if(begin(s_callbacks), end(s_callbacks), [&](const Callback& c) { return c.id == id; }); - if (it != s_callbacks.end()) { - if (it->close) { it->close(it->user_data); } - s_callbacks.erase(it); - on_callback_change(); - return true; - } else { - LOG_F(ERROR, "Failed to locate callback with id '" LOGURU_FMT(s) "'", id); - return false; - } - } - - void remove_all_callbacks() - { - std::lock_guard lock(s_mutex); - for (auto& callback : s_callbacks) { - if (callback.close) { - callback.close(callback.user_data); - } - } - s_callbacks.clear(); - on_callback_change(); - } - - // Returns the maximum of g_stderr_verbosity and all file/custom outputs. - Verbosity current_verbosity_cutoff() - { - return g_stderr_verbosity > s_max_out_verbosity ? - g_stderr_verbosity : s_max_out_verbosity; - } - - // ------------------------------------------------------------------------ - // Threads names - -#if LOGURU_PTLS_NAMES - static pthread_once_t s_pthread_key_once = PTHREAD_ONCE_INIT; - static pthread_key_t s_pthread_key_name; - - void make_pthread_key_name() - { - (void)pthread_key_create(&s_pthread_key_name, free); - } -#endif - -#if LOGURU_WINTHREADS - // Where we store the custom thread name set by `set_thread_name` - char* thread_name_buffer() - { - __declspec( thread ) static char thread_name[LOGURU_THREADNAME_WIDTH + 1] = {0}; - return &thread_name[0]; - } -#endif // LOGURU_WINTHREADS - - void set_thread_name(const char* name) - { - #if LOGURU_PTLS_NAMES - // Store thread name in thread-local storage at `s_pthread_key_name` - (void)pthread_once(&s_pthread_key_once, make_pthread_key_name); - (void)pthread_setspecific(s_pthread_key_name, STRDUP(name)); - #elif LOGURU_PTHREADS - // Tell the OS the thread name - #ifdef __APPLE__ - pthread_setname_np(name); - #elif defined(__FreeBSD__) || defined(__OpenBSD__) - pthread_set_name_np(pthread_self(), name); - #elif defined(__linux__) || defined(__sun) - pthread_setname_np(pthread_self(), name); - #endif - #elif LOGURU_WINTHREADS - // Store thread name in a thread-local storage: - strncpy_s(thread_name_buffer(), LOGURU_THREADNAME_WIDTH + 1, name, _TRUNCATE); - #else // LOGURU_PTHREADS - // TODO: on these weird platforms we should also store the thread name - // in a generic thread-local storage. - (void)name; - #endif // LOGURU_PTHREADS - } - - void get_thread_name(char* buffer, unsigned long long length, bool right_align_hex_id) - { - CHECK_NE_F(length, 0u, "Zero length buffer in get_thread_name"); - CHECK_NOTNULL_F(buffer, "nullptr in get_thread_name"); - - #if LOGURU_PTLS_NAMES - (void)pthread_once(&s_pthread_key_once, make_pthread_key_name); - if (const char* name = static_cast(pthread_getspecific(s_pthread_key_name))) { - snprintf(buffer, length, "%s", name); - } else { - buffer[0] = 0; - } - #elif LOGURU_PTHREADS - // Ask the OS about the thread name. - // This is what we *want* to do on all platforms, but - // only some platforms support it (currently). - pthread_getname_np(pthread_self(), buffer, length); - #elif LOGURU_WINTHREADS - snprintf(buffer, (size_t)length, "%s", thread_name_buffer()); - #else - // Thread names unsupported - buffer[0] = 0; - #endif - - if (buffer[0] == 0) { - // We failed to get a readable thread name. - // Write a HEX thread ID instead. - // We try to get an ID that is the same as the ID you could - // read in your debugger, system monitor etc. - - #ifdef __APPLE__ - uint64_t thread_id; - pthread_threadid_np(pthread_self(), &thread_id); - #elif defined(__FreeBSD__) - long thread_id; - (void)thr_self(&thread_id); - #elif LOGURU_PTHREADS - uint64_t thread_id = pthread_self(); - #else - // This ID does not correlate to anything we can get from the OS, - // so this is the worst way to get the ID. - const auto thread_id = std::hash{}(std::this_thread::get_id()); - #endif - - if (right_align_hex_id) { - snprintf(buffer, length, "%*X", static_cast(length - 1), static_cast(thread_id)); - } else { - snprintf(buffer, length, "%X", static_cast(thread_id)); - } - } - } - - // ------------------------------------------------------------------------ - // Stack traces - -#if LOGURU_STACKTRACES - Text demangle(const char* name) - { - int status = -1; - char* demangled = abi::__cxa_demangle(name, 0, 0, &status); - Text result{status == 0 ? demangled : STRDUP(name)}; - return result; - } - - #if LOGURU_RTTI - template - std::string type_name() - { - auto demangled = demangle(typeid(T).name()); - return demangled.c_str(); - } - #endif // LOGURU_RTTI - - static const StringPairList REPLACE_LIST = { - #if LOGURU_RTTI - { type_name(), "std::string" }, - { type_name(), "std::wstring" }, - { type_name(), "std::u16string" }, - { type_name(), "std::u32string" }, - #endif // LOGURU_RTTI - { "std::__1::", "std::" }, - { "__thiscall ", "" }, - { "__cdecl ", "" }, - }; - - void do_replacements(const StringPairList& replacements, std::string& str) - { - for (auto&& p : replacements) { - if (p.first.size() <= p.second.size()) { - // On gcc, "type_name()" is "std::string" - continue; - } - - size_t it; - while ((it=str.find(p.first)) != std::string::npos) { - str.replace(it, p.first.size(), p.second); - } - } - } - - std::string prettify_stacktrace(const std::string& input) - { - std::string output = input; - - do_replacements(s_user_stack_cleanups, output); - do_replacements(REPLACE_LIST, output); - - try { - std::regex std_allocator_re(R"(,\s*std::allocator<[^<>]+>)"); - output = std::regex_replace(output, std_allocator_re, std::string("")); - - std::regex template_spaces_re(R"(<\s*([^<> ]+)\s*>)"); - output = std::regex_replace(output, template_spaces_re, std::string("<$1>")); - } catch (std::regex_error&) { - // Probably old GCC. - } - - return output; - } - - std::string stacktrace_as_stdstring(int skip) - { - // From https://gist.github.com/fmela/591333 - void* callstack[128]; - const auto max_frames = sizeof(callstack) / sizeof(callstack[0]); - int num_frames = backtrace(callstack, max_frames); - char** symbols = backtrace_symbols(callstack, num_frames); - - std::string result; - // Print stack traces so the most relevant ones are written last - // Rationale: http://yellerapp.com/posts/2015-01-22-upside-down-stacktraces.html - for (int i = num_frames - 1; i >= skip; --i) { - char buf[1024]; - Dl_info info; - if (dladdr(callstack[i], &info) && info.dli_sname) { - char* demangled = NULL; - int status = -1; - if (info.dli_sname[0] == '_') { - demangled = abi::__cxa_demangle(info.dli_sname, 0, 0, &status); - } - snprintf(buf, sizeof(buf), "%-3d %*p %s + %zd\n", - i - skip, int(2 + sizeof(void*) * 2), callstack[i], - status == 0 ? demangled : - info.dli_sname == 0 ? symbols[i] : info.dli_sname, - static_cast(callstack[i]) - static_cast(info.dli_saddr)); - free(demangled); - } else { - snprintf(buf, sizeof(buf), "%-3d %*p %s\n", - i - skip, int(2 + sizeof(void*) * 2), callstack[i], symbols[i]); - } - result += buf; - } - free(symbols); - - if (num_frames == max_frames) { - result = "[truncated]\n" + result; - } - - if (!result.empty() && result[result.size() - 1] == '\n') { - result.resize(result.size() - 1); - } - - return prettify_stacktrace(result); - } - -#else // LOGURU_STACKTRACES - Text demangle(const char* name) - { - return Text(STRDUP(name)); - } - - std::string stacktrace_as_stdstring(int) - { - // No stacktraces available on this platform" - return ""; - } - -#endif // LOGURU_STACKTRACES - - Text stacktrace(int skip) - { - auto str = stacktrace_as_stdstring(skip + 1); - return Text(STRDUP(str.c_str())); - } - - // ------------------------------------------------------------------------ - - static void print_preamble_header(char* out_buff, size_t out_buff_size) - { - if (out_buff_size == 0) { return; } - out_buff[0] = '\0'; - long pos = 0; - if (g_preamble_date && pos < out_buff_size) { - pos += snprintf(out_buff + pos, out_buff_size - pos, "date "); - } - if (g_preamble_time && pos < out_buff_size) { - pos += snprintf(out_buff + pos, out_buff_size - pos, "time "); - } - if (g_preamble_uptime && pos < out_buff_size) { - pos += snprintf(out_buff + pos, out_buff_size - pos, "( uptime ) "); - } - if (g_preamble_thread && pos < out_buff_size) { - pos += snprintf(out_buff + pos, out_buff_size - pos, "[%-*s]", LOGURU_THREADNAME_WIDTH, " thread name/id"); - } - if (g_preamble_file && pos < out_buff_size) { - pos += snprintf(out_buff + pos, out_buff_size - pos, "%*s:line ", LOGURU_FILENAME_WIDTH, "file"); - } - if (g_preamble_verbose && pos < out_buff_size) { - pos += snprintf(out_buff + pos, out_buff_size - pos, " v"); - } - if (g_preamble_pipe && pos < out_buff_size) { - pos += snprintf(out_buff + pos, out_buff_size - pos, "| "); - } - } - - static void print_preamble(char* out_buff, size_t out_buff_size, Verbosity verbosity, const char* file, unsigned line) - { - if (out_buff_size == 0) { return; } - out_buff[0] = '\0'; - if (!g_preamble) { return; } - long long ms_since_epoch = duration_cast(system_clock::now().time_since_epoch()).count(); - time_t sec_since_epoch = time_t(ms_since_epoch / 1000); - tm time_info; - localtime_r(&sec_since_epoch, &time_info); - - auto uptime_ms = duration_cast(steady_clock::now() - s_start_time).count(); - auto uptime_sec = static_cast (uptime_ms) / 1000.0; - - char thread_name[LOGURU_THREADNAME_WIDTH + 1] = {0}; - get_thread_name(thread_name, LOGURU_THREADNAME_WIDTH + 1, true); - - if (s_strip_file_path) { - file = filename(file); - } - - char level_buff[6]; - const char* custom_level_name = get_verbosity_name(verbosity); - if (custom_level_name) { - snprintf(level_buff, sizeof(level_buff) - 1, "%s", custom_level_name); - } else { - snprintf(level_buff, sizeof(level_buff) - 1, "% 4d", verbosity); - } - - long pos = 0; - - if (g_preamble_date && pos < out_buff_size) { - pos += snprintf(out_buff + pos, out_buff_size - pos, "%04d-%02d-%02d ", - 1900 + time_info.tm_year, 1 + time_info.tm_mon, time_info.tm_mday); - } - if (g_preamble_time && pos < out_buff_size) { - pos += snprintf(out_buff + pos, out_buff_size - pos, "%02d:%02d:%02d.%03lld ", - time_info.tm_hour, time_info.tm_min, time_info.tm_sec, ms_since_epoch % 1000); - } - if (g_preamble_uptime && pos < out_buff_size) { - pos += snprintf(out_buff + pos, out_buff_size - pos, "(%8.3fs) ", - uptime_sec); - } - if (g_preamble_thread && pos < out_buff_size) { - pos += snprintf(out_buff + pos, out_buff_size - pos, "[%-*s]", - LOGURU_THREADNAME_WIDTH, thread_name); - } - if (g_preamble_file && pos < out_buff_size) { - char shortened_filename[LOGURU_FILENAME_WIDTH + 1]; - snprintf(shortened_filename, LOGURU_FILENAME_WIDTH + 1, "%s", file); - pos += snprintf(out_buff + pos, out_buff_size - pos, "%*s:%-5u ", - LOGURU_FILENAME_WIDTH, shortened_filename, line); - } - if (g_preamble_verbose && pos < out_buff_size) { - pos += snprintf(out_buff + pos, out_buff_size - pos, "%4s", - level_buff); - } - if (g_preamble_pipe && pos < out_buff_size) { - pos += snprintf(out_buff + pos, out_buff_size - pos, "| "); - } - } - - // stack_trace_skip is just if verbosity == FATAL. - static void log_message(int stack_trace_skip, Message& message, bool with_indentation, bool abort_if_fatal) - { - const auto verbosity = message.verbosity; - std::lock_guard lock(s_mutex); - - if (message.verbosity == Verbosity_FATAL) { - auto st = loguru::stacktrace(stack_trace_skip + 2); - if (!st.empty()) { - RAW_LOG_F(ERROR, "Stack trace:\n" LOGURU_FMT(s) "", st.c_str()); - } - - auto ec = loguru::get_error_context(); - if (!ec.empty()) { - RAW_LOG_F(ERROR, "" LOGURU_FMT(s) "", ec.c_str()); - } - } - - if (with_indentation) { - message.indentation = indentation(s_stderr_indentation); - } - - if (verbosity <= g_stderr_verbosity) { - if (g_colorlogtostderr && s_terminal_has_color) { - if (verbosity > Verbosity_WARNING) { - fprintf(stderr, "%s%s%s%s%s%s%s%s\n", - terminal_reset(), - terminal_dim(), - message.preamble, - message.indentation, - verbosity == Verbosity_INFO ? terminal_reset() : "", // un-dim for info - message.prefix, - message.message, - terminal_reset()); - } else { - fprintf(stderr, "%s%s%s%s%s%s%s\n", - terminal_reset(), - verbosity == Verbosity_WARNING ? terminal_yellow() : terminal_red(), - message.preamble, - message.indentation, - message.prefix, - message.message, - terminal_reset()); - } - } else { - fprintf(stderr, "%s%s%s%s\n", - message.preamble, message.indentation, message.prefix, message.message); - } - - if (g_flush_interval_ms == 0) { - fflush(stderr); - } else { - s_needs_flushing = true; - } - } - - for (auto& p : s_callbacks) { - if (verbosity <= p.verbosity) { - if (with_indentation) { - message.indentation = indentation(p.indentation); - } - p.callback(p.user_data, message); - if (g_flush_interval_ms == 0) { - if (p.flush) { p.flush(p.user_data); } - } else { - s_needs_flushing = true; - } - } - } - - if (g_flush_interval_ms > 0 && !s_flush_thread) { - s_flush_thread = new std::thread([](){ - for (;;) { - if (s_needs_flushing) { - flush(); - } - std::this_thread::sleep_for(std::chrono::milliseconds(g_flush_interval_ms)); - } - }); - } - - if (message.verbosity == Verbosity_FATAL) { - flush(); - - if (s_fatal_handler) { - s_fatal_handler(message); - flush(); - } - - if (abort_if_fatal) { -#if !defined(_WIN32) - if (s_signal_options.sigabrt) { - // Make sure we don't catch our own abort: - signal(SIGABRT, SIG_DFL); - } -#endif - abort(); - } - } - } - - // stack_trace_skip is just if verbosity == FATAL. - void log_to_everywhere(int stack_trace_skip, Verbosity verbosity, - const char* file, unsigned line, - const char* prefix, const char* buff) - { - char preamble_buff[LOGURU_PREAMBLE_WIDTH]; - print_preamble(preamble_buff, sizeof(preamble_buff), verbosity, file, line); - auto message = Message{verbosity, file, line, preamble_buff, "", prefix, buff}; - log_message(stack_trace_skip + 1, message, true, true); - } - -#if LOGURU_USE_FMTLIB - void vlog(Verbosity verbosity, const char* file, unsigned line, const char* format, fmt::format_args args) - { - auto formatted = fmt::vformat(format, args); - log_to_everywhere(1, verbosity, file, line, "", formatted.c_str()); - } - - void raw_vlog(Verbosity verbosity, const char* file, unsigned line, const char* format, fmt::format_args args) - { - auto formatted = fmt::vformat(format, args); - auto message = Message{verbosity, file, line, "", "", "", formatted.c_str()}; - log_message(1, message, false, true); - } -#else - void log(Verbosity verbosity, const char* file, unsigned line, const char* format, ...) - { - va_list vlist; - va_start(vlist, format); - auto buff = vtextprintf(format, vlist); - log_to_everywhere(1, verbosity, file, line, "", buff.c_str()); - va_end(vlist); - } - - void raw_log(Verbosity verbosity, const char* file, unsigned line, const char* format, ...) - { - va_list vlist; - va_start(vlist, format); - auto buff = vtextprintf(format, vlist); - auto message = Message{verbosity, file, line, "", "", "", buff.c_str()}; - log_message(1, message, false, true); - va_end(vlist); - } -#endif - - void flush() - { - std::lock_guard lock(s_mutex); - fflush(stderr); - for (const auto& callback : s_callbacks) - { - if (callback.flush) { - callback.flush(callback.user_data); - } - } - s_needs_flushing = false; - } - - LogScopeRAII::LogScopeRAII(Verbosity verbosity, const char* file, unsigned line, const char* format, ...) - : _verbosity(verbosity), _file(file), _line(line) - { - if (verbosity <= current_verbosity_cutoff()) { - std::lock_guard lock(s_mutex); - _indent_stderr = (verbosity <= g_stderr_verbosity); - _start_time_ns = now_ns(); - va_list vlist; - va_start(vlist, format); - vsnprintf(_name, sizeof(_name), format, vlist); - log_to_everywhere(1, _verbosity, file, line, "{ ", _name); - va_end(vlist); - - if (_indent_stderr) { - ++s_stderr_indentation; - } - - for (auto& p : s_callbacks) { - if (verbosity <= p.verbosity) { - ++p.indentation; - } - } - } else { - _file = nullptr; - } - } - - LogScopeRAII::~LogScopeRAII() - { - if (_file) { - std::lock_guard lock(s_mutex); - if (_indent_stderr && s_stderr_indentation > 0) { - --s_stderr_indentation; - } - for (auto& p : s_callbacks) { - // Note: Callback indentation cannot change! - if (_verbosity <= p.verbosity) { - // in unlikely case this callback is new - if (p.indentation > 0) { - --p.indentation; - } - } - } -#if LOGURU_VERBOSE_SCOPE_ENDINGS - auto duration_sec = static_cast(now_ns() - _start_time_ns) / 1e9; -#if LOGURU_USE_FMTLIB - auto buff = textprintf("{:.{}f} s: {:s}", duration_sec, LOGURU_SCOPE_TIME_PRECISION, _name); -#else - auto buff = textprintf("%.*f s: %s", LOGURU_SCOPE_TIME_PRECISION, duration_sec, _name); -#endif - log_to_everywhere(1, _verbosity, _file, _line, "} ", buff.c_str()); -#else - log_to_everywhere(1, _verbosity, _file, _line, "}", ""); -#endif - } - } - -#if LOGURU_USE_FMTLIB - void vlog_and_abort(int stack_trace_skip, const char* expr, const char* file, unsigned line, const char* format, fmt::format_args args) - { - auto formatted = fmt::vformat(format, args); - log_to_everywhere(stack_trace_skip + 1, Verbosity_FATAL, file, line, expr, formatted.c_str()); - abort(); // log_to_everywhere already does this, but this makes the analyzer happy. - } -#else - void log_and_abort(int stack_trace_skip, const char* expr, const char* file, unsigned line, const char* format, ...) - { - va_list vlist; - va_start(vlist, format); - auto buff = vtextprintf(format, vlist); - log_to_everywhere(stack_trace_skip + 1, Verbosity_FATAL, file, line, expr, buff.c_str()); - va_end(vlist); - abort(); // log_to_everywhere already does this, but this makes the analyzer happy. - } -#endif - - void log_and_abort(int stack_trace_skip, const char* expr, const char* file, unsigned line) - { - log_and_abort(stack_trace_skip + 1, expr, file, line, " "); - } - - // ---------------------------------------------------------------------------- - // Streams: - -#if LOGURU_USE_FMTLIB - template - std::string vstrprintf(const char* format, const Args&... args) - { - auto text = textprintf(format, args...); - std::string result = text.c_str(); - return result; - } - - template - std::string strprintf(const char* format, const Args&... args) - { - return vstrprintf(format, args...); - } -#else - std::string vstrprintf(const char* format, va_list vlist) - { - auto text = vtextprintf(format, vlist); - std::string result = text.c_str(); - return result; - } - - std::string strprintf(const char* format, ...) - { - va_list vlist; - va_start(vlist, format); - auto result = vstrprintf(format, vlist); - va_end(vlist); - return result; - } -#endif - - #if LOGURU_WITH_STREAMS - - StreamLogger::~StreamLogger() noexcept(false) - { - auto message = _ss.str(); - log(_verbosity, _file, _line, LOGURU_FMT(s), message.c_str()); - } - - AbortLogger::~AbortLogger() noexcept(false) - { - auto message = _ss.str(); - loguru::log_and_abort(1, _expr, _file, _line, LOGURU_FMT(s), message.c_str()); - } - - #endif // LOGURU_WITH_STREAMS - - // ---------------------------------------------------------------------------- - // 888888 88""Yb 88""Yb dP"Yb 88""Yb dP""b8 dP"Yb 88b 88 888888 888888 Yb dP 888888 - // 88__ 88__dP 88__dP dP Yb 88__dP dP `" dP Yb 88Yb88 88 88__ YbdP 88 - // 88"" 88"Yb 88"Yb Yb dP 88"Yb Yb Yb dP 88 Y88 88 88"" dPYb 88 - // 888888 88 Yb 88 Yb YbodP 88 Yb YboodP YbodP 88 Y8 88 888888 dP Yb 88 - // ---------------------------------------------------------------------------- - - struct StringStream - { - std::string str; - }; - - // Use this in your EcPrinter implementations. - void stream_print(StringStream& out_string_stream, const char* text) - { - out_string_stream.str += text; - } - - // ---------------------------------------------------------------------------- - - using ECPtr = EcEntryBase*; - -#if defined(_WIN32) || (defined(__APPLE__) && !TARGET_OS_IPHONE) - #ifdef __APPLE__ - #define LOGURU_THREAD_LOCAL __thread - #else - #define LOGURU_THREAD_LOCAL thread_local - #endif - static LOGURU_THREAD_LOCAL ECPtr thread_ec_ptr = nullptr; - - ECPtr& get_thread_ec_head_ref() - { - return thread_ec_ptr; - } -#else // !thread_local - static pthread_once_t s_ec_pthread_once = PTHREAD_ONCE_INIT; - static pthread_key_t s_ec_pthread_key; - - void free_ec_head_ref(void* io_error_context) - { - delete reinterpret_cast(io_error_context); - } - - void ec_make_pthread_key() - { - (void)pthread_key_create(&s_ec_pthread_key, free_ec_head_ref); - } - - ECPtr& get_thread_ec_head_ref() - { - (void)pthread_once(&s_ec_pthread_once, ec_make_pthread_key); - auto ec = reinterpret_cast(pthread_getspecific(s_ec_pthread_key)); - if (ec == nullptr) { - ec = new ECPtr(nullptr); - (void)pthread_setspecific(s_ec_pthread_key, ec); - } - return *ec; - } -#endif // !thread_local - - // ---------------------------------------------------------------------------- - - EcHandle get_thread_ec_handle() - { - return get_thread_ec_head_ref(); - } - - Text get_error_context() - { - return get_error_context_for(get_thread_ec_head_ref()); - } - - Text get_error_context_for(const EcEntryBase* ec_head) - { - std::vector stack; - while (ec_head) { - stack.push_back(ec_head); - ec_head = ec_head->_previous; - } - std::reverse(stack.begin(), stack.end()); - - StringStream result; - if (!stack.empty()) { - result.str += "------------------------------------------------\n"; - for (auto entry : stack) { - const auto description = std::string(entry->_descr) + ":"; -#if LOGURU_USE_FMTLIB - auto prefix = textprintf("[ErrorContext] {.{}s}:{:-5u} {:-20s} ", - filename(entry->_file), LOGURU_FILENAME_WIDTH, entry->_line, description.c_str()); -#else - auto prefix = textprintf("[ErrorContext] %*s:%-5u %-20s ", - LOGURU_FILENAME_WIDTH, filename(entry->_file), entry->_line, description.c_str()); -#endif - result.str += prefix.c_str(); - entry->print_value(result); - result.str += "\n"; - } - result.str += "------------------------------------------------"; - } - return Text(STRDUP(result.str.c_str())); - } - - EcEntryBase::EcEntryBase(const char* file, unsigned line, const char* descr) - : _file(file), _line(line), _descr(descr) - { - EcEntryBase*& ec_head = get_thread_ec_head_ref(); - _previous = ec_head; - ec_head = this; - } - - EcEntryBase::~EcEntryBase() - { - get_thread_ec_head_ref() = _previous; - } - - // ------------------------------------------------------------------------ - - Text ec_to_text(const char* value) - { - // Add quotes around the string to make it obvious where it begin and ends. - // This is great for detecting erroneous leading or trailing spaces in e.g. an identifier. - auto str = "\"" + std::string(value) + "\""; - return Text{STRDUP(str.c_str())}; - } - - Text ec_to_text(signed char c) - { - // Add quotes around the character to make it obvious where it begin and ends. - std::string str = "'"; - - auto write_hex_digit = [&](unsigned num) - { - if (num < 10u) { str += char('0' + num); } - else { str += char('a' + num - 10); } - }; - - auto write_hex_16 = [&](uint16_t n) - { - write_hex_digit((n >> 12u) & 0x0f); - write_hex_digit((n >> 8u) & 0x0f); - write_hex_digit((n >> 4u) & 0x0f); - write_hex_digit((n >> 0u) & 0x0f); - }; - - if (c == '\\') { str += "\\\\"; } - else if (c == '\"') { str += "\\\""; } - else if (c == '\'') { str += "\\\'"; } - else if (c == '\0') { str += "\\0"; } - else if (c == '\b') { str += "\\b"; } - else if (c == '\f') { str += "\\f"; } - else if (c == '\n') { str += "\\n"; } - else if (c == '\r') { str += "\\r"; } - else if (c == '\t') { str += "\\t"; } - else if (0 <= c && c < 0x20) { - str += "\\u"; - write_hex_16(static_cast(c)); - } else { str += c; } - - str += "'"; - - return Text{STRDUP(str.c_str())}; - } - - #define DEFINE_EC(Type) \ - Text ec_to_text(Type value) \ - { \ - auto str = std::to_string(value); \ - return Text{STRDUP(str.c_str())}; \ - } - - DEFINE_EC(int) - DEFINE_EC(unsigned int) - DEFINE_EC(long) - DEFINE_EC(unsigned long) - DEFINE_EC(long long) - DEFINE_EC(unsigned long long) - DEFINE_EC(float) - DEFINE_EC(double) - DEFINE_EC(long double) - - #undef DEFINE_EC - - Text ec_to_text(EcHandle ec_handle) - { - Text parent_ec = get_error_context_for(ec_handle); - size_t buffer_size = strlen(parent_ec.c_str()) + 2; - char* with_newline = reinterpret_cast(malloc(buffer_size)); - with_newline[0] = '\n'; - #ifdef _WIN32 - strncpy_s(with_newline + 1, buffer_size, parent_ec.c_str(), buffer_size - 2); - #else - strcpy(with_newline + 1, parent_ec.c_str()); - #endif - return Text(with_newline); - } - - // ---------------------------------------------------------------------------- - -} // namespace loguru - -// ---------------------------------------------------------------------------- -// .dP"Y8 88 dP""b8 88b 88 db 88 .dP"Y8 -// `Ybo." 88 dP `" 88Yb88 dPYb 88 `Ybo." -// o.`Y8b 88 Yb "88 88 Y88 dP__Yb 88 .o o.`Y8b -// 8bodP' 88 YboodP 88 Y8 dP""""Yb 88ood8 8bodP' -// ---------------------------------------------------------------------------- - -#ifdef _WIN32 -namespace loguru { - void install_signal_handlers(const SignalOptions& signal_options) - { - (void)signal_options; - // TODO: implement signal handlers on windows - } -} // namespace loguru - -#else // _WIN32 - -namespace loguru -{ - void write_to_stderr(const char* data, size_t size) - { - auto result = write(STDERR_FILENO, data, size); - (void)result; // Ignore errors. - } - - void write_to_stderr(const char* data) - { - write_to_stderr(data, strlen(data)); - } - - void call_default_signal_handler(int signal_number) - { - struct sigaction sig_action; - memset(&sig_action, 0, sizeof(sig_action)); - sigemptyset(&sig_action.sa_mask); - sig_action.sa_handler = SIG_DFL; - sigaction(signal_number, &sig_action, NULL); - kill(getpid(), signal_number); - } - - void signal_handler(int signal_number, siginfo_t*, void*) - { - const char* signal_name = "UNKNOWN SIGNAL"; - - if (signal_number == SIGABRT) { signal_name = "SIGABRT"; } - if (signal_number == SIGBUS) { signal_name = "SIGBUS"; } - if (signal_number == SIGFPE) { signal_name = "SIGFPE"; } - if (signal_number == SIGILL) { signal_name = "SIGILL"; } - if (signal_number == SIGINT) { signal_name = "SIGINT"; } - if (signal_number == SIGSEGV) { signal_name = "SIGSEGV"; } - if (signal_number == SIGTERM) { signal_name = "SIGTERM"; } - - // -------------------------------------------------------------------- - /* There are few things that are safe to do in a signal handler, - but writing to stderr is one of them. - So we first print out what happened to stderr so we're sure that gets out, - then we do the unsafe things, like logging the stack trace. - */ - - if (g_colorlogtostderr && s_terminal_has_color) { - write_to_stderr(terminal_reset()); - write_to_stderr(terminal_bold()); - write_to_stderr(terminal_light_red()); - } - write_to_stderr("\n"); - write_to_stderr("Loguru caught a signal: "); - write_to_stderr(signal_name); - write_to_stderr("\n"); - if (g_colorlogtostderr && s_terminal_has_color) { - write_to_stderr(terminal_reset()); - } - - // -------------------------------------------------------------------- - - if (s_signal_options.unsafe_signal_handler) { - // -------------------------------------------------------------------- - /* Now we do unsafe things. This can for example lead to deadlocks if - the signal was triggered from the system's memory management functions - and the code below tries to do allocations. - */ - - flush(); - char preamble_buff[LOGURU_PREAMBLE_WIDTH]; - print_preamble(preamble_buff, sizeof(preamble_buff), Verbosity_FATAL, "", 0); - auto message = Message{Verbosity_FATAL, "", 0, preamble_buff, "", "Signal: ", signal_name}; - try { - log_message(1, message, false, false); - } catch (...) { - // This can happed due to s_fatal_handler. - write_to_stderr("Exception caught and ignored by Loguru signal handler.\n"); - } - flush(); - - // -------------------------------------------------------------------- - } - - call_default_signal_handler(signal_number); - } - - void install_signal_handlers(const SignalOptions& signal_options) - { - s_signal_options = signal_options; - - struct sigaction sig_action; - memset(&sig_action, 0, sizeof(sig_action)); - sigemptyset(&sig_action.sa_mask); - sig_action.sa_flags |= SA_SIGINFO; - sig_action.sa_sigaction = &signal_handler; - - if (signal_options.sigabrt) { - CHECK_F(sigaction(SIGABRT, &sig_action, NULL) != -1, "Failed to install handler for SIGABRT"); - } - if (signal_options.sigbus) { - CHECK_F(sigaction(SIGBUS, &sig_action, NULL) != -1, "Failed to install handler for SIGBUS"); - } - if (signal_options.sigfpe) { - CHECK_F(sigaction(SIGFPE, &sig_action, NULL) != -1, "Failed to install handler for SIGFPE"); - } - if (signal_options.sigill) { - CHECK_F(sigaction(SIGILL, &sig_action, NULL) != -1, "Failed to install handler for SIGILL"); - } - if (signal_options.sigint) { - CHECK_F(sigaction(SIGINT, &sig_action, NULL) != -1, "Failed to install handler for SIGINT"); - } - if (signal_options.sigsegv) { - CHECK_F(sigaction(SIGSEGV, &sig_action, NULL) != -1, "Failed to install handler for SIGSEGV"); - } - if (signal_options.sigterm) { - CHECK_F(sigaction(SIGTERM, &sig_action, NULL) != -1, "Failed to install handler for SIGTERM"); - } - } -} // namespace loguru - -#endif // _WIN32 - -#ifdef _WIN32 -#ifdef _MSC_VER -#pragma warning(pop) -#endif // _MSC_VER -#endif // _WIN32 - -#endif // LOGURU_IMPLEMENTATION diff --git a/cpp/dolfinx/common/loguru.hpp b/cpp/dolfinx/common/loguru.hpp deleted file mode 100644 index 27a93c85108..00000000000 --- a/cpp/dolfinx/common/loguru.hpp +++ /dev/null @@ -1,1445 +0,0 @@ -/* -Loguru logging library for C++, by Emil Ernerfeldt. -www.github.com/emilk/loguru -If you find Loguru useful, please let me know on twitter or in a mail! -Twitter: @ernerfeldt -Mail: emil.ernerfeldt@gmail.com -Website: www.ilikebigbits.com - -# License - This software is in the public domain. Where that dedication is not - recognized, you are granted a perpetual, irrevocable license to - copy, modify and distribute it as you see fit. - -# Inspiration - Much of Loguru was inspired by GLOG, https://code.google.com/p/google-glog/. - The choice of public domain is fully due Sean T. Barrett - and his wonderful stb libraries at https://github.com/nothings/stb. - -# Version history - * Version 0.1.0 - 2015-03-22 - Works great on Mac. - * Version 0.2.0 - 2015-09-17 - Removed the only dependency. - * Version 0.3.0 - 2015-10-02 - Drop-in replacement for most of GLOG - * Version 0.4.0 - 2015-10-07 - Single-file! - * Version 0.5.0 - 2015-10-17 - Improved file logging - * Version 0.6.0 - 2015-10-24 - Add stack traces - * Version 0.7.0 - 2015-10-27 - Signals - * Version 0.8.0 - 2015-10-30 - Color logging. - * Version 0.9.0 - 2015-11-26 - ABORT_S and proper handling of FATAL - * Version 1.0.0 - 2016-02-14 - ERROR_CONTEXT - * Version 1.1.0 - 2016-02-19 - -v OFF, -v INFO etc - * Version 1.1.1 - 2016-02-20 - textprintf vs strprintf - * Version 1.1.2 - 2016-02-22 - Remove g_alsologtostderr - * Version 1.1.3 - 2016-02-29 - ERROR_CONTEXT as linked list - * Version 1.2.0 - 2016-03-19 - Add get_thread_name() - * Version 1.2.1 - 2016-03-20 - Minor fixes - * Version 1.2.2 - 2016-03-29 - Fix issues with set_fatal_handler throwing an exception - * Version 1.2.3 - 2016-05-16 - Log current working directory in loguru::init(). - * Version 1.2.4 - 2016-05-18 - Custom replacement for -v in loguru::init() by bjoernpollex - * Version 1.2.5 - 2016-05-18 - Add ability to print ERROR_CONTEXT of parent thread. - * Version 1.2.6 - 2016-05-19 - Bug fix regarding VLOG verbosity argument lacking (). - * Version 1.2.7 - 2016-05-23 - Fix PATH_MAX problem. - * Version 1.2.8 - 2016-05-26 - Add shutdown() and remove_all_callbacks() - * Version 1.2.9 - 2016-06-09 - Use a monotonic clock for uptime. - * Version 1.3.0 - 2016-07-20 - Fix issues with callback flush/close not being called. - * Version 1.3.1 - 2016-07-20 - Add LOGURU_UNSAFE_SIGNAL_HANDLER to toggle stacktrace on signals. - * Version 1.3.2 - 2016-07-20 - Add loguru::arguments() - * Version 1.4.0 - 2016-09-15 - Semantic versioning + add loguru::create_directories - * Version 1.4.1 - 2016-09-29 - Customize formatting with LOGURU_FILENAME_WIDTH - * Version 1.5.0 - 2016-12-22 - LOGURU_USE_FMTLIB by kolis and LOGURU_WITH_FILEABS by scinart - * Version 1.5.1 - 2017-08-08 - Terminal colors on Windows 10 thanks to looki - * Version 1.6.0 - 2018-01-03 - Add LOGURU_RTTI and LOGURU_STACKTRACES settings - * Version 1.7.0 - 2018-01-03 - Add ability to turn off the preamble with loguru::g_preamble - * Version 1.7.1 - 2018-04-05 - Add function get_fatal_handler - * Version 1.7.2 - 2018-04-22 - Fix a bug where large file names could cause stack corruption (thanks @ccamporesi) - * Version 1.8.0 - 2018-04-23 - Shorten long file names to keep preamble fixed width - * Version 1.9.0 - 2018-09-22 - Adjust terminal colors, add LOGURU_VERBOSE_SCOPE_ENDINGS, add LOGURU_SCOPE_TIME_PRECISION, add named log levels - * Version 2.0.0 - 2018-09-22 - Split loguru.hpp into loguru.hpp and loguru.cpp - * Version 2.1.0 - 2019-09-23 - Update fmtlib + add option to loguru::init to NOT set main thread name. - * Version 2.2.0 - 2020-07-31 - Replace LOGURU_CATCH_SIGABRT with struct SignalOptions - -# Compiling - Just include where you want to use Loguru. - Then, in one .cpp file #include - Make sure you compile with -std=c++11 -lstdc++ -lpthread -ldl - -# Usage - For details, please see the official documentation at emilk.github.io/loguru - - #include - - int main(int argc, char* argv[]) { - loguru::init(argc, argv); - - // Put every log message in "everything.log": - loguru::add_file("everything.log", loguru::Append, loguru::Verbosity_MAX); - - LOG_F(INFO, "The magic number is %d", 42); - } - -*/ - -#if defined(LOGURU_IMPLEMENTATION) - #error "You are defining LOGURU_IMPLEMENTATION. This is for older versions of Loguru. You should now instead include loguru.cpp (or build it and link with it)" -#endif - -// Disable all warnings from gcc/clang: -#if defined(__clang__) - #pragma clang system_header -#elif defined(__GNUC__) - #pragma GCC system_header -#endif - -#ifndef LOGURU_HAS_DECLARED_FORMAT_HEADER -#define LOGURU_HAS_DECLARED_FORMAT_HEADER - -// Semantic versioning. Loguru version can be printed with printf("%d.%d.%d", LOGURU_VERSION_MAJOR, LOGURU_VERSION_MINOR, LOGURU_VERSION_PATCH); -#define LOGURU_VERSION_MAJOR 2 -#define LOGURU_VERSION_MINOR 1 -#define LOGURU_VERSION_PATCH 0 - -#if defined(_MSC_VER) -#include // Needed for _In_z_ etc annotations -#endif - -#if defined(__linux__) || defined(__APPLE__) -#define LOGURU_SYSLOG 1 -#else -#define LOGURU_SYSLOG 0 -#endif - -// ---------------------------------------------------------------------------- - -#ifndef LOGURU_EXPORT - // Define to your project's export declaration if needed for use in a shared library. - #define LOGURU_EXPORT -#endif - -#ifndef LOGURU_SCOPE_TEXT_SIZE - // Maximum length of text that can be printed by a LOG_SCOPE. - // This should be long enough to get most things, but short enough not to clutter the stack. - #define LOGURU_SCOPE_TEXT_SIZE 196 -#endif - -#ifndef LOGURU_FILENAME_WIDTH - // Width of the column containing the file name - #define LOGURU_FILENAME_WIDTH 23 -#endif - -#ifndef LOGURU_THREADNAME_WIDTH - // Width of the column containing the thread name - #define LOGURU_THREADNAME_WIDTH 16 -#endif - -#ifndef LOGURU_SCOPE_TIME_PRECISION - // Resolution of scope timers. 3=ms, 6=us, 9=ns - #define LOGURU_SCOPE_TIME_PRECISION 3 -#endif - -#ifdef LOGURU_CATCH_SIGABRT - #error "You are defining LOGURU_CATCH_SIGABRT. his is for older versions of Loguru. You should now instead set the options passed to loguru::init" -#endif - -#ifndef LOGURU_VERBOSE_SCOPE_ENDINGS - // Show milliseconds and scope name at end of scope. - #define LOGURU_VERBOSE_SCOPE_ENDINGS 1 -#endif - -#ifndef LOGURU_REDEFINE_ASSERT - #define LOGURU_REDEFINE_ASSERT 0 -#endif - -#ifndef LOGURU_WITH_STREAMS - #define LOGURU_WITH_STREAMS 0 -#endif - -#ifndef LOGURU_REPLACE_GLOG - #define LOGURU_REPLACE_GLOG 0 -#endif - -#if LOGURU_REPLACE_GLOG - #undef LOGURU_WITH_STREAMS - #define LOGURU_WITH_STREAMS 1 -#endif - -#if defined(LOGURU_UNSAFE_SIGNAL_HANDLER) - #error "You are defining LOGURU_UNSAFE_SIGNAL_HANDLER. This is for older versions of Loguru. You should now instead set the unsafe_signal_handler option when you call loguru::init." -#endif - -#if LOGURU_IMPLEMENTATION - #undef LOGURU_WITH_STREAMS - #define LOGURU_WITH_STREAMS 1 -#endif - -#ifndef LOGURU_USE_FMTLIB - #define LOGURU_USE_FMTLIB 0 -#endif - -#ifndef LOGURU_WITH_FILEABS - #define LOGURU_WITH_FILEABS 0 -#endif - -#ifndef LOGURU_RTTI -#if defined(__clang__) - #if __has_feature(cxx_rtti) - #define LOGURU_RTTI 1 - #endif -#elif defined(__GNUG__) - #if defined(__GXX_RTTI) - #define LOGURU_RTTI 1 - #endif -#elif defined(_MSC_VER) - #if defined(_CPPRTTI) - #define LOGURU_RTTI 1 - #endif -#endif -#endif - -// -------------------------------------------------------------------- -// Utility macros - -#define LOGURU_CONCATENATE_IMPL(s1, s2) s1 ## s2 -#define LOGURU_CONCATENATE(s1, s2) LOGURU_CONCATENATE_IMPL(s1, s2) - -#ifdef __COUNTER__ -# define LOGURU_ANONYMOUS_VARIABLE(str) LOGURU_CONCATENATE(str, __COUNTER__) -#else -# define LOGURU_ANONYMOUS_VARIABLE(str) LOGURU_CONCATENATE(str, __LINE__) -#endif - -#if defined(__clang__) || defined(__GNUC__) - // Helper macro for declaring functions as having similar signature to printf. - // This allows the compiler to catch format errors at compile-time. - #define LOGURU_PRINTF_LIKE(fmtarg, firstvararg) __attribute__((__format__ (__printf__, fmtarg, firstvararg))) - #define LOGURU_FORMAT_STRING_TYPE const char* -#elif defined(_MSC_VER) - #define LOGURU_PRINTF_LIKE(fmtarg, firstvararg) - #define LOGURU_FORMAT_STRING_TYPE _In_z_ _Printf_format_string_ const char* -#else - #define LOGURU_PRINTF_LIKE(fmtarg, firstvararg) - #define LOGURU_FORMAT_STRING_TYPE const char* -#endif - -// Used to mark log_and_abort for the benefit of the static analyzer and optimizer. -#if defined(_MSC_VER) -#define LOGURU_NORETURN __declspec(noreturn) -#else -#define LOGURU_NORETURN __attribute__((noreturn)) -#endif - -#if defined(_MSC_VER) -#define LOGURU_PREDICT_FALSE(x) (x) -#define LOGURU_PREDICT_TRUE(x) (x) -#else -#define LOGURU_PREDICT_FALSE(x) (__builtin_expect(x, 0)) -#define LOGURU_PREDICT_TRUE(x) (__builtin_expect(!!(x), 1)) -#endif - -#if LOGURU_USE_FMTLIB - #include - #define LOGURU_FMT(x) "{:" #x "}" -#else - #define LOGURU_FMT(x) "%" #x -#endif - -#ifdef _WIN32 - #define STRDUP(str) _strdup(str) -#else - #define STRDUP(str) strdup(str) -#endif - -// -------------------------------------------------------------------- - -namespace loguru -{ - // Simple RAII ownership of a char*. - class LOGURU_EXPORT Text - { - public: - explicit Text(char* owned_str) : _str(owned_str) {} - ~Text(); - Text(Text&& t) - { - _str = t._str; - t._str = nullptr; - } - Text(Text& t) = delete; - Text& operator=(Text& t) = delete; - void operator=(Text&& t) = delete; - - const char* c_str() const { return _str; } - bool empty() const { return _str == nullptr || *_str == '\0'; } - - char* release() - { - auto result = _str; - _str = nullptr; - return result; - } - - private: - char* _str; - }; - - // Like printf, but returns the formatted text. -#if LOGURU_USE_FMTLIB - LOGURU_EXPORT - Text vtextprintf(const char* format, fmt::format_args args); - - template - LOGURU_EXPORT - Text textprintf(LOGURU_FORMAT_STRING_TYPE format, const Args&... args) { - return vtextprintf(format, fmt::make_format_args(args...)); - } -#else - LOGURU_EXPORT - Text textprintf(LOGURU_FORMAT_STRING_TYPE format, ...) LOGURU_PRINTF_LIKE(1, 2); -#endif - - // Overloaded for variadic template matching. - LOGURU_EXPORT - Text textprintf(); - - using Verbosity = int; - -#undef FATAL -#undef ERROR -#undef WARNING -#undef INFO -#undef MAX - - enum NamedVerbosity : Verbosity - { - // Used to mark an invalid verbosity. Do not log to this level. - Verbosity_INVALID = -10, // Never do LOG_F(INVALID) - - // You may use Verbosity_OFF on g_stderr_verbosity, but for nothing else! - Verbosity_OFF = -9, // Never do LOG_F(OFF) - - // Prefer to use ABORT_F or ABORT_S over LOG_F(FATAL) or LOG_S(FATAL). - Verbosity_FATAL = -3, - Verbosity_ERROR = -2, - Verbosity_WARNING = -1, - - // Normal messages. By default written to stderr. - Verbosity_INFO = 0, - - // Same as Verbosity_INFO in every way. - Verbosity_0 = 0, - - // Verbosity levels 1-9 are generally not written to stderr, but are written to file. - Verbosity_1 = +1, - Verbosity_2 = +2, - Verbosity_3 = +3, - Verbosity_4 = +4, - Verbosity_5 = +5, - Verbosity_6 = +6, - Verbosity_7 = +7, - Verbosity_8 = +8, - Verbosity_9 = +9, - - // Don not use higher verbosity levels, as that will make grepping log files harder. - Verbosity_MAX = +9, - }; - - struct Message - { - // You would generally print a Message by just concatenating the buffers without spacing. - // Optionally, ignore preamble and indentation. - Verbosity verbosity; // Already part of preamble - const char* filename; // Already part of preamble - unsigned line; // Already part of preamble - const char* preamble; // Date, time, uptime, thread, file:line, verbosity. - const char* indentation; // Just a bunch of spacing. - const char* prefix; // Assertion failure info goes here (or ""). - const char* message; // User message goes here. - }; - - /* Everything with a verbosity equal or greater than g_stderr_verbosity will be - written to stderr. You can set this in code or via the -v argument. - Set to loguru::Verbosity_OFF to write nothing to stderr. - Default is 0, i.e. only log ERROR, WARNING and INFO are written to stderr. - */ - LOGURU_EXPORT extern Verbosity g_stderr_verbosity; - LOGURU_EXPORT extern bool g_colorlogtostderr; // True by default. - LOGURU_EXPORT extern unsigned g_flush_interval_ms; // 0 (unbuffered) by default. - LOGURU_EXPORT extern bool g_preamble_header; // Prepend each log start by a descriptions line with all columns name? True by default. - LOGURU_EXPORT extern bool g_preamble; // Prefix each log line with date, time etc? True by default. - - /* Specify the verbosity used by loguru to log its info messages including the header - logged when logged::init() is called or on exit. Default is 0 (INFO). - */ - LOGURU_EXPORT extern Verbosity g_internal_verbosity; - - // Turn off individual parts of the preamble - LOGURU_EXPORT extern bool g_preamble_date; // The date field - LOGURU_EXPORT extern bool g_preamble_time; // The time of the current day - LOGURU_EXPORT extern bool g_preamble_uptime; // The time since init call - LOGURU_EXPORT extern bool g_preamble_thread; // The logging thread - LOGURU_EXPORT extern bool g_preamble_file; // The file from which the log originates from - LOGURU_EXPORT extern bool g_preamble_verbose; // The verbosity field - LOGURU_EXPORT extern bool g_preamble_pipe; // The pipe symbol right before the message - - // May not throw! - typedef void (*log_handler_t)(void* user_data, const Message& message); - typedef void (*close_handler_t)(void* user_data); - typedef void (*flush_handler_t)(void* user_data); - - // May throw if that's how you'd like to handle your errors. - typedef void (*fatal_handler_t)(const Message& message); - - // Given a verbosity level, return the level's name or nullptr. - typedef const char* (*verbosity_to_name_t)(Verbosity verbosity); - - // Given a verbosity level name, return the verbosity level or - // Verbosity_INVALID if name is not recognized. - typedef Verbosity (*name_to_verbosity_t)(const char* name); - - struct SignalOptions - { - /// Make Loguru try to do unsafe but useful things, - /// like printing a stack trace, when catching signals. - /// This may lead to bad things like deadlocks in certain situations. - bool unsafe_signal_handler = true; - - /// Should Loguru catch SIGABRT ? - bool sigabrt = true; - - /// Should Loguru catch SIGBUS ? - bool sigbus = true; - - /// Should Loguru catch SIGFPE ? - bool sigfpe = true; - - /// Should Loguru catch SIGILL ? - bool sigill = true; - - /// Should Loguru catch SIGINT ? - bool sigint = true; - - /// Should Loguru catch SIGSEGV ? - bool sigsegv = true; - - /// Should Loguru catch SIGTERM ? - bool sigterm = true; - - static SignalOptions none() - { - SignalOptions options; - options.unsafe_signal_handler = false; - options.sigabrt = false; - options.sigbus = false; - options.sigfpe = false; - options.sigill = false; - options.sigint = false; - options.sigsegv = false; - options.sigterm = false; - return options; - } - }; - - // Runtime options passed to loguru::init - struct Options - { - // This allows you to use something else instead of "-v" via verbosity_flag. - // Set to nullptr to if you don't want Loguru to parse verbosity from the args.' - const char* verbosity_flag = "-v"; - - // loguru::init will set the name of the calling thread to this. - // If you don't want Loguru to set the name of the main thread, - // set this to nullptr. - // NOTE: on SOME platforms loguru::init will only overwrite the thread name - // if a thread name has not already been set. - // To always set a thread name, use loguru::set_thread_name instead. - const char* main_thread_name = "main thread"; - - SignalOptions signals; - }; - - /* Should be called from the main thread. - You don't *need* to call this, but if you do you get: - * Signal handlers installed - * Program arguments logged - * Working dir logged - * Optional -v verbosity flag parsed - * Main thread name set to "main thread" - * Explanation of the preamble (date, threanmae etc) logged - - loguru::init() will look for arguments meant for loguru and remove them. - Arguments meant for loguru are: - -v n Set loguru::g_stderr_verbosity level. Examples: - -v 3 Show verbosity level 3 and lower. - -v 0 Only show INFO, WARNING, ERROR, FATAL (default). - -v INFO Only show INFO, WARNING, ERROR, FATAL (default). - -v WARNING Only show WARNING, ERROR, FATAL. - -v ERROR Only show ERROR, FATAL. - -v FATAL Only show FATAL. - -v OFF Turn off logging to stderr. - - Tip: You can set g_stderr_verbosity before calling loguru::init. - That way you can set the default but have the user override it with the -v flag. - Note that -v does not affect file logging (see loguru::add_file). - - You can you something other than the -v flag by setting the verbosity_flag option. - */ - LOGURU_EXPORT - void init(int& argc, char* argv[], const Options& options = {}); - - // Will call remove_all_callbacks(). After calling this, logging will still go to stderr. - // You generally don't need to call this. - LOGURU_EXPORT - void shutdown(); - - // What ~ will be replaced with, e.g. "/home/your_user_name/" - LOGURU_EXPORT - const char* home_dir(); - - /* Returns the name of the app as given in argv[0] but without leading path. - That is, if argv[0] is "../foo/app" this will return "app". - */ - LOGURU_EXPORT - const char* argv0_filename(); - - // Returns all arguments given to loguru::init(), but escaped with a single space as separator. - LOGURU_EXPORT - const char* arguments(); - - // Returns the path to the current working dir when loguru::init() was called. - LOGURU_EXPORT - const char* current_dir(); - - // Returns the part of the path after the last / or \ (if any). - LOGURU_EXPORT - const char* filename(const char* path); - - // e.g. "foo/bar/baz.ext" will create the directories "foo/" and "foo/bar/" - LOGURU_EXPORT - bool create_directories(const char* file_path_const); - - // Writes date and time with millisecond precision, e.g. "20151017_161503.123" - LOGURU_EXPORT - void write_date_time(char* buff, unsigned buff_size); - - // Helper: thread-safe version strerror - LOGURU_EXPORT - Text errno_as_text(); - - /* Given a prefix of e.g. "~/loguru/" this might return - "/home/your_username/loguru/app_name/20151017_161503.123.log" - - where "app_name" is a sanitized version of argv[0]. - */ - LOGURU_EXPORT - void suggest_log_path(const char* prefix, char* buff, unsigned buff_size); - - enum FileMode { Truncate, Append }; - - /* Will log to a file at the given path. - Any logging message with a verbosity lower or equal to - the given verbosity will be included. - The function will create all directories in 'path' if needed. - If path starts with a ~, it will be replaced with loguru::home_dir() - To stop the file logging, just call loguru::remove_callback(path) with the same path. - */ - LOGURU_EXPORT - bool add_file(const char* path, FileMode mode, Verbosity verbosity); - - LOGURU_EXPORT - // Send logs to syslog with LOG_USER facility (see next call) - bool add_syslog(const char* app_name, Verbosity verbosity); - LOGURU_EXPORT - // Send logs to syslog with your own choice of facility (LOG_USER, LOG_AUTH, ...) - // see loguru.cpp: syslog_log() for more details. - bool add_syslog(const char* app_name, Verbosity verbosity, int facility); - - /* Will be called right before abort(). - You can for instance use this to print custom error messages, or throw an exception. - Feel free to call LOG:ing function from this, but not FATAL ones! */ - LOGURU_EXPORT - void set_fatal_handler(fatal_handler_t handler); - - // Get the current fatal handler, if any. Default value is nullptr. - LOGURU_EXPORT - fatal_handler_t get_fatal_handler(); - - /* Will be called on each log messages with a verbosity less or equal to the given one. - Useful for displaying messages on-screen in a game, for example. - The given on_close is also expected to flush (if desired). - */ - LOGURU_EXPORT - void add_callback( - const char* id, - log_handler_t callback, - void* user_data, - Verbosity verbosity, - close_handler_t on_close = nullptr, - flush_handler_t on_flush = nullptr); - - /* Set a callback that returns custom verbosity level names. If callback - is nullptr or returns nullptr, default log names will be used. - */ - LOGURU_EXPORT - void set_verbosity_to_name_callback(verbosity_to_name_t callback); - - /* Set a callback that returns the verbosity level matching a name. The - callback should return Verbosity_INVALID if the name is not - recognized. - */ - LOGURU_EXPORT - void set_name_to_verbosity_callback(name_to_verbosity_t callback); - - /* Get a custom name for a specific verbosity, if one exists, or nullptr. */ - LOGURU_EXPORT - const char* get_verbosity_name(Verbosity verbosity); - - /* Get the verbosity enum value from a custom 4-character level name, if one exists. - If the name does not match a custom level name, Verbosity_INVALID is returned. - */ - LOGURU_EXPORT - Verbosity get_verbosity_from_name(const char* name); - - // Returns true iff the callback was found (and removed). - LOGURU_EXPORT - bool remove_callback(const char* id); - - // Shut down all file logging and any other callback hooks installed. - LOGURU_EXPORT - void remove_all_callbacks(); - - // Returns the maximum of g_stderr_verbosity and all file/custom outputs. - LOGURU_EXPORT - Verbosity current_verbosity_cutoff(); - -#if LOGURU_USE_FMTLIB - // Internal functions - LOGURU_EXPORT - void vlog(Verbosity verbosity, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, fmt::format_args args); - LOGURU_EXPORT - void raw_vlog(Verbosity verbosity, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, fmt::format_args args); - - // Actual logging function. Use the LOG macro instead of calling this directly. - template - LOGURU_EXPORT - void log(Verbosity verbosity, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, const Args &... args) { - vlog(verbosity, file, line, format, fmt::make_format_args(args...)); - } - - // Log without any preamble or indentation. - template - LOGURU_EXPORT - void raw_log(Verbosity verbosity, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, const Args &... args) { - raw_vlog(verbosity, file, line, format, fmt::make_format_args(args...)); - } -#else // LOGURU_USE_FMTLIB? - // Actual logging function. Use the LOG macro instead of calling this directly. - LOGURU_EXPORT - void log(Verbosity verbosity, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, ...) LOGURU_PRINTF_LIKE(4, 5); - - // Log without any preamble or indentation. - LOGURU_EXPORT - void raw_log(Verbosity verbosity, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, ...) LOGURU_PRINTF_LIKE(4, 5); -#endif // !LOGURU_USE_FMTLIB - - // Helper class for LOG_SCOPE_F - class LOGURU_EXPORT LogScopeRAII - { - public: - LogScopeRAII() : _file(nullptr) {} // No logging - LogScopeRAII(Verbosity verbosity, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, ...) LOGURU_PRINTF_LIKE(5, 6); - ~LogScopeRAII(); - -#if defined(_MSC_VER) && _MSC_VER > 1800 - // older MSVC default move ctors close the scope on move. See - // issue #43 - LogScopeRAII(LogScopeRAII&& other) - : _verbosity(other._verbosity) - , _file(other._file) - , _line(other._line) - , _indent_stderr(other._indent_stderr) - , _start_time_ns(other._start_time_ns) - { - // Make sure the tmp object's destruction doesn't close the scope: - other._file = nullptr; - - for (unsigned int i = 0; i < LOGURU_SCOPE_TEXT_SIZE; ++i) { - _name[i] = other._name[i]; - } - } -#else - LogScopeRAII(LogScopeRAII&&) = default; -#endif - - private: - LogScopeRAII(const LogScopeRAII&) = delete; - LogScopeRAII& operator=(const LogScopeRAII&) = delete; - void operator=(LogScopeRAII&&) = delete; - - Verbosity _verbosity; - const char* _file; // Set to null if we are disabled due to verbosity - unsigned _line; - bool _indent_stderr; // Did we? - long long _start_time_ns; - char _name[LOGURU_SCOPE_TEXT_SIZE]; - }; - - // Marked as 'noreturn' for the benefit of the static analyzer and optimizer. - // stack_trace_skip is the number of extrace stack frames to skip above log_and_abort. -#if LOGURU_USE_FMTLIB - LOGURU_EXPORT - LOGURU_NORETURN void vlog_and_abort(int stack_trace_skip, const char* expr, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, fmt::format_args); - template - LOGURU_EXPORT - LOGURU_NORETURN void log_and_abort(int stack_trace_skip, const char* expr, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, const Args&... args) { - vlog_and_abort(stack_trace_skip, expr, file, line, format, fmt::make_format_args(args...)); - } -#else - LOGURU_EXPORT - LOGURU_NORETURN void log_and_abort(int stack_trace_skip, const char* expr, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, ...) LOGURU_PRINTF_LIKE(5, 6); -#endif - LOGURU_EXPORT - LOGURU_NORETURN void log_and_abort(int stack_trace_skip, const char* expr, const char* file, unsigned line); - - // Flush output to stderr and files. - // If g_flush_interval_ms is set to non-zero, this will be called automatically this often. - // If not set, you do not need to call this at all. - LOGURU_EXPORT - void flush(); - - template inline Text format_value(const T&) { return textprintf("N/A"); } - template<> inline Text format_value(const char& v) { return textprintf(LOGURU_FMT(c), v); } - template<> inline Text format_value(const int& v) { return textprintf(LOGURU_FMT(d), v); } - template<> inline Text format_value(const unsigned int& v) { return textprintf(LOGURU_FMT(u), v); } - template<> inline Text format_value(const long& v) { return textprintf(LOGURU_FMT(lu), v); } - template<> inline Text format_value(const unsigned long& v) { return textprintf(LOGURU_FMT(ld), v); } - template<> inline Text format_value(const long long& v) { return textprintf(LOGURU_FMT(llu), v); } - template<> inline Text format_value(const unsigned long long& v) { return textprintf(LOGURU_FMT(lld), v); } - template<> inline Text format_value(const float& v) { return textprintf(LOGURU_FMT(f), v); } - template<> inline Text format_value(const double& v) { return textprintf(LOGURU_FMT(f), v); } - - /* Thread names can be set for the benefit of readable logs. - If you do not set the thread name, a hex id will be shown instead. - These thread names may or may not be the same as the system thread names, - depending on the system. - Try to limit the thread name to 15 characters or less. */ - LOGURU_EXPORT - void set_thread_name(const char* name); - - /* Returns the thread name for this thread. - On most *nix systems this will return the system thread name (settable from both within and without Loguru). - On other systems it will return whatever you set in `set_thread_name()`; - If no thread name is set, this will return a hexadecimal thread id. - `length` should be the number of bytes available in the buffer. - 17 is a good number for length. - `right_align_hex_id` means any hexadecimal thread id will be written to the end of buffer. - */ - LOGURU_EXPORT - void get_thread_name(char* buffer, unsigned long long length, bool right_align_hex_id); - - /* Generates a readable stacktrace as a string. - 'skip' specifies how many stack frames to skip. - For instance, the default skip (1) means: - don't include the call to loguru::stacktrace in the stack trace. */ - LOGURU_EXPORT - Text stacktrace(int skip = 1); - - /* Add a string to be replaced with something else in the stack output. - - For instance, instead of having a stack trace look like this: - 0x41f541 some_function(std::basic_ofstream >&) - You can clean it up with: - auto verbose_type_name = loguru::demangle(typeid(std::ofstream).name()); - loguru::add_stack_cleanup(verbose_type_name.c_str(); "std::ofstream"); - So the next time you will instead see: - 0x41f541 some_function(std::ofstream&) - - `replace_with_this` must be shorter than `find_this`. - */ - LOGURU_EXPORT - void add_stack_cleanup(const char* find_this, const char* replace_with_this); - - // Example: demangle(typeid(std::ofstream).name()) -> "std::basic_ofstream >" - LOGURU_EXPORT - Text demangle(const char* name); - - // ------------------------------------------------------------------------ - /* - Not all terminals support colors, but if they do, and g_colorlogtostderr - is set, Loguru will write them to stderr to make errors in red, etc. - - You also have the option to manually use them, via the function below. - - Note, however, that if you do, the color codes could end up in your logfile! - - This means if you intend to use them functions you should either: - * Use them on the stderr/stdout directly (bypass Loguru). - * Don't add file outputs to Loguru. - * Expect some \e[1m things in your logfile. - - Usage: - printf("%sRed%sGreen%sBold green%sClear again\n", - loguru::terminal_red(), loguru::terminal_green(), - loguru::terminal_bold(), loguru::terminal_reset()); - - If the terminal at hand does not support colors the above output - will just not have funky \e[1m things showing. - */ - - // Do the output terminal support colors? - LOGURU_EXPORT - bool terminal_has_color(); - - // Colors - LOGURU_EXPORT const char* terminal_black(); - LOGURU_EXPORT const char* terminal_red(); - LOGURU_EXPORT const char* terminal_green(); - LOGURU_EXPORT const char* terminal_yellow(); - LOGURU_EXPORT const char* terminal_blue(); - LOGURU_EXPORT const char* terminal_purple(); - LOGURU_EXPORT const char* terminal_cyan(); - LOGURU_EXPORT const char* terminal_light_gray(); - LOGURU_EXPORT const char* terminal_light_red(); - LOGURU_EXPORT const char* terminal_white(); - - // Formatting - LOGURU_EXPORT const char* terminal_bold(); - LOGURU_EXPORT const char* terminal_underline(); - - // You should end each line with this! - LOGURU_EXPORT const char* terminal_reset(); - - // -------------------------------------------------------------------- - // Error context related: - - struct StringStream; - - // Use this in your EcEntryBase::print_value overload. - LOGURU_EXPORT - void stream_print(StringStream& out_string_stream, const char* text); - - class LOGURU_EXPORT EcEntryBase - { - public: - EcEntryBase(const char* file, unsigned line, const char* descr); - ~EcEntryBase(); - EcEntryBase(const EcEntryBase&) = delete; - EcEntryBase(EcEntryBase&&) = delete; - EcEntryBase& operator=(const EcEntryBase&) = delete; - EcEntryBase& operator=(EcEntryBase&&) = delete; - - virtual void print_value(StringStream& out_string_stream) const = 0; - - EcEntryBase* previous() const { return _previous; } - - // private: - const char* _file; - unsigned _line; - const char* _descr; - EcEntryBase* _previous; - }; - - template - class EcEntryData : public EcEntryBase - { - public: - using Printer = Text(*)(T data); - - EcEntryData(const char* file, unsigned line, const char* descr, T data, Printer&& printer) - : EcEntryBase(file, line, descr), _data(data), _printer(printer) {} - - virtual void print_value(StringStream& out_string_stream) const override - { - const auto str = _printer(_data); - stream_print(out_string_stream, str.c_str()); - } - - private: - T _data; - Printer _printer; - }; - - // template - // class EcEntryLambda : public EcEntryBase - // { - // public: - // EcEntryLambda(const char* file, unsigned line, const char* descr, Printer&& printer) - // : EcEntryBase(file, line, descr), _printer(std::move(printer)) {} - - // virtual void print_value(StringStream& out_string_stream) const override - // { - // const auto str = _printer(); - // stream_print(out_string_stream, str.c_str()); - // } - - // private: - // Printer _printer; - // }; - - // template - // EcEntryLambda make_ec_entry_lambda(const char* file, unsigned line, const char* descr, Printer&& printer) - // { - // return {file, line, descr, std::move(printer)}; - // } - - template - struct decay_char_array { using type = T; }; - - template - struct decay_char_array { using type = const char*; }; - - template - struct make_const_ptr { using type = T; }; - - template - struct make_const_ptr { using type = const T*; }; - - template - struct make_ec_type { using type = typename make_const_ptr::type>::type; }; - - /* A stack trace gives you the names of the function at the point of a crash. - With ERROR_CONTEXT, you can also get the values of select local variables. - Usage: - - void process_customers(const std::string& filename) - { - ERROR_CONTEXT("Processing file", filename.c_str()); - for (int customer_index : ...) - { - ERROR_CONTEXT("Customer index", customer_index); - ... - } - } - - The context is in effect during the scope of the ERROR_CONTEXT. - Use loguru::get_error_context() to get the contents of the active error contexts. - - Example result: - - ------------------------------------------------ - [ErrorContext] main.cpp:416 Processing file: "customers.json" - [ErrorContext] main.cpp:417 Customer index: 42 - ------------------------------------------------ - - Error contexts are printed automatically on crashes, and only on crashes. - This makes them much faster than logging the value of a variable. - */ - #define ERROR_CONTEXT(descr, data) \ - const loguru::EcEntryData::type> \ - LOGURU_ANONYMOUS_VARIABLE(error_context_scope_)( \ - __FILE__, __LINE__, descr, data, \ - static_cast::type>::Printer>(loguru::ec_to_text) ) // For better error messages - -/* - #define ERROR_CONTEXT(descr, data) \ - const auto LOGURU_ANONYMOUS_VARIABLE(error_context_scope_)( \ - loguru::make_ec_entry_lambda(__FILE__, __LINE__, descr, \ - [=](){ return loguru::ec_to_text(data); })) -*/ - - using EcHandle = const EcEntryBase*; - - /* - Get a light-weight handle to the error context stack on this thread. - The handle is valid as long as the current thread has no changes to its error context stack. - You can pass the handle to loguru::get_error_context on another thread. - This can be very useful for when you have a parent thread spawning several working threads, - and you want the error context of the parent thread to get printed (too) when there is an - error on the child thread. You can accomplish this thusly: - - void foo(const char* parameter) - { - ERROR_CONTEXT("parameter", parameter) - const auto parent_ec_handle = loguru::get_thread_ec_handle(); - - std::thread([=]{ - loguru::set_thread_name("child thread"); - ERROR_CONTEXT("parent context", parent_ec_handle); - dangerous_code(); - }.join(); - } - - */ - LOGURU_EXPORT - EcHandle get_thread_ec_handle(); - - // Get a string describing the current stack of error context. Empty string if there is none. - LOGURU_EXPORT - Text get_error_context(); - - // Get a string describing the error context of the given thread handle. - LOGURU_EXPORT - Text get_error_context_for(EcHandle ec_handle); - - // ------------------------------------------------------------------------ - - LOGURU_EXPORT Text ec_to_text(const char* data); - LOGURU_EXPORT Text ec_to_text(char data); - LOGURU_EXPORT Text ec_to_text(int data); - LOGURU_EXPORT Text ec_to_text(unsigned int data); - LOGURU_EXPORT Text ec_to_text(long data); - LOGURU_EXPORT Text ec_to_text(unsigned long data); - LOGURU_EXPORT Text ec_to_text(long long data); - LOGURU_EXPORT Text ec_to_text(unsigned long long data); - LOGURU_EXPORT Text ec_to_text(float data); - LOGURU_EXPORT Text ec_to_text(double data); - LOGURU_EXPORT Text ec_to_text(long double data); - LOGURU_EXPORT Text ec_to_text(EcHandle); - - /* - You can add ERROR_CONTEXT support for your own types by overloading ec_to_text. Here's how: - - some.hpp: - namespace loguru { - Text ec_to_text(MySmallType data) - Text ec_to_text(const MyBigType* data) - } // namespace loguru - - some.cpp: - namespace loguru { - Text ec_to_text(MySmallType small_value) - { - // Called only when needed, i.e. on a crash. - std::string str = small_value.as_string(); // Format 'small_value' here somehow. - return Text{STRDUP(str.c_str())}; - } - - Text ec_to_text(const MyBigType* big_value) - { - // Called only when needed, i.e. on a crash. - std::string str = big_value->as_string(); // Format 'big_value' here somehow. - return Text{STRDUP(str.c_str())}; - } - } // namespace loguru - - Any file that include some.hpp: - void foo(MySmallType small, const MyBigType& big) - { - ERROR_CONTEXT("Small", small); // Copy ´small` by value. - ERROR_CONTEXT("Big", &big); // `big` should not change during this scope! - .... - } - */ -} // namespace loguru - -// -------------------------------------------------------------------- -// Logging macros - -// LOG_F(2, "Only logged if verbosity is 2 or higher: %d", some_number); -#define VLOG_F(verbosity, ...) \ - ((verbosity) > loguru::current_verbosity_cutoff()) ? (void)0 \ - : loguru::log(verbosity, __FILE__, __LINE__, __VA_ARGS__) - -// LOG_F(INFO, "Foo: %d", some_number); -#define LOG_F(verbosity_name, ...) VLOG_F(loguru::Verbosity_ ## verbosity_name, __VA_ARGS__) - -#define VLOG_IF_F(verbosity, cond, ...) \ - ((verbosity) > loguru::current_verbosity_cutoff() || (cond) == false) \ - ? (void)0 \ - : loguru::log(verbosity, __FILE__, __LINE__, __VA_ARGS__) - -#define LOG_IF_F(verbosity_name, cond, ...) \ - VLOG_IF_F(loguru::Verbosity_ ## verbosity_name, cond, __VA_ARGS__) - -#define VLOG_SCOPE_F(verbosity, ...) \ - loguru::LogScopeRAII LOGURU_ANONYMOUS_VARIABLE(error_context_RAII_) = \ - ((verbosity) > loguru::current_verbosity_cutoff()) ? loguru::LogScopeRAII() : \ - loguru::LogScopeRAII(verbosity, __FILE__, __LINE__, __VA_ARGS__) - -// Raw logging - no preamble, no indentation. Slightly faster than full logging. -#define RAW_VLOG_F(verbosity, ...) \ - ((verbosity) > loguru::current_verbosity_cutoff()) ? (void)0 \ - : loguru::raw_log(verbosity, __FILE__, __LINE__, __VA_ARGS__) - -#define RAW_LOG_F(verbosity_name, ...) RAW_VLOG_F(loguru::Verbosity_ ## verbosity_name, __VA_ARGS__) - -// Use to book-end a scope. Affects logging on all threads. -#define LOG_SCOPE_F(verbosity_name, ...) \ - VLOG_SCOPE_F(loguru::Verbosity_ ## verbosity_name, __VA_ARGS__) - -#define LOG_SCOPE_FUNCTION(verbosity_name) LOG_SCOPE_F(verbosity_name, __func__) - -// ----------------------------------------------- -// ABORT_F macro. Usage: ABORT_F("Cause of error: %s", error_str); - -// Message is optional -#define ABORT_F(...) loguru::log_and_abort(0, "ABORT: ", __FILE__, __LINE__, __VA_ARGS__) - -// -------------------------------------------------------------------- -// CHECK_F macros: - -#define CHECK_WITH_INFO_F(test, info, ...) \ - LOGURU_PREDICT_TRUE((test) == true) ? (void)0 : loguru::log_and_abort(0, "CHECK FAILED: " info " ", __FILE__, \ - __LINE__, ##__VA_ARGS__) - -/* Checked at runtime too. Will print error, then call fatal_handler (if any), then 'abort'. - Note that the test must be boolean. - CHECK_F(ptr); will not compile, but CHECK_F(ptr != nullptr); will. */ -#define CHECK_F(test, ...) CHECK_WITH_INFO_F(test, #test, ##__VA_ARGS__) - -#define CHECK_NOTNULL_F(x, ...) CHECK_WITH_INFO_F((x) != nullptr, #x " != nullptr", ##__VA_ARGS__) - -#define CHECK_OP_F(expr_left, expr_right, op, ...) \ - do \ - { \ - auto val_left = expr_left; \ - auto val_right = expr_right; \ - if (! LOGURU_PREDICT_TRUE(val_left op val_right)) \ - { \ - auto str_left = loguru::format_value(val_left); \ - auto str_right = loguru::format_value(val_right); \ - auto fail_info = loguru::textprintf("CHECK FAILED: " LOGURU_FMT(s) " " LOGURU_FMT(s) " " LOGURU_FMT(s) " (" LOGURU_FMT(s) " " LOGURU_FMT(s) " " LOGURU_FMT(s) ") ", \ - #expr_left, #op, #expr_right, str_left.c_str(), #op, str_right.c_str()); \ - auto user_msg = loguru::textprintf(__VA_ARGS__); \ - loguru::log_and_abort(0, fail_info.c_str(), __FILE__, __LINE__, \ - LOGURU_FMT(s), user_msg.c_str()); \ - } \ - } while (false) - -#ifndef LOGURU_DEBUG_LOGGING - #ifndef NDEBUG - #define LOGURU_DEBUG_LOGGING 1 - #else - #define LOGURU_DEBUG_LOGGING 0 - #endif -#endif - -#if LOGURU_DEBUG_LOGGING - // Debug logging enabled: - #define DLOG_F(verbosity_name, ...) LOG_F(verbosity_name, __VA_ARGS__) - #define DVLOG_F(verbosity, ...) VLOG_F(verbosity, __VA_ARGS__) - #define DLOG_IF_F(verbosity_name, ...) LOG_IF_F(verbosity_name, __VA_ARGS__) - #define DVLOG_IF_F(verbosity, ...) VLOG_IF_F(verbosity, __VA_ARGS__) - #define DRAW_LOG_F(verbosity_name, ...) RAW_LOG_F(verbosity_name, __VA_ARGS__) - #define DRAW_VLOG_F(verbosity, ...) RAW_VLOG_F(verbosity, __VA_ARGS__) -#else - // Debug logging disabled: - #define DLOG_F(verbosity_name, ...) - #define DVLOG_F(verbosity, ...) - #define DLOG_IF_F(verbosity_name, ...) - #define DVLOG_IF_F(verbosity, ...) - #define DRAW_LOG_F(verbosity_name, ...) - #define DRAW_VLOG_F(verbosity, ...) -#endif - -#define CHECK_EQ_F(a, b, ...) CHECK_OP_F(a, b, ==, ##__VA_ARGS__) -#define CHECK_NE_F(a, b, ...) CHECK_OP_F(a, b, !=, ##__VA_ARGS__) -#define CHECK_LT_F(a, b, ...) CHECK_OP_F(a, b, < , ##__VA_ARGS__) -#define CHECK_GT_F(a, b, ...) CHECK_OP_F(a, b, > , ##__VA_ARGS__) -#define CHECK_LE_F(a, b, ...) CHECK_OP_F(a, b, <=, ##__VA_ARGS__) -#define CHECK_GE_F(a, b, ...) CHECK_OP_F(a, b, >=, ##__VA_ARGS__) - -#ifndef LOGURU_DEBUG_CHECKS - #ifndef NDEBUG - #define LOGURU_DEBUG_CHECKS 1 - #else - #define LOGURU_DEBUG_CHECKS 0 - #endif -#endif - -#if LOGURU_DEBUG_CHECKS - // Debug checks enabled: - #define DCHECK_F(test, ...) CHECK_F(test, ##__VA_ARGS__) - #define DCHECK_NOTNULL_F(x, ...) CHECK_NOTNULL_F(x, ##__VA_ARGS__) - #define DCHECK_EQ_F(a, b, ...) CHECK_EQ_F(a, b, ##__VA_ARGS__) - #define DCHECK_NE_F(a, b, ...) CHECK_NE_F(a, b, ##__VA_ARGS__) - #define DCHECK_LT_F(a, b, ...) CHECK_LT_F(a, b, ##__VA_ARGS__) - #define DCHECK_LE_F(a, b, ...) CHECK_LE_F(a, b, ##__VA_ARGS__) - #define DCHECK_GT_F(a, b, ...) CHECK_GT_F(a, b, ##__VA_ARGS__) - #define DCHECK_GE_F(a, b, ...) CHECK_GE_F(a, b, ##__VA_ARGS__) -#else - // Debug checks disabled: - #define DCHECK_F(test, ...) - #define DCHECK_NOTNULL_F(x, ...) - #define DCHECK_EQ_F(a, b, ...) - #define DCHECK_NE_F(a, b, ...) - #define DCHECK_LT_F(a, b, ...) - #define DCHECK_LE_F(a, b, ...) - #define DCHECK_GT_F(a, b, ...) - #define DCHECK_GE_F(a, b, ...) -#endif // NDEBUG - - -#if LOGURU_REDEFINE_ASSERT - #undef assert - #ifndef NDEBUG - // Debug: - #define assert(test) CHECK_WITH_INFO_F(!!(test), #test) // HACK - #else - #define assert(test) - #endif -#endif // LOGURU_REDEFINE_ASSERT - -#endif // LOGURU_HAS_DECLARED_FORMAT_HEADER - -// ---------------------------------------------------------------------------- -// .dP"Y8 888888 88""Yb 888888 db 8b d8 .dP"Y8 -// `Ybo." 88 88__dP 88__ dPYb 88b d88 `Ybo." -// o.`Y8b 88 88"Yb 88"" dP__Yb 88YbdP88 o.`Y8b -// 8bodP' 88 88 Yb 888888 dP""""Yb 88 YY 88 8bodP' - -#if LOGURU_WITH_STREAMS -#ifndef LOGURU_HAS_DECLARED_STREAMS_HEADER -#define LOGURU_HAS_DECLARED_STREAMS_HEADER - -/* This file extends loguru to enable std::stream-style logging, a la Glog. - It's an optional feature behind the LOGURU_WITH_STREAMS settings - because including it everywhere will slow down compilation times. -*/ - -#include -#include // Adds about 38 kLoC on clang. -#include - -namespace loguru -{ - // Like sprintf, but returns the formatted text. - LOGURU_EXPORT - std::string strprintf(LOGURU_FORMAT_STRING_TYPE format, ...) LOGURU_PRINTF_LIKE(1, 2); - - // Like vsprintf, but returns the formatted text. - LOGURU_EXPORT - std::string vstrprintf(LOGURU_FORMAT_STRING_TYPE format, va_list) LOGURU_PRINTF_LIKE(1, 0); - - class LOGURU_EXPORT StreamLogger - { - public: - StreamLogger(Verbosity verbosity, const char* file, unsigned line) : _verbosity(verbosity), _file(file), _line(line) {} - ~StreamLogger() noexcept(false); - - template - StreamLogger& operator<<(const T& t) - { - _ss << t; - return *this; - } - - // std::endl and other iomanip:s. - StreamLogger& operator<<(std::ostream&(*f)(std::ostream&)) - { - f(_ss); - return *this; - } - - private: - Verbosity _verbosity; - const char* _file; - unsigned _line; - std::ostringstream _ss; - }; - - class LOGURU_EXPORT AbortLogger - { - public: - AbortLogger(const char* expr, const char* file, unsigned line) : _expr(expr), _file(file), _line(line) { } - LOGURU_NORETURN ~AbortLogger() noexcept(false); - - template - AbortLogger& operator<<(const T& t) - { - _ss << t; - return *this; - } - - // std::endl and other iomanip:s. - AbortLogger& operator<<(std::ostream&(*f)(std::ostream&)) - { - f(_ss); - return *this; - } - - private: - const char* _expr; - const char* _file; - unsigned _line; - std::ostringstream _ss; - }; - - class LOGURU_EXPORT Voidify - { - public: - Voidify() {} - // This has to be an operator with a precedence lower than << but higher than ?: - void operator&(const StreamLogger&) { } - void operator&(const AbortLogger&) { } - }; - - /* Helper functions for CHECK_OP_S macro. - GLOG trick: The (int, int) specialization works around the issue that the compiler - will not instantiate the template version of the function on values of unnamed enum type. */ - #define DEFINE_CHECK_OP_IMPL(name, op) \ - template \ - inline std::string* name(const char* expr, const T1& v1, const char* op_str, const T2& v2) \ - { \ - if (LOGURU_PREDICT_TRUE(v1 op v2)) { return NULL; } \ - std::ostringstream ss; \ - ss << "CHECK FAILED: " << expr << " (" << v1 << " " << op_str << " " << v2 << ") "; \ - return new std::string(ss.str()); \ - } \ - inline std::string* name(const char* expr, int v1, const char* op_str, int v2) \ - { \ - return name(expr, v1, op_str, v2); \ - } - - DEFINE_CHECK_OP_IMPL(check_EQ_impl, ==) - DEFINE_CHECK_OP_IMPL(check_NE_impl, !=) - DEFINE_CHECK_OP_IMPL(check_LE_impl, <=) - DEFINE_CHECK_OP_IMPL(check_LT_impl, < ) - DEFINE_CHECK_OP_IMPL(check_GE_impl, >=) - DEFINE_CHECK_OP_IMPL(check_GT_impl, > ) - #undef DEFINE_CHECK_OP_IMPL - - /* GLOG trick: Function is overloaded for integral types to allow static const integrals - declared in classes and not defined to be used as arguments to CHECK* macros. */ - template - inline const T& referenceable_value(const T& t) { return t; } - inline char referenceable_value(char t) { return t; } - inline unsigned char referenceable_value(unsigned char t) { return t; } - inline signed char referenceable_value(signed char t) { return t; } - inline short referenceable_value(short t) { return t; } - inline unsigned short referenceable_value(unsigned short t) { return t; } - inline int referenceable_value(int t) { return t; } - inline unsigned int referenceable_value(unsigned int t) { return t; } - inline long referenceable_value(long t) { return t; } - inline unsigned long referenceable_value(unsigned long t) { return t; } - inline long long referenceable_value(long long t) { return t; } - inline unsigned long long referenceable_value(unsigned long long t) { return t; } -} // namespace loguru - -// ----------------------------------------------- -// Logging macros: - -// usage: LOG_STREAM(INFO) << "Foo " << std::setprecision(10) << some_value; -#define VLOG_IF_S(verbosity, cond) \ - ((verbosity) > loguru::current_verbosity_cutoff() || (cond) == false) \ - ? (void)0 \ - : loguru::Voidify() & loguru::StreamLogger(verbosity, __FILE__, __LINE__) -#define LOG_IF_S(verbosity_name, cond) VLOG_IF_S(loguru::Verbosity_ ## verbosity_name, cond) -#define VLOG_S(verbosity) VLOG_IF_S(verbosity, true) -#define LOG_S(verbosity_name) VLOG_S(loguru::Verbosity_ ## verbosity_name) - -// ----------------------------------------------- -// ABORT_S macro. Usage: ABORT_S() << "Causo of error: " << details; - -#define ABORT_S() loguru::Voidify() & loguru::AbortLogger("ABORT: ", __FILE__, __LINE__) - -// ----------------------------------------------- -// CHECK_S macros: - -#define CHECK_WITH_INFO_S(cond, info) \ - LOGURU_PREDICT_TRUE((cond) == true) \ - ? (void)0 \ - : loguru::Voidify() & loguru::AbortLogger("CHECK FAILED: " info " ", __FILE__, __LINE__) - -#define CHECK_S(cond) CHECK_WITH_INFO_S(cond, #cond) -#define CHECK_NOTNULL_S(x) CHECK_WITH_INFO_S((x) != nullptr, #x " != nullptr") - -#define CHECK_OP_S(function_name, expr1, op, expr2) \ - while (auto error_string = loguru::function_name(#expr1 " " #op " " #expr2, \ - loguru::referenceable_value(expr1), #op, \ - loguru::referenceable_value(expr2))) \ - loguru::AbortLogger(error_string->c_str(), __FILE__, __LINE__) - -#define CHECK_EQ_S(expr1, expr2) CHECK_OP_S(check_EQ_impl, expr1, ==, expr2) -#define CHECK_NE_S(expr1, expr2) CHECK_OP_S(check_NE_impl, expr1, !=, expr2) -#define CHECK_LE_S(expr1, expr2) CHECK_OP_S(check_LE_impl, expr1, <=, expr2) -#define CHECK_LT_S(expr1, expr2) CHECK_OP_S(check_LT_impl, expr1, < , expr2) -#define CHECK_GE_S(expr1, expr2) CHECK_OP_S(check_GE_impl, expr1, >=, expr2) -#define CHECK_GT_S(expr1, expr2) CHECK_OP_S(check_GT_impl, expr1, > , expr2) - -#if LOGURU_DEBUG_LOGGING - // Debug logging enabled: - #define DVLOG_IF_S(verbosity, cond) VLOG_IF_S(verbosity, cond) - #define DLOG_IF_S(verbosity_name, cond) LOG_IF_S(verbosity_name, cond) - #define DVLOG_S(verbosity) VLOG_S(verbosity) - #define DLOG_S(verbosity_name) LOG_S(verbosity_name) -#else - // Debug logging disabled: - #define DVLOG_IF_S(verbosity, cond) \ - (true || (verbosity) > loguru::current_verbosity_cutoff() || (cond) == false) \ - ? (void)0 \ - : loguru::Voidify() & loguru::StreamLogger(verbosity, __FILE__, __LINE__) - - #define DLOG_IF_S(verbosity_name, cond) DVLOG_IF_S(loguru::Verbosity_ ## verbosity_name, cond) - #define DVLOG_S(verbosity) DVLOG_IF_S(verbosity, true) - #define DLOG_S(verbosity_name) DVLOG_S(loguru::Verbosity_ ## verbosity_name) -#endif - -#if LOGURU_DEBUG_CHECKS - // Debug checks enabled: - #define DCHECK_S(cond) CHECK_S(cond) - #define DCHECK_NOTNULL_S(x) CHECK_NOTNULL_S(x) - #define DCHECK_EQ_S(a, b) CHECK_EQ_S(a, b) - #define DCHECK_NE_S(a, b) CHECK_NE_S(a, b) - #define DCHECK_LT_S(a, b) CHECK_LT_S(a, b) - #define DCHECK_LE_S(a, b) CHECK_LE_S(a, b) - #define DCHECK_GT_S(a, b) CHECK_GT_S(a, b) - #define DCHECK_GE_S(a, b) CHECK_GE_S(a, b) -#else -// Debug checks disabled: - #define DCHECK_S(cond) CHECK_S(true || (cond)) - #define DCHECK_NOTNULL_S(x) CHECK_S(true || (x) != nullptr) - #define DCHECK_EQ_S(a, b) CHECK_S(true || (a) == (b)) - #define DCHECK_NE_S(a, b) CHECK_S(true || (a) != (b)) - #define DCHECK_LT_S(a, b) CHECK_S(true || (a) < (b)) - #define DCHECK_LE_S(a, b) CHECK_S(true || (a) <= (b)) - #define DCHECK_GT_S(a, b) CHECK_S(true || (a) > (b)) - #define DCHECK_GE_S(a, b) CHECK_S(true || (a) >= (b)) -#endif - -#if LOGURU_REPLACE_GLOG - #undef LOG - #undef VLOG - #undef LOG_IF - #undef VLOG_IF - #undef CHECK - #undef CHECK_NOTNULL - #undef CHECK_EQ - #undef CHECK_NE - #undef CHECK_LT - #undef CHECK_LE - #undef CHECK_GT - #undef CHECK_GE - #undef DLOG - #undef DVLOG - #undef DLOG_IF - #undef DVLOG_IF - #undef DCHECK - #undef DCHECK_NOTNULL - #undef DCHECK_EQ - #undef DCHECK_NE - #undef DCHECK_LT - #undef DCHECK_LE - #undef DCHECK_GT - #undef DCHECK_GE - #undef VLOG_IS_ON - - #define LOG LOG_S - #define VLOG VLOG_S - #define LOG_IF LOG_IF_S - #define VLOG_IF VLOG_IF_S - #define CHECK(cond) CHECK_S(!!(cond)) - #define CHECK_NOTNULL CHECK_NOTNULL_S - #define CHECK_EQ CHECK_EQ_S - #define CHECK_NE CHECK_NE_S - #define CHECK_LT CHECK_LT_S - #define CHECK_LE CHECK_LE_S - #define CHECK_GT CHECK_GT_S - #define CHECK_GE CHECK_GE_S - #define DLOG DLOG_S - #define DVLOG DVLOG_S - #define DLOG_IF DLOG_IF_S - #define DVLOG_IF DVLOG_IF_S - #define DCHECK DCHECK_S - #define DCHECK_NOTNULL DCHECK_NOTNULL_S - #define DCHECK_EQ DCHECK_EQ_S - #define DCHECK_NE DCHECK_NE_S - #define DCHECK_LT DCHECK_LT_S - #define DCHECK_LE DCHECK_LE_S - #define DCHECK_GT DCHECK_GT_S - #define DCHECK_GE DCHECK_GE_S - #define VLOG_IS_ON(verbosity) ((verbosity) <= loguru::current_verbosity_cutoff()) - -#endif // LOGURU_REPLACE_GLOG - -#endif // LOGURU_WITH_STREAMS - -#endif // LOGURU_HAS_DECLARED_STREAMS_HEADER diff --git a/cpp/dolfinx/common/math.h b/cpp/dolfinx/common/math.h index 6d088bf93ba..a4ab21d8e2c 100644 --- a/cpp/dolfinx/common/math.h +++ b/cpp/dolfinx/common/math.h @@ -7,6 +7,7 @@ #pragma once #include "types.h" +#include #include #include #include @@ -235,7 +236,7 @@ void pinv(U A, V P) for (std::size_t j = 0; j < AT.extent(1); ++j) AT(i, j) = A(j, i); - std::fill(ATAb.begin(), ATAb.end(), 0.0); + std::ranges::fill(ATAb, 0.0); for (std::size_t i = 0; i < P.extent(0); ++i) for (std::size_t j = 0; j < P.extent(1); ++j) P(i, j) = 0; diff --git a/cpp/dolfinx/common/sort.h b/cpp/dolfinx/common/sort.h index a5165289db9..aefaadb4a23 100644 --- a/cpp/dolfinx/common/sort.h +++ b/cpp/dolfinx/common/sort.h @@ -7,160 +7,121 @@ #pragma once #include -#include +#include +#include #include -#include +#include +#include #include #include #include +#include #include namespace dolfinx { -/// Sort a vector of integers with radix sorting algorithm. The bucket -/// size is determined by the number of bits to sort at a time (2^BITS). -/// @tparam T Integral type -/// @tparam BITS The number of bits to sort at a time. -/// @param[in, out] array The array to sort. -template -void radix_sort(std::span array) +struct __radix_sort { - static_assert(std::is_integral(), "This function only sorts integers."); - if (array.size() <= 1) - return; - - T max_value = *std::max_element(array.begin(), array.end()); - - // Sort N bits at a time - constexpr int bucket_size = 1 << BITS; - T mask = (T(1) << BITS) - 1; - - // Compute number of iterations, most significant digit (N bits) of - // maxvalue - int its = 0; - while (max_value) - { - max_value >>= BITS; - its++; - } - - // Adjacency list arrays for computing insertion position - std::array counter; - std::array offset; - - std::int32_t mask_offset = 0; - std::vector buffer(array.size()); - std::span current_perm = array; - std::span next_perm = buffer; - for (int i = 0; i < its; i++) + /// @brief Sort a range with radix sorting algorithm. The bucket + /// size is determined by the number of bits to sort at a time (2^BITS). + /// + /// This allows usage with standard range containers of integral types, for + /// example + /// @code + /// std::array a{2, 3, 1}; + /// dolfixn::radix_sort(a); // a = {1, 2, 3} + /// @endcode + /// Additionally the projection based approach of the STL library is adpated, + /// which allows for versatile usage, for example the easy realization of an + /// argsort + /// @code + /// std::array a{2, 3, 1}; + /// std::array i{0, 1, 2}; + /// dolfixn::radix_sort(i, [&](auto i){ return a[i]; }); // yields i = {2, 0, + /// 1} and a[i] = {1, 2, 3}; + /// @endcode + /// @tparam R Type of range to be sorted. + /// @tparam P Projection type to be applied on range elements to produce a + /// sorting index. + /// @tparam BITS The number of bits to sort at a time. + /// @param[in, out] range The range to sort. + /// @param[in] P Element projection. + template < + std::ranges::random_access_range R, typename P = std::identity, + std::remove_cvref_t>> BITS + = 8> + requires std::integral + constexpr void operator()(R&& range, P proj = {}) const { - // Zero counter array - std::fill(counter.begin(), counter.end(), 0); - - // Count number of elements per bucket - for (T c : current_perm) - counter[(c & mask) >> mask_offset]++; - - // Prefix sum to get the inserting position - offset[0] = 0; - std::partial_sum(counter.begin(), counter.end(), std::next(offset.begin())); - for (T c : current_perm) - { - std::int32_t bucket = (c & mask) >> mask_offset; - std::int32_t new_pos = offset[bucket + 1] - counter[bucket]; - next_perm[new_pos] = c; - counter[bucket]--; - } - - mask = mask << BITS; - mask_offset += BITS; - - std::swap(current_perm, next_perm); - } + // value type + using T = std::iter_value_t; - // Copy data back to array - if (its % 2 != 0) - std::copy(buffer.begin(), buffer.end(), array.begin()); -} - -/// Returns the indices that would sort (lexicographic) a vector of -/// bitsets. -/// @tparam T The size of the bitset, which corresponds to the number of -/// bits necessary to represent a set of integers. For example, N = 96 -/// for mapping three std::int32_t. -/// @tparam BITS The number of bits to sort at a time -/// @param[in] array The array to sort -/// @param[in] perm FIXME -template -void argsort_radix(std::span array, std::span perm) -{ - static_assert(std::is_integral_v, "Integral required."); - - if (array.size() <= 1) - return; - - const auto [min, max] = std::minmax_element(array.begin(), array.end()); - T range = *max - *min + 1; + // index type (if no projection is provided it holds I == T) + using I = std::remove_cvref_t>; - // Sort N bits at a time - constexpr int bucket_size = 1 << BITS; - T mask = (T(1) << BITS) - 1; - std::int32_t mask_offset = 0; + if (range.size() <= 1) + return; - // Compute number of iterations, most significant digit (N bits) of - // maxvalue - int its = 0; - while (range) - { - range >>= BITS; - its++; - } + T max_value = proj(*std::ranges::max_element(range, std::less{}, proj)); - // Adjacency list arrays for computing insertion position - std::array counter; - std::array offset; - - std::vector perm2(perm.size()); - std::span current_perm = perm; - std::span next_perm = perm2; - for (int i = 0; i < its; i++) - { - // Zero counter - std::fill(counter.begin(), counter.end(), 0); + // Sort N bits at a time + constexpr I bucket_size = 1 << BITS; + T mask = (T(1) << BITS) - 1; - // Count number of elements per bucket - for (auto cp : current_perm) + // Compute number of iterations, most significant digit (N bits) of + // maxvalue + I its = 0; + while (max_value) { - T value = array[cp] - *min; - std::int32_t bucket = (value & mask) >> mask_offset; - counter[bucket]++; + max_value >>= BITS; + its++; } - // Prefix sum to get the inserting position - offset[0] = 0; - std::partial_sum(counter.begin(), counter.end(), std::next(offset.begin())); + // Adjacency list arrays for computing insertion position + std::array counter; + std::array offset; - // Sort py permutation - for (auto cp : current_perm) + I mask_offset = 0; + std::vector buffer(range.size()); + std::span current_perm = range; + std::span next_perm = buffer; + for (I i = 0; i < its; i++) { - T value = array[cp] - *min; - std::int32_t bucket = (value & mask) >> mask_offset; - std::int32_t pos = offset[bucket + 1] - counter[bucket]; - next_perm[pos] = cp; - counter[bucket]--; + // Zero counter array + std::ranges::fill(counter, 0); + + // Count number of elements per bucket + for (const auto& c : current_perm) + counter[(proj(c) & mask) >> mask_offset]++; + + // Prefix sum to get the inserting position + offset[0] = 0; + std::partial_sum(counter.begin(), counter.end(), + std::next(offset.begin())); + for (const auto& c : current_perm) + { + I bucket = (proj(c) & mask) >> mask_offset; + I new_pos = offset[bucket + 1] - counter[bucket]; + next_perm[new_pos] = c; + counter[bucket]--; + } + + mask = mask << BITS; + mask_offset += BITS; + + std::swap(current_perm, next_perm); } - std::swap(current_perm, next_perm); - - mask = mask << BITS; - mask_offset += BITS; + // Copy data back to array + if (its % 2 != 0) + std::ranges::copy(buffer, range.begin()); } +}; - if (its % 2 == 1) - std::copy(perm2.begin(), perm2.end(), perm.begin()); -} +/// Radix sort +inline constexpr __radix_sort radix_sort{}; /// @brief Compute the permutation array that sorts a 2D array by row. /// @@ -189,7 +150,8 @@ std::vector sort_by_perm(std::span x, std::size_t shape1) int col = shape1 - 1 - i; for (std::size_t j = 0; j < shape0; ++j) column[j] = x[j * shape1 + col]; - argsort_radix(column, perm); + + radix_sort(perm, [&column](auto index) { return column[index]; }); } return perm; diff --git a/cpp/dolfinx/common/timing.cpp b/cpp/dolfinx/common/timing.cpp index 26726ef4fba..36c0674c9a1 100644 --- a/cpp/dolfinx/common/timing.cpp +++ b/cpp/dolfinx/common/timing.cpp @@ -5,29 +5,26 @@ // SPDX-License-Identifier: LGPL-3.0-or-later #include "timing.h" +#include "Table.h" +#include "TimeLogManager.h" #include "TimeLogger.h" #include "Timer.h" -#include -#include - -using namespace dolfinx; -using namespace dolfinx::common; //----------------------------------------------------------------------- -Table dolfinx::timings(std::set type) +dolfinx::Table dolfinx::timings(std::set type) { - return TimeLogManager::logger().timings(type); + return dolfinx::common::TimeLogManager::logger().timings(type); } //----------------------------------------------------------------------------- void dolfinx::list_timings(MPI_Comm comm, std::set type, Table::Reduction reduction) { - TimeLogManager::logger().list_timings(comm, type, reduction); + dolfinx::common::TimeLogManager::logger().list_timings(comm, type, reduction); } //----------------------------------------------------------------------------- std::tuple dolfinx::timing(std::string task) { - return TimeLogManager::logger().timing(task); + return dolfinx::common::TimeLogManager::logger().timing(task); } //----------------------------------------------------------------------------- diff --git a/cpp/dolfinx/common/timing.h b/cpp/dolfinx/common/timing.h index 753d03ca90d..a739b930425 100644 --- a/cpp/dolfinx/common/timing.h +++ b/cpp/dolfinx/common/timing.h @@ -6,7 +6,7 @@ #pragma once -#include +#include "Table.h" #include #include #include @@ -14,38 +14,34 @@ namespace dolfinx { - -/// Timing types: -/// * ``TimingType::wall`` wall-clock time -/// * ``TimingType::user`` user (cpu) time -/// * ``TimingType::system`` system (kernel) time +/// @brief Timing types. enum class TimingType : int { - wall = 0, - user = 1, - system = 2 + wall = 0, ///< Wall-clock time + user = 1, ///< User (cpu) time + system = 2 ///< System (kernel) time }; -/// Return a summary of timings and tasks in a Table -/// @param[in] type subset of { TimingType::wall, TimingType::user, -/// TimingType::system } -/// @returns Table with timings +/// @brief Return a summary of timings and tasks in a Table. +/// @param[in] type Timing type. +/// @return Table with timings. Table timings(std::set type); -/// List a summary of timings and tasks. ``MPI_AVG`` reduction is -/// printed. -/// @param[in] comm MPI Communicator -/// @param[in] type Subset of { TimingType::wall, TimingType::user, -/// TimingType::system } -/// @param[in] reduction MPI Reduction to apply (min, max or average) +/// @brief List a summary of timings and tasks. +/// +/// ``MPI_AVG`` reduction is printed. +/// +/// @param[in] comm MPI Communicator. +/// @param[in] type Timing type. +/// @param[in] reduction MPI Reduction to apply (min, max or average). void list_timings(MPI_Comm comm, std::set type, Table::Reduction reduction = Table::Reduction::max); -/// Return timing (count, total wall time, total user time, total system -/// time) for given task. +/// @brief Return timing (count, total wall time, total user time, total +/// system time) for given task. /// @param[in] task Name of a task -/// @returns The (count, total wall time, total user time, total system -/// time) for the task +/// @return The (count, total wall time, total user time, total system +/// time) for the task. std::tuple timing(std::string task); } // namespace dolfinx diff --git a/cpp/dolfinx/common/utils.h b/cpp/dolfinx/common/utils.h index e041a6f0ed7..945e5ad44d7 100644 --- a/cpp/dolfinx/common/utils.h +++ b/cpp/dolfinx/common/utils.h @@ -6,9 +6,9 @@ #pragma once +#include "MPI.h" #include #include -#include #include #include #include @@ -16,12 +16,14 @@ /// Generic tools namespace dolfinx::common { -/// Sort two arrays based on the values in array `indices`. Any -/// duplicate indices and the corresponding value are removed. In the -/// case of duplicates, the entry with the smallest value is retained. -/// @param[in] indices Array of indices -/// @param[in] values Array of values -/// @return Sorted (indices, values), with sorting based on indices +///@brief Sort two arrays based on the values in array `indices`. +/// +/// Any duplicate indices and the corresponding value are removed. In +/// the case of duplicates, the entry with the smallest value is +/// retained. +/// @param[in] indices Array of indices. +/// @param[in] values Array of values. +/// @return Sorted (indices, values), with sorting based on indices. template std::pair, std::vector> @@ -32,13 +34,14 @@ sort_unique(const U& indices, const V& values) using T = typename std::pair; std::vector data(indices.size()); - std::transform(indices.begin(), indices.end(), values.begin(), data.begin(), - [](auto& idx, auto& v) -> T { return {idx, v}; }); + std::ranges::transform(indices, values, data.begin(), + [](auto& idx, auto& v) -> T { return {idx, v}; }); // Sort make unique - std::sort(data.begin(), data.end()); - auto it = std::unique(data.begin(), data.end(), - [](auto& a, auto& b) { return a.first == b.first; }); + std::ranges::sort(data); + auto it = std::ranges::unique(data, [](auto& a, auto& b) + { return a.first == b.first; }) + .begin(); std::vector indices_new; std::vector values_new; diff --git a/cpp/dolfinx/fem/CoordinateElement.cpp b/cpp/dolfinx/fem/CoordinateElement.cpp index d635262e883..fc3122b486a 100644 --- a/cpp/dolfinx/fem/CoordinateElement.cpp +++ b/cpp/dolfinx/fem/CoordinateElement.cpp @@ -5,6 +5,7 @@ // SPDX-License-Identifier: LGPL-3.0-or-later #include "CoordinateElement.h" +#include #include #include #include @@ -60,6 +61,17 @@ void CoordinateElement::tabulate(int nd, std::span X, } //-------------------------------------------------------------------------------- template +void CoordinateElement::permute_subentity_closure(std::span d, + std::uint32_t cell_info, + mesh::CellType entity_type, + int entity_index) const +{ + assert(_element); + _element->permute_subentity_closure_inv( + d, cell_info, mesh::cell_type_to_basix_type(entity_type), entity_index); +} +//-------------------------------------------------------------------------------- +template ElementDofLayout CoordinateElement::create_dof_layout() const { assert(_element); @@ -108,20 +120,20 @@ void CoordinateElement::pull_back_nonaffine(mdspan2_t X, std::vector phi(basis.extent(2)); for (std::size_t p = 0; p < num_points; ++p) { - std::fill(Xk_b.begin(), Xk_b.end(), 0.0); + std::ranges::fill(Xk_b, 0.0); int k; for (k = 0; k < maxit; ++k) { _element->tabulate(1, Xk_b, {1, tdim}, basis_b); // x = cell_geometry * phi - std::fill(xk.begin(), xk.end(), 0.0); + std::ranges::fill(xk, 0.0); for (std::size_t i = 0; i < cell_geometry.extent(0); ++i) for (std::size_t j = 0; j < cell_geometry.extent(1); ++j) xk[j] += cell_geometry(i, j) * basis(0, 0, i, 0); // Compute Jacobian, its inverse and determinant - std::fill(J_b.begin(), J_b.end(), 0.0); + std::ranges::fill(J_b, 0.0); for (std::size_t i = 0; i < tdim; ++i) for (std::size_t j = 0; j < basis.extent(2); ++j) dphi(i, j) = basis(i + 1, 0, j, 0); @@ -130,14 +142,14 @@ void CoordinateElement::pull_back_nonaffine(mdspan2_t X, compute_jacobian_inverse(J, K); // Compute dX = K * (x_p - x_k) - std::fill(dX.begin(), dX.end(), 0); + std::ranges::fill(dX, 0); for (std::size_t i = 0; i < K.extent(0); ++i) for (std::size_t j = 0; j < K.extent(1); ++j) dX[i] += K(i, j) * (x(p, j) - xk[j]); // Compute Xk += dX - std::transform(dX.begin(), dX.end(), Xk_b.begin(), Xk_b.begin(), - [](auto a, auto b) { return a + b; }); + std::ranges::transform(dX, Xk_b, Xk_b.begin(), + [](auto a, auto b) { return a + b; }); // Compute norm(dX) if (auto dX_squared diff --git a/cpp/dolfinx/fem/CoordinateElement.h b/cpp/dolfinx/fem/CoordinateElement.h index ba22ffff4a4..01c6428ee24 100644 --- a/cpp/dolfinx/fem/CoordinateElement.h +++ b/cpp/dolfinx/fem/CoordinateElement.h @@ -93,6 +93,27 @@ class CoordinateElement void tabulate(int nd, std::span X, std::array shape, std::span basis) const; + /// @brief Given the closure DOFs \f$\tilde{d}\f$ of a cell sub-entity in + /// reference ordering, this function computes the permuted degrees-of-freedom + /// \f[ d = P \tilde{d},\f] + /// ordered to be consistent with the entity's mesh orientation, where + /// \f$P\f$ is a permutation matrix. This accounts for orientation + /// discrepancies between the entity's cell and mesh orientation. All DOFs are + /// rotated and reflected together, unlike `permute`, which considered + /// sub-entities independently. + /// + /// @param[in,out] d Indices associated with the reference element + /// degree-of-freedom (in). Indices associated with each physical + /// element degree-of-freedom (out). + /// @param[in] cell_info Permutation info for the cell + /// @param[in] entity_type The cell type of the sub-entity + /// @param[in] entity_index The local (with respect to the cell) index of the + /// entity + void permute_subentity_closure(std::span d, + std::uint32_t cell_info, + mesh::CellType entity_type, + int entity_index) const; + /// Compute Jacobian for a cell with given geometry using the /// basis functions and first order derivatives. /// @param[in] dphi Derivatives of the basis functions (shape=(tdim, diff --git a/cpp/dolfinx/fem/DirichletBC.cpp b/cpp/dolfinx/fem/DirichletBC.cpp index 85bce35c725..97d9b0689d9 100644 --- a/cpp/dolfinx/fem/DirichletBC.cpp +++ b/cpp/dolfinx/fem/DirichletBC.cpp @@ -41,14 +41,16 @@ find_local_entity_index(const mesh::Topology& topology, if (!e_to_c) { throw std::runtime_error( - "Entity-to-cell connectivity has not been computed."); + "Entity-to-cell connectivity has not been computed. Missing dims " + + std::to_string(dim) + "->" + std::to_string(tdim)); } auto c_to_e = topology.connectivity(tdim, dim); if (!c_to_e) { throw std::runtime_error( - "Cell-to-entity connectivity has not been computed."); + "Cell-to-entity connectivity has not been computed. Missing dims " + + std::to_string(tdim) + "->" + std::to_string(dim)); } std::vector> entity_indices; @@ -113,18 +115,17 @@ get_remote_dofs(MPI_Comm comm, const common::IndexMap& map, int bs_map, // Convert dofs indices to 'block' map indices std::vector dofs_local_m; dofs_local_m.reserve(dofs_local.size()); - std::transform(dofs_local.begin(), dofs_local.end(), - std::back_inserter(dofs_local_m), - [bs_map](auto dof) { return dof / bs_map; }); + std::ranges::transform(dofs_local, std::back_inserter(dofs_local_m), + [bs_map](auto dof) { return dof / bs_map; }); // Compute global index of each block map.local_to_global(dofs_local_m, dofs_global); // Add offset - std::transform(dofs_global.begin(), dofs_global.end(), dofs_local.begin(), - dofs_global.begin(), - [bs_map](auto global_block, auto local_dof) - { return bs_map * global_block + (local_dof % bs_map); }); + std::ranges::transform( + dofs_global, dofs_local, dofs_global.begin(), + [bs_map](auto global_block, auto local_dof) + { return bs_map * global_block + (local_dof % bs_map); }); } MPI_Wait(&request, MPI_STATUS_IGNORE); @@ -247,8 +248,9 @@ std::vector fem::locate_dofs_topological( // TODO: is removing duplicates at this point worth the effort? // Remove duplicates - std::sort(dofs.begin(), dofs.end()); - dofs.erase(std::unique(dofs.begin(), dofs.end()), dofs.end()); + std::ranges::sort(dofs); + auto [unique_end, range_end] = std::ranges::unique(dofs); + dofs.erase(unique_end, range_end); if (remote) { @@ -264,9 +266,9 @@ std::vector fem::locate_dofs_topological( std::span src = map->src(); std::span dest = map->dest(); std::vector ranks; - std::set_union(src.begin(), src.end(), dest.begin(), dest.end(), - std::back_inserter(ranks)); - ranks.erase(std::unique(ranks.begin(), ranks.end()), ranks.end()); + std::ranges::set_union(src, dest, std::back_inserter(ranks)); + auto [unique_end, range_end] = std::ranges::unique(ranks); + ranks.erase(unique_end, range_end); MPI_Dist_graph_create_adjacent( map->comm(), ranks.size(), ranks.data(), MPI_UNWEIGHTED, ranks.size(), ranks.data(), MPI_UNWEIGHTED, MPI_INFO_NULL, false, &comm); @@ -283,8 +285,9 @@ std::vector fem::locate_dofs_topological( // Add received bc indices to dofs_local, sort, and remove // duplicates dofs.insert(dofs.end(), dofs_remote.begin(), dofs_remote.end()); - std::sort(dofs.begin(), dofs.end()); - dofs.erase(std::unique(dofs.begin(), dofs.end()), dofs.end()); + std::ranges::sort(dofs); + auto [unique_end, range_end] = std::ranges::unique(dofs); + dofs.erase(unique_end, range_end); } return dofs; @@ -355,15 +358,17 @@ std::array, 2> fem::locate_dofs_topological( // Remove duplicates std::vector perm(bc_dofs[0].size()); std::iota(perm.begin(), perm.end(), 0); - dolfinx::argsort_radix(bc_dofs[0], perm); + dolfinx::radix_sort(perm, + [&dofs = bc_dofs[0]](auto index) { return dofs[index]; }); + std::array, 2> sorted_bc_dofs = bc_dofs; for (std::size_t b = 0; b < 2; ++b) { - std::transform(perm.cbegin(), perm.cend(), sorted_bc_dofs[b].begin(), - [&bc_dofs = bc_dofs[b]](auto p) { return bc_dofs[p]; }); - sorted_bc_dofs[b].erase( - std::unique(sorted_bc_dofs[b].begin(), sorted_bc_dofs[b].end()), - sorted_bc_dofs[b].end()); + std::ranges::transform(perm, sorted_bc_dofs[b].begin(), + [&bc_dofs = bc_dofs[b]](auto p) + { return bc_dofs[p]; }); + auto [unique_end, range_end] = std::ranges::unique(sorted_bc_dofs[b]); + sorted_bc_dofs[b].erase(unique_end, range_end); } if (!remote) @@ -383,9 +388,9 @@ std::array, 2> fem::locate_dofs_topological( std::span src = map0->src(); std::span dest = map0->dest(); std::vector ranks; - std::set_union(src.begin(), src.end(), dest.begin(), dest.end(), - std::back_inserter(ranks)); - ranks.erase(std::unique(ranks.begin(), ranks.end()), ranks.end()); + std::ranges::set_union(src, dest, std::back_inserter(ranks)); + auto [unique_end, range_end] = std::ranges::unique(ranks); + ranks.erase(unique_end, range_end); MPI_Dist_graph_create_adjacent(map0->comm(), ranks.size(), ranks.data(), MPI_UNWEIGHTED, ranks.size(), ranks.data(), MPI_UNWEIGHTED, MPI_INFO_NULL, false, @@ -410,15 +415,17 @@ std::array, 2> fem::locate_dofs_topological( // Remove duplicates and sort perm.resize(sorted_bc_dofs[0].size()); std::iota(perm.begin(), perm.end(), 0); - dolfinx::argsort_radix(sorted_bc_dofs[0], perm); + dolfinx::radix_sort(perm, [&dofs = sorted_bc_dofs[0]](auto index) + { return dofs[index]; }); + std::array, 2> out_dofs = sorted_bc_dofs; for (std::size_t b = 0; b < 2; ++b) { - std::transform(perm.cbegin(), perm.cend(), out_dofs[b].begin(), - [&sorted_dofs = sorted_bc_dofs[b]](auto p) - { return sorted_dofs[p]; }); - out_dofs[b].erase(std::unique(out_dofs[b].begin(), out_dofs[b].end()), - out_dofs[b].end()); + std::ranges::transform(perm, out_dofs[b].begin(), + [&sorted_dofs = sorted_bc_dofs[b]](auto p) + { return sorted_dofs[p]; }); + auto [unique_end, range_end] = std::ranges::unique(out_dofs[b]); + out_dofs[b].erase(unique_end, range_end); } assert(out_dofs[0].size() == out_dofs[1].size()); diff --git a/cpp/dolfinx/fem/DirichletBC.h b/cpp/dolfinx/fem/DirichletBC.h index b2d39691490..b43066ff253 100644 --- a/cpp/dolfinx/fem/DirichletBC.h +++ b/cpp/dolfinx/fem/DirichletBC.h @@ -11,11 +11,13 @@ #include "DofMap.h" #include "Function.h" #include "FunctionSpace.h" +#include #include #include #include #include #include +#include #include #include #include @@ -229,24 +231,23 @@ std::array, 2> locate_dofs_geometrical( } // Remove duplicates - std::sort(bc_dofs.begin(), bc_dofs.end()); - bc_dofs.erase(std::unique(bc_dofs.begin(), bc_dofs.end()), bc_dofs.end()); + std::ranges::sort(bc_dofs); + auto [unique_end, range_end] = std::ranges::unique(bc_dofs); + bc_dofs.erase(unique_end, range_end); // Copy to separate array std::array dofs = {std::vector(bc_dofs.size()), std::vector(bc_dofs.size())}; - std::transform(bc_dofs.cbegin(), bc_dofs.cend(), dofs[0].begin(), - [](auto dof) { return dof[0]; }); - std::transform(bc_dofs.cbegin(), bc_dofs.cend(), dofs[1].begin(), - [](auto dof) { return dof[1]; }); + std::ranges::transform(bc_dofs, dofs[0].begin(), + [](auto dof) { return dof[0]; }); + std::ranges::transform(bc_dofs, dofs[1].begin(), + [](auto dof) { return dof[1]; }); return dofs; } /// Object for setting (strong) Dirichlet boundary conditions -/// -/// \f$u = g \ \text{on} \ G\f$, -/// +/// \f[u = g \ \text{on} \ G,\f] /// where \f$u\f$ is the solution to be computed, \f$g\f$ is a function /// and \f$G\f$ is a sub domain of the mesh. /// @@ -266,7 +267,7 @@ class DirichletBC int bs = dofmap.index_map_bs(); std::int32_t map_size = dofmap.index_map->size_local(); std::int32_t owned_size = bs * map_size; - auto it = std::lower_bound(dofs.begin(), dofs.end(), owned_size); + auto it = std::ranges::lower_bound(dofs, owned_size); return std::distance(dofs.begin(), it); } @@ -469,129 +470,118 @@ class DirichletBC return {_dofs0, _owned_indices0}; } - /// Set bc entries in `x` to `scale * x_bc` + /// @brief Set entries in an array that are constrained by Dirichlet + /// boundary conditions. + /// + /// Entries in `x` that are constrained by a Dirichlet boundary + /// conditions are set to `alpha * (x_bc - x0)`, where `x_bc` is the + /// (interpolated) boundary condition value. + /// + /// For elements with point-wise evaluated degrees-of-freedom, e.g. + /// Lagrange elements, `x_bc` is the value of the boundary condition + /// at the degree-of-freedom. For elements with moment + /// degrees-of-freedom, `x_bc` is the value of the boundary condition + /// interpolated into the finite element space. /// - /// @param[in] x The array in which to set `scale * x_bc[i]`, where - /// x_bc[i] is the boundary value of x[i]. Entries in x that do not - /// have a Dirichlet condition applied to them are unchanged. The - /// length of x must be less than or equal to the index of the - /// greatest boundary dof index. To set values only for - /// degrees-of-freedom that are owned by the calling rank, the length - /// of the array @p x should be equal to the number of dofs owned by - /// this rank. - /// @param[in] scale The scaling value to apply - void set(std::span x, T scale = 1) const + /// If `x` includes ghosted entries (entries available on the calling + /// rank but owned by another rank), ghosted entries constrained by a + /// Dirichlet condition will also be set. + /// + /// @param[in,out] x Array to modify for Dirichlet boundary + /// conditions. + /// @param[in] x0 Optional array used in computing the value to set. + /// If not provided it is treated as zero. + /// @param[in] alpha Scaling to apply. + void set(std::span x, std::optional> x0, + T alpha = 1) const { - if (std::holds_alternative>>(_g)) + std::int32_t x_size = x.size(); + if (alpha == T(0)) // Optimisation for when alpha == 0 { - auto g = std::get>>(_g); - assert(g); - std::span values = g->x()->array(); - auto dofs1_g = _dofs1_g.empty() ? std::span(_dofs0) : std::span(_dofs1_g); - std::int32_t x_size = x.size(); - for (std::size_t i = 0; i < _dofs0.size(); ++i) + for (std::int32_t idx : _dofs0) { - if (_dofs0[i] < x_size) - { - assert(dofs1_g[i] < (std::int32_t)values.size()); - x[_dofs0[i]] = scale * values[dofs1_g[i]]; - } + if (idx < x_size) + x[idx] = 0; } } - else if (std::holds_alternative>>(_g)) + else { - auto g = std::get>>(_g); - std::vector value = g->value; - int bs = _function_space->dofmap()->bs(); - std::int32_t x_size = x.size(); - std::for_each(_dofs0.cbegin(), _dofs0.cend(), - [x_size, bs, scale, &value, &x](auto dof) - { - if (dof < x_size) - x[dof] = scale * value[dof % bs]; - }); - } - } - - /// Set bc entries in `x` to `scale * (x0 - x_bc)` - /// @param[in] x The array in which to set `scale * (x0 - x_bc)` - /// @param[in] x0 The array used in compute the value to set - /// @param[in] scale The scaling value to apply - void set(std::span x, std::span x0, T scale = 1) const - { - if (std::holds_alternative>>(_g)) - { - auto g = std::get>>(_g); - assert(g); - std::span values = g->x()->array(); - assert(x.size() <= x0.size()); - auto dofs1_g = _dofs1_g.empty() ? std::span(_dofs0) : std::span(_dofs1_g); - std::int32_t x_size = x.size(); - for (std::size_t i = 0; i < _dofs0.size(); ++i) + if (std::holds_alternative>>(_g)) { - if (_dofs0[i] < x_size) + auto g = std::get>>(_g); + assert(g); + auto dofs1_g + = _dofs1_g.empty() ? std::span(_dofs0) : std::span(_dofs1_g); + std::span values = g->x()->array(); + if (x0.has_value()) { - assert(dofs1_g[i] < (std::int32_t)values.size()); - x[_dofs0[i]] = scale * (values[dofs1_g[i]] - x0[_dofs0[i]]); + std::span _x0 = x0.value(); + assert(x.size() <= _x0.size()); + for (std::size_t i = 0; i < _dofs0.size(); ++i) + { + if (_dofs0[i] < x_size) + { + assert(dofs1_g[i] < (std::int32_t)values.size()); + x[_dofs0[i]] = alpha * (values[dofs1_g[i]] - _x0[_dofs0[i]]); + } + } + } + else + { + for (std::size_t i = 0; i < _dofs0.size(); ++i) + { + if (_dofs0[i] < x_size) + { + assert(dofs1_g[i] < (std::int32_t)values.size()); + x[_dofs0[i]] = alpha * values[dofs1_g[i]]; + } + } + } + } + else if (std::holds_alternative>>(_g)) + { + auto g = std::get>>(_g); + const std::vector& value = g->value; + std::int32_t bs = _function_space->dofmap()->bs(); + if (x0.has_value()) + { + assert(x.size() <= x0.value().size()); + std::ranges::for_each( + _dofs0, + [x_size, &x, x0 = x0.value(), &value, alpha, bs](auto dof) + { + if (dof < x_size) + x[dof] = alpha * (value[dof % bs] - x0[dof]); + }); + } + else + { + std::ranges::for_each(_dofs0, + [x_size, bs, alpha, &value, &x](auto dof) + { + if (dof < x_size) + x[dof] = alpha * value[dof % bs]; + }); } } - } - else if (std::holds_alternative>>(_g)) - { - auto g = std::get>>(_g); - const std::vector& value = g->value; - std::int32_t bs = _function_space->dofmap()->bs(); - std::for_each(_dofs0.begin(), _dofs0.end(), - [&x, &x0, &value, scale, bs](auto dof) - { - if (dof < (std::int32_t)x.size()) - x[dof] = scale * (value[dof % bs] - x0[dof]); - }); } } - /// @todo Review this function - it is almost identical to the - /// 'DirichletBC::set' function + /// @brief Set `markers[i] = true` if dof `i` has a boundary condition + /// applied. /// - /// Set boundary condition value for entries with an applied boundary - /// condition. Other entries are not modified. - /// @param[out] values The array in which to set the dof values. - /// The array must be at least as long as the array associated with V1 - /// (the space of the function that provides the dof values) - void dof_values(std::span values) const - { - if (std::holds_alternative>>(_g)) - { - auto g = std::get>>(_g); - assert(g); - std::span g_values = g->x()->array(); - auto dofs1_g = _dofs1_g.empty() ? std::span(_dofs0) : std::span(_dofs1_g); - for (std::size_t i = 0; i < dofs1_g.size(); ++i) - values[_dofs0[i]] = g_values[dofs1_g[i]]; - } - else if (std::holds_alternative>>(_g)) - { - auto g = std::get>>(_g); - assert(g); - const std::vector& g_value = g->value; - const std::int32_t bs = _function_space->dofmap()->bs(); - for (std::size_t i = 0; i < _dofs0.size(); ++i) - values[_dofs0[i]] = g_value[_dofs0[i] % bs]; - } - } - - /// Set markers[i] = true if dof i has a boundary condition applied. - /// Value of markers[i] is not changed otherwise. - /// @param[in,out] markers Entry makers[i] is set to true if dof i in - /// V0 had a boundary condition applied, i.e. dofs which are fixed by - /// a boundary condition. Other entries in @p markers are left + /// Value of `markers[i]` is not changed otherwise. + /// + /// @param[in,out] markers Entry `makers[i]` is set to true if dof `i` + /// in V0 had a boundary condition applied, i.e. dofs which are fixed + /// by a boundary condition. Other entries in `markers` are left /// unchanged. void mark_dofs(std::span markers) const { - for (std::size_t i = 0; i < _dofs0.size(); ++i) + for (std::int32_t idx : _dofs0) { - assert(_dofs0[i] < (std::int32_t)markers.size()); - markers[_dofs0[i]] = true; + assert(idx < (std::int32_t)markers.size()); + markers[idx] = true; } } @@ -604,8 +594,8 @@ class DirichletBC std::shared_ptr>> _g; - // Dof indices (_dofs0) in _function_space and (_dofs1_g) in the - // space of _g. _dofs1_g may be empty if _dofs0 can be re-used + // Dof indices (_dofs0) in _function_space and (_dofs1_g) in the space + // of _g. _dofs1_g may be empty if _dofs0 can be re-used std::vector _dofs0, _dofs1_g; // The first _owned_indices in _dofs are owned by this process diff --git a/cpp/dolfinx/fem/DofMap.cpp b/cpp/dolfinx/fem/DofMap.cpp index 78ac563d7bc..5c10ac8d178 100644 --- a/cpp/dolfinx/fem/DofMap.cpp +++ b/cpp/dolfinx/fem/DofMap.cpp @@ -46,9 +46,9 @@ fem::DofMap build_collapsed_dofmap(const DofMap& dofmap_view, std::vector dofs_view(dofs_view_md.data_handle(), dofs_view_md.data_handle() + dofs_view_md.size()); - dolfinx::radix_sort(std::span(dofs_view)); - dofs_view.erase(std::unique(dofs_view.begin(), dofs_view.end()), - dofs_view.end()); + dolfinx::radix_sort(dofs_view); + auto [unique_end, range_end] = std::ranges::unique(dofs_view); + dofs_view.erase(unique_end, range_end); // Get block size int bs_view = dofmap_view.index_map_bs(); @@ -69,9 +69,8 @@ fem::DofMap build_collapsed_dofmap(const DofMap& dofmap_view, { std::vector indices; indices.reserve(dofs_view.size()); - std::transform(dofs_view.begin(), dofs_view.end(), - std::back_inserter(indices), - [bs_view](auto idx) { return idx / bs_view; }); + std::ranges::transform(dofs_view, std::back_inserter(indices), + [bs_view](auto idx) { return idx / bs_view; }); auto [_index_map, _sub_imap_to_imap] = common::create_sub_index_map( *dofmap_view.index_map, indices, common::IndexMapOrder::preserve); index_map = std::make_shared(std::move(_index_map)); @@ -79,7 +78,8 @@ fem::DofMap build_collapsed_dofmap(const DofMap& dofmap_view, } // Create a map from old dofs to new dofs - std::vector old_to_new(dofs_view.back() + bs_view, -1); + std::size_t array_size = dofs_view.empty() ? 0 : dofs_view.back() + bs_view; + std::vector old_to_new(array_size, -1); for (std::size_t new_idx = 0; new_idx < sub_imap_to_imap.size(); ++new_idx) { for (int k = 0; k < bs_view; ++k) @@ -208,9 +208,14 @@ DofMap DofMap::extract_sub_dofmap(std::span component) const //----------------------------------------------------------------------------- std::pair> DofMap::collapse( MPI_Comm comm, const mesh::Topology& topology, - const std::function( - const graph::AdjacencyList&)>& reorder_fn) const + std::function(const graph::AdjacencyList&)>&& + reorder_fn) const { + if (!reorder_fn) + { + reorder_fn = [](const graph::AdjacencyList& g) + { return graph::reorder_gps(g); }; + } // Create new dofmap auto create_subdofmap = [](MPI_Comm comm, auto index_map_bs, auto& layout, auto& topology, auto& reorder_fn, auto& dmap) @@ -222,7 +227,6 @@ std::pair> DofMap::collapse( // Create new element dof layout and reset parent ElementDofLayout collapsed_dof_layout = layout.copy(); - auto [_index_map, bs, dofmaps] = build_dofmap_data( comm, topology, {collapsed_dof_layout}, reorder_fn); auto index_map diff --git a/cpp/dolfinx/fem/DofMap.h b/cpp/dolfinx/fem/DofMap.h index d2c1d654b53..15006a192c9 100644 --- a/cpp/dolfinx/fem/DofMap.h +++ b/cpp/dolfinx/fem/DofMap.h @@ -146,12 +146,11 @@ class DofMap /// @param[in] reorder_fn Graph re-ordering function to apply to the /// dof data /// @return The collapsed dofmap - std::pair> collapse( - MPI_Comm comm, const mesh::Topology& topology, - const std::function( - const graph::AdjacencyList&)>& reorder_fn - = [](const graph::AdjacencyList& g) - { return graph::reorder_gps(g); }) const; + std::pair> + collapse(MPI_Comm comm, const mesh::Topology& topology, + std::function( + const graph::AdjacencyList&)>&& reorder_fn + = nullptr) const; /// @brief Get dofmap data /// @return The adjacency list with dof indices for each cell diff --git a/cpp/dolfinx/fem/Expression.h b/cpp/dolfinx/fem/Expression.h index 4369b24f97c..f519cca5cd2 100644 --- a/cpp/dolfinx/fem/Expression.h +++ b/cpp/dolfinx/fem/Expression.h @@ -40,7 +40,7 @@ template `, etc. @@ -51,16 +51,18 @@ class Expression /// @brief Create an Expression. /// - /// @note Users should prefer the @ref create_expression factory functions. + /// @note Users should prefer the @ref create_expression factory + /// functions. /// - /// @param[in] coefficients Coefficients in the Expression + /// @param[in] coefficients Coefficients in the Expression. /// @param[in] constants Constants in the Expression - /// @param[in] X points on reference cell, `shape=(number of points, - /// tdim)` and storage is row-major. + /// @param[in] X Points on the reference cell, `shape=(number of + /// points, tdim)` and storage is row-major. /// @param[in] Xshape Shape of `X`. - /// @param[in] fn function for tabulating expression - /// @param[in] value_shape shape of expression evaluated at single point - /// @param[in] argument_function_space Function space for Argument + /// @param[in] fn Function for tabulating the Expression. + /// @param[in] value_shape Shape of Expression evaluated at single + /// point. + /// @param[in] argument_function_space Function space for Argument. Expression( const std::vector>>& coefficients, @@ -142,14 +144,15 @@ class Expression } /// @brief Evaluate Expression on cells or facets. + /// /// @param[in] mesh Cells on which to evaluate the Expression. - /// @param[in] entities List of entities to evaluate the expression on. This - /// could be either a list of cells or a list of (cell, local facet index) - /// tuples. Array is flattened per entity. - /// @param[out] values A 2D array to store the result. Caller - /// is responsible for correct sizing which should be `(num_cells, + /// @param[in] entities List of entities to evaluate the expression + /// on. This could be either a list of cells or a list of (cell, local + /// @param[out] values A 2D array to store the result. Caller is + /// responsible for correct sizing which should be `(num_cells, /// num_points * value_size * num_all_argument_dofs columns)`. - /// @param[in] vshape The shape of @p values (row-major storage). + /// facet index) tuples. Array is flattened per entity. + /// @param[in] vshape The shape of `values` (row-major storage). void eval(const mesh::Mesh& mesh, std::span entities, std::span values, @@ -157,20 +160,15 @@ class Expression { std::size_t estride; if (mesh.topology()->dim() == _x_ref.second[1]) - { estride = 1; - } else if (mesh.topology()->dim() == _x_ref.second[1] + 1) - { estride = 2; - } else - { throw std::runtime_error("Invalid dimension of evaluation points."); - } + // Prepare coefficients and constants - const auto [coeffs, cstride] = pack_coefficients(*this, entities, estride); - const std::vector constant_data = pack_constants(*this); + auto [coeffs, cstride] = pack_coefficients(*this, entities, estride); + std::vector constant_data = pack_constants(*this); auto fn = this->get_tabulate_expression(); // Prepare cell geometry @@ -179,7 +177,7 @@ class Expression // Get geometry data auto& cmap = mesh.geometry().cmap(); - const std::size_t num_dofs_g = cmap.dim(); + std::size_t num_dofs_g = cmap.dim(); auto x_g = mesh.geometry().x(); // Create data structures used in evaluation @@ -201,7 +199,7 @@ class Expression num_argument_dofs = _argument_function_space->dofmap()->element_dof_layout().num_dofs(); auto element = _argument_function_space->element(); - + num_argument_dofs *= _argument_function_space->dofmap()->bs(); assert(element); if (element->needs_dof_transformations()) { @@ -227,11 +225,11 @@ class Expression } // Iterate over cells and 'assemble' into values - const int size0 = _x_ref.second[0] * value_size(); + int size0 = _x_ref.second[0] * value_size(); std::vector values_local(size0 * num_argument_dofs, 0); for (std::size_t e = 0; e < entities.size() / estride; ++e) { - const std::int32_t entity = entities[e * estride]; + std::int32_t entity = entities[e * estride]; auto x_dofs = MDSPAN_IMPL_STANDARD_NAMESPACE::submdspan( x_dofmap, entity, MDSPAN_IMPL_STANDARD_NAMESPACE::full_extent); for (std::size_t i = 0; i < x_dofs.size(); ++i) @@ -243,10 +241,9 @@ class Expression const scalar_type* coeff_cell = coeffs.data() + e * cstride; const int* entity_index = get_entity_index(entities, e); - std::fill(values_local.begin(), values_local.end(), 0); + std::ranges::fill(values_local, 0); _fn(values_local.data(), coeff_cell, constant_data.data(), coord_dofs.data(), entity_index, nullptr); - post_dof_transform(values_local, cell_info, e, size0); for (std::size_t j = 0; j < values_local.size(); ++j) values[e * vshape[1] + j] = values_local[j]; diff --git a/cpp/dolfinx/fem/FiniteElement.cpp b/cpp/dolfinx/fem/FiniteElement.cpp index af34bf25799..58e973ecb49 100644 --- a/cpp/dolfinx/fem/FiniteElement.cpp +++ b/cpp/dolfinx/fem/FiniteElement.cpp @@ -13,7 +13,6 @@ #include #include #include -#include #include #include @@ -22,64 +21,11 @@ using namespace dolfinx::fem; namespace { -//----------------------------------------------------------------------------- - -/// Check if an element is a Basix element (or a blocked element -/// containing a Basix element) -bool is_basix_element(const ufcx_finite_element& element) -{ - if (element.element_type == ufcx_basix_element) - return true; - else if (element.block_size != 1) - { - // TODO: what should happen if the element is a blocked element - // containing a blocked element containing a Basix element? - return element.sub_elements[0]->element_type == ufcx_basix_element; - } - else - return false; -} -//----------------------------------------------------------------------------- - -/// Check if an element is a quadrature element (or a blocked element -/// containing a quadrature element) -bool is_quadrature_element(const ufcx_finite_element& element) -{ - if (element.element_type == ufcx_quadrature_element) - return true; - else if (element.block_size != 1) - { - // TODO: what should happen if the element is a blocked element - // containing a blocked element containing a quadrature element? - return element.sub_elements[0]->element_type == ufcx_quadrature_element; - } - else - return false; -} -//----------------------------------------------------------------------------- - -/// Check if an element is a custom Basix element (or a blocked element -/// containing a custom Basix element) -bool is_basix_custom_element(const ufcx_finite_element& element) -{ - if (element.element_type == ufcx_basix_custom_element) - return true; - else if (element.block_size != 1) - { - // TODO: what should happen if the element is a blocked element - // containing a blocked element containing a Basix element? - return element.sub_elements[0]->element_type == ufcx_basix_custom_element; - } - else - return false; -} -//----------------------------------------------------------------------------- - /// Recursively extract sub finite element template std::shared_ptr> _extract_sub_element(const FiniteElement& finite_element, - const std::vector& component) + std::span component) { // Check that a sub system has been specified if (component.empty()) @@ -115,210 +61,64 @@ _extract_sub_element(const FiniteElement& finite_element, return _extract_sub_element(*sub_element, sub_component); } -//----------------------------------------------------------------------------- - } // namespace //----------------------------------------------------------------------------- template -FiniteElement::FiniteElement(const ufcx_finite_element& e) - : _signature(e.signature), _space_dim(e.space_dimension), - _reference_value_shape(e.reference_value_shape, - e.reference_value_shape + e.reference_value_rank), - _bs(e.block_size), _is_mixed(e.element_type == ufcx_mixed_element), - _symmetric(e.symmetric) -{ - const ufcx_shape _shape = e.cell_shape; - switch (_shape) - { - case interval: - _cell_shape = mesh::CellType::interval; - break; - case triangle: - _cell_shape = mesh::CellType::triangle; - break; - case quadrilateral: - _cell_shape = mesh::CellType::quadrilateral; - break; - case tetrahedron: - _cell_shape = mesh::CellType::tetrahedron; - break; - case prism: - _cell_shape = mesh::CellType::prism; - break; - case hexahedron: - _cell_shape = mesh::CellType::hexahedron; - break; - default: - throw std::runtime_error( - "Unknown UFC cell type when building FiniteElement."); - } - assert(mesh::cell_dim(_cell_shape) == e.topological_dimension); - - static const std::map ufcx_to_cell - = {{vertex, "point"}, {interval, "interval"}, - {triangle, "triangle"}, {tetrahedron, "tetrahedron"}, - {prism, "prism"}, {quadrilateral, "quadrilateral"}, - {hexahedron, "hexahedron"}}; - const std::string cell_shape = ufcx_to_cell.at(e.cell_shape); - - _needs_dof_transformations = false; - _needs_dof_permutations = false; - // Create all sub-elements - for (int i = 0; i < e.num_sub_elements; ++i) - { - ufcx_finite_element* ufcx_sub_element = e.sub_elements[i]; - _sub_elements.push_back( - std::make_shared>(*ufcx_sub_element)); - if (_sub_elements[i]->needs_dof_permutations()) - _needs_dof_permutations = true; - if (_sub_elements[i]->needs_dof_transformations()) - _needs_dof_transformations = true; - } - - if (is_basix_custom_element(e)) - { - // Recreate the custom Basix element using information written into - // the generated code - ufcx_basix_custom_finite_element* ce = e.custom_element; - const basix::cell::type cell_type - = static_cast(ce->cell_type); - - const std::vector value_shape( - ce->value_shape, ce->value_shape + ce->value_shape_length); - const std::size_t value_size = std::reduce( - value_shape.begin(), value_shape.end(), 1, std::multiplies{}); - - const int nderivs = ce->interpolation_nderivs; - const std::size_t nderivs_dim = basix::polyset::nderivs(cell_type, nderivs); - - using array2_t = std::pair, std::array>; - using array4_t = std::pair, std::array>; - std::array, 4> x; - std::array, 4> M; - { // scope - int pt_n = 0; - int p_e = 0; - int m_e = 0; - const std::size_t dim = static_cast( - basix::cell::topological_dimension(cell_type)); - for (std::size_t d = 0; d <= dim; ++d) - { - const int num_entities = basix::cell::num_sub_entities(cell_type, d); - for (int entity = 0; entity < num_entities; ++entity) - { - std::size_t npts = ce->npts[pt_n + entity]; - std::size_t ndofs = ce->ndofs[pt_n + entity]; - - std::array pshape = {npts, dim}; - auto& pts - = x[d].emplace_back(std::vector(pshape[0] * pshape[1]), pshape) - .first; - std::copy_n(ce->x + p_e, pts.size(), pts.begin()); - p_e += pts.size(); - - std::array mshape = {ndofs, value_size, npts, nderivs_dim}; - std::size_t msize - = std::reduce(mshape.begin(), mshape.end(), 1, std::multiplies{}); - auto& mat = M[d].emplace_back(std::vector(msize), mshape).first; - std::copy_n(ce->M + m_e, mat.size(), mat.begin()); - m_e += mat.size(); - } - - pt_n += num_entities; - } - } - - using cmdspan2_t = MDSPAN_IMPL_STANDARD_NAMESPACE::mdspan< - const T, MDSPAN_IMPL_STANDARD_NAMESPACE::dextents>; - using cmdspan4_t = MDSPAN_IMPL_STANDARD_NAMESPACE::mdspan< - const T, MDSPAN_IMPL_STANDARD_NAMESPACE::dextents>; - - std::array, 4> _x; - for (std::size_t i = 0; i < x.size(); ++i) - for (auto& [buffer, shape] : x[i]) - _x[i].push_back(cmdspan2_t(buffer.data(), shape)); - - std::array, 4> _M; - for (std::size_t i = 0; i < M.size(); ++i) - for (auto& [buffer, shape] : M[i]) - _M[i].push_back(cmdspan4_t(buffer.data(), shape)); - - std::vector wcoeffs_b(ce->wcoeffs_rows * ce->wcoeffs_cols); - cmdspan2_t wcoeffs(wcoeffs_b.data(), ce->wcoeffs_rows, ce->wcoeffs_cols); - std::copy_n(ce->wcoeffs, wcoeffs_b.size(), wcoeffs_b.begin()); - - _element = std::make_unique>( - basix::create_custom_element( - cell_type, value_shape, wcoeffs, _x, _M, nderivs, - static_cast(ce->map_type), - static_cast(ce->sobolev_space), - ce->discontinuous, ce->embedded_subdegree, ce->embedded_superdegree, - static_cast(ce->polyset_type))); - _needs_dof_transformations - = !_element->dof_transformations_are_identity() - and !_element->dof_transformations_are_permutations(); - - _needs_dof_permutations - = !_element->dof_transformations_are_identity() - and _element->dof_transformations_are_permutations(); - } - else if (is_basix_element(e)) +FiniteElement::FiniteElement(mesh::CellType cell_type, + std::span points, + std::array pshape, + std::size_t block_size, bool symmetric) + : _signature("Quadrature element " + std::to_string(pshape[0]) + " " + + std::to_string(block_size)), + _space_dim(pshape[0] * block_size), _reference_value_shape({}), + _bs(block_size), _is_mixed(false), _symmetric(symmetric), + _needs_dof_permutations(false), _needs_dof_transformations(false), + _entity_dofs(mesh::cell_dim(cell_type) + 1), + _entity_closure_dofs(mesh::cell_dim(cell_type) + 1), + _points(std::vector(points.begin(), points.end()), pshape) +{ + const int tdim = mesh::cell_dim(cell_type); + for (int d = 0; d <= tdim; ++d) { - _element - = std::make_unique>(basix::create_element( - static_cast(e.basix_family), - static_cast(e.basix_cell), e.degree, - static_cast(e.lagrange_variant), - static_cast(e.dpc_variant), - e.discontinuous)); - _needs_dof_transformations - = !_element->dof_transformations_are_identity() - and !_element->dof_transformations_are_permutations(); - _needs_dof_permutations - = !_element->dof_transformations_are_identity() - and _element->dof_transformations_are_permutations(); + int num_entities = mesh::cell_num_entities(cell_type, d); + _entity_dofs[d].resize(num_entities); + _entity_closure_dofs[d].resize(num_entities); } - if (is_quadrature_element(e)) + for (std::size_t i = 0; i < pshape[0]; ++i) { - assert(e.custom_quadrature); - ufcx_quadrature_rule* qr = e.custom_quadrature; - std::size_t npts = qr->npts; - std::size_t tdim = qr->topological_dimension; - std::array qshape = {npts, tdim}; - _points = std::make_pair(std::vector(qshape[0] * qshape[1]), qshape); - std::copy_n(qr->points, _points.first.size(), _points.first.begin()); + _entity_dofs[tdim][0].push_back(i); + _entity_closure_dofs[tdim][0].push_back(i); } } //----------------------------------------------------------------------------- template FiniteElement::FiniteElement(const basix::FiniteElement& element, - const std::size_t block_size, - const bool symmetric) - : _reference_value_shape(element.value_shape()), _bs(block_size), - _is_mixed(false), _symmetric(symmetric) + std::size_t block_size, bool symmetric) + : _space_dim(block_size * element.dim()), + _reference_value_shape(element.value_shape()), _bs(block_size), + _is_mixed(false), + _element(std::make_unique>(element)), + _symmetric(symmetric), + _needs_dof_permutations( + !_element->dof_transformations_are_identity() + and _element->dof_transformations_are_permutations()), + _needs_dof_transformations( + !_element->dof_transformations_are_identity() + and !_element->dof_transformations_are_permutations()), + _entity_dofs(element.entity_dofs()), + _entity_closure_dofs(element.entity_closure_dofs()) { - _space_dim = _bs * element.dim(); - // Create all sub-elements if (_bs > 1) { - for (int i = 0; i < _bs; ++i) - { - _sub_elements.push_back(std::make_shared>(element, 1)); - } + _sub_elements + = std::vector>>( + _bs, std::make_shared>(element, 1)); _reference_value_shape = {block_size}; } - _element = std::make_unique>(element); - assert(_element); - _needs_dof_transformations - = !_element->dof_transformations_are_identity() - and !_element->dof_transformations_are_permutations(); - _needs_dof_permutations - = !_element->dof_transformations_are_identity() - and _element->dof_transformations_are_permutations(); std::string family; switch (_element->family()) { @@ -337,6 +137,65 @@ FiniteElement::FiniteElement(const basix::FiniteElement& element, } //----------------------------------------------------------------------------- template +FiniteElement::FiniteElement( + const std::vector>>& elements) + : _space_dim(0), _sub_elements(elements), _bs(1), _is_mixed(true), + _symmetric(false), _needs_dof_permutations(false), + _needs_dof_transformations(false) +{ + std::size_t vsize = 0; + _signature = "Mixed element ("; + + const std::vector>>& ed + = elements[0]->entity_dofs(); + _entity_dofs.resize(ed.size()); + _entity_closure_dofs.resize(ed.size()); + for (std::size_t i = 0; i < ed.size(); ++i) + { + _entity_dofs[i].resize(ed[i].size()); + _entity_closure_dofs[i].resize(ed[i].size()); + } + + int dof_offset = 0; + for (auto& e : elements) + { + vsize += e->reference_value_size(); + _signature += e->signature() + ", "; + + if (e->needs_dof_permutations()) + _needs_dof_permutations = true; + if (e->needs_dof_transformations()) + _needs_dof_transformations = true; + + const std::size_t sub_bs = e->block_size(); + for (std::size_t i = 0; i < _entity_dofs.size(); ++i) + { + for (std::size_t j = 0; j < _entity_dofs[i].size(); ++j) + { + const std::vector sub_ed = e->entity_dofs()[i][j]; + const std::vector sub_ecd = e->entity_closure_dofs()[i][j]; + for (auto k : sub_ed) + { + for (std::size_t b = 0; b < sub_bs; ++b) + _entity_dofs[i][j].push_back(dof_offset + k * sub_bs + b); + } + for (auto k : sub_ecd) + { + for (std::size_t b = 0; b < sub_bs; ++b) + _entity_closure_dofs[i][j].push_back(dof_offset + k * sub_bs + b); + } + } + } + + dof_offset += e->space_dimension(); + } + + _space_dim = dof_offset; + _reference_value_shape = {vsize}; + _signature += ")"; +} +//----------------------------------------------------------------------------- +template bool FiniteElement::operator==(const FiniteElement& e) const { if (!_element or !e._element) @@ -361,21 +220,30 @@ std::string FiniteElement::signature() const noexcept } //----------------------------------------------------------------------------- template -mesh::CellType FiniteElement::cell_shape() const noexcept +int FiniteElement::space_dimension() const noexcept { - return _cell_shape; + return _space_dim; } //----------------------------------------------------------------------------- template -int FiniteElement::space_dimension() const noexcept +std::span +FiniteElement::reference_value_shape() const noexcept { - return _space_dim; + return _reference_value_shape; } //----------------------------------------------------------------------------- template -std::span FiniteElement::reference_value_shape() const +const std::vector>>& +FiniteElement::entity_dofs() const noexcept { - return _reference_value_shape; + return _entity_dofs; +} +//----------------------------------------------------------------------------- +template +const std::vector>>& +FiniteElement::entity_closure_dofs() const noexcept +{ + return _entity_closure_dofs; } //----------------------------------------------------------------------------- template @@ -440,8 +308,8 @@ FiniteElement::extract_sub_element(const std::vector& component) const { // Recursively extract sub element auto sub_finite_element = _extract_sub_element(*this, component); - DLOG(INFO) << "Extracted finite element for sub-system: " - << sub_finite_element->signature().c_str(); + spdlog::debug("Extracted finite element for sub-system: {}", + sub_finite_element->signature().c_str()); return sub_finite_element; } //----------------------------------------------------------------------------- diff --git a/cpp/dolfinx/fem/FiniteElement.h b/cpp/dolfinx/fem/FiniteElement.h index 86cbec6ad8d..348b513330b 100644 --- a/cpp/dolfinx/fem/FiniteElement.h +++ b/cpp/dolfinx/fem/FiniteElement.h @@ -18,8 +18,6 @@ #include #include -struct ufcx_finite_element; - namespace dolfinx::fem { /// DOF transformation type @@ -42,16 +40,28 @@ class FiniteElement /// Geometry type of the Mesh that the FunctionSpace is defined on. using geometry_type = T; - /// @brief Create finite element from UFC finite element. - /// @param[in] e UFC finite element. - explicit FiniteElement(const ufcx_finite_element& e); - - /// @brief Create finite element from a Basix finite element. + /// @brief Create a finite element from a Basix finite element. /// @param[in] element Basix finite element /// @param[in] block_size The block size for the element /// @param[in] symmetric Is the element a symmetric tensor? FiniteElement(const basix::FiniteElement& element, - const std::size_t block_size, const bool symmetric = false); + std::size_t block_size, bool symmetric = false); + + /// @brief Create mixed finite element from a list of finite elements. + /// @param[in] elements Basix finite elements + FiniteElement( + const std::vector>>& + elements); + + /// @brief Create a quadrature element + /// @param[in] cell_type The cell type + /// @param[in] points Quadrature points + /// @param[in] pshape Shape of points array + /// @param[in] block_size The block size for the element + /// @param[in] symmetric Is the element a symmetric tensor? + FiniteElement(mesh::CellType cell_type, std::span points, + std::array pshape, std::size_t block_size, + bool symmetric = false); /// Copy constructor FiniteElement(const FiniteElement& element) = delete; @@ -88,10 +98,6 @@ class FiniteElement /// properties. std::string signature() const noexcept; - /// Cell shape - /// @return Element cell shape - mesh::CellType cell_shape() const noexcept; - /// Dimension of the finite element function space (the number of /// degrees-of-freedom for the element) /// @return Dimension of the finite element space @@ -109,7 +115,15 @@ class FiniteElement int reference_value_size() const; /// The reference value shape - std::span reference_value_shape() const; + std::span reference_value_shape() const noexcept; + + /// The local DOFs associated with each subentity of the cell + const std::vector>>& + entity_dofs() const noexcept; + + /// The local DOFs associated with the closure of each subentity of the cell + const std::vector>>& + entity_closure_dofs() const noexcept; /// Does the element represent a symmetric 2-tensor? bool symmetric() const; @@ -315,25 +329,25 @@ class FiniteElement // Mixed element std::vector, std::span, std::int32_t, int)>> - sub_element_functions; + sub_element_fns; std::vector dims; for (std::size_t i = 0; i < _sub_elements.size(); ++i) { - sub_element_functions.push_back( + sub_element_fns.push_back( _sub_elements[i]->template dof_transformation_fn(ttype)); dims.push_back(_sub_elements[i]->space_dimension()); } - return [dims, sub_element_functions]( - std::span data, std::span cell_info, - std::int32_t cell, int block_size) + return [dims, sub_element_fns](std::span data, + std::span cell_info, + std::int32_t cell, int block_size) { std::size_t offset = 0; - for (std::size_t e = 0; e < sub_element_functions.size(); ++e) + for (std::size_t e = 0; e < sub_element_fns.size(); ++e) { const std::size_t width = dims[e] * block_size; - sub_element_functions[e](data.subspan(offset, width), cell_info, - cell, block_size); + sub_element_fns[e](data.subspan(offset, width), cell_info, cell, + block_size); offset += width; } }; @@ -343,13 +357,12 @@ class FiniteElement // Blocked element std::function, std::span, std::int32_t, int)> - sub_function - = _sub_elements[0]->template dof_transformation_fn(ttype); + sub_fn = _sub_elements[0]->template dof_transformation_fn(ttype); const int ebs = _bs; - return [ebs, sub_function](std::span data, - std::span cell_info, - std::int32_t cell, int data_block_size) - { sub_function(data, cell_info, cell, ebs * data_block_size); }; + return [ebs, sub_fn](std::span data, + std::span cell_info, + std::int32_t cell, int data_block_size) + { sub_fn(data, cell_info, cell, ebs * data_block_size); }; } } @@ -420,22 +433,22 @@ class FiniteElement // Mixed element std::vector, std::span, std::int32_t, int)>> - sub_element_functions; + sub_element_fns; for (std::size_t i = 0; i < _sub_elements.size(); ++i) { - sub_element_functions.push_back( + sub_element_fns.push_back( _sub_elements[i]->template dof_transformation_right_fn(ttype)); } - return [this, sub_element_functions]( - std::span data, std::span cell_info, - std::int32_t cell, int block_size) + return [this, sub_element_fns](std::span data, + std::span cell_info, + std::int32_t cell, int block_size) { std::size_t offset = 0; - for (std::size_t e = 0; e < sub_element_functions.size(); ++e) + for (std::size_t e = 0; e < sub_element_fns.size(); ++e) { - sub_element_functions[e](data.subspan(offset, data.size() - offset), - cell_info, cell, block_size); + sub_element_fns[e](data.subspan(offset, data.size() - offset), + cell_info, cell, block_size); offset += _sub_elements[e]->space_dimension(); } }; @@ -449,18 +462,17 @@ class FiniteElement // transformation from the left to data using xxxyyyzzz ordering std::function, std::span, std::int32_t, int)> - sub_function - = _sub_elements[0]->template dof_transformation_fn(ttype); - return [this, sub_function](std::span data, - std::span cell_info, - std::int32_t cell, int data_block_size) + sub_fn = _sub_elements[0]->template dof_transformation_fn(ttype); + return [this, sub_fn](std::span data, + std::span cell_info, + std::int32_t cell, int data_block_size) { const int ebs = block_size(); const std::size_t dof_count = data.size() / data_block_size; for (int block = 0; block < data_block_size; ++block) { - sub_function(data.subspan(block * dof_count, dof_count), cell_info, - cell, ebs); + sub_fn(data.subspan(block * dof_count, dof_count), cell_info, cell, + ebs); } }; } @@ -706,8 +718,6 @@ class FiniteElement private: std::string _signature; - mesh::CellType _cell_shape; - int _space_dim; // List of sub-elements (if any) @@ -724,6 +734,9 @@ class FiniteElement // Indicate whether this is a mixed element bool _is_mixed; + // Basix Element (nullptr for mixed elements) + std::unique_ptr> _element; + // Indicate whether this element represents a symmetric 2-tensor bool _symmetric; @@ -731,8 +744,8 @@ class FiniteElement bool _needs_dof_permutations; bool _needs_dof_transformations; - // Basix Element (nullptr for mixed elements) - std::unique_ptr> _element; + std::vector>> _entity_dofs; + std::vector>> _entity_closure_dofs; // Quadrature points of a quadrature element (0 dimensional array for // all elements except quadrature elements) diff --git a/cpp/dolfinx/fem/Form.h b/cpp/dolfinx/fem/Form.h index 593658a6a09..6b61e4a8490 100644 --- a/cpp/dolfinx/fem/Form.h +++ b/cpp/dolfinx/fem/Form.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -44,47 +45,65 @@ template > struct integral_data { /// @brief Create a structure to hold integral data. - /// @tparam U Entity indices type. - /// @param id Domain ID. - /// @param kernel Integration kernel. - /// @param entities Entities to integrate over. - template + /// @param[in] id Domain ID. + /// @param[in] kernel Integration kernel. + /// @param[in] entities Indices of entities to integrate over. + /// @param[in] coeffs Indicies of the coefficients are present + /// (active) in `kernel`. + template requires std::is_convertible_v< std::remove_cvref_t, std::function> and std::is_convertible_v, std::vector> - integral_data(int id, K&& kernel, V&& entities) + and std::is_convertible_v, + std::vector> + integral_data(int id, K&& kernel, V&& entities, W&& coeffs) : id(id), kernel(std::forward(kernel)), - entities(std::forward(entities)) + entities(std::forward(entities)), coeffs(std::forward(coeffs)) { } /// @brief Create a structure to hold integral data. - /// @param id Domain ID - /// @param kernel Integration kernel. - /// @param e Entities to integrate over. - template + /// + /// @param[in] id Domain ID. + /// @param[in] kernel Integration kernel. + /// @param[in] entities Indices of entities to integrate over. + /// @param[in] coeffs Indicies of the coefficients that are active in + /// the `kernel`. + /// + /// @note This version allows `entities` to be passed as a std::span, + /// which is then copied. + template requires std::is_convertible_v< std::remove_cvref_t, std::function> - integral_data(int id, K&& kernel, std::span e) - : id(id), kernel(std::forward(kernel)), entities(e.begin(), e.end()) + and std::is_convertible_v, + std::vector> + integral_data(int id, K&& kernel, std::span entities, + W&& coeffs) + : id(id), kernel(std::forward(kernel)), + entities(entities.begin(), entities.end()), + coeffs(std::forward(coeffs)) { } - /// Integral ID + /// @brief Integral ID. int id; - /// The integration kernel + /// @brief The integration kernel. std::function kernel; - /// The entities to integrate over + /// @brief The entities to integrate over. std::vector entities; + + /// @brief Indices of coefficients (from the form) that are in this + /// integral. + std::vector coeffs; }; /// @brief A representation of finite element variational forms. @@ -122,6 +141,9 @@ class Form /// Scalar type using scalar_type = T; + /// Geometry type + using geometry_type = U; + /// @brief Create a finite element form. /// /// @note User applications will normally call a factory function @@ -153,17 +175,20 @@ class Form template requires std::is_convertible_v< std::remove_cvref_t, - std::map>>> - Form(const std::vector>>& V, - X&& integrals, - const std::vector>>& - coefficients, - const std::vector>>& - constants, - bool needs_facet_permutations, - const std::map>, - std::span>& entity_maps, - std::shared_ptr> mesh = nullptr) + std::map>>> + Form( + const std::vector>>& V, + X&& integrals, + const std::vector< + std::shared_ptr>>& + coefficients, + const std::vector>>& + constants, + bool needs_facet_permutations, + const std::map>, + std::span>& entity_maps, + std::shared_ptr> mesh = nullptr) : _function_spaces(V), _coefficients(coefficients), _constants(constants), _mesh(mesh), _needs_facet_permutations(needs_facet_permutations) { @@ -185,16 +210,16 @@ class Form // Store kernels, looping over integrals by domain type (dimension) for (auto&& [domain_type, data] : integrals) { - if (!std::is_sorted(data.begin(), data.end(), - [](auto& a, auto& b) { return a.id < b.id; })) + if (!std::ranges::is_sorted(data, + [](auto& a, auto& b) { return a.id < b.id; })) { throw std::runtime_error("Integral IDs not sorted"); } - std::vector>& itg + std::vector>& itg = _integrals[static_cast(domain_type)]; - for (auto&& [id, kern, e] : data) - itg.emplace_back(id, kern, std::move(e)); + for (auto&& [id, kern, e, c] : data) + itg.emplace_back(id, kern, std::move(e), std::move(c)); } // Store entity maps @@ -220,11 +245,14 @@ class Form /// @brief Extract common mesh for the form. /// @return The mesh. - std::shared_ptr> mesh() const { return _mesh; } + std::shared_ptr> mesh() const + { + return _mesh; + } /// @brief Function spaces for all arguments. /// @return Function spaces. - const std::vector>>& + const std::vector>>& function_spaces() const { return _function_spaces; @@ -232,17 +260,16 @@ class Form /// @brief Get the kernel function for integral `i` on given domain /// type. - /// @param[in] type Integral type - /// @param[in] i Domain identifier (index) - /// @return Function to call for tabulate_tensor - std::function + /// @param[in] type Integral type. + /// @param[in] i Domain identifier (index). + /// @return Function to call for `tabulate_tensor`. + std::function kernel(IntegralType type, int i) const { const auto& integrals = _integrals[static_cast(type)]; - auto it = std::lower_bound(integrals.begin(), integrals.end(), i, - [](auto& itg_data, int i) - { return itg_data.id < i; }); + auto it = std::ranges::lower_bound(integrals, i, std::less<>{}, + [](const auto& a) { return a.id; }); if (it != integrals.end() and it->id == i) return it->kernel; else @@ -271,6 +298,22 @@ class Form return _integrals[static_cast(type)].size(); } + /// @brief Indices of coefficients that are active for a given + /// integral (kernel). + /// + /// A form is split into multiple integrals (kernels) and each + /// integral might container only a subset of all coefficients in the + /// form. This function returns an indicator array for a given + /// integral kernel that signifies which coefficients are present. + /// + /// @param[in] type Integral type. + /// @param[in] i Index of the integral. + std::vector active_coeffs(IntegralType type, std::size_t i) const + { + assert(i < _integrals[static_cast(type)].size()); + return _integrals[static_cast(type)][i].coeffs; + } + /// @brief Get the IDs for integrals (kernels) for given integral type. /// /// The IDs correspond to the domain IDs which the integrals are @@ -282,8 +325,8 @@ class Form { std::vector ids; const auto& integrals = _integrals[static_cast(type)]; - std::transform(integrals.begin(), integrals.end(), std::back_inserter(ids), - [](auto& integral) { return integral.id; }); + std::ranges::transform(integrals, std::back_inserter(ids), + [](auto& integral) { return integral.id; }); return ids; } @@ -307,9 +350,8 @@ class Form std::span domain(IntegralType type, int i) const { const auto& integrals = _integrals[static_cast(type)]; - auto it = std::lower_bound(integrals.begin(), integrals.end(), i, - [](auto& itg_data, int i) - { return itg_data.id < i; }); + auto it = std::ranges::lower_bound(integrals, i, std::less<>{}, + [](const auto& a) { return a.id; }); if (it != integrals.end() and it->id == i) return it->entities; else @@ -325,11 +367,11 @@ class Form /// @param mesh The mesh the entities are numbered with respect to. /// @return List of active entities in `mesh` for the given integral. std::vector domain(IntegralType type, int i, - const mesh::Mesh& mesh) const + const mesh::Mesh& mesh) const { // Hack to avoid passing shared pointer to this function - std::shared_ptr> msh_ptr(&mesh, - [](const mesh::Mesh*) {}); + std::shared_ptr> msh_ptr( + &mesh, [](const mesh::Mesh*) {}); std::span entities = domain(type, i); if (msh_ptr == _mesh) @@ -343,21 +385,78 @@ class Form { case IntegralType::cell: { - std::transform(entities.begin(), entities.end(), - std::back_inserter(mapped_entities), - [&entity_map](auto e) { return entity_map[e]; }); + std::ranges::transform(entities, std::back_inserter(mapped_entities), + [&entity_map](auto e) { return entity_map[e]; }); break; } case IntegralType::exterior_facet: - // Exterior and interior facets are treated the same - [[fallthrough]]; + { + // Get the codimension of the mesh + const int tdim = _mesh->topology()->dim(); + const int codim = tdim - mesh.topology()->dim(); + assert(codim >= 0); + if (codim == 0) + { + for (std::size_t i = 0; i < entities.size(); i += 2) + { + // Add cell and the local facet index + mapped_entities.insert(mapped_entities.end(), + {entity_map[entities[i]], entities[i + 1]}); + } + } + else if (codim == 1) + { + // In this case, the entity maps take facets in (`_mesh`) to cells in + // `mesh`, so we need to get the facet number from the (cell, + // local_facet pair) first. + auto c_to_f = _mesh->topology()->connectivity(tdim, tdim - 1); + assert(c_to_f); + for (std::size_t i = 0; i < entities.size(); i += 2) + { + // Get the facet index + const std::int32_t facet + = c_to_f->links(entities[i])[entities[i + 1]]; + // Add cell and the local facet index + mapped_entities.insert(mapped_entities.end(), + {entity_map[facet], entities[i + 1]}); + } + } + else + throw std::runtime_error("Codimension > 1 not supported."); + + break; + } case IntegralType::interior_facet: { - for (std::size_t i = 0; i < entities.size(); i += 2) + // Get the codimension of the mesh + const int tdim = _mesh->topology()->dim(); + const int codim = tdim - mesh.topology()->dim(); + assert(codim >= 0); + if (codim == 0) + { + for (std::size_t i = 0; i < entities.size(); i += 2) + { + // Add cell and the local facet index + mapped_entities.insert(mapped_entities.end(), + {entity_map[entities[i]], entities[i + 1]}); + } + } + else if (codim == 1) { - // Add cell and the local facet index - mapped_entities.insert(mapped_entities.end(), - {entity_map[entities[i]], entities[i + 1]}); + // In this case, the entity maps take facets in (`_mesh`) to cells in + // `mesh`, so we need to get the facet number from the (cell, + // local_facet pair) first. + auto c_to_f = _mesh->topology()->connectivity(tdim, tdim - 1); + assert(c_to_f); + for (std::size_t i = 0; i < entities.size(); i += 2) + { + // Get the facet index + const std::int32_t facet + = c_to_f->links(entities[i])[entities[i + 1]]; + // Add cell and the local facet index + mapped_entities.insert(mapped_entities.end(), + {entity_map[facet], entities[i + 1]}); + } } break; } @@ -370,7 +469,9 @@ class Form } /// @brief Access coefficients. - const std::vector>>& coefficients() const + const std::vector< + std::shared_ptr>>& + coefficients() const { return _coefficients; } @@ -397,33 +498,38 @@ class Form } /// @brief Access constants. - const std::vector>>& constants() const + const std::vector>>& + constants() const { return _constants; } private: // Function spaces (one for each argument) - std::vector>> _function_spaces; + std::vector>> + _function_spaces; // Integrals. Array index is // static_cast>, 4> _integrals; + std::array>, 4> + _integrals; // Form coefficients - std::vector>> _coefficients; + std::vector>> + _coefficients; // Constants associated with the Form - std::vector>> _constants; + std::vector>> _constants; // The mesh - std::shared_ptr> _mesh; + std::shared_ptr> _mesh; // True if permutation data needs to be passed into these integrals bool _needs_facet_permutations; // Entity maps (see Form documentation) - std::map>, std::vector> + std::map>, + std::vector> _entity_maps; }; // namespace dolfinx::fem } // namespace dolfinx::fem diff --git a/cpp/dolfinx/fem/Function.h b/cpp/dolfinx/fem/Function.h index 92b4019fc99..2ec8298e71c 100644 --- a/cpp/dolfinx/fem/Function.h +++ b/cpp/dolfinx/fem/Function.h @@ -1,4 +1,4 @@ -// Copyright (C) 2003-2022 Anders Logg, Garth N. Wells and Massimiliano Leoni +// Copyright (C) 2003-2024 Anders Logg, Garth N. Wells and Massimiliano Leoni // // This file is part of DOLFINx (https://www.fenicsproject.org) // @@ -10,6 +10,7 @@ #include "FiniteElement.h" #include "FunctionSpace.h" #include "interpolate.h" +#include #include #include #include @@ -50,7 +51,7 @@ class Function /// Geometry type of the Mesh that the Function is defined on. using geometry_type = U; - /// Create function on given function space + /// @brief Create function on given function space. /// @param[in] V The function space explicit Function(std::shared_ptr> V) : _function_space(V), @@ -68,16 +69,16 @@ class Function /// vector. /// /// @warning This constructor is intended for internal library use - /// only + /// only. /// - /// @param[in] V The function space - /// @param[in] x The vector + /// @param[in] V The function space. + /// @param[in] x The vector. Function(std::shared_ptr> V, std::shared_ptr> x) : _function_space(V), _x(x) { - // We do not check for a subspace since this constructor is used for - // creating subfunctions + // NOTE: We do not check for a subspace since this constructor is + // used for creating subfunctions // Assertion uses '<=' to deal with sub-functions assert(V->dofmap()); @@ -138,69 +139,42 @@ class Function } /// @brief Access the function space. - /// @return The function space + /// @return The function space. std::shared_ptr> function_space() const { return _function_space; } - /// @brief Underlying vector + /// @brief Underlying vector (const version). std::shared_ptr> x() const { return _x; } - /// @brief Underlying vector + /// @brief Underlying vector. std::shared_ptr> x() { return _x; } - /// @brief Interpolate a provided Function. - /// @param[in] v The function to be interpolated - /// @param[in] cells The cells to interpolate on - /// @param[in] cell_map A map from cells in the mesh associated with `this` - /// function to cells in mesh associated with `v` - /// @param[in] nmm_interpolation_data Auxiliary data to interpolate on - /// nonmatching meshes. This data can be generated with - /// generate_nonmatching_meshes_interpolation_data (optional). - void interpolate( - const Function& v, - std::span cells, - std::span cell_map, - const std::tuple, - std::span, - std::span, - std::span>& nmm_interpolation_data - = {}) - { - fem::interpolate(*this, v, cells, cell_map, nmm_interpolation_data); - } - - /// @brief Interpolate a provided Function. - /// @param[in] v The function to be interpolated - /// @param[in] cell_map Map from cells in self to cell indices in `v` - /// @param[in] nmm_interpolation_data Auxiliary data to interpolate on - /// nonmatching meshes. This data can be generated with - /// generate_nonmatching_meshes_interpolation_data (optional). - void interpolate( - const Function& v, - std::span cell_map = std::span() - = {}, - const std::tuple, - std::span, - std::span, - std::span>& nmm_interpolation_data - = {}) + /// @brief Interpolate an expression f(x) on the whole domain. + /// @param[in] f Expression to be interpolated. + void + interpolate(const std::function< + std::pair, std::vector>( + MDSPAN_IMPL_STANDARD_NAMESPACE::mdspan< + const geometry_type, + MDSPAN_IMPL_STANDARD_NAMESPACE::extents< + std::size_t, 3, + MDSPAN_IMPL_STANDARD_NAMESPACE::dynamic_extent>>)>& f) { assert(_function_space); assert(_function_space->mesh()); int tdim = _function_space->mesh()->topology()->dim(); - auto cell_imap = _function_space->mesh()->topology()->index_map(tdim); - assert(cell_imap); - std::int32_t num_cells = cell_imap->size_local() + cell_imap->num_ghosts(); - std::vector cells(num_cells, 0); + auto cmap = _function_space->mesh()->topology()->index_map(tdim); + assert(cmap); + std::vector cells(cmap->size_local() + cmap->num_ghosts(), 0); std::iota(cells.begin(), cells.end(), 0); - interpolate(v, cells, cell_map, nmm_interpolation_data); + interpolate(f, cells); } - /// Interpolate an expression function on a list of cells - /// @param[in] f The expression function to be interpolated - /// @param[in] cells The cells to interpolate on + /// @brief Interpolate an expression f(x) over a set of cells. + /// @param[in] f Expression function to be interpolated. + /// @param[in] cells Cells to interpolate on. void interpolate( const std::function< std::pair, std::vector>( @@ -261,58 +235,133 @@ class Function _fshape, cells); } - /// @brief Interpolate an expression function on the whole domain. - /// @param[in] f Expression to be interpolated - void - interpolate(const std::function< - std::pair, std::vector>( - MDSPAN_IMPL_STANDARD_NAMESPACE::mdspan< - const geometry_type, - MDSPAN_IMPL_STANDARD_NAMESPACE::extents< - std::size_t, 3, - MDSPAN_IMPL_STANDARD_NAMESPACE::dynamic_extent>>)>& f) + /// @brief Interpolate a Function over all cells. + /// + /// @param[in] u Function to be interpolated. + /// @pre The mesh associated with `this` and the mesh associated with + /// `u` must be the same mesh::Mesh. + void interpolate(const Function& u) { assert(_function_space); assert(_function_space->mesh()); - const int tdim = _function_space->mesh()->topology()->dim(); - auto cell_map = _function_space->mesh()->topology()->index_map(tdim); - assert(cell_map); - std::int32_t num_cells = cell_map->size_local() + cell_map->num_ghosts(); - std::vector cells(num_cells, 0); + int tdim = _function_space->mesh()->topology()->dim(); + auto cmap = _function_space->mesh()->topology()->index_map(tdim); + assert(cmap); + std::vector cells(cmap->size_local() + cmap->num_ghosts(), 0); std::iota(cells.begin(), cells.end(), 0); - interpolate(f, cells); + interpolate(u, cells, cells); } - /// @brief Interpolate an Expression (based on UFL) - /// @param[in] e Expression to be interpolated. The Expression - /// must have been created using the reference coordinates - /// `FiniteElement::interpolation_points()` for the element associated - /// with `u`. - /// @param[in] cells The cells to interpolate on - /// @param[in] expr_mesh The mesh to evaluate the expression on - /// @param[in] cell_map Map from `cells` to cells in expression - void interpolate(const Expression& e, - std::span cells, - const dolfinx::mesh::Mesh& expr_mesh, - std::span cell_map - = std::span()) + /// @brief Interpolate a Function over a subset of cells. + /// + /// The Function being interpolated from and the Function being + /// interpolated into can be defined on different sub-meshes, i.e. + /// views into a subset a cells. + /// + /// @param[in] u0 Function to be interpolated. + /// @param[in] cells0 Cells to interpolate from. These are the indices + /// of the cells in the mesh associated with `u0`. + /// @param[in] cells1 Cell indices associated with the mesh of `this` + /// that will be interpolated to. If `cells0[i]` is the index of a + /// cell in the mesh associated with `u0`, then `cells1[i]` is the + /// index of the *same* cell but in the mesh associated with `this`. + /// This argument can be empty when `this` and `u0` share the same + /// mesh. Otherwise the length of `cells` and the length of `cells0` + /// must be the same. + void interpolate(const Function& u0, + std::span cells0, + std::span cells1 = {}) + { + if (cells1.empty()) + cells1 = cells0; + fem::interpolate(*this, cells1, u0, cells0); + } + + /// @brief Interpolate an Expression on all cells. + /// + /// @param[in] e Expression to be interpolated. + /// @pre If a mesh is associated with Function coefficients of `e`, it + /// must be the same as the mesh::Mesh associated with `this`. + void interpolate(const Expression& e) { - // Check that spaces are compatible assert(_function_space); - assert(_function_space->element()); - std::size_t value_size = e.value_size(); - if (e.argument_function_space()) - throw std::runtime_error("Cannot interpolate Expression with Argument"); + assert(_function_space->mesh()); + int tdim = _function_space->mesh()->topology()->dim(); + auto cmap = _function_space->mesh()->topology()->index_map(tdim); + assert(cmap); + std::vector cells(cmap->size_local() + cmap->num_ghosts(), 0); + std::iota(cells.begin(), cells.end(), 0); + interpolate(e, cells); + } + /// @brief Interpolate an Expression over a subset of cells. + /// + /// @param[in] e0 Expression to be interpolated. The Expression must + /// have been created using the reference coordinates created by + /// FiniteElement::interpolation_points for the element associated + /// with `this`. + /// @param[in] cells0 Cells in the mesh associated with `e0` to + /// interpolate from if `e0` has Function coefficients. If no mesh can + /// be associated with `e0` then the mesh associated with `this` is used. + /// @param[in] cells1 Cell indices associated with the mesh of `this` + /// that will be interpolated to. If `cells0[i]` is the index of a + /// cell in the mesh associated with `u0`, then `cells1[i]` is the + /// index of the *same* cell but in the mesh associated with `this`. + /// This argument can be empty when `this` and `u0` share the same + /// mesh. Otherwise the length of `cells` and the length of + /// `cells0` must be the same. + void interpolate(const Expression& e0, + std::span cells0, + std::span cells1 = {}) + { + // Extract mesh + const mesh::Mesh* mesh0 = nullptr; + for (auto& c : e0.coefficients()) + { + assert(c); + assert(c->function_space()); + assert(c->function_space()->mesh()); + if (const mesh::Mesh* mesh + = c->function_space()->mesh().get(); + !mesh0) + { + mesh0 = mesh; + } + else if (mesh != mesh0) + { + throw std::runtime_error( + "Expression coefficient Functions have different meshes."); + } + } + + // If Expression has no Function coefficients take mesh from `this`. + assert(_function_space); + assert(_function_space->mesh()); + if (!mesh0) + mesh0 = _function_space->mesh().get(); + + // If cells1 is empty and Function and Expression meshes are the + // same, make cells1 the same as cells0. Otherwise check that + // lengths of cells0 and cells1 are the same. + if (cells1.empty() and mesh0 == _function_space->mesh().get()) + cells1 = cells0; + else if (cells0.size() != cells1.size()) + throw std::runtime_error("Cells lists have different lengths."); + + // Check that Function and Expression spaces are compatible + assert(_function_space->element()); + std::size_t value_size = e0.value_size(); + if (e0.argument_function_space()) + throw std::runtime_error("Cannot interpolate Expression with Argument."); if (value_size != _function_space->value_size()) { throw std::runtime_error( - "Function value size not equal to Expression value size"); + "Function value size not equal to Expression value size."); } + // Compatibility check { - // Compatibility check - auto [X0, shape0] = e.X(); + auto [X0, shape0] = e0.X(); auto [X1, shape1] = _function_space->element()->interpolation_points(); if (shape0 != shape1) { @@ -332,8 +381,8 @@ class Function } // Array to hold evaluated Expression - std::size_t num_cells = cells.size(); - std::size_t num_points = e.X().second[0]; + std::size_t num_cells = cells0.size(); + std::size_t num_points = e0.X().second[0]; std::vector fdata(num_cells * num_points * value_size); MDSPAN_IMPL_STANDARD_NAMESPACE::mdspan< const value_type, @@ -341,32 +390,13 @@ class Function f(fdata.data(), num_cells, num_points, value_size); // Evaluate Expression at points - assert(_function_space->mesh()); + e0.eval(*mesh0, cells0, fdata, {num_cells, num_points * value_size}); - std::vector cells_expr; - cells_expr.reserve(num_cells); - // Get mesh and check if mesh is shared - if (auto mesh_v = _function_space->mesh(); - expr_mesh.topology() == mesh_v->topology()) - { - cells_expr.insert(cells_expr.end(), cells.begin(), cells.end()); - } - // If meshes are different and input mapping is given - else if (!cell_map.empty()) - { - std::transform(cells.begin(), cells.end(), std::back_inserter(cells_expr), - [&cell_map](std::int32_t c) { return cell_map[c]; }); - } - else - std::runtime_error("Meshes are different and no cell map is provided"); - - e.eval(expr_mesh, cells_expr, fdata, {num_cells, num_points * value_size}); - - // Reshape evaluated data to fit interpolate + // Reshape evaluated data to fit interpolate. // Expression returns matrix of shape (num_cells, num_points * // value_size), i.e. xyzxyz ordering of dof values per cell per // point. The interpolation uses xxyyzz input, ordered for all - // points of each cell, i.e. (value_size, num_cells*num_points) + // points of each cell, i.e. (value_size, num_cells*num_points). std::vector fdata1(num_cells * num_points * value_size); MDSPAN_IMPL_STANDARD_NAMESPACE::mdspan< value_type, MDSPAN_IMPL_STANDARD_NAMESPACE::dextents> @@ -379,46 +409,36 @@ class Function // Interpolate values into appropriate space fem::interpolate(*this, std::span(fdata1.data(), fdata1.size()), - {value_size, num_cells * num_points}, cells); + {value_size, num_cells * num_points}, cells1); } - /// Interpolate an Expression (based on UFL) on all cells - /// @param[in] e The function to be interpolated - /// @param[in] expr_mesh Mesh the Expression `e` is defined on. - /// @param[in] cell_map Map from `cells` to cells in expression if - /// receiving function is defined on a different mesh than the expression - void interpolate(const Expression& e, - const dolfinx::mesh::Mesh& expr_mesh, - std::span cell_map - = std::span() = {}) + /// @brief Interpolate a Function defined on a different mesh. + /// + /// @param[in] v Function to be interpolated. + /// @param[in] cells Cells in the mesh associated with `this` to + /// interpolate into. + /// @param[in] interpolation_data Data required for associating the + /// interpolation points of `this` with cells in `v`. Can be computed + /// with `fem::create_interpolation_data`. + void interpolate(const Function& v, + std::span cells, + const geometry::PointOwnershipData& interpolation_data) { - assert(_function_space); - assert(_function_space->mesh()); - const int tdim = _function_space->mesh()->topology()->dim(); - auto cell_imap = _function_space->mesh()->topology()->index_map(tdim); - assert(cell_imap); - std::int32_t num_cells = cell_imap->size_local() + cell_imap->num_ghosts(); - std::vector cells(num_cells, 0); - std::iota(cells.begin(), cells.end(), 0); - - if (cell_map.size() == 0) - interpolate(e, cells, expr_mesh, cell_map); - else - interpolate(e, cells, expr_mesh, std::span(cells.data(), cells.size())); + fem::interpolate(*this, v, cells, interpolation_data); } /// @brief Evaluate the Function at points. /// /// @param[in] x The coordinates of the points. It has shape /// (num_points, 3) and storage is row-major. - /// @param[in] xshape The shape of `x`. - /// @param[in] cells An array of cell indices. cells[i] is the index - /// of the cell that contains the point x(i). Negative cell indices - /// can be passed, and the corresponding point will be ignored. - /// @param[out] u The values at the points. Values are not computed - /// for points with a negative cell index. This argument must be - /// passed with the correct size. Storage is row-major. - /// @param[in] ushape The shape of `u`. + /// @param[in] xshape Shape of `x`. + /// @param[in] cells Cell indices such that `cells[i]` is the index of + /// the cell that contains the point x(i). Negative cell indices can + /// be passed, in which case the corresponding point is ignored. + /// @param[out] u Values at the points. Values are not computed for + /// points with a negative cell index. This argument must be passed + /// with the correct size. Storage is row-major. + /// @param[in] ushape Shape of `u`. void eval(std::span x, std::array xshape, std::span cells, std::span u, std::array ushape) const @@ -429,8 +449,8 @@ class Function assert(x.size() == xshape[0] * xshape[1]); assert(u.size() == ushape[0] * ushape[1]); - // TODO: This could be easily made more efficient by exploiting points - // being ordered by the cell to which they belong. + // TODO: This could be easily made more efficient by exploiting + // points being ordered by the cell to which they belong. if (xshape[0] != cells.size()) { @@ -501,7 +521,7 @@ class Function impl::mdspan_t xp(xp_b.data(), 1, gdim); // Loop over points - std::fill(u.data(), u.data() + u.size(), 0.0); + std::ranges::fill(u, 0.0); std::span _v = _x->array(); // Evaluate geometry basis at point (0, 0, 0) on the reference cell. @@ -592,7 +612,6 @@ class Function { // Pull-back physical point xp to reference coordinate Xp cmap.pull_back_nonaffine(Xp, xp, coord_dofs); - cmap.tabulate(1, std::span(Xpb.data(), tdim), {1, tdim}, phi_b); CoordinateElement::compute_jacobian(dphi, coord_dofs, _J); @@ -632,8 +651,8 @@ class Function = element->template dof_transformation_fn( doftransform::standard); - // Size of tensor for symmetric elements, unused in non-symmetric case, but placed outside the loop - // for pre-computation. + // Size of tensor for symmetric elements, unused in non-symmetric case, but + // placed outside the loop for pre-computation. int matrix_size; if (element->symmetric()) { @@ -641,6 +660,7 @@ class Function while (matrix_size * matrix_size < ushape[1]) ++matrix_size; } + const std::size_t num_basis_values = space_dimension * reference_value_size; for (std::size_t p = 0; p < cells.size(); ++p) { @@ -727,10 +747,10 @@ class Function std::string name = "u"; private: - // The function space + // Function space std::shared_ptr> _function_space; - // The vector of expansion coefficients (local) + // Vector of expansion coefficients (local) std::shared_ptr> _x; }; diff --git a/cpp/dolfinx/fem/assemble_matrix_impl.h b/cpp/dolfinx/fem/assemble_matrix_impl.h index 5ff43931f8c..c3ff3838cb8 100644 --- a/cpp/dolfinx/fem/assemble_matrix_impl.h +++ b/cpp/dolfinx/fem/assemble_matrix_impl.h @@ -107,7 +107,7 @@ void assemble_cells( } // Tabulate tensor - std::fill(Ae.begin(), Ae.end(), 0); + std::ranges::fill(Ae, 0); kernel(Ae.data(), coeffs.data() + index * cstride, constants.data(), coordinate_dofs.data(), nullptr, nullptr); @@ -159,36 +159,42 @@ void assemble_cells( /// @brief Execute kernel over exterior facets and accumulate result in /// a matrix. /// @tparam T Matrix/form scalar type. -/// @param mat_set Function that accumulates computed entries into a +/// @param[in] mat_set Function that accumulates computed entries into a /// matrix. -/// @param x_dofmap Dofmap for the mesh geometry. -/// @param x Mesh geometry (coordinates). -/// @param facets Facet indices (in the integration domain mesh) to +/// @param[in] x_dofmap Dofmap for the mesh geometry. +/// @param[in] x Mesh geometry (coordinates). +/// @param[in] num_facets_per_cell Number of cell facets +/// @param[in] facets Facet indices (in the integration domain mesh) to /// execute the kernel over. -/// @param dofmap0 Test function (row) degree-of-freedom data holding -/// the (0) dofmap, (1) dofmap block size and (2) dofmap cell indices. -/// @param P0 Function that applies transformation P0.A in-place to +/// @param[in] dofmap0 Test function (row) degree-of-freedom data +/// holding the (0) dofmap, (1) dofmap block size and (2) dofmap cell +/// indices. +/// @param[in] P0 Function that applies transformation P0.A in-place to /// transform test degrees-of-freedom. -/// @param dofmap1 Trial function (column) degree-of-freedom data +/// @param[in] dofmap1 Trial function (column) degree-of-freedom data /// holding the (0) dofmap, (1) dofmap block size and (2) dofmap cell /// indices. -/// @param P1T Function that applies transformation A.P1^T in-place to -/// transform trial degrees-of-freedom. -/// @param bc0 Marker for rows with Dirichlet boundary conditions applied -/// @param bc1 Marker for columns with Dirichlet boundary conditions applied -/// @param kernel Kernel function to execute over each cell. -/// @param coeffs The coefficient data array of shape (cells.size(), cstride), -/// flattened into row-major format. -/// @param cstride The coefficient stride -/// @param constants The constant data -/// @param cell_info0 The cell permutation information for the test function -/// mesh -/// @param cell_info1 The cell permutation information for the trial function -/// mesh +/// @param[in] P1T Function that applies transformation A.P1^T in-place +/// to transform trial degrees-of-freedom. +/// @param[in] bc0 Marker for rows with Dirichlet boundary conditions +/// applied. +/// @param[in] bc1 Marker for columns with Dirichlet boundary conditions +/// applied. +/// @param[in] kernel Kernel function to execute over each cell. +/// @param[in] coeffs Coefficient data array of shape `(cells.size(), +/// cstride)`, flattened into row-major format. +/// @param[in] cstride Coefficient stride. +/// @param[in] constants Constant data. +/// @param[in] cell_info0 Cell permutation information for the test +/// function mesh. +/// @param[in] cell_info1 Cell permutation information for the trial +/// function mesh. +/// @param[in] perms Facet permutation integer. Empty if facet +/// permutations are not required. template void assemble_exterior_facets( la::MatSet auto mat_set, mdspan2_t x_dofmap, - std::span> x, + std::span> x, int num_facets_per_cell, std::span facets, std::tuple> dofmap0, fem::DofTransformKernel auto P0, @@ -197,7 +203,8 @@ void assemble_exterior_facets( std::span bc1, FEkernel auto kernel, std::span coeffs, int cstride, std::span constants, std::span cell_info0, - std::span cell_info1) + std::span cell_info1, + std::span perms) { if (facets.empty()) return; @@ -235,10 +242,14 @@ void assemble_exterior_facets( std::next(coordinate_dofs.begin(), 3 * i)); } + // Permutations + std::uint8_t perm + = perms.empty() ? 0 : perms[cell * num_facets_per_cell + local_facet]; + // Tabulate tensor - std::fill(Ae.begin(), Ae.end(), 0); + std::ranges::fill(Ae, 0); kernel(Ae.data(), coeffs.data() + index / 2 * cstride, constants.data(), - coordinate_dofs.data(), &local_facet, nullptr); + coordinate_dofs.data(), &local_facet, &perm); P0(_Ae, cell_info0, cell0, ndim1); P1T(_Ae, cell_info1, cell1, ndim0); @@ -282,42 +293,47 @@ void assemble_exterior_facets( } } -/// @brief Execute kernel over interior facets and accumulate result in a -/// matrix. +/// @brief Execute kernel over interior facets and accumulate result in +/// a matrix. /// @tparam T Matrix/form scalar type. /// @param mat_set Function that accumulates computed entries into a /// matrix. -/// @param x_dofmap Dofmap for the mesh geometry. -/// @param x Mesh geometry (coordinates). -/// @param num_cell_facets Number of facets of a cell -/// @param facets Facet indices (in the integration domain mesh) to +/// @param[in] x_dofmap Dofmap for the mesh geometry. +/// @param[in] x Mesh geometry (coordinates). +/// @param[in] num_facets_per_cell Number of facets of a cell +/// @param[in] facets Facet indices (in the integration domain mesh) to /// execute the kernel over. -/// @param dofmap0 Test function (row) degree-of-freedom data holding -/// the (0) dofmap, (1) dofmap block size and (2) dofmap cell indices. -/// @param P0 Function that applies transformation P0.A in-place to +/// @param[in] dofmap0 Test function (row) degree-of-freedom data +/// holding the (0) dofmap, (1) dofmap block size and (2) dofmap cell +/// indices. +/// @param[in] P0 Function that applies transformation P0.A in-place to /// transform test degrees-of-freedom. -/// @param dofmap1 Trial function (column) degree-of-freedom data +/// @param[in] dofmap1 Trial function (column) degree-of-freedom data /// holding the (0) dofmap, (1) dofmap block size and (2) dofmap cell /// indices. -/// @param P1T Function that applies transformation A.P1^T in-place to -/// transform trial degrees-of-freedom. -/// @param bc0 Marker for rows with Dirichlet boundary conditions applied -/// @param bc1 Marker for columns with Dirichlet boundary conditions applied -/// @param kernel Kernel function to execute over each cell. -/// @param coeffs The coefficient data array of shape (cells.size(), cstride), +/// @param[in] P1T Function that applies transformation A.P1^T in-place +/// to transform trial degrees-of-freedom. +/// @param[in] bc0 Marker for rows with Dirichlet boundary conditions +/// applied. +/// @param[in] bc1 Marker for columns with Dirichlet boundary conditions +/// applied. +/// @param[in] coeffs The coefficient data array of shape (cells.size(), +/// cstride), +/// @param[in] kernel Kernel function to execute over each cell. /// flattened into row-major format. -/// @param cstride The coefficient stride -/// @param offsets The coefficient offsets -/// @param constants The constant data -/// @param cell_info0 The cell permutation information for the test function -/// mesh -/// @param cell_info1 The cell permutation information for the trial function -/// mesh -/// @param get_perm Function to apply facet permutations +/// @param[in] cstride Coefficient stride. +/// @param[in] offsets Coefficient offsets. +/// @param[in] constants Constant data. +/// @param[in] cell_info0 Cell permutation information for the test +/// function mesh. +/// @param[in] cell_info1 Cell permutation information for the trial +/// function mesh. +/// @param[in] perms Facet permutation integer. Empty if facet +/// permutations are not required. template void assemble_interior_facets( la::MatSet auto mat_set, mdspan2_t x_dofmap, - std::span> x, int num_cell_facets, + std::span> x, int num_facets_per_cell, std::span facets, std::tuple> dofmap0, fem::DofTransformKernel auto P0, @@ -327,7 +343,7 @@ void assemble_interior_facets( std::span coeffs, int cstride, std::span offsets, std::span constants, std::span cell_info0, std::span cell_info1, - const std::function& get_perm) + std::span perms) { if (facets.empty()) return; @@ -382,27 +398,30 @@ void assemble_interior_facets( std::span dmap0_cell0 = dmap0.cell_dofs(cells0[0]); std::span dmap0_cell1 = dmap0.cell_dofs(cells0[1]); dmapjoint0.resize(dmap0_cell0.size() + dmap0_cell1.size()); - std::copy(dmap0_cell0.begin(), dmap0_cell0.end(), dmapjoint0.begin()); - std::copy(dmap0_cell1.begin(), dmap0_cell1.end(), - std::next(dmapjoint0.begin(), dmap0_cell0.size())); + std::ranges::copy(dmap0_cell0, dmapjoint0.begin()); + std::ranges::copy(dmap0_cell1, + std::next(dmapjoint0.begin(), dmap0_cell0.size())); std::span dmap1_cell0 = dmap1.cell_dofs(cells1[0]); std::span dmap1_cell1 = dmap1.cell_dofs(cells1[1]); dmapjoint1.resize(dmap1_cell0.size() + dmap1_cell1.size()); - std::copy(dmap1_cell0.begin(), dmap1_cell0.end(), dmapjoint1.begin()); - std::copy(dmap1_cell1.begin(), dmap1_cell1.end(), - std::next(dmapjoint1.begin(), dmap1_cell0.size())); + std::ranges::copy(dmap1_cell0, dmapjoint1.begin()); + std::ranges::copy(dmap1_cell1, + std::next(dmapjoint1.begin(), dmap1_cell0.size())); const int num_rows = bs0 * dmapjoint0.size(); const int num_cols = bs1 * dmapjoint1.size(); // Tabulate tensor Ae.resize(num_rows * num_cols); - std::fill(Ae.begin(), Ae.end(), 0); - - const std::array perm{ - get_perm(cells[0] * num_cell_facets + local_facet[0]), - get_perm(cells[1] * num_cell_facets + local_facet[1])}; + std::ranges::fill(Ae, 0); + + std::array perm + = perms.empty() + ? std::array{0, 0} + : std::array{ + perms[cells[0] * num_facets_per_cell + local_facet[0]], + perms[cells[1] * num_facets_per_cell + local_facet[1]]}; kernel(Ae.data(), coeffs.data() + index / 2 * cstride, constants.data(), coordinate_dofs.data(), local_facet.data(), perm.data()); @@ -534,6 +553,16 @@ void assemble_matrix( fn, coeffs, cstride, constants, cell_info0, cell_info1); } + std::span perms; + if (a.needs_facet_permutations()) + { + mesh->topology_mutable()->create_entity_permutations(); + perms = std::span(mesh->topology()->get_facet_permutations()); + } + + mesh::CellType cell_type = mesh->topology()->cell_type(); + int num_facets_per_cell + = mesh::cell_num_entities(cell_type, mesh->topology()->dim() - 1); for (int i : a.integral_ids(IntegralType::exterior_facet)) { auto fn = a.kernel(IntegralType::exterior_facet, i); @@ -541,44 +570,28 @@ void assemble_matrix( auto& [coeffs, cstride] = coefficients.at({IntegralType::exterior_facet, i}); impl::assemble_exterior_facets( - mat_set, x_dofmap, x, a.domain(IntegralType::exterior_facet, i), + mat_set, x_dofmap, x, num_facets_per_cell, + a.domain(IntegralType::exterior_facet, i), {dofs0, bs0, a.domain(IntegralType::exterior_facet, i, *mesh0)}, P0, {dofs1, bs1, a.domain(IntegralType::exterior_facet, i, *mesh1)}, P1T, - bc0, bc1, fn, coeffs, cstride, constants, cell_info0, cell_info1); + bc0, bc1, fn, coeffs, cstride, constants, cell_info0, cell_info1, + perms); } - if (a.num_integrals(IntegralType::interior_facet) > 0) + for (int i : a.integral_ids(IntegralType::interior_facet)) { - std::function get_perm; - if (a.needs_facet_permutations()) - { - mesh->topology_mutable()->create_entity_permutations(); - const std::vector& perms - = mesh->topology()->get_facet_permutations(); - get_perm = [&perms](std::size_t i) { return perms[i]; }; - } - else - get_perm = [](std::size_t) { return 0; }; - - mesh::CellType cell_type = mesh->topology()->cell_type(); - int num_cell_facets - = mesh::cell_num_entities(cell_type, mesh->topology()->dim() - 1); const std::vector c_offsets = a.coefficient_offsets(); - for (int i : a.integral_ids(IntegralType::interior_facet)) - { - auto fn = a.kernel(IntegralType::interior_facet, i); - assert(fn); - auto& [coeffs, cstride] - = coefficients.at({IntegralType::interior_facet, i}); - impl::assemble_interior_facets( - mat_set, x_dofmap, x, num_cell_facets, - a.domain(IntegralType::interior_facet, i), - {*dofmap0, bs0, a.domain(IntegralType::interior_facet, i, *mesh0)}, - P0, - {*dofmap1, bs1, a.domain(IntegralType::interior_facet, i, *mesh1)}, - P1T, bc0, bc1, fn, coeffs, cstride, c_offsets, constants, cell_info0, - cell_info1, get_perm); - } + auto fn = a.kernel(IntegralType::interior_facet, i); + assert(fn); + auto& [coeffs, cstride] + = coefficients.at({IntegralType::interior_facet, i}); + impl::assemble_interior_facets( + mat_set, x_dofmap, x, num_facets_per_cell, + a.domain(IntegralType::interior_facet, i), + {*dofmap0, bs0, a.domain(IntegralType::interior_facet, i, *mesh0)}, P0, + {*dofmap1, bs1, a.domain(IntegralType::interior_facet, i, *mesh1)}, P1T, + bc0, bc1, fn, coeffs, cstride, c_offsets, constants, cell_info0, + cell_info1, perms); } } diff --git a/cpp/dolfinx/fem/assemble_scalar_impl.h b/cpp/dolfinx/fem/assemble_scalar_impl.h index 87384ca05d4..2f957d0d9f7 100644 --- a/cpp/dolfinx/fem/assemble_scalar_impl.h +++ b/cpp/dolfinx/fem/assemble_scalar_impl.h @@ -61,9 +61,11 @@ T assemble_cells(mdspan2_t x_dofmap, std::span> x, template T assemble_exterior_facets(mdspan2_t x_dofmap, std::span> x, + int num_facets_per_cell, std::span facets, FEkernel auto fn, std::span constants, - std::span coeffs, int cstride) + std::span coeffs, int cstride, + std::span perms) { T value(0); if (facets.empty()) @@ -88,9 +90,12 @@ T assemble_exterior_facets(mdspan2_t x_dofmap, std::next(coordinate_dofs.begin(), 3 * i)); } + // Permutations + std::uint8_t perm + = perms.empty() ? 0 : perms[cell * num_facets_per_cell + local_facet]; const T* coeff_cell = coeffs.data() + index / 2 * cstride; fn(&value, coeff_cell, constants.data(), coordinate_dofs.data(), - &local_facet, nullptr); + &local_facet, &perm); } return value; @@ -100,7 +105,7 @@ T assemble_exterior_facets(mdspan2_t x_dofmap, template T assemble_interior_facets(mdspan2_t x_dofmap, std::span> x, - int num_cell_facets, + int num_facets_per_cell, std::span facets, FEkernel auto fn, std::span constants, std::span coeffs, int cstride, @@ -145,8 +150,12 @@ T assemble_interior_facets(mdspan2_t x_dofmap, std::next(cdofs1.begin(), 3 * i)); } - const std::array perm{perms[cells[0] * num_cell_facets + local_facet[0]], - perms[cells[1] * num_cell_facets + local_facet[1]]}; + std::array perm + = perms.empty() + ? std::array{0, 0} + : std::array{ + perms[cells[0] * num_facets_per_cell + local_facet[0]], + perms[cells[1] * num_facets_per_cell + local_facet[1]]}; fn(&value, coeffs.data() + index / 2 * cstride, constants.data(), coordinate_dofs.data(), local_facet.data(), perm.data()); } @@ -176,6 +185,16 @@ T assemble_scalar( cstride); } + std::span perms; + if (M.needs_facet_permutations()) + { + mesh->topology_mutable()->create_entity_permutations(); + perms = std::span(mesh->topology()->get_facet_permutations()); + } + + mesh::CellType cell_type = mesh->topology()->cell_type(); + int num_facets_per_cell + = mesh::cell_num_entities(cell_type, mesh->topology()->dim() - 1); for (int i : M.integral_ids(IntegralType::exterior_facet)) { auto fn = M.kernel(IntegralType::exterior_facet, i); @@ -183,30 +202,22 @@ T assemble_scalar( auto& [coeffs, cstride] = coefficients.at({IntegralType::exterior_facet, i}); value += impl::assemble_exterior_facets( - x_dofmap, x, M.domain(IntegralType::exterior_facet, i), fn, constants, - coeffs, cstride); + x_dofmap, x, num_facets_per_cell, + M.domain(IntegralType::exterior_facet, i), fn, constants, coeffs, + cstride, perms); } - if (M.num_integrals(IntegralType::interior_facet) > 0) + for (int i : M.integral_ids(IntegralType::interior_facet)) { - mesh->topology_mutable()->create_entity_permutations(); - const std::vector& perms - = mesh->topology()->get_facet_permutations(); - mesh::CellType cell_type = mesh->topology()->cell_type(); - int num_cell_facets - = mesh::cell_num_entities(cell_type, mesh->topology()->dim() - 1); const std::vector c_offsets = M.coefficient_offsets(); - for (int i : M.integral_ids(IntegralType::interior_facet)) - { - auto fn = M.kernel(IntegralType::interior_facet, i); - assert(fn); - auto& [coeffs, cstride] - = coefficients.at({IntegralType::interior_facet, i}); - value += impl::assemble_interior_facets( - x_dofmap, x, num_cell_facets, - M.domain(IntegralType::interior_facet, i), fn, constants, coeffs, - cstride, c_offsets, perms); - } + auto fn = M.kernel(IntegralType::interior_facet, i); + assert(fn); + auto& [coeffs, cstride] + = coefficients.at({IntegralType::interior_facet, i}); + value += impl::assemble_interior_facets( + x_dofmap, x, num_facets_per_cell, + M.domain(IntegralType::interior_facet, i), fn, constants, coeffs, + cstride, c_offsets, perms); } return value; diff --git a/cpp/dolfinx/fem/assemble_vector_impl.h b/cpp/dolfinx/fem/assemble_vector_impl.h index 97e51c63891..fb1980f009e 100644 --- a/cpp/dolfinx/fem/assemble_vector_impl.h +++ b/cpp/dolfinx/fem/assemble_vector_impl.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -70,7 +71,7 @@ using mdspan2_t = MDSPAN_IMPL_STANDARD_NAMESPACE::mdspan< /// @param[in] bc_markers1 Marker to identify which DOFs have boundary /// conditions applied. /// @param[in] x0 Vector used in the lifting. -/// @param[in] scale Scaling to apply. +/// @param[in] alpha Scaling to apply. template void _lift_bc_cells( std::span b, mdspan2_t x_dofmap, @@ -83,7 +84,7 @@ void _lift_bc_cells( std::span coeffs, int cstride, std::span cell_info0, std::span cell_info1, std::span bc_values1, - std::span bc_markers1, std::span x0, T scale) + std::span bc_markers1, std::span x0, T alpha) { if (cells.empty()) return; @@ -161,7 +162,7 @@ void _lift_bc_cells( const T* coeff_array = coeffs.data() + index * cstride; Ae.resize(num_rows * num_cols); - std::fill(Ae.begin(), Ae.end(), 0); + std::ranges::fill(Ae, 0); kernel(Ae.data(), coeff_array, constants.data(), coordinate_dofs.data(), nullptr, nullptr); P0(Ae, cell_info0, c0, num_cols); @@ -169,7 +170,7 @@ void _lift_bc_cells( // Size data structure for assembly be.resize(num_rows); - std::fill(be.begin(), be.end(), 0); + std::ranges::fill(be, 0); for (std::size_t j = 0; j < dofs1.size(); ++j) { if constexpr (_bs1 > 0) @@ -183,9 +184,9 @@ void _lift_bc_cells( const T bc = bc_values1[jj]; const T _x0 = x0.empty() ? 0 : x0[jj]; // const T _x0 = 0; - // be -= Ae.col(bs1 * j + k) * scale * (bc - _x0); + // be -= Ae.col(bs1 * j + k) * alpha * (bc - _x0); for (int m = 0; m < num_rows; ++m) - be[m] -= Ae[m * num_cols + _bs1 * j + k] * scale * (bc - _x0); + be[m] -= Ae[m * num_cols + _bs1 * j + k] * alpha * (bc - _x0); } } } @@ -199,9 +200,9 @@ void _lift_bc_cells( { const T bc = bc_values1[jj]; const T _x0 = x0.empty() ? 0 : x0[jj]; - // be -= Ae.col(bs1 * j + k) * scale * (bc - _x0); + // be -= Ae.col(bs1 * j + k) * alpha * (bc - _x0); for (int m = 0; m < num_rows; ++m) - be[m] -= Ae[m * num_cols + bs1 * j + k] * scale * (bc - _x0); + be[m] -= Ae[m * num_cols + bs1 * j + k] * alpha * (bc - _x0); } } } @@ -223,42 +224,46 @@ void _lift_bc_cells( } } -/// @brief Apply lifting for exterior facet integrals +/// @brief Apply lifting for exterior facet integrals. /// @tparam T The scalar type /// @tparam _bs FIXME This is unused -/// @param b The vector to modify -/// @param x_dofmap Dofmap for the mesh geometry. -/// @param x Mesh geometry (coordinates). -/// @param kernel Kernel function to execute over each cell. -/// @param facets Facet indices (in the integration domain mesh) to +/// @param[in,out] b The vector to modify +/// @param[in] x_dofmap Dofmap for the mesh geometry. +/// @param[in] x Mesh geometry (coordinates). +/// @param[in] num_facets_per_cell Number of cell facets +/// @param[in] kernel Kernel function to execute over each cell. +/// @param[in] facets Facet indices (in the integration domain mesh) to /// execute the kernel over. -/// @param dofmap0 Test function (row) degree-of-freedom data holding +/// @param[in] dofmap0 Test function (row) degree-of-freedom data holding /// the (0) dofmap, (1) dofmap block size and (2) dofmap cell indices. -/// @param P0 Function that applies transformation P_0 A in-place to +/// @param[in] P0 Function that applies transformation P_0 A in-place to /// transform test degrees-of-freedom. -/// @param dofmap1 Trial function (column) degree-of-freedom data +/// @param[in] dofmap1 Trial function (column) degree-of-freedom data /// holding the (0) dofmap, (1) dofmap block size and (2) dofmap cell /// indices. -/// @param P1T Function that applies transformation A P_1^T in-place to -/// transform trial degrees-of-freedom. -/// @param constants The constant data -/// @param coeffs The coefficient data array of shape (cells.size(), cstride), -/// flattened into row-major format. -/// @param cstride The coefficient stride -/// @param cell_info0 The cell permutation information for the test function -/// mesh -/// @param cell_info1 The cell permutation information for the trial function -/// mesh -/// @param bc_values1 The value for entries with an applied boundary condition -/// @param bc_markers1 Marker to identify which DOFs have boundary conditions -/// applied -/// @param x0 The vector used in the lifting -/// @param scale The scaling to apply +/// @param[in] P1T Function that applies transformation A P_1^T in-place +/// to transform trial degrees-of-freedom. +/// @param[in] constants The constant data. +/// @param[in] coeffs The coefficient data array of shape (cells.size(), +/// cstride), flattened into row-major format. +/// @param[in] cstride The coefficient stride. +/// @param[in] cell_info0 The cell permutation information for the test +/// function mesh. +/// @param[in] cell_info1 The cell permutation information for the trial +/// function mesh. +/// @param[in] bc_values1 The value for entries with an applied boundary +/// condition. +/// @param[in] bc_markers1 Marker to identify which DOFs have boundary +/// conditions applied. +/// @param[in] x0 The vector used in the lifting. +/// @param[in] alpha The scaling to apply. +/// @param[in] perms Facet permutation integer. Empty if facet +/// permutations are not required. template void _lift_bc_exterior_facets( std::span b, mdspan2_t x_dofmap, - std::span> x, FEkernel auto kernel, - std::span facets, + std::span> x, int num_facets_per_cell, + FEkernel auto kernel, std::span facets, std::tuple> dofmap0, fem::DofTransformKernel auto P0, std::tuple> dofmap1, @@ -266,7 +271,8 @@ void _lift_bc_exterior_facets( std::span coeffs, int cstride, std::span cell_info0, std::span cell_info1, std::span bc_values1, - std::span bc_markers1, std::span x0, T scale) + std::span bc_markers1, std::span x0, T alpha, + std::span perms) { if (facets.empty()) return; @@ -329,17 +335,21 @@ void _lift_bc_exterior_facets( const int num_rows = bs0 * dofs0.size(); const int num_cols = bs1 * dofs1.size(); + // Permutations + std::uint8_t perm + = perms.empty() ? 0 : perms[cell * num_facets_per_cell + local_facet]; + const T* coeff_array = coeffs.data() + index / 2 * cstride; Ae.resize(num_rows * num_cols); - std::fill(Ae.begin(), Ae.end(), 0); + std::ranges::fill(Ae, 0); kernel(Ae.data(), coeff_array, constants.data(), coordinate_dofs.data(), - &local_facet, nullptr); + &local_facet, &perm); P0(Ae, cell_info0, cell0, num_cols); P1T(Ae, cell_info1, cell1, num_rows); // Size data structure for assembly be.resize(num_rows); - std::fill(be.begin(), be.end(), 0); + std::ranges::fill(be, 0); for (std::size_t j = 0; j < dofs1.size(); ++j) { for (int k = 0; k < bs1; ++k) @@ -349,9 +359,9 @@ void _lift_bc_exterior_facets( { const T bc = bc_values1[jj]; const T _x0 = x0.empty() ? 0 : x0[jj]; - // be -= Ae.col(bs1 * j + k) * scale * (bc - _x0); + // be -= Ae.col(bs1 * j + k) * alpha * (bc - _x0); for (int m = 0; m < num_rows; ++m) - be[m] -= Ae[m * num_cols + bs1 * j + k] * scale * (bc - _x0); + be[m] -= Ae[m * num_cols + bs1 * j + k] * alpha * (bc - _x0); } } } @@ -362,43 +372,46 @@ void _lift_bc_exterior_facets( } } -/// @brief Apply lifting for interior facet integrals -/// @tparam T The scalar type -/// @tparam _bs FIXME This is unused -/// @param b The vector to modify -/// @param x_dofmap Dofmap for the mesh geometry. -/// @param x Mesh geometry (coordinates). -/// @param num_cell_facets Number of facets of a cell. -/// @param kernel Kernel function to execute over each cell. -/// @param facets Facet indices (in the integration domain mesh) to +/// @brief Apply lifting for interior facet integrals. +/// @tparam T Scalar type. +/// @tparam _bs FIXME This is unused. +/// @param[in,out] b The vector to modify +/// @param[in] x_dofmap Dofmap for the mesh geometry. +/// @param[in] x Mesh geometry (coordinates). +/// @param[in] num_facets_per_cell Number of facets of a cell. +/// @param[in] kernel Kernel function to execute over each cell. +/// @param[in] facets Facet indices (in the integration domain mesh) to /// execute the kernel over. -/// @param dofmap0 Test function (row) degree-of-freedom data holding -/// the (0) dofmap, (1) dofmap block size and (2) dofmap cell indices. -/// @param P0 Function that applies transformation P_0 A in-place to +/// @param[in] dofmap0 Test function (row) degree-of-freedom data +/// holding the (0) dofmap, (1) dofmap block size and (2) dofmap cell +/// indices. +/// @param[in] P0 Function that applies transformation P_0 A in-place to /// transform test degrees-of-freedom. -/// @param dofmap1 Trial function (column) degree-of-freedom data +/// @param[in] dofmap1 Trial function (column) degree-of-freedom data /// holding the (0) dofmap, (1) dofmap block size and (2) dofmap cell /// indices. -/// @param P1T Function that applies transformation A P_1^T in-place to -/// transform trial degrees-of-freedom. -/// @param constants The constant data -/// @param coeffs The coefficient data array of shape (cells.size(), cstride), -/// flattened into row-major format. -/// @param cstride The coefficient stride -/// @param cell_info0 The cell permutation information for the test function -/// mesh -/// @param cell_info1 The cell permutation information for the trial function -/// mesh -/// @param get_perm Function to apply facet permutations -/// @param bc_values1 The value for entries with an applied boundary condition -/// @param bc_markers1 Marker to identify which DOFs have boundary conditions -/// applied -/// @param x0 he vector used in the lifting -/// @param scale The scaling to apply +/// @param[in] P1T Function that applies transformation A P_1^T in-place +/// to transform trial degrees-of-freedom. +/// @param[in] constants The constant data. +/// @param[in] coeffs The coefficient data array of shape (cells.size(), +/// cstride), flattened into row-major format. +/// @param[in] cstride The coefficient stride. +/// @param[in] cell_info0 The cell permutation information for the test +/// function mesh. +/// @param[in] cell_info1 The cell permutation information for the trial +/// function mesh. +/// @param[in] perms Facet permutation integer. Empty if facet +/// permutations are not required. +/// @param[in] bc_values1 The value for entries with an applied boundary +/// condition. +/// @param[in] bc_markers1 Marker to identify which DOFs have boundary +/// conditions applied. +/// @param[in] x0 The vector used in the lifting. +/// @param[in] alpha The scaling to apply template void _lift_bc_interior_facets( std::span b, mdspan2_t x_dofmap, - std::span> x, int num_cell_facets, + std::span> x, int num_facets_per_cell, FEkernel auto kernel, std::span facets, std::tuple> dofmap0, fem::DofTransformKernel auto P0, @@ -407,9 +420,8 @@ void _lift_bc_interior_facets( std::span coeffs, int cstride, std::span cell_info0, std::span cell_info1, - const std::function& get_perm, - std::span bc_values1, std::span bc_markers1, - std::span x0, T scale) + std::span perms, std::span bc_values1, + std::span bc_markers1, std::span x0, T alpha) { if (facets.empty()) return; @@ -468,9 +480,9 @@ void _lift_bc_interior_facets( = std::span(dmap0.data_handle() + cells0[1] * num_dofs0, num_dofs0); dmapjoint0.resize(dmap0_cell0.size() + dmap0_cell1.size()); - std::copy(dmap0_cell0.begin(), dmap0_cell0.end(), dmapjoint0.begin()); - std::copy(dmap0_cell1.begin(), dmap0_cell1.end(), - std::next(dmapjoint0.begin(), dmap0_cell0.size())); + std::ranges::copy(dmap0_cell0, dmapjoint0.begin()); + std::ranges::copy(dmap0_cell1, + std::next(dmapjoint0.begin(), dmap0_cell0.size())); auto dmap1_cell0 = std::span(dmap1.data_handle() + cells1[0] * num_dofs1, num_dofs1); @@ -478,9 +490,9 @@ void _lift_bc_interior_facets( = std::span(dmap1.data_handle() + cells1[1] * num_dofs1, num_dofs1); dmapjoint1.resize(dmap1_cell0.size() + dmap1_cell1.size()); - std::copy(dmap1_cell0.begin(), dmap1_cell0.end(), dmapjoint1.begin()); - std::copy(dmap1_cell1.begin(), dmap1_cell1.end(), - std::next(dmapjoint1.begin(), dmap1_cell0.size())); + std::ranges::copy(dmap1_cell0, dmapjoint1.begin()); + std::ranges::copy(dmap1_cell1, + std::next(dmapjoint1.begin(), dmap1_cell0.size())); // Check if bc is applied to cell0 bool has_bc = false; @@ -517,10 +529,13 @@ void _lift_bc_interior_facets( // Tabulate tensor Ae.resize(num_rows * num_cols); - std::fill(Ae.begin(), Ae.end(), 0); - const std::array perm{ - get_perm(cells[0] * num_cell_facets + local_facet[0]), - get_perm(cells[1] * num_cell_facets + local_facet[1])}; + std::ranges::fill(Ae, 0); + std::array perm + = perms.empty() + ? std::array{0, 0} + : std::array{ + perms[cells[0] * num_facets_per_cell + local_facet[0]], + perms[cells[1] * num_facets_per_cell + local_facet[1]]}; kernel(Ae.data(), coeffs.data() + index / 2 * cstride, constants.data(), coordinate_dofs.data(), local_facet.data(), perm.data()); @@ -542,7 +557,7 @@ void _lift_bc_interior_facets( } be.resize(num_rows); - std::fill(be.begin(), be.end(), 0); + std::ranges::fill(be, 0); // Compute b = b - A*b for cell0 for (std::size_t j = 0; j < dmap1_cell0.size(); ++j) @@ -554,9 +569,9 @@ void _lift_bc_interior_facets( { const T bc = bc_values1[jj]; const T _x0 = x0.empty() ? 0 : x0[jj]; - // be -= Ae.col(bs1 * j + k) * scale * (bc - _x0); + // be -= Ae.col(bs1 * j + k) * alpha * (bc - _x0); for (int m = 0; m < num_rows; ++m) - be[m] -= Ae[m * num_cols + bs1 * j + k] * scale * (bc - _x0); + be[m] -= Ae[m * num_cols + bs1 * j + k] * alpha * (bc - _x0); } } } @@ -572,11 +587,11 @@ void _lift_bc_interior_facets( { const T bc = bc_values1[jj]; const T _x0 = x0.empty() ? 0 : x0[jj]; - // be -= Ae.col(offset + bs1 * j + k) * scale * (bc - x0[jj]); + // be -= Ae.col(offset + bs1 * j + k) * alpha * (bc - x0[jj]); for (int m = 0; m < num_rows; ++m) { be[m] - -= Ae[m * num_cols + offset + bs1 * j + k] * scale * (bc - _x0); + -= Ae[m * num_cols + offset + bs1 * j + k] * alpha * (bc - _x0); } } } @@ -653,7 +668,7 @@ void assemble_cells( } // Tabulate vector for cell - std::fill(be.begin(), be.end(), 0); + std::ranges::fill(be, 0); kernel(be.data(), coeffs.data() + index * cstride, constants.data(), coordinate_dofs.data(), nullptr, nullptr); P0(_be, cell_info0, c0, 1); @@ -676,7 +691,7 @@ void assemble_cells( } } -/// @brief Execute kernel over cells and accumulate result in vector +/// @brief Execute kernel over cells and accumulate result in vector. /// @tparam T The scalar type /// @tparam _bs The block size of the form test function dof map. If /// less than zero the block size is determined at runtime. If `_bs` is @@ -684,29 +699,33 @@ void assemble_cells( /// has performance benefits. /// @param P0 Function that applies transformation P0.b in-place to /// transform test degrees-of-freedom. -/// @param b The vector to accumulate into -/// @param x_dofmap Dofmap for the mesh geometry. -/// @param x Mesh geometry (coordinates). -/// @param facets Facets (in the integration domain mesh) to -/// execute the kernel over. -/// @param dofmap Test function (row) degree-of-freedom data holding +/// @param[in,out] b The vector to accumulate into. +/// @param[in] x_dofmap Dofmap for the mesh geometry. +/// @param[in] x Mesh geometry (coordinates). +/// @param[in] num_facets_per_cell Number of cell facets +/// @param[in] facets Facets (in the integration domain mesh) to execute +/// the kernel over. +/// @param[in] dofmap Test function (row) degree-of-freedom data holding /// the (0) dofmap, (1) dofmap block size and (2) dofmap cell indices. -/// @param fn Kernel function to execute over each cell. -/// @param constants The constant data -/// @param coeffs The coefficient data array of shape (cells.size(), cstride), -/// flattened into row-major format. -/// @param cstride The coefficient stride -/// @param cell_info0 The cell permutation information for the test function -/// mesh +/// @param[in] fn Kernel function to execute over each cell. +/// @param[in] constants The constant data. +/// @param[in] coeffs The coefficient data array of shape +/// `(cells.size(), cstride)`, flattened into row-major format. +/// @param[in] cstride The coefficient stride. +/// @param[in] cell_info0 The cell permutation information for the test +/// function mesh. +/// @param[in] perms Facet permutation integer. Empty if facet +/// permutations are not required. template void assemble_exterior_facets( fem::DofTransformKernel auto P0, std::span b, mdspan2_t x_dofmap, - std::span> x, + std::span> x, int num_facets_per_cell, std::span facets, std::tuple> dofmap, FEkernel auto fn, std::span constants, std::span coeffs, int cstride, - std::span cell_info0) + std::span cell_info0, + std::span perms) { if (facets.empty()) return; @@ -739,10 +758,14 @@ void assemble_exterior_facets( std::next(coordinate_dofs.begin(), 3 * i)); } + // Permutations + std::uint8_t perm + = perms.empty() ? 0 : perms[cell * num_facets_per_cell + local_facet]; + // Tabulate element vector - std::fill(be.begin(), be.end(), 0); + std::ranges::fill(be, 0); fn(be.data(), coeffs.data() + index / 2 * cstride, constants.data(), - coordinate_dofs.data(), &local_facet, nullptr); + coordinate_dofs.data(), &local_facet, &perm); P0(_be, cell_info0, cell0, 1); @@ -764,40 +787,41 @@ void assemble_exterior_facets( } } -/// @brief Assemble linear form interior facet integrals into an vector -/// @tparam T The scalar type -/// @tparam _bs The block size of the form test function dof map. If -/// less than zero the block size is determined at runtime. If `_bs` is +/// @brief Assemble linear form interior facet integrals into an vector. +/// @tparam T Scalar type. +/// @tparam _bs Block size of the form test function dof map. If less +/// than zero the block size is determined at runtime. If `_bs` is /// positive the block size is used as a compile-time constant, which /// has performance benefits. /// @param P0 Function that applies transformation P0.A in-place to /// transform trial degrees-of-freedom. -/// @param b The vector to accumulate into -/// @param x_dofmap Dofmap for the mesh geometry. -/// @param x Mesh geometry (coordinates). -/// @param num_cell_facets Number of facets of a cell -/// @param facets Facets (in the integration domain mesh) to -/// execute the kernel over. -/// @param dofmap Test function (row) degree-of-freedom data holding +/// @param[in,out] b The vector to accumulate into. +/// @param[in] x_dofmap Dofmap for the mesh geometry. +/// @param[in] x Mesh geometry (coordinates). +/// @param[in] num_facets_per_cell Number of facets of a cell +/// @param[in] facets Facets (in the integration domain mesh) to execute +/// the kernel over. +/// @param[in] dofmap Test function (row) degree-of-freedom data holding /// the (0) dofmap, (1) dofmap block size and (2) dofmap cell indices. -/// @param fn Kernel function to execute over each cell. -/// @param constants The constant data -/// @param coeffs The coefficient data array of shape (cells.size(), cstride), -/// flattened into row-major format. -/// @param cstride The coefficient stride -/// @param cell_info0 The cell permutation information for the test function -/// mesh -/// @param get_perm Function to apply facet permutations +/// @param[in] fn Kernel function to execute over each cell. +/// @param[in] constants The constant data +/// @param[in] coeffs The coefficient data array of shape (cells.size(), +/// cstride), flattened into row-major format. +/// @param[in] cstride The coefficient stride +/// @param[in] cell_info0 The cell permutation information for the test +/// function mesh. +/// @param[in] perms Facet permutation integer. Empty if facet +/// permutations are not required. template void assemble_interior_facets( fem::DofTransformKernel auto P0, std::span b, mdspan2_t x_dofmap, - std::span> x, int num_cell_facets, + std::span> x, int num_facets_per_cell, std::span facets, std::tuple> dofmap, FEkernel auto fn, std::span constants, std::span coeffs, int cstride, std::span cell_info0, - const std::function& get_perm) + std::span perms) { if (facets.empty()) return; @@ -847,10 +871,13 @@ void assemble_interior_facets( // Tabulate element vector be.resize(bs * (dmap0.size() + dmap1.size())); - std::fill(be.begin(), be.end(), 0); - const std::array perm{ - get_perm(cells[0] * num_cell_facets + local_facet[0]), - get_perm(cells[1] * num_cell_facets + local_facet[1])}; + std::ranges::fill(be, 0); + std::array perm + = perms.empty() + ? std::array{0, 0} + : std::array{ + perms[cells[0] * num_facets_per_cell + local_facet[0]], + perms[cells[1] * num_facets_per_cell + local_facet[1]]}; fn(be.data(), coeffs.data() + index / 2 * cstride, constants.data(), coordinate_dofs.data(), local_facet.data(), perm.data()); @@ -884,7 +911,7 @@ void assemble_interior_facets( /// Modify RHS vector to account for boundary condition such that: /// -/// b <- b - scale * A (x_bc - x0) +/// b <- b - alpha * A.(x_bc - x0) /// /// @param[in,out] b The vector to be modified /// @param[in] a The bilinear form that generates A @@ -897,7 +924,7 @@ void assemble_interior_facets( /// which bcs belong /// @param[in] x0 The array used in the lifting, typically a 'current /// solution' in a Newton method -/// @param[in] scale Scaling to apply +/// @param[in] alpha Scaling to apply template void lift_bc(std::span b, const Form& a, mdspan2_t x_dofmap, std::span> x, @@ -906,7 +933,7 @@ void lift_bc(std::span b, const Form& a, mdspan2_t x_dofmap, std::pair, int>>& coefficients, std::span bc_values1, std::span bc_markers1, std::span x0, - T scale) + T alpha) { // Integration domain mesh std::shared_ptr> mesh = a.mesh(); @@ -960,7 +987,7 @@ void lift_bc(std::span b, const Form& a, mdspan2_t x_dofmap, {dofmap0, bs0, a.domain(IntegralType::cell, i, *mesh0)}, P0, {dofmap1, bs1, a.domain(IntegralType::cell, i, *mesh1)}, P1T, constants, coeffs, cstride, cell_info0, cell_info1, bc_values1, - bc_markers1, x0, scale); + bc_markers1, x0, alpha); } else if (bs0 == 3 and bs1 == 3) { @@ -969,7 +996,7 @@ void lift_bc(std::span b, const Form& a, mdspan2_t x_dofmap, {dofmap0, bs0, a.domain(IntegralType::cell, i, *mesh0)}, P0, {dofmap1, bs1, a.domain(IntegralType::cell, i, *mesh1)}, P1T, constants, coeffs, cstride, cell_info0, cell_info1, bc_values1, - bc_markers1, x0, scale); + bc_markers1, x0, alpha); } else { @@ -978,10 +1005,20 @@ void lift_bc(std::span b, const Form& a, mdspan2_t x_dofmap, P0, {dofmap1, bs1, a.domain(IntegralType::cell, i, *mesh1)}, P1T, constants, coeffs, cstride, cell_info0, cell_info1, - bc_values1, bc_markers1, x0, scale); + bc_values1, bc_markers1, x0, alpha); } } + std::span perms; + if (a.needs_facet_permutations()) + { + mesh->topology_mutable()->create_entity_permutations(); + perms = std::span(mesh->topology()->get_facet_permutations()); + } + + mesh::CellType cell_type = mesh->topology()->cell_type(); + int num_facets_per_cell + = mesh::cell_num_entities(cell_type, mesh->topology()->dim() - 1); for (int i : a.integral_ids(IntegralType::exterior_facet)) { auto kernel = a.kernel(IntegralType::exterior_facet, i); @@ -989,79 +1026,60 @@ void lift_bc(std::span b, const Form& a, mdspan2_t x_dofmap, auto& [coeffs, cstride] = coefficients.at({IntegralType::exterior_facet, i}); _lift_bc_exterior_facets( - b, x_dofmap, x, kernel, a.domain(IntegralType::exterior_facet, i), + b, x_dofmap, x, num_facets_per_cell, kernel, + a.domain(IntegralType::exterior_facet, i), {dofmap0, bs0, a.domain(IntegralType::exterior_facet, i, *mesh0)}, P0, {dofmap1, bs1, a.domain(IntegralType::exterior_facet, i, *mesh1)}, P1T, constants, coeffs, cstride, cell_info0, cell_info1, bc_values1, - bc_markers1, x0, scale); + bc_markers1, x0, alpha, perms); } - if (a.num_integrals(IntegralType::interior_facet) > 0) + for (int i : a.integral_ids(IntegralType::interior_facet)) { - std::function get_perm; - if (a.needs_facet_permutations()) - { - mesh->topology_mutable()->create_entity_permutations(); - const std::vector& perms - = mesh->topology()->get_facet_permutations(); - get_perm = [&perms](std::size_t i) { return perms[i]; }; - } - else - get_perm = [](std::size_t) { return 0; }; - - mesh::CellType cell_type = mesh->topology()->cell_type(); - int num_cell_facets - = mesh::cell_num_entities(cell_type, mesh->topology()->dim() - 1); - for (int i : a.integral_ids(IntegralType::interior_facet)) - { - auto kernel = a.kernel(IntegralType::interior_facet, i); - assert(kernel); - auto& [coeffs, cstride] - = coefficients.at({IntegralType::interior_facet, i}); - _lift_bc_interior_facets( - b, x_dofmap, x, num_cell_facets, kernel, - a.domain(IntegralType::interior_facet, i), - {dofmap0, bs0, a.domain(IntegralType::interior_facet, i, *mesh0)}, P0, - {dofmap1, bs1, a.domain(IntegralType::interior_facet, i, *mesh1)}, - P1T, constants, coeffs, cstride, cell_info0, cell_info1, get_perm, - bc_values1, bc_markers1, x0, scale); - } + auto kernel = a.kernel(IntegralType::interior_facet, i); + assert(kernel); + auto& [coeffs, cstride] + = coefficients.at({IntegralType::interior_facet, i}); + _lift_bc_interior_facets( + b, x_dofmap, x, num_facets_per_cell, kernel, + a.domain(IntegralType::interior_facet, i), + {dofmap0, bs0, a.domain(IntegralType::interior_facet, i, *mesh0)}, P0, + {dofmap1, bs1, a.domain(IntegralType::interior_facet, i, *mesh1)}, P1T, + constants, coeffs, cstride, cell_info0, cell_info1, perms, bc_values1, + bc_markers1, x0, alpha); } } /// Modify b such that: /// -/// b <- b - scale * A_j (g_j - x0_j) +/// b <- b - alpha * A_j.(g_j - x0_j) +/// +/// where j is a block (nest) row index. For a non-blocked problem j = +/// 0. The boundary conditions bc1 are on the trial spaces V_j. The +/// forms in [a] must have the same test space as L (from which b was +/// built), but the trial space may differ. If x0 is not supplied, then +/// it is treated as zero. /// -/// where j is a block (nest) row index. For a non-blocked problem j = 0. -/// The boundary conditions bc1 are on the trial spaces V_j. The forms -/// in [a] must have the same test space as L (from which b was built), -/// but the trial space may differ. If x0 is not supplied, then it is -/// treated as zero. /// @param[in,out] b The vector to be modified /// @param[in] a The bilinear forms, where a[j] is the form that /// generates A_j -/// @param[in] x_dofmap Mesh geometry dofmap -/// @param[in] x Mesh coordinates /// @param[in] constants Constants that appear in `a` /// @param[in] coeffs Coefficients that appear in `a` /// @param[in] bcs1 List of boundary conditions for each block, i.e. /// bcs1[2] are the boundary conditions applied to the columns of a[2] / /// x0[2] block /// @param[in] x0 The vectors used in the lifting -/// @param[in] scale Scaling to apply +/// @param[in] alpha Scaling to apply template void apply_lifting( std::span b, const std::vector>> a, - mdspan2_t x_dofmap, std::span> x, const std::vector>& constants, const std::vector, std::pair, int>>>& coeffs, const std::vector>>>& bcs1, - const std::vector>& x0, T scale) + const std::vector>& x0, T alpha) { - // FIXME: make changes to reactivate this check if (!x0.empty() and x0.size() != a.size()) { throw std::runtime_error( @@ -1080,6 +1098,13 @@ void apply_lifting( std::vector bc_values1; if (a[j] and !bcs1[j].empty()) { + // Extract data from mesh + std::shared_ptr> mesh = a[j]->mesh(); + if (!mesh) + throw std::runtime_error("Unable to extract a mesh."); + mdspan2_t x_dofmap = mesh->geometry().dofmap(); + auto x = mesh->geometry().x(); + assert(a[j]->function_spaces().at(0)); auto V1 = a[j]->function_spaces()[1]; @@ -1093,31 +1118,31 @@ void apply_lifting( for (const std::shared_ptr>& bc : bcs1[j]) { bc->mark_dofs(bc_markers1); - bc->dof_values(bc_values1); + bc->set(bc_values1, std::nullopt, 1); } if (!x0.empty()) { lift_bc(b, *a[j], x_dofmap, x, constants[j], coeffs[j], bc_values1, - bc_markers1, x0[j], scale); + bc_markers1, x0[j], alpha); } else { lift_bc(b, *a[j], x_dofmap, x, constants[j], coeffs[j], bc_values1, - bc_markers1, std::span(), scale); + bc_markers1, std::span(), alpha); } } } } -/// Assemble linear form into a vector +/// @brief Assemble linear form into a vector. /// @param[in,out] b The vector to be assembled. It will not be zeroed /// before assembly. -/// @param[in] L The linear forms to assemble into b -/// @param[in] x_dofmap Mesh geometry dofmap -/// @param[in] x Mesh coordinates -/// @param[in] constants Packed constants that appear in `L` -/// @param[in] coefficients Packed coefficients that appear in `L` +/// @param[in] L Linear forms to assemble into b. +/// @param[in] x_dofmap Mesh geometry dofmap. +/// @param[in] x Mesh coordinates. +/// @param[in] constants Packed constants that appear in `L`. +/// @param[in] coefficients Packed coefficients that appear in `L`. template void assemble_vector( std::span b, const Form& L, mdspan2_t x_dofmap, @@ -1181,6 +1206,16 @@ void assemble_vector( } } + std::span perms; + if (L.needs_facet_permutations()) + { + mesh->topology_mutable()->create_entity_permutations(); + perms = std::span(mesh->topology()->get_facet_permutations()); + } + + mesh::CellType cell_type = mesh->topology()->cell_type(); + int num_facets_per_cell + = mesh::cell_num_entities(cell_type, mesh->topology()->dim() - 1); for (int i : L.integral_ids(IntegralType::exterior_facet)) { auto fn = L.kernel(IntegralType::exterior_facet, i); @@ -1192,71 +1227,54 @@ void assemble_vector( if (bs == 1) { impl::assemble_exterior_facets( - P0, b, x_dofmap, x, facets, + P0, b, x_dofmap, x, num_facets_per_cell, facets, {dofs, bs, L.domain(IntegralType::exterior_facet, i, *mesh0)}, fn, - constants, coeffs, cstride, cell_info0); + constants, coeffs, cstride, cell_info0, perms); } else if (bs == 3) { impl::assemble_exterior_facets( - P0, b, x_dofmap, x, facets, + P0, b, x_dofmap, x, num_facets_per_cell, facets, {dofs, bs, L.domain(IntegralType::exterior_facet, i, *mesh0)}, fn, - constants, coeffs, cstride, cell_info0); + constants, coeffs, cstride, cell_info0, perms); } else { impl::assemble_exterior_facets( - P0, b, x_dofmap, x, facets, + P0, b, x_dofmap, x, num_facets_per_cell, facets, {dofs, bs, L.domain(IntegralType::exterior_facet, i, *mesh0)}, fn, - constants, coeffs, cstride, cell_info0); + constants, coeffs, cstride, cell_info0, perms); } } - if (L.num_integrals(IntegralType::interior_facet) > 0) + for (int i : L.integral_ids(IntegralType::interior_facet)) { - std::function get_perm; - if (L.needs_facet_permutations()) + auto fn = L.kernel(IntegralType::interior_facet, i); + assert(fn); + auto& [coeffs, cstride] + = coefficients.at({IntegralType::interior_facet, i}); + std::span facets + = L.domain(IntegralType::interior_facet, i); + if (bs == 1) + { + impl::assemble_interior_facets( + P0, b, x_dofmap, x, num_facets_per_cell, facets, + {*dofmap, bs, L.domain(IntegralType::interior_facet, i, *mesh0)}, fn, + constants, coeffs, cstride, cell_info0, perms); + } + else if (bs == 3) { - mesh->topology_mutable()->create_entity_permutations(); - const std::vector& perms - = mesh->topology()->get_facet_permutations(); - get_perm = [&perms](std::size_t i) { return perms[i]; }; + impl::assemble_interior_facets( + P0, b, x_dofmap, x, num_facets_per_cell, facets, + {*dofmap, bs, L.domain(IntegralType::interior_facet, i, *mesh0)}, fn, + constants, coeffs, cstride, cell_info0, perms); } else - get_perm = [](std::size_t) { return 0; }; - - mesh::CellType cell_type = mesh->topology()->cell_type(); - int num_cell_facets - = mesh::cell_num_entities(cell_type, mesh->topology()->dim() - 1); - for (int i : L.integral_ids(IntegralType::interior_facet)) { - auto fn = L.kernel(IntegralType::interior_facet, i); - assert(fn); - auto& [coeffs, cstride] - = coefficients.at({IntegralType::interior_facet, i}); - std::span facets - = L.domain(IntegralType::interior_facet, i); - if (bs == 1) - { - impl::assemble_interior_facets( - P0, b, x_dofmap, x, num_cell_facets, facets, - {*dofmap, bs, L.domain(IntegralType::interior_facet, i, *mesh0)}, - fn, constants, coeffs, cstride, cell_info0, get_perm); - } - else if (bs == 3) - { - impl::assemble_interior_facets( - P0, b, x_dofmap, x, num_cell_facets, facets, - {*dofmap, bs, L.domain(IntegralType::interior_facet, i, *mesh0)}, - fn, constants, coeffs, cstride, cell_info0, get_perm); - } - else - { - impl::assemble_interior_facets( - P0, b, x_dofmap, x, num_cell_facets, facets, - {*dofmap, bs, L.domain(IntegralType::interior_facet, i, *mesh0)}, - fn, constants, coeffs, cstride, cell_info0, get_perm); - } + impl::assemble_interior_facets( + P0, b, x_dofmap, x, num_facets_per_cell, facets, + {*dofmap, bs, L.domain(IntegralType::interior_facet, i, *mesh0)}, fn, + constants, coeffs, cstride, cell_info0, perms); } } } diff --git a/cpp/dolfinx/fem/assembler.h b/cpp/dolfinx/fem/assembler.h index b7d5475ca51..decd5d35411 100644 --- a/cpp/dolfinx/fem/assembler.h +++ b/cpp/dolfinx/fem/assembler.h @@ -11,9 +11,11 @@ #include "assemble_vector_impl.h" #include "traits.h" #include "utils.h" +#include #include #include #include +#include #include #include @@ -36,9 +38,10 @@ make_coefficients_span(const std::map, { using Key = typename std::remove_reference_t::key_type; std::map, int>> c; - std::transform(coeffs.cbegin(), coeffs.cend(), std::inserter(c, c.end()), - [](auto& e) -> typename decltype(c)::value_type - { return {e.first, {e.second.first, e.second.second}}; }); + std::ranges::transform( + coeffs, std::inserter(c, c.end()), + [](auto& e) -> typename decltype(c)::value_type + { return {e.first, {e.second.first, e.second.second}}; }); return c; } @@ -135,7 +138,7 @@ void assemble_vector(std::span b, const Form& L) /// Modify b such that: /// -/// b <- b - scale * A_j (g_j - x0_j) +/// b <- b - alpha * A_j (g_j - x0_j) /// /// where j is a block (nest) index. For a non-blocked problem j = 0. The /// boundary conditions bcs1 are on the trial spaces V_j. The forms in @@ -153,38 +156,18 @@ void apply_lifting( std::pair, int>>>& coeffs, const std::vector>>>& bcs1, - const std::vector>& x0, T scale) + const std::vector>& x0, T alpha) { - std::shared_ptr> mesh; - for (auto& a_i : a) - { - if (a_i and !mesh) - mesh = a_i->mesh(); - if (a_i and mesh and a_i->mesh() != mesh) - throw std::runtime_error("Mismatch between meshes."); - } + // If all forms are null, there is nothing to do + if (std::ranges::all_of(a, [](auto ptr) { return ptr == nullptr; })) + return; - if (!mesh) - throw std::runtime_error("Unable to extract a mesh."); - - if constexpr (std::is_same_v>) - { - impl::apply_lifting(b, a, mesh->geometry().dofmap(), - mesh->geometry().x(), constants, coeffs, bcs1, x0, - scale); - } - else - { - auto x = mesh->geometry().x(); - std::vector> _x(x.begin(), x.end()); - impl::apply_lifting(b, a, mesh->geometry().dofmap(), _x, constants, - coeffs, bcs1, x0, scale); - } + impl::apply_lifting(b, a, constants, coeffs, bcs1, x0, alpha); } /// Modify b such that: /// -/// b <- b - scale * A_j (g_j - x0_j) +/// b <- b - alpha * A_j.(g_j - x0_j) /// /// where j is a block (nest) index. For a non-blocked problem j = 0. The /// boundary conditions bcs1 are on the trial spaces V_j. The forms in @@ -199,7 +182,7 @@ void apply_lifting( std::span b, const std::vector>>& a, const std::vector>>>& bcs1, - const std::vector>& x0, T scale) + const std::vector>& x0, T alpha) { std::vector< std::map, std::pair, int>>> @@ -226,10 +209,10 @@ void apply_lifting( std::vector, std::pair, int>>> _coeffs; - std::transform(coeffs.cbegin(), coeffs.cend(), std::back_inserter(_coeffs), - [](auto& c) { return make_coefficients_span(c); }); + std::ranges::transform(coeffs, std::back_inserter(_coeffs), + [](auto& c) { return make_coefficients_span(c); }); - apply_lifting(b, a, _constants, _coeffs, bcs1, x0, scale); + apply_lifting(b, a, _constants, _coeffs, bcs1, x0, alpha); } // -- Matrices --------------------------------------------------------------- @@ -422,44 +405,4 @@ void set_diagonal( } } } - -// -- Setting bcs ------------------------------------------------------------ - -// FIXME: Move these function elsewhere? - -// FIXME: clarify x0 -// FIXME: clarify what happens with ghosts - -/// Set bc values in owned (local) part of the vector, multiplied by -/// 'scale'. The vectors b and x0 must have the same local size. The bcs -/// should be on (sub-)spaces of the form L that b represents. -template -void set_bc(std::span b, - const std::vector>>& bcs, - std::span x0, T scale = 1) -{ - if (b.size() > x0.size()) - throw std::runtime_error("Size mismatch between b and x0 vectors."); - for (auto& bc : bcs) - { - assert(bc); - bc->set(b, x0, scale); - } -} - -/// Set bc values in owned (local) part of the vector, multiplied by -/// 'scale'. The bcs should be on (sub-)spaces of the form L that b -/// represents. -template -void set_bc(std::span b, - const std::vector>>& bcs, - T scale = 1) -{ - for (auto& bc : bcs) - { - assert(bc); - bc->set(b, scale); - } -} - } // namespace dolfinx::fem diff --git a/cpp/dolfinx/fem/discreteoperators.h b/cpp/dolfinx/fem/discreteoperators.h index dcc69c8fd7d..3989e77e9e0 100644 --- a/cpp/dolfinx/fem/discreteoperators.h +++ b/cpp/dolfinx/fem/discreteoperators.h @@ -9,6 +9,7 @@ #include "DofMap.h" #include "FiniteElement.h" #include "FunctionSpace.h" +#include #include #include #include @@ -129,7 +130,7 @@ void discrete_gradient(mesh::Topology& topology, std::vector Ae(Ab.size()); for (std::int32_t c = 0; c < num_cells; ++c) { - std::copy(Ab.cbegin(), Ab.cend(), Ae.begin()); + std::ranges::copy(Ab, Ae.begin()); apply_inverse_dof_transform(Ae, cell_info, c, ndofs0); mat_set(dofmap1.cell_dofs(c), dofmap0.cell_dofs(c), Ae); } @@ -230,10 +231,9 @@ void interpolation_matrix(const FunctionSpace& V0, e0->tabulate(basis_derivatives_reference0_b, X, Xshape, 0); // Clamp values - std::transform(basis_derivatives_reference0_b.begin(), - basis_derivatives_reference0_b.end(), - basis_derivatives_reference0_b.begin(), [atol = 1e-14](auto x) - { return std::abs(x) < atol ? 0.0 : x; }); + std::ranges::transform( + basis_derivatives_reference0_b, basis_derivatives_reference0_b.begin(), + [atol = 1e-14](auto x) { return std::abs(x) < atol ? 0.0 : x; }); // Create working arrays std::vector basis_reference0_b(Xshape[0] * dim0 * value_size_ref0); @@ -302,7 +302,7 @@ void interpolation_matrix(const FunctionSpace& V0, } // Compute Jacobians and reference points for current cell - std::fill(J_b.begin(), J_b.end(), 0); + std::ranges::fill(J_b, 0); for (std::size_t p = 0; p < Xshape[0]; ++p) { auto dphi = MDSPAN_IMPL_STANDARD_NAMESPACE::submdspan( diff --git a/cpp/dolfinx/fem/dofmapbuilder.cpp b/cpp/dolfinx/fem/dofmapbuilder.cpp index 0ee0652677c..f7cfcf25d7b 100644 --- a/cpp/dolfinx/fem/dofmapbuilder.cpp +++ b/cpp/dolfinx/fem/dofmapbuilder.cpp @@ -116,16 +116,14 @@ reorder_owned(const std::vector& dofmaps, std::int32_t owned_size, std::int32_t current_offset = 0; for (std::size_t i = 0; i < num_edges.size(); ++i) { - std::sort(std::next(edges.begin(), current_offset), - std::next(edges.begin(), current_offset + num_edges[i])); - const auto it - = std::unique(std::next(edges.begin(), current_offset), - std::next(edges.begin(), current_offset + num_edges[i])); - graph_data.insert(graph_data.end(), - std::next(edges.begin(), current_offset), it); - graph_offsets[i + 1] - = graph_offsets[i] - + std::distance(std::next(edges.begin(), current_offset), it); + auto range_begin = std::next(edges.begin(), current_offset); + auto edge_range = std::ranges::subrange( + range_begin, std::next(range_begin, num_edges[i])); + std::ranges::sort(edge_range); + auto it = std::ranges::unique(edge_range).begin(); + + graph_data.insert(graph_data.end(), range_begin, it); + graph_offsets[i + 1] = graph_offsets[i] + std::distance(range_begin, it); current_offset += num_edges[i]; } @@ -163,7 +161,7 @@ build_basic_dofmaps( const std::size_t D = topology.dim(); const std::size_t num_cell_types = topology.entity_types(D).size(); - LOG(INFO) << "Checking required entities per dimension"; + spdlog::info("Checking required entities per dimension"); // Find which dimensions (d) and entity types (et) are required // and the number of dofs which are required for each (d, et) combination. @@ -242,7 +240,7 @@ build_basic_dofmaps( << (int)required_dim_et[i].second << ")=" << num_entity_dofs_et[i] << " "; } - LOG(INFO) << s.str(); + spdlog::info("{}", s.str()); } #endif @@ -261,8 +259,7 @@ build_basic_dofmaps( std::int32_t dofmap_width = element_dof_layouts[i].num_dofs(); dofs[i].width = dofmap_width; dofs[i].array.resize(num_cells * dofmap_width); - LOG(INFO) << "Cell type:" << i << ", dofmap:" << num_cells << "x" - << dofmap_width; + spdlog::info("Cell type: {} dofmap: {}x{}", i, num_cells, dofmap_width); std::int32_t dofmap_offset = 0; for (std::int32_t c = 0; c < num_cells; ++c) @@ -317,7 +314,7 @@ build_basic_dofmaps( } } - LOG(INFO) << "Global index computation"; + spdlog::info("Global index computation"); // TODO: Put Global index computations in separate function // Global index computations @@ -391,8 +388,8 @@ std::pair, std::int32_t> compute_reordering_map( // Get mesh entity ownership offset for each IndexMap std::vector offset(index_maps.size(), -1); - std::transform(index_maps.begin(), index_maps.end(), offset.begin(), - [](auto map) { return map->size_local(); }); + std::ranges::transform(index_maps, offset.begin(), + [](auto& map) { return map->size_local(); }); // Compute the number of dofs 'owned' by this process const std::int32_t owned_size = std::accumulate( @@ -442,10 +439,10 @@ std::pair, std::int32_t> compute_reordering_map( // Apply graph reordering to owned dofs const std::vector node_remap = reorder_owned( dofmaps, owned_size, original_to_contiguous, reorder_fn); - std::transform(original_to_contiguous.begin(), original_to_contiguous.end(), - original_to_contiguous.begin(), - [&node_remap, owned_size](auto index) - { return index < owned_size ? node_remap[index] : index; }); + std::ranges::transform( + original_to_contiguous, original_to_contiguous.begin(), + [&node_remap, owned_size](auto index) + { return index < owned_size ? node_remap[index] : index; }); } return {std::move(original_to_contiguous), owned_size}; @@ -485,9 +482,9 @@ std::pair, std::vector> get_global_indices( shared_entity[d] = std::vector(map->size_local(), false); const std::vector forward_indices = map->shared_indices(); - std::for_each(forward_indices.begin(), forward_indices.end(), - [&entities = shared_entity[d]](auto idx) - { entities[idx] = true; }); + std::ranges::for_each(forward_indices, + [&entities = shared_entity[d]](auto idx) + { entities[idx] = true; }); } // Build list of (global old, global new) index pairs for dofs that @@ -580,13 +577,12 @@ std::pair, std::vector> get_global_indices( global_old_new.reserve(disp_recv[d].back()); for (std::size_t j = 0; j < all_dofs_received[d].size(); j += 2) { - const auto pos - = std::upper_bound(disp_recv[d].begin(), disp_recv[d].end(), j); + const auto pos = std::ranges::upper_bound(disp_recv[d], j); const int owner = std::distance(disp_recv[d].begin(), pos) - 1; global_old_new.push_back( {all_dofs_received[d][j], {all_dofs_received[d][j + 1], src[owner]}}); } - std::sort(global_old_new.begin(), global_old_new.end()); + std::ranges::sort(global_old_new); // Build the dimension d part of local_to_global_new vector for (std::size_t i = 0; i < local_new_to_global_old[d].size(); i += 2) @@ -594,9 +590,9 @@ std::pair, std::vector> get_global_indices( std::pair> idx_old = {local_new_to_global_old[d][i], {0, 0}}; - auto it = std::lower_bound(global_old_new.begin(), global_old_new.end(), - idx_old, [](auto& a, auto& b) - { return a.first < b.first; }); + auto it = std::ranges::lower_bound(global_old_new, idx_old, + [](auto& a, auto& b) + { return a.first < b.first; }); assert(it != global_old_new.end() and it->first == idx_old.first); local_to_global_new[local_new_to_global_old[d][i + 1]] = it->second.first; @@ -634,14 +630,14 @@ fem::build_dofmap_data( offset] = build_basic_dofmaps(topology, element_dof_layouts); - LOG(INFO) << "Got " << topo_index_maps.size() << " index_maps"; + spdlog::info("Got {} index_maps", topo_index_maps.size()); // Build re-ordering map for data locality and get number of owned // nodes const auto [old_to_new, num_owned] = compute_reordering_map( node_graphs, dof_entity0, topo_index_maps, reorder_fn); - LOG(INFO) << "Get global indices"; + spdlog::info("Get global indices"); // Get global indices for unowned dofs const auto [local_to_global_unowned, local_to_global_owner] diff --git a/cpp/dolfinx/fem/interpolate.h b/cpp/dolfinx/fem/interpolate.h index 9e37714fdb4..e25fdcbfa2b 100644 --- a/cpp/dolfinx/fem/interpolate.h +++ b/cpp/dolfinx/fem/interpolate.h @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2022 Garth N. Wells, Igor A. Baratta, Massimiliano Leoni +// Copyright (C) 2020-2024 Garth N. Wells, Igor A. Baratta, Massimiliano Leoni // and Jørgen S.Dokken // // This file is part of DOLFINx (https://www.fenicsproject.org) @@ -11,11 +11,11 @@ #include "DofMap.h" #include "FiniteElement.h" #include "FunctionSpace.h" +#include #include #include #include #include -#include #include #include #include @@ -32,7 +32,6 @@ template concept MDSpan = requires(T x, std::size_t idx) { x(idx, idx); { x.extent(0) } -> std::integral; - { x.extent(1) } -> std::integral; }; @@ -40,12 +39,13 @@ concept MDSpan = requires(T x, std::size_t idx) { /// an expression should be computed to interpolate it in a finite /// element space. /// -/// @param[in] element The element to be interpolated into -/// @param[in] geometry Mesh geometry +/// @param[in] element Element to be interpolated into. +/// @param[in] geometry Mesh geometry. /// @param[in] cells Indices of the cells in the mesh to compute -/// interpolation coordinates for +/// interpolation coordinates for. /// @return The coordinates in the physical space at which to evaluate -/// an expression. The shape is (3, num_points) and storage is row-major. +/// an expression. The shape is (3, num_points) and storage is +/// row-major. template std::vector interpolation_coords(const fem::FiniteElement& element, const mesh::Geometry& geometry, @@ -105,20 +105,21 @@ std::vector interpolation_coords(const fem::FiniteElement& element, return x; } -/// @brief Interpolate an expression f(x) in a finite element space. +/// @brief Interpolate an evaluated expression f(x) in a finite element +/// space. /// -/// @param[out] u The Function object to interpolate into +/// @tparam T Scalar type +/// @tparam U Mesh geometry type +/// @param[out] u Function object to interpolate into /// @param[in] f Evaluation of the function `f(x)` at the physical -/// points `x` given by fem::interpolation_coords. The element used in -/// fem::interpolation_coords should be the same element as associated -/// with `u`. The shape of `f` should be (value_size, num_points), with +/// points `x` given by \ref interpolation_coords. The element used in +/// \ref interpolation_coords should be the same element as associated +/// with `u`. The shape of `f` is `(value_size, num_points)`, with /// row-major storage. -/// @param[in] fshape The shape of `f`. +/// @param[in] fshape Shape of `f`. /// @param[in] cells Indices of the cells in the mesh on which to -/// interpolate. Should be the same as the list used when calling -/// fem::interpolation_coords. -/// @tparam T Scalar type -/// @tparam U Mesh geometry type +/// interpolate. Should be the same as the list of cells used when +/// calling \ref interpolation_coords. template void interpolate(Function& u, std::span f, std::array fshape, @@ -133,31 +134,27 @@ using mdspan_t = MDSPAN_IMPL_STANDARD_NAMESPACE::mdspan< /// @brief Scatter data into non-contiguous memory. /// -/// Scatter blocked data `send_values` to its corresponding `src_rank` and -/// insert the data into `recv_values`. The insert location in +/// Scatter blocked data `send_values` to its corresponding `src_rank` +/// and insert the data into `recv_values`. The insert location in /// `recv_values` is determined by `dest_ranks`. If the j-th dest rank /// is -1, then `recv_values[j*block_size:(j+1)*block_size]) = 0`. /// -/// @param[in] comm The mpi communicator -/// @param[in] src_ranks The rank owning the values of each row in -/// send_values +/// @param[in] comm The MPI communicator. +/// @param[in] src_ranks Rank owning the values of each row in +/// `send_values`. /// @param[in] dest_ranks List of ranks receiving data. Size of array is /// how many values we are receiving (not unrolled for block_size). -/// @param[in] send_values The values to send back to owner. Shape -/// (src_ranks.size(), block_size). +/// @param[in] send_values Values to send back to owner. Shape is +/// `(src_ranks.size(), block_size)`. /// @param[in,out] recv_values Array to fill with values. Shape -/// (dest_ranks.size(), block_size). Storage is row-major. +/// `(dest_ranks.size(), block_size)`. Storage is row-major. /// @pre It is required that src_ranks are sorted. -/// @note dest_ranks can contain repeated entries -/// @note dest_ranks might contain -1 (no process owns the point) +/// @note `dest_ranks` can contain repeated entries. +/// @note `dest_ranks` might contain -1 (no process owns the point). template -void scatter_values( - MPI_Comm comm, std::span src_ranks, - std::span dest_ranks, - MDSPAN_IMPL_STANDARD_NAMESPACE::mdspan< - const T, MDSPAN_IMPL_STANDARD_NAMESPACE::dextents> - send_values, - std::span recv_values) +void scatter_values(MPI_Comm comm, std::span src_ranks, + std::span dest_ranks, + mdspan_t send_values, std::span recv_values) { const std::size_t block_size = send_values.extent(1); assert(src_ranks.size() * block_size == send_values.size()); @@ -166,8 +163,8 @@ void scatter_values( // Build unique set of the sorted src_ranks std::vector out_ranks(src_ranks.size()); out_ranks.assign(src_ranks.begin(), src_ranks.end()); - out_ranks.erase(std::unique(out_ranks.begin(), out_ranks.end()), - out_ranks.end()); + auto [unique_end, range_end] = std::ranges::unique(out_ranks); + out_ranks.erase(unique_end, range_end); out_ranks.reserve(out_ranks.size() + 1); // Remove negative entries from dest_ranks @@ -178,8 +175,11 @@ void scatter_values( [](auto rank) { return rank >= 0; }); // Create unique set of sorted in-ranks - std::sort(in_ranks.begin(), in_ranks.end()); - in_ranks.erase(std::unique(in_ranks.begin(), in_ranks.end()), in_ranks.end()); + { + std::ranges::sort(in_ranks); + auto [unique_end, range_end] = std::ranges::unique(in_ranks); + in_ranks.erase(unique_end, range_end); + } in_ranks.reserve(in_ranks.size() + 1); // Create neighborhood communicator @@ -194,19 +194,24 @@ void scatter_values( std::vector recv_offsets(in_ranks.size() + 1, 0); { // Build map from parent to neighborhood communicator ranks - std::map rank_to_neighbor; + std::vector> rank_to_neighbor; + rank_to_neighbor.reserve(in_ranks.size()); for (std::size_t i = 0; i < in_ranks.size(); i++) - rank_to_neighbor[in_ranks[i]] = i; + rank_to_neighbor.push_back({in_ranks[i], i}); + std::ranges::sort(rank_to_neighbor); // Compute receive sizes - std::for_each( - dest_ranks.begin(), dest_ranks.end(), + std::ranges::for_each( + dest_ranks, [&dest_ranks, &rank_to_neighbor, &recv_sizes, block_size](auto rank) { if (rank >= 0) { - const int neighbor = rank_to_neighbor[rank]; - recv_sizes[neighbor] += block_size; + auto it = std::ranges::lower_bound(rank_to_neighbor, rank, + std::ranges::less(), + [](auto e) { return e.first; }); + assert(it != rank_to_neighbor.end() and it->first == rank); + recv_sizes[it->second] += block_size; } }); @@ -221,10 +226,13 @@ void scatter_values( { if (const std::int32_t rank = dest_ranks[i]; rank >= 0) { - const int neighbor = rank_to_neighbor[rank]; - int insert_pos = recv_offsets[neighbor] + recv_counter[neighbor]; + auto it = std::ranges::lower_bound(rank_to_neighbor, rank, + std::ranges::less(), + [](auto e) { return e.first; }); + assert(it != rank_to_neighbor.end() and it->first == rank); + int insert_pos = recv_offsets[it->second] + recv_counter[it->second]; comm_to_output[insert_pos / block_size] = i * block_size; - recv_counter[neighbor] += block_size; + recv_counter[it->second] += block_size; } } } @@ -232,15 +240,28 @@ void scatter_values( std::vector send_sizes(out_ranks.size()); send_sizes.reserve(1); { - // Compute map from parent mpi rank to neigbor rank for outgoing data - std::map rank_to_neighbor; + // Compute map from parent MPI rank to neighbor rank for outgoing + // data. `out_ranks` is sorted, so rank_to_neighbor will be sorted + // too. + std::vector> rank_to_neighbor; + rank_to_neighbor.reserve(out_ranks.size()); for (std::size_t i = 0; i < out_ranks.size(); i++) - rank_to_neighbor[out_ranks[i]] = i; - - // Compute send sizes - std::for_each(src_ranks.begin(), src_ranks.end(), - [&rank_to_neighbor, &send_sizes, block_size](auto rank) - { send_sizes[rank_to_neighbor[rank]] += block_size; }); + rank_to_neighbor.push_back({out_ranks[i], i}); + + // Compute send sizes. As `src_ranks` is sorted, we can move 'start' + // in search forward. + auto start = rank_to_neighbor.begin(); + std::ranges::for_each( + src_ranks, + [&rank_to_neighbor, &send_sizes, block_size, &start](auto rank) + { + auto it = std::ranges::lower_bound(start, rank_to_neighbor.end(), + rank, std::ranges::less(), + [](auto e) { return e.first; }); + assert(it != rank_to_neighbor.end() and it->first == rank); + send_sizes[it->second] += block_size; + start = it; + }); } // Compute sending offsets @@ -257,8 +278,9 @@ void scatter_values( dolfinx::MPI::mpi_type(), reverse_comm); MPI_Comm_free(&reverse_comm); - // Insert values received from neighborhood communicator in output span - std::fill(recv_values.begin(), recv_values.end(), T(0)); + // Insert values received from neighborhood communicator in output + // span + std::ranges::fill(recv_values, T(0)); for (std::size_t i = 0; i < comm_to_output.size(); i++) { auto vals = std::next(recv_values.begin(), comm_to_output[i]); @@ -311,15 +333,23 @@ void interpolation_apply(U&& Pi, V&& data, std::span coeffs, int bs) } } -/// Interpolate from one finite element Function to another on the same -/// mesh. The function is for cases where the finite element basis -/// functions are mapped in the same way, e.g. both use the same Piola -/// map. -/// @param[out] u1 The function to interpolate to -/// @param[in] u0 The function to interpolate from -/// @param[in] cells1 The cells to interpolate on -/// @param[in] cells0 Equivalent cell in u0 for each cell in u1 -/// @pre The functions `u1` and `u0` must share the same mesh and the +/// @brief Interpolate from one finite element Function to another on +/// the same mesh. +/// +/// The function is for cases where the finite element basis functions +/// are mapped in the same way, e.g. both use the same Piola map. +/// +/// @param[out] u1 Function to interpolate into. +/// @param[in] u0 Function to b interpolated from. +/// @param[in] cells1 Cell indices associated with the mesh of `u1` that +/// will be interpolated onto. +/// @param[in] cells0 Cell indices associated with the mesh of `u0` that +/// will be interpolated from. If `cells1[i]` is the index of a cell in +/// the mesh associated with `u1`, then `cells0[i]` is the index of the +/// *same* cell but in the mesh associated with `u0`. `cells0` and +/// `cells1` have be the same size. +/// +/// @pre fem::Functions `u1` and `u0` must share the same mesh and the /// elements must share the same basis function map. Neither is checked /// by the function. template @@ -395,7 +425,7 @@ void interpolate_same_map(Function& u1, const Function& u0, // FIXME: Get compile-time ranges from Basix // Apply interpolation operator - std::fill(local1.begin(), local1.end(), 0); + std::ranges::fill(local1, 0); for (std::size_t i = 0; i < im_shape[0]; ++i) for (std::size_t j = 0; j < im_shape[1]; ++j) local1[i] += static_cast(i_m[im_shape[1] * i + j]) * local0[j]; @@ -408,20 +438,24 @@ void interpolate_same_map(Function& u1, const Function& u0, } } -/// Interpolate from one finite element Function to another on the same -/// mesh. This interpolation function is for cases where the finite -/// element basis functions for the two elements are mapped differently, -/// e.g. one may be subject to a Piola mapping and the other to a -/// standard isoparametric mapping. -/// @param[out] u1 The function to interpolate to -/// @param[in] u0 The function to interpolate from -/// @param[in] cells1 The cells to interpolate on -/// @param[in] cells0 Equivalent cell in u0 for each cell in u1 +/// @brief Interpolate from one finite element Function to another on +/// the same mesh. +/// +/// This interpolation function is for cases where the finite element +/// basis functions for the two elements are mapped differently, e.g. +/// one may be subject to a Piola mapping and the other to a standard +/// isoparametric mapping. +/// +/// @param[out] u1 Function to interpolate to. +/// @param[in] cells1 Cells to interpolate on. +/// @param[in] u0 Function to interpolate from. +/// @param[in] cells0 Equivalent cell in `u0` for each cell in `u1`. /// @pre The functions `u1` and `u0` must share the same mesh. This is /// not checked by the function. template -void interpolate_nonmatching_maps(Function& u1, const Function& u0, +void interpolate_nonmatching_maps(Function& u1, std::span cells1, + const Function& u0, std::span cells0) { // Get mesh @@ -446,7 +480,6 @@ void interpolate_nonmatching_maps(Function& u1, const Function& u0, std::span cell_info0; std::span cell_info1; - if (element1->needs_dof_transformations() or element0->needs_dof_transformations()) { @@ -529,19 +562,14 @@ void interpolate_nonmatching_maps(Function& u1, const Function& u0, impl::mdspan_t Pi_1(_Pi_1.data(), pi_shape); using u_t = impl::mdspan_t; - using U_t = MDSPAN_IMPL_STANDARD_NAMESPACE::mdspan< - const U, MDSPAN_IMPL_STANDARD_NAMESPACE::dextents>; - using J_t = MDSPAN_IMPL_STANDARD_NAMESPACE::mdspan< - const U, MDSPAN_IMPL_STANDARD_NAMESPACE::dextents>; - using K_t = MDSPAN_IMPL_STANDARD_NAMESPACE::mdspan< - const U, MDSPAN_IMPL_STANDARD_NAMESPACE::dextents>; + using U_t = impl::mdspan_t; + using J_t = impl::mdspan_t; + using K_t = impl::mdspan_t; auto push_forward_fn0 = element0->basix_element().template map_fn(); - using v_t = MDSPAN_IMPL_STANDARD_NAMESPACE::mdspan< - const T, MDSPAN_IMPL_STANDARD_NAMESPACE::dextents>; - using V_t = MDSPAN_IMPL_STANDARD_NAMESPACE::mdspan< - T, MDSPAN_IMPL_STANDARD_NAMESPACE::dextents>; + using v_t = impl::mdspan_t; + using V_t = impl::mdspan_t; auto pull_back_fn1 = element1->basix_element().template map_fn(); @@ -561,7 +589,7 @@ void interpolate_nonmatching_maps(Function& u1, const Function& u0, } // Compute Jacobians and reference points for current cell - std::fill(J_b.begin(), J_b.end(), 0); + std::ranges::fill(J_b, 0); for (std::size_t p = 0; p < Xshape[0]; ++p) { auto dphi = MDSPAN_IMPL_STANDARD_NAMESPACE::submdspan( @@ -668,70 +696,6 @@ void interpolate_nonmatching_maps(Function& u1, const Function& u0, } } -template -void interpolate_nonmatching_meshes( - Function& u, const Function& v, - std::span cells, - const std::tuple, - std::span, std::span, - std::span>& nmm_interpolation_data) -{ - auto mesh = u.function_space()->mesh(); - assert(mesh); - MPI_Comm comm = mesh->comm(); - - { - auto mesh_v = v.function_space()->mesh(); - assert(mesh_v); - int result; - MPI_Comm_compare(comm, mesh_v->comm(), &result); - if (result == MPI_UNEQUAL) - { - throw std::runtime_error("Interpolation on different meshes is only " - "supported with the same communicator."); - } - } - - assert(mesh->topology()); - auto cell_map = mesh->topology()->index_map(mesh->topology()->dim()); - assert(cell_map); - - auto element_u = u.function_space()->element(); - assert(element_u); - const std::size_t value_size = u.function_space()->value_size(); - - const auto& [dest_ranks, src_ranks, recv_points, evaluation_cells] - = nmm_interpolation_data; - - // Evaluate the interpolating function where possible - std::vector send_values(recv_points.size() / 3 * value_size); - v.eval(recv_points, {recv_points.size() / 3, (std::size_t)3}, - evaluation_cells, send_values, {recv_points.size() / 3, value_size}); - - // Send values back to owning process - std::vector values_b(dest_ranks.size() * value_size); - MDSPAN_IMPL_STANDARD_NAMESPACE::mdspan< - const T, MDSPAN_IMPL_STANDARD_NAMESPACE::dextents> - _send_values(send_values.data(), src_ranks.size(), value_size); - impl::scatter_values(comm, src_ranks, dest_ranks, _send_values, - std::span(values_b)); - - // Transpose received data - MDSPAN_IMPL_STANDARD_NAMESPACE::mdspan< - const T, MDSPAN_IMPL_STANDARD_NAMESPACE::dextents> - values(values_b.data(), dest_ranks.size(), value_size); - std::vector valuesT_b(value_size * dest_ranks.size()); - MDSPAN_IMPL_STANDARD_NAMESPACE::mdspan< - T, MDSPAN_IMPL_STANDARD_NAMESPACE::dextents> - valuesT(valuesT_b.data(), value_size, dest_ranks.size()); - for (std::size_t i = 0; i < values.extent(0); ++i) - for (std::size_t j = 0; j < values.extent(1); ++j) - valuesT(j, i) = values(i, j); - - // Call local interpolation operator - fem::interpolate(u, valuesT_b, {valuesT.extent(0), valuesT.extent(1)}, - cells); -} //---------------------------------------------------------------------------- } // namespace impl @@ -969,11 +933,12 @@ void interpolate(Function& u, std::span f, std::vector coord_dofs_b(num_dofs_g * gdim); mdspan2_t coord_dofs(coord_dofs_b.data(), num_dofs_g, gdim); - - std::vector ref_data_b(Xshape[0] * 1 * value_size); + const std::size_t value_size_ref + = element->reference_value_size() / element_bs; + std::vector ref_data_b(Xshape[0] * 1 * value_size_ref); MDSPAN_IMPL_STANDARD_NAMESPACE::mdspan< T, MDSPAN_IMPL_STANDARD_NAMESPACE::dextents> - ref_data(ref_data_b.data(), Xshape[0], 1, value_size); + ref_data(ref_data_b.data(), Xshape[0], 1, value_size_ref); std::vector _vals_b(Xshape[0] * 1 * value_size); MDSPAN_IMPL_STANDARD_NAMESPACE::mdspan< @@ -1026,7 +991,7 @@ void interpolate(Function& u, std::span f, } // Compute J, detJ and K - std::fill(J_b.begin(), J_b.end(), 0); + std::ranges::fill(J_b, 0); for (std::size_t p = 0; p < Xshape[0]; ++p) { auto _dphi = MDSPAN_IMPL_STANDARD_NAMESPACE::submdspan( @@ -1093,34 +1058,32 @@ void interpolate(Function& u, std::span f, } } -/// @brief Generate data needed to interpolate discrete functions across -/// different meshes. +/// @brief Generate data needed to interpolate finite element Functions +/// across different meshes. +/// /// @param[in] geometry0 Mesh geometry of the space to interpolate into /// @param[in] element0 Element of the space to interpolate into /// @param[in] mesh1 Mesh of the function to interpolate from /// @param[in] cells Indices of the cells in the destination mesh on /// which to interpolate. Should be the same as the list used when -/// calling fem::interpolation_coords. +/// calling \ref interpolation_coords. /// @param[in] padding Absolute padding of bounding boxes of all /// entities on `mesh1`. This is used avoid floating point issues when /// an interpolation point from `mesh0` is on the surface of a cell in /// `mesh1`. This parameter can also be used for extrapolation, i.e. if /// cells in `mesh0` is not overlapped by `mesh1`. /// -/// @note Setting the `padding` to a large value will increase runtime -/// of this function, as one has to determine what entity is closest if -/// there is no intersection. +/// @note Setting the `padding` to a large value will increase the +/// runtime of this function, as one has to determine what entity is +/// closest if there is no intersection. template -std::tuple, std::vector, std::vector, - std::vector> -create_nonmatching_meshes_interpolation_data( +geometry::PointOwnershipData create_interpolation_data( const mesh::Geometry& geometry0, const FiniteElement& element0, const mesh::Mesh& mesh1, std::span cells, T padding) { // Collect all the points at which values are needed to define the // interpolating function - const std::vector coords - = interpolation_coords(element0, geometry0, cells); + std::vector coords = interpolation_coords(element0, geometry0, cells); // Transpose interpolation coords std::vector x(coords.size()); @@ -1133,61 +1096,106 @@ create_nonmatching_meshes_interpolation_data( return geometry::determine_point_ownership(mesh1, x, padding); } -/// @brief Generate data needed to interpolate discrete functions -/// defined on different meshes. Interpolate on all cells in the mesh. -/// @param[in] mesh0 Mesh of the space to interpolate into -/// @param[in] element0 Element of the space to interpolate into -/// @param[in] mesh1 Mesh of the function to interpolate from -/// @param[in] padding Absolute padding of bounding boxes of all entities on -/// `mesh1`. This is used avoid floating point issues when an interpolation -/// point from `mesh0` is on the surface of a cell in `mesh1`. This parameter -/// can also be used for extrapolation, i.e. if cells in `mesh0` is not -/// overlapped by `mesh1`. -/// @note Setting the `padding` to a large value will increase runtime of this -/// function, as one has to determine what entity is closest if there is no -/// intersection. -template -std::tuple, std::vector, std::vector, - std::vector> -create_nonmatching_meshes_interpolation_data(const mesh::Mesh& mesh0, - const FiniteElement& element0, - const mesh::Mesh& mesh1, - T padding) +/// @brief Interpolate a finite element Function defined on a mesh to a +/// finite element Function defined on different (non-matching) mesh. +/// @tparam T Function scalar type. +/// @tparam U mesh::Mesh geometry scalar type. +/// @param u Function to interpolate into. +/// @param v Function to interpolate from. +/// @param cells Cells indices relative to the mesh associated with `u` +/// that will be interpolated into. +/// @param interpolation_data Data required for associating the +/// interpolation points of `u` with cells in `v`. This is computed by +/// fem::create_interpolation_data. +template +void interpolate(Function& u, const Function& v, + std::span cells, + const geometry::PointOwnershipData& interpolation_data) { - int tdim = mesh0.topology()->dim(); - auto cell_map = mesh0.topology()->index_map(tdim); + auto mesh = u.function_space()->mesh(); + assert(mesh); + MPI_Comm comm = mesh->comm(); + { + auto mesh_v = v.function_space()->mesh(); + assert(mesh_v); + int result; + MPI_Comm_compare(comm, mesh_v->comm(), &result); + if (result == MPI_UNEQUAL) + { + throw std::runtime_error("Interpolation on different meshes is only " + "supported on the same communicator."); + } + } + + assert(mesh->topology()); + auto cell_map = mesh->topology()->index_map(mesh->topology()->dim()); assert(cell_map); - std::int32_t num_cells = cell_map->size_local() + cell_map->num_ghosts(); - std::vector cells(num_cells, 0); - std::iota(cells.begin(), cells.end(), 0); - return create_nonmatching_meshes_interpolation_data( - mesh0.geometry(), element0, mesh1, cells, padding); + auto element_u = u.function_space()->element(); + assert(element_u); + const std::size_t value_size = u.function_space()->value_size(); + + const std::vector& dest_ranks = interpolation_data.src_owner; + const std::vector& src_ranks = interpolation_data.dest_owners; + const std::vector& recv_points = interpolation_data.dest_points; + const std::vector& evaluation_cells + = interpolation_data.dest_cells; + + // Evaluate the interpolating function where possible + std::vector send_values(recv_points.size() / 3 * value_size); + v.eval(recv_points, {recv_points.size() / 3, (std::size_t)3}, + evaluation_cells, send_values, {recv_points.size() / 3, value_size}); + + using dextents2 = MDSPAN_IMPL_STANDARD_NAMESPACE::dextents; + + // Send values back to owning process + std::vector values_b(dest_ranks.size() * value_size); + MDSPAN_IMPL_STANDARD_NAMESPACE::mdspan _send_values( + send_values.data(), src_ranks.size(), value_size); + impl::scatter_values(comm, src_ranks, dest_ranks, _send_values, + std::span(values_b)); + + // Transpose received data + MDSPAN_IMPL_STANDARD_NAMESPACE::mdspan values( + values_b.data(), dest_ranks.size(), value_size); + std::vector valuesT_b(value_size * dest_ranks.size()); + MDSPAN_IMPL_STANDARD_NAMESPACE::mdspan valuesT( + valuesT_b.data(), value_size, dest_ranks.size()); + for (std::size_t i = 0; i < values.extent(0); ++i) + for (std::size_t j = 0; j < values.extent(1); ++j) + valuesT(j, i) = values(i, j); + + // Call local interpolation operator + fem::interpolate(u, valuesT_b, {valuesT.extent(0), valuesT.extent(1)}, + cells); } -/// @brief Interpolate from one finite element Function to another one. -/// @param[out] u1 The function to interpolate into -/// @param[in] u0 The function to be interpolated -/// @param[in] cells1 List of cell indices associated with the mesh of `u1` that -/// will be interpolated onto -/// @param[in] cell_map Mapping of cells in mesh associated with `u1` to cells -/// associated with `u0` -/// @param[in] nmm_interpolation_data Auxiliary data to interpolate on -/// nonmatching meshes. This data can be generated with -/// create_nonmatching_meshes_interpolation_data (optional). +/// @brief Interpolate from one finite element Function to another +/// Function on the same (sub)mesh. +/// +/// Interpolation can be performed on a subset of mesh cells and +/// Functions may be defined on 'sub-meshes'. +/// +/// @param[out] u1 Function to interpolate into. +/// @param[in] cells1 Cell indices associated with the mesh of `u1` that +/// will be interpolated onto. +/// @param[in] u0 Function to b interpolated from. +/// @param[in] cells0 Cell indices associated with the mesh of `u0` that +/// will be interpolated from. If `cells1[i]` is the index of a cell in +/// the mesh associated with `u1`, then `cells0[i]` is the index of the +/// *same* cell but in the mesh associated with `u0`. `cells0` and +/// `cells1` must be the same size. template -void interpolate( - Function& u1, const Function& u0, - std::span cells1, - std::span cell_map, - const std::tuple, - std::span, std::span, - std::span>& nmm_interpolation_data - = {}) +void interpolate(Function& u1, std::span cells1, + const Function& u0, std::span cells0) { + if (cells0.size() != cells1.size()) + throw std::runtime_error("Length of cell lists do not match."); + assert(u1.function_space()); assert(u0.function_space()); auto mesh = u1.function_space()->mesh(); assert(mesh); + assert(cells0.size() == cells1.size()); auto cell_map0 = mesh->topology()->index_map(mesh->topology()->dim()); assert(cell_map0); @@ -1198,31 +1206,10 @@ void interpolate( // Same function spaces and on whole mesh std::span u1_array = u1.x()->mutable_array(); std::span u0_array = u0.x()->array(); - std::copy(u0_array.begin(), u0_array.end(), u1_array.begin()); + std::ranges::copy(u0_array, u1_array.begin()); } else { - std::vector cells0; - cells0.reserve(cells1.size()); - // Get mesh and check that functions share the same mesh - if (auto mesh_v = u0.function_space()->mesh(); mesh == mesh_v) - { - cells0.insert(cells0.end(), cells1.begin(), cells1.end()); - } - // If meshes are different and input mapping is given - else if (cell_map.size() > 0) - { - std::transform(cells1.begin(), cells1.end(), std::back_inserter(cells0), - [&cell_map](std::int32_t c) { return cell_map[c]; }); - } - // Non-matching meshes - if (cells0.empty()) - { - impl::interpolate_nonmatching_meshes(u1, u0, cells1, - nmm_interpolation_data); - return; - } - // Get elements and check value shape auto fs0 = u0.function_space(); auto element0 = fs0->element(); @@ -1241,11 +1228,9 @@ void interpolate( if (element1 == element0 or *element1 == *element0) { // Same element, different dofmaps (or just a subset of cells) - const int tdim = mesh->topology()->dim(); auto cell_map1 = mesh->topology()->index_map(tdim); assert(cell_map1); - assert(element1->block_size() == element0->block_size()); // Get dofmaps @@ -1285,7 +1270,7 @@ void interpolate( else { // Different elements with different maps for basis functions - impl::interpolate_nonmatching_maps(u1, u0, cells1, cells0); + impl::interpolate_nonmatching_maps(u1, cells1, u0, cells0); } } } diff --git a/cpp/dolfinx/fem/petsc.h b/cpp/dolfinx/fem/petsc.h index edb4aa4bfdc..a94af0b0513 100644 --- a/cpp/dolfinx/fem/petsc.h +++ b/cpp/dolfinx/fem/petsc.h @@ -34,7 +34,7 @@ class DirichletBC; /// @brief Helper functions for assembly into PETSc data structures namespace petsc { -/// Create a matrix +/// @brief Create a matrix /// @param[in] a A bilinear form /// @param[in] type The PETSc matrix type to create /// @return A sparse matrix with a layout and sparsity that matches the @@ -49,7 +49,8 @@ Mat create_matrix(const Form& a, return la::petsc::create_matrix(a.mesh()->comm(), pattern, type); } -/// Initialise a monolithic matrix for an array of bilinear forms +/// @brief Initialise a monolithic matrix for an array of bilinear +/// forms. /// @param[in] a Rectangular array of bilinear forms. The `a(i, j)` form /// will correspond to the `(i, j)` block in the returned matrix /// @param[in] type The type of PETSc Mat. If empty the PETSc default is @@ -316,7 +317,7 @@ void assemble_vector(Vec b, const Form& L) /// /// Modify b such that: /// -/// b <- b - scale * A_j (g_j - x0_j) +/// b <- b - alpha * A_j (g_j - x0_j) /// /// where j is a block (nest) index. For a non-blocked problem j = 0. The /// boundary conditions bcs1 are on the trial spaces V_j. The forms in @@ -335,7 +336,7 @@ void apply_lifting( coeffs, const std::vector< std::vector>>>& bcs1, - const std::vector& x0, PetscScalar scale) + const std::vector& x0, PetscScalar alpha) { Vec b_local; VecGhostGetLocalForm(b, &b_local); @@ -346,7 +347,7 @@ void apply_lifting( std::span _b(array, n); if (x0.empty()) - fem::apply_lifting(_b, a, constants, coeffs, bcs1, {}, scale); + fem::apply_lifting(_b, a, constants, coeffs, bcs1, {}, alpha); else { std::vector> x0_ref; @@ -363,7 +364,7 @@ void apply_lifting( } std::vector x0_tmp(x0_ref.begin(), x0_ref.end()); - fem::apply_lifting(_b, a, constants, coeffs, bcs1, x0_tmp, scale); + fem::apply_lifting(_b, a, constants, coeffs, bcs1, x0_tmp, alpha); for (std::size_t i = 0; i < x0_local.size(); ++i) { @@ -384,7 +385,7 @@ void apply_lifting( /// Modify b such that: /// -/// b <- b - scale * A_j (g_j - x0_j) +/// b <- b - alpha * A_j (g_j - x0_j) /// /// where j is a block (nest) index. For a non-blocked problem j = 0. The /// boundary conditions bcs1 are on the trial spaces V_j. The forms in @@ -401,7 +402,7 @@ void apply_lifting( const std::vector< std::vector>>>& bcs1, - const std::vector& x0, PetscScalar scale) + const std::vector& x0, PetscScalar alpha) { Vec b_local; VecGhostGetLocalForm(b, &b_local); @@ -412,7 +413,7 @@ void apply_lifting( std::span _b(array, n); if (x0.empty()) - fem::apply_lifting(_b, a, bcs1, {}, scale); + fem::apply_lifting(_b, a, bcs1, {}, alpha); else { std::vector> x0_ref; @@ -429,7 +430,7 @@ void apply_lifting( } std::vector x0_tmp(x0_ref.begin(), x0_ref.end()); - fem::apply_lifting(_b, a, bcs1, x0_tmp, scale); + fem::apply_lifting(_b, a, bcs1, x0_tmp, alpha); for (std::size_t i = 0; i < x0_local.size(); ++i) { @@ -450,13 +451,13 @@ void apply_lifting( // FIXME: clarify what happens with ghosts /// Set bc values in owned (local) part of the PETSc vector, multiplied -/// by 'scale'. The vectors b and x0 must have the same local size. The +/// by 'alpha'. The vectors b and x0 must have the same local size. The /// bcs should be on (sub-)spaces of the form L that b represents. template void set_bc( Vec b, const std::vector>>& bcs, - const Vec x0, PetscScalar scale = 1) + const Vec x0, PetscScalar alpha = 1) { PetscInt n = 0; VecGetLocalSize(b, &n); @@ -472,13 +473,16 @@ void set_bc( const PetscScalar* array = nullptr; VecGetArrayRead(x0_local, &array); std::span _x0(array, n); - fem::set_bc(_b, bcs, _x0, scale); + for (auto& bc : bcs) + bc->set(_b, _x0, alpha); VecRestoreArrayRead(x0_local, &array); VecGhostRestoreLocalForm(x0, &x0_local); } else - fem::set_bc(_b, bcs, scale); - + { + for (auto& bc : bcs) + bc->set(_b, std::nullopt, alpha); + } VecRestoreArray(b, &array); } diff --git a/cpp/dolfinx/fem/sparsitybuild.cpp b/cpp/dolfinx/fem/sparsitybuild.cpp index 7a8cc1443b3..b8c6ac3b3d4 100644 --- a/cpp/dolfinx/fem/sparsitybuild.cpp +++ b/cpp/dolfinx/fem/sparsitybuild.cpp @@ -6,6 +6,7 @@ #include "sparsitybuild.h" #include "DofMap.h" +#include #include using namespace dolfinx; @@ -43,17 +44,15 @@ void sparsitybuild::interior_facets( auto dofs00 = dofmap0.cell_dofs(cells0[f]); auto dofs01 = dofmap0.cell_dofs(cells0[f + 1]); macro_dofs0.resize(dofs00.size() + dofs01.size()); - std::copy(dofs00.begin(), dofs00.end(), macro_dofs0.begin()); - std::copy(dofs01.begin(), dofs01.end(), - std::next(macro_dofs0.begin(), dofs00.size())); + std::ranges::copy(dofs00, macro_dofs0.begin()); + std::ranges::copy(dofs01, std::next(macro_dofs0.begin(), dofs00.size())); // Trial function dofs (sparsity pattern columns) auto dofs10 = dofmap1.cell_dofs(cells1[f]); auto dofs11 = dofmap1.cell_dofs(cells1[f + 1]); macro_dofs1.resize(dofs10.size() + dofs11.size()); - std::copy(dofs10.begin(), dofs10.end(), macro_dofs1.begin()); - std::copy(dofs11.begin(), dofs11.end(), - std::next(macro_dofs1.begin(), dofs10.size())); + std::ranges::copy(dofs10, macro_dofs1.begin()); + std::ranges::copy(dofs11, std::next(macro_dofs1.begin(), dofs10.size())); pattern.insert(macro_dofs0, macro_dofs1); } diff --git a/cpp/dolfinx/fem/sparsitybuild.h b/cpp/dolfinx/fem/sparsitybuild.h index 2279cd0445f..ee6eb49cfa8 100644 --- a/cpp/dolfinx/fem/sparsitybuild.h +++ b/cpp/dolfinx/fem/sparsitybuild.h @@ -27,7 +27,7 @@ namespace sparsitybuild /// /// Inserts the rectangular blocks of indices `dofmap[0][cells[0][i]] x /// dofmap[1][cells[1][i]]` into the sparsity pattern, i.e. entries -/// (dofmap[0][cells[0][i]][k0], dofmap[0][cells[0][i]][k1])` will +/// `(dofmap[0][cells[0][i]][k0], dofmap[0][cells[0][i]][k1])` will /// appear in the sparsity pattern. /// /// @param pattern Sparsity pattern to insert into. @@ -51,6 +51,7 @@ void cells(la::SparsityPattern& pattern, /// list of `(cell0, cell1)` pairs for each interior facet to index into /// `dofmap[i]`. `cells[0]` and `cells[1]` must have the same size. /// @param[in] dofmaps Dofmaps to use in building the sparsity pattern. +/// /// @note The sparsity pattern is not finalised. void interior_facets( la::SparsityPattern& pattern, diff --git a/cpp/dolfinx/fem/utils.cpp b/cpp/dolfinx/fem/utils.cpp index 3fd0a2673f7..8f098aa686d 100644 --- a/cpp/dolfinx/fem/utils.cpp +++ b/cpp/dolfinx/fem/utils.cpp @@ -27,73 +27,6 @@ using namespace dolfinx; -//----------------------------------------------------------------------------- -fem::ElementDofLayout -fem::create_element_dof_layout(const ufcx_dofmap& dofmap, - const mesh::CellType cell_type, - const std::vector& parent_map) -{ - const int element_block_size = dofmap.block_size; - - // Fill entity dof indices - const int tdim = mesh::cell_dim(cell_type); - std::vector>> entity_dofs(tdim + 1); - std::vector>> entity_closure_dofs(tdim + 1); - { - int* offset0 = dofmap.entity_dof_offsets; - int* offset1 = dofmap.entity_closure_dof_offsets; - for (int d = 0; d <= tdim; ++d) - { - int num_entities = mesh::cell_num_entities(cell_type, d); - entity_dofs[d].resize(num_entities); - entity_closure_dofs[d].resize(num_entities); - for (int i = 0; i < num_entities; ++i) - { - std::copy(dofmap.entity_dofs + *offset0, - dofmap.entity_dofs + *(offset0 + 1), - std::back_inserter(entity_dofs[d][i])); - std::copy(dofmap.entity_closure_dofs + *offset1, - dofmap.entity_closure_dofs + *(offset1 + 1), - std::back_inserter(entity_closure_dofs[d][i])); - ++offset0; - ++offset1; - } - } - } - - // TODO: UFC dofmaps just use simple offset for each field but this - // could be different for custom dofmaps. This data should come - // directly from the UFC interface in place of the implicit - // assumption. - - // Create UFC subdofmaps and compute offset - std::vector offsets(1, 0); - std::vector sub_doflayout; - for (int i = 0; i < dofmap.num_sub_dofmaps; ++i) - { - ufcx_dofmap* ufcx_sub_dofmap = dofmap.sub_dofmaps[i]; - if (element_block_size == 1) - { - offsets.push_back(offsets.back() - + ufcx_sub_dofmap->num_element_support_dofs - * ufcx_sub_dofmap->block_size); - } - else - offsets.push_back(offsets.back() + 1); - - std::vector parent_map_sub(ufcx_sub_dofmap->num_element_support_dofs - * ufcx_sub_dofmap->block_size); - for (std::size_t j = 0; j < parent_map_sub.size(); ++j) - parent_map_sub[j] = offsets[i] + element_block_size * j; - sub_doflayout.push_back( - create_element_dof_layout(*ufcx_sub_dofmap, cell_type, parent_map_sub)); - } - - // Check for "block structure". This should ultimately be replaced, - // but keep for now to mimic existing code. - return ElementDofLayout(element_block_size, entity_dofs, entity_closure_dofs, - parent_map, sub_doflayout); -} //----------------------------------------------------------------------------- fem::DofMap fem::create_dofmap( MPI_Comm comm, const ElementDofLayout& layout, mesh::Topology& topology, @@ -195,43 +128,42 @@ std::vector fem::get_constant_names(const ufcx_form& ufcx_form) + ufcx_form.num_constants); } //----------------------------------------------------------------------------- -std::vector>> -fem::compute_integration_domains(fem::IntegralType integral_type, - const mesh::Topology& topology, - std::span entities, - int dim, std::span values) +std::vector fem::compute_integration_domains( + fem::IntegralType integral_type, const mesh::Topology& topology, + std::span entities, int dim) { const int tdim = topology.dim(); if ((integral_type == IntegralType::cell ? tdim : tdim - 1) != dim) { - throw std::runtime_error("Invalid MeshTags dimension: " + throw std::runtime_error("Invalid mesh entity dimension: " + std::to_string(dim)); } { + // Create span of the owned entities (leaves off any ghosts) assert(topology.index_map(dim)); - auto it0 = entities.begin(); - auto it1 = std::lower_bound(it0, entities.end(), - topology.index_map(dim)->size_local()); - entities = entities.first(std::distance(it0, it1)); - values = values.first(std::distance(it0, it1)); + auto it1 = std::ranges::lower_bound(entities, + topology.index_map(dim)->size_local()); + entities = entities.first(std::distance(entities.begin(), it1)); } std::vector entity_data; - std::vector values1; switch (integral_type) { case IntegralType::cell: + { entity_data.insert(entity_data.begin(), entities.begin(), entities.end()); - values1.insert(values1.begin(), values.begin(), values.end()); break; + } default: + { auto f_to_c = topology.connectivity(tdim - 1, tdim); if (!f_to_c) { throw std::runtime_error( "Topology facet-to-cell connectivity has not been computed."); } + auto c_to_f = topology.connectivity(tdim, tdim - 1); if (!c_to_f) { @@ -246,25 +178,31 @@ fem::compute_integration_domains(fem::IntegralType integral_type, // Create list of tagged boundary facets const std::vector bfacets = mesh::exterior_facet_indices(topology); std::vector facets; - std::set_intersection(entities.begin(), entities.end(), bfacets.begin(), - bfacets.end(), std::back_inserter(facets)); + std::ranges::set_intersection(entities, bfacets, + std::back_inserter(facets)); for (auto f : facets) { - auto index_it = std::lower_bound(entities.begin(), entities.end(), f); - assert(index_it != entities.end() and *index_it == f); - std::size_t pos = std::distance(entities.begin(), index_it); + // Get the facet as a pair of (cell, local facet) auto facet = impl::get_cell_facet_pairs<1>(f, f_to_c->links(f), *c_to_f); entity_data.insert(entity_data.end(), facet.begin(), facet.end()); - values1.push_back(values[pos]); } } break; case IntegralType::interior_facet: { - for (std::size_t j = 0; j < entities.size(); ++j) + // Create indicator for interprocess facets + assert(topology.index_map(tdim - 1)); + const std::vector& interprocess_facets + = topology.interprocess_facets(); + std::vector interprocess_marker( + topology.index_map(tdim - 1)->size_local() + + topology.index_map(tdim - 1)->num_ghosts(), + 0); + std::ranges::for_each(interprocess_facets, [&interprocess_marker](auto f) + { interprocess_marker[f] = 1; }); + for (auto f : entities) { - const std::int32_t f = entities[j]; if (f_to_c->num_links(f) == 2) { // Get the facet as a pair of (cell, local facet) pairs, one @@ -272,7 +210,12 @@ fem::compute_integration_domains(fem::IntegralType integral_type, auto facets = impl::get_cell_facet_pairs<2>(f, f_to_c->links(f), *c_to_f); entity_data.insert(entity_data.end(), facets.begin(), facets.end()); - values1.push_back(values[j]); + } + else if (interprocess_marker[f]) + { + throw std::runtime_error( + "Cannot compute interior facet integral over interprocess facet. " + "Use \"shared facet\" ghost mode when creating the mesh."); } } } @@ -282,42 +225,8 @@ fem::compute_integration_domains(fem::IntegralType integral_type, "Cannot compute integration domains. Integral type not supported."); } } - - // Build permutation that sorts by meshtag value - std::vector perm(values1.size()); - std::iota(perm.begin(), perm.end(), 0); - std::stable_sort(perm.begin(), perm.end(), [&values1](auto p0, auto p1) - { return values1[p0] < values1[p1]; }); - - std::size_t shape = 1; - if (integral_type == IntegralType::exterior_facet) - shape = 2; - else if (integral_type == IntegralType::interior_facet) - shape = 4; - std::vector>> integrals; - { - // Iterator to mark the start of the group - auto p0 = perm.begin(); - while (p0 != perm.end()) - { - auto id0 = values1[*p0]; - auto p1 = std::find_if_not(p0, perm.end(), [id0, &values1](auto idx) - { return id0 == values1[idx]; }); - - std::vector data; - data.reserve(shape * std::distance(p0, p1)); - for (auto it = p0; it != p1; ++it) - { - auto e_it0 = std::next(entity_data.begin(), (*it) * shape); - auto e_it1 = std::next(e_it0, shape); - data.insert(data.end(), e_it0, e_it1); - } - - integrals.push_back({id0, std::move(data)}); - p0 = p1; - } } - return integrals; + return entity_data; } //----------------------------------------------------------------------------- diff --git a/cpp/dolfinx/fem/utils.h b/cpp/dolfinx/fem/utils.h index 69ce8d1c3bb..2cde72b617a 100644 --- a/cpp/dolfinx/fem/utils.h +++ b/cpp/dolfinx/fem/utils.h @@ -15,6 +15,7 @@ #include "Function.h" #include "FunctionSpace.h" #include "sparsitybuild.h" +#include #include #include #include @@ -82,36 +83,32 @@ get_cell_facet_pairs(std::int32_t f, std::span cells, } // namespace impl -/// @brief Given an integral type and mesh tag data, compute the +/// @brief Given an integral type and a set of entities, compute the /// entities that should be integrated over. /// -/// This function returns as list `[(id, entities)]`, where `entities` -/// are the entities in `meshtags` with tag value `id`. For cell +/// This function returns a list `[(id, entities)]`. For cell /// integrals `entities` are the cell indices. For exterior facet /// integrals, `entities` is a list of `(cell_index, local_facet_index)` /// pairs. For interior facet integrals, `entities` is a list of -/// `(cell_index0, local_facet_index0, cell_index1, -/// local_facet_index1)`. +/// `(cell_index0, local_facet_index0, cell_index1, local_facet_index1)`. +/// `id` refers to the subdomain id used in the definition of the integration +/// measures of the variational form. /// /// @note Owned mesh entities only are returned. Ghost entities are not /// included. /// /// @param[in] integral_type Integral type /// @param[in] topology Mesh topology -/// @param[in] entities List of tagged mesh entities -/// @param[in] dim Topological dimension of tagged entities -/// @param[in] values Value associated with each entity -/// @return List of `(integral id, entities)` pairs -/// @pre The topological dimension of the integral entity type and the -/// topological dimension of mesh tag data must be equal. +/// @param[in] entities List of mesh entities +/// @param[in] dim Topological dimension of entities +/// @return List of integration entities /// @pre For facet integrals, the topology facet-to-cell and /// cell-to-facet connectivity must be computed before calling this /// function. -std::vector>> +std::vector compute_integration_domains(IntegralType integral_type, const mesh::Topology& topology, - std::span entities, int dim, - std::span values); + std::span entities, int dim); /// @brief Extract test (0) and trial (1) function spaces pairs for each /// bilinear form for a rectangular array of forms. @@ -239,11 +236,40 @@ la::SparsityPattern create_sparsity_pattern(const Form& a) return pattern; } -/// Create an ElementDofLayout from a ufcx_dofmap -ElementDofLayout create_element_dof_layout(const ufcx_dofmap& dofmap, - const mesh::CellType cell_type, +/// Create an ElementDofLayout from a FiniteElement +template +ElementDofLayout create_element_dof_layout(const fem::FiniteElement& element, const std::vector& parent_map - = {}); + = {}) +{ + // Create subdofmaps and compute offset + std::vector offsets(1, 0); + std::vector sub_doflayout; + int bs = element.block_size(); + for (int i = 0; i < element.num_sub_elements(); ++i) + { + // The ith sub-element. For mixed elements this is subelements()[i]. For + // blocked elements, the sub-element will always be the same, so we'll use + // sub_elements()[0] + std::shared_ptr> sub_e + = element.sub_elements()[bs > 1 ? 0 : i]; + + // In a mixed element DOFs are ordered element by element, so the offset to + // the next sub-element is sub_e->space_dimension(). Blocked elements use + // xxyyzz ordering, so the offset to the next sub-element is 1 + + std::vector parent_map_sub(sub_e->space_dimension(), offsets.back()); + for (std::size_t j = 0; j < parent_map_sub.size(); ++j) + parent_map_sub[j] += bs * j; + offsets.push_back(offsets.back() + (bs > 1 ? 1 : sub_e->space_dimension())); + sub_doflayout.push_back( + dolfinx::fem::create_element_dof_layout(*sub_e, parent_map_sub)); + } + + return ElementDofLayout(bs, element.entity_dofs(), + element.entity_closure_dofs(), parent_map, + sub_doflayout); +} /// @brief Create a dof map on mesh /// @param[in] comm MPI communicator @@ -331,20 +357,17 @@ Form create_form_factory( } // Check argument function spaces -#ifndef NDEBUG for (std::size_t i = 0; i < spaces.size(); ++i) { assert(spaces[i]->element()); - ufcx_finite_element* ufcx_element = ufcx_form.finite_elements[i]; - assert(ufcx_element); - if (std::string(ufcx_element->signature) - != spaces[i]->element()->signature()) + if (auto element_hash = ufcx_form.finite_element_hashes[i]; + element_hash != 0 + and element_hash != spaces[i]->element()->basix_element().hash()) { - throw std::runtime_error( - "Cannot create form. Wrong type of function space for argument."); + throw std::runtime_error("Cannot create form. Elements are different to " + "those used to compile the form."); } } -#endif // Extract mesh from FunctionSpace, and check they are the same if (!mesh and !spaces.empty()) @@ -398,9 +421,18 @@ Form create_form_factory( = ufcx_form.form_integrals[integral_offsets[cell] + i]; assert(integral); + // Build list of active coefficients + std::vector active_coeffs; + for (int j = 0; j < ufcx_form.num_coefficients; ++j) + { + if (integral->enabled_coefficients[j]) + active_coeffs.push_back(j); + } + kern_t k = nullptr; if constexpr (std::is_same_v) k = integral->tabulate_tensor_float32; +#ifndef DOLFINX_NO_STDC_COMPLEX_KERNELS else if constexpr (std::is_same_v>) { k = reinterpret_cast create_form_factory( const typename scalar_value_type::value_type*, const int*, const unsigned char*)>(integral->tabulate_tensor_complex64); } +#endif // DOLFINX_NO_STDC_COMPLEX_KERNELS else if constexpr (std::is_same_v) k = integral->tabulate_tensor_float64; +#ifndef DOLFINX_NO_STDC_COMPLEX_KERNELS else if constexpr (std::is_same_v>) { k = reinterpret_cast create_form_factory( const typename scalar_value_type::value_type*, const int*, const unsigned char*)>(integral->tabulate_tensor_complex128); } +#endif // DOLFINX_NO_STDC_COMPLEX_KERNELS + if (!k) { throw std::runtime_error( @@ -430,16 +466,16 @@ Form create_form_factory( assert(topology->index_map(tdim)); default_cells.resize(topology->index_map(tdim)->size_local(), 0); std::iota(default_cells.begin(), default_cells.end(), 0); - itg.first->second.emplace_back(id, k, default_cells); + itg.first->second.emplace_back(id, k, default_cells, active_coeffs); } else if (sd != subdomains.end()) { // NOTE: This requires that pairs are sorted - auto it = std::lower_bound(sd->second.begin(), sd->second.end(), id, - [](auto& pair, auto val) - { return pair.first < val; }); + auto it + = std::ranges::lower_bound(sd->second, id, std::less<>{}, + [](const auto& a) { return a.first; }); if (it != sd->second.end() and it->first == id) - itg.first->second.emplace_back(id, k, it->second); + itg.first->second.emplace_back(id, k, it->second, active_coeffs); } if (integral->needs_facet_permutations) @@ -461,10 +497,17 @@ Form create_form_factory( ufcx_integral* integral = ufcx_form.form_integrals[integral_offsets[exterior_facet] + i]; assert(integral); + std::vector active_coeffs; + for (int j = 0; j < ufcx_form.num_coefficients; ++j) + { + if (integral->enabled_coefficients[j]) + active_coeffs.push_back(j); + } kern_t k = nullptr; if constexpr (std::is_same_v) k = integral->tabulate_tensor_float32; +#ifndef DOLFINX_NO_STDC_COMPLEX_KERNELS else if constexpr (std::is_same_v>) { k = reinterpret_cast create_form_factory( const typename scalar_value_type::value_type*, const int*, const unsigned char*)>(integral->tabulate_tensor_complex64); } +#endif // DOLFINX_NO_STDC_COMPLEX_KERNELS else if constexpr (std::is_same_v) k = integral->tabulate_tensor_float64; +#ifndef DOLFINX_NO_STDC_COMPLEX_KERNELS else if constexpr (std::is_same_v>) { k = reinterpret_cast create_form_factory( const typename scalar_value_type::value_type*, const int*, const unsigned char*)>(integral->tabulate_tensor_complex128); } +#endif // DOLFINX_NO_STDC_COMPLEX_KERNELS assert(k); // Build list of entities to assembler over @@ -501,16 +547,17 @@ Form create_form_factory( default_facets_ext.insert(default_facets_ext.end(), pair.begin(), pair.end()); } - itg.first->second.emplace_back(id, k, default_facets_ext); + itg.first->second.emplace_back(id, k, default_facets_ext, + active_coeffs); } else if (sd != subdomains.end()) { // NOTE: This requires that pairs are sorted - auto it = std::lower_bound(sd->second.begin(), sd->second.end(), id, - [](auto& pair, auto val) - { return pair.first < val; }); + auto it + = std::ranges::lower_bound(sd->second, id, std::less<>{}, + [](const auto& a) { return a.first; }); if (it != sd->second.end() and it->first == id) - itg.first->second.emplace_back(id, k, it->second); + itg.first->second.emplace_back(id, k, it->second, active_coeffs); } if (integral->needs_facet_permutations) @@ -526,16 +573,38 @@ Form create_form_factory( num_integrals_type[interior_facet]); auto itg = integrals.insert({IntegralType::interior_facet, {}}); auto sd = subdomains.find(IntegralType::interior_facet); + + // Create indicator for interprocess facets + std::vector interprocess_marker; + if (num_integrals_type[interior_facet] > 0) + { + assert(topology->index_map(tdim - 1)); + const std::vector& interprocess_facets + = topology->interprocess_facets(); + std::int32_t num_facets = topology->index_map(tdim - 1)->size_local() + + topology->index_map(tdim - 1)->num_ghosts(); + interprocess_marker.resize(num_facets, 0); + std::ranges::for_each(interprocess_facets, [&interprocess_marker](auto f) + { interprocess_marker[f] = 1; }); + } + for (int i = 0; i < num_integrals_type[interior_facet]; ++i) { const int id = ids[i]; ufcx_integral* integral = ufcx_form.form_integrals[integral_offsets[interior_facet] + i]; assert(integral); + std::vector active_coeffs; + for (int j = 0; j < ufcx_form.num_coefficients; ++j) + { + if (integral->enabled_coefficients[j]) + active_coeffs.push_back(j); + } kern_t k = nullptr; if constexpr (std::is_same_v) k = integral->tabulate_tensor_float32; +#ifndef DOLFINX_NO_STDC_COMPLEX_KERNELS else if constexpr (std::is_same_v>) { k = reinterpret_cast create_form_factory( const typename scalar_value_type::value_type*, const int*, const unsigned char*)>(integral->tabulate_tensor_complex64); } +#endif // DOLFINX_NO_STDC_COMPLEX_KERNELS else if constexpr (std::is_same_v) k = integral->tabulate_tensor_float64; +#ifndef DOLFINX_NO_STDC_COMPLEX_KERNELS else if constexpr (std::is_same_v>) { k = reinterpret_cast create_form_factory( const typename scalar_value_type::value_type*, const int*, const unsigned char*)>(integral->tabulate_tensor_complex128); } +#endif // DOLFINX_NO_STDC_COMPLEX_KERNELS assert(k); // Build list of entities to assembler over @@ -574,16 +646,24 @@ Form create_form_factory( default_facets_int.insert(default_facets_int.end(), pairs.begin(), pairs.end()); } + else if (interprocess_marker[f]) + { + throw std::runtime_error( + "Cannot compute interior facet integral over interprocess " + "facet. Please use ghost mode shared facet when creating the " + "mesh"); + } } - itg.first->second.emplace_back(id, k, default_facets_int); + itg.first->second.emplace_back(id, k, default_facets_int, + active_coeffs); } else if (sd != subdomains.end()) { - auto it = std::lower_bound(sd->second.begin(), sd->second.end(), id, - [](auto& pair, auto val) - { return pair.first < val; }); + auto it + = std::ranges::lower_bound(sd->second, id, std::less<>{}, + [](const auto& a) { return a.first; }); if (it != sd->second.end() and it->first == id) - itg.first->second.emplace_back(id, k, it->second); + itg.first->second.emplace_back(id, k, it->second, active_coeffs); } if (integral->needs_facet_permutations) @@ -614,6 +694,8 @@ Form create_form_factory( /// @param[in] constants Spatial constants in the form (by name). /// @param[in] subdomains Subdomain markers. /// @pre Each value in `subdomains` must be sorted by domain id. +/// @param[in] entity_maps The entity maps for the form. Empty for +/// single domain problems. /// @param[in] mesh Mesh of the domain. This is required if the form has /// no arguments, e.g. a functional. /// @return A Form @@ -628,6 +710,8 @@ Form create_form( IntegralType, std::vector>>>& subdomains, + const std::map>, + std::span>& entity_maps, std::shared_ptr> mesh = nullptr) { // Place coefficients in appropriate order @@ -644,6 +728,7 @@ Form create_form( } // Place constants in appropriate order + std::vector>> const_map; for (const std::string& name : get_constant_names(ufcx_form)) { @@ -654,7 +739,7 @@ Form create_form( } return create_form_factory(ufcx_form, spaces, coeff_map, const_map, - subdomains, {}, mesh); + subdomains, entity_maps, mesh); } /// @brief Create a Form using a factory function that returns a pointer @@ -669,6 +754,8 @@ Form create_form( /// @param[in] constants Spatial constants in the form (by name), /// @param[in] subdomains Subdomain markers. /// @pre Each value in `subdomains` must be sorted by domain id. +/// @param[in] entity_maps The entity maps for the form. Empty for +/// single domain problems. /// @param[in] mesh Mesh of the domain. This is required if the form has /// no arguments, e.g. a functional. /// @return A Form @@ -683,11 +770,13 @@ Form create_form( IntegralType, std::vector>>>& subdomains, + const std::map>, + std::span>& entity_maps, std::shared_ptr> mesh = nullptr) { ufcx_form* form = fptr(); Form L = create_form(*form, spaces, coefficients, constants, - subdomains, mesh); + subdomains, entity_maps, mesh); std::free(form); return L; } @@ -717,6 +806,10 @@ FunctionSpace create_functionspace( "Cannot specify value shape for non-scalar base element."); } + if (mesh::cell_type_from_basix_type(e.cell_type()) + != mesh->topology()->cell_type()) + throw std::runtime_error("Cell type of element and mesh must match."); + std::size_t bs = value_shape.empty() ? 1 : std::accumulate(value_shape.begin(), value_shape.end(), @@ -760,68 +853,6 @@ FunctionSpace create_functionspace( return FunctionSpace(mesh, _e, dofmap, _value_shape); } -/// @brief Create a FunctionSpace from UFC data. -/// @param[in] fptr Pointer to a ufcx_function_space_create function. -/// @param[in] function_name Name of a function whose function space is to -/// create. Function name is the name of the Python variable for -/// ufl.Coefficient, ufl.TrialFunction or ufl.TestFunction as defined in -/// the UFL file. -/// @param[in] mesh Mesh -/// @param[in] reorder_fn Graph reordering function to call on the -/// dofmap. If `nullptr`, the default re-ordering is used. -/// @return The created function space. -template -FunctionSpace create_functionspace( - ufcx_function_space* (*fptr)(const char*), const std::string& function_name, - std::shared_ptr> mesh, - std::function(const graph::AdjacencyList&)> - reorder_fn - = nullptr) -{ - ufcx_function_space* space = fptr(function_name.c_str()); - if (!space) - { - throw std::runtime_error( - "Could not create UFC function space with function name " - + function_name); - } - - ufcx_finite_element* ufcx_element = space->finite_element; - assert(ufcx_element); - std::vector value_shape(space->value_shape, - space->value_shape + space->value_rank); - - const auto& geometry = mesh->geometry(); - auto& cmap = geometry.cmap(); - if (space->geometry_degree != cmap.degree() - or static_cast(space->geometry_basix_cell) - != mesh::cell_type_to_basix_type(cmap.cell_shape()) - or static_cast( - space->geometry_basix_variant) - != cmap.variant()) - { - throw std::runtime_error("UFL mesh and CoordinateElement do not match."); - } - - auto element = std::make_shared>(*ufcx_element); - assert(element); - ufcx_dofmap* ufcx_map = space->dofmap; - assert(ufcx_map); - const auto topology = mesh->topology(); - assert(topology); - ElementDofLayout layout - = create_element_dof_layout(*ufcx_map, topology->cell_type()); - - std::function, std::uint32_t)> permute_inv; - if (element->needs_dof_permutations()) - permute_inv = element->dof_permutation_fn(true, true); - return FunctionSpace( - mesh, element, - std::make_shared(create_dofmap(mesh->comm(), layout, *topology, - permute_inv, reorder_fn)), - value_shape); -} - /// @private namespace impl { @@ -843,7 +874,7 @@ get_cell_orientation_info(const Function& coefficient) } /// Pack a single coefficient for a single cell -template +template void pack(std::span coeffs, std::int32_t cell, int bs, std::span v, std::span cell_info, const DofMap& dofmap, auto transform) @@ -860,6 +891,7 @@ void pack(std::span coeffs, std::int32_t cell, int bs, std::span v, } else { + assert(_bs == bs); const int pos_c = _bs * i; const int pos_v = _bs * dofs[i]; for (int k = 0; k < _bs; ++k) @@ -917,36 +949,47 @@ void pack_coefficient_entity(std::span c, int cstride, for (std::size_t e = 0; e < entities.size(); e += estride) { auto entity = entities.subspan(e, estride); - std::int32_t cell = fetch_cells(entity); - auto cell_coeff = c.subspan((e / estride) * cstride + offset, space_dim); - pack(cell_coeff, cell, bs, v, cell_info, dofmap, transformation); + if (std::int32_t cell = fetch_cells(entity); cell >= 0) + { + auto cell_coeff + = c.subspan((e / estride) * cstride + offset, space_dim); + pack<1>(cell_coeff, cell, bs, v, cell_info, dofmap, transformation); + } } break; case 2: for (std::size_t e = 0; e < entities.size(); e += estride) { auto entity = entities.subspan(e, estride); - std::int32_t cell = fetch_cells(entity); - auto cell_coeff = c.subspan((e / estride) * cstride + offset, space_dim); - pack(cell_coeff, cell, bs, v, cell_info, dofmap, transformation); + if (std::int32_t cell = fetch_cells(entity); cell >= 0) + { + auto cell_coeff + = c.subspan((e / estride) * cstride + offset, space_dim); + pack<2>(cell_coeff, cell, bs, v, cell_info, dofmap, transformation); + } } break; case 3: for (std::size_t e = 0; e < entities.size(); e += estride) { auto entity = entities.subspan(e, estride); - std::int32_t cell = fetch_cells(entity); - auto cell_coeff = c.subspan(e / estride * cstride + offset, space_dim); - pack(cell_coeff, cell, bs, v, cell_info, dofmap, transformation); + if (std::int32_t cell = fetch_cells(entity); cell >= 0) + { + auto cell_coeff = c.subspan(e / estride * cstride + offset, space_dim); + pack<3>(cell_coeff, cell, bs, v, cell_info, dofmap, transformation); + } } break; default: for (std::size_t e = 0; e < entities.size(); e += estride) { auto entity = entities.subspan(e, estride); - std::int32_t cell = fetch_cells(entity); - auto cell_coeff = c.subspan((e / estride) * cstride + offset, space_dim); - pack(cell_coeff, cell, bs, v, cell_info, dofmap, transformation); + if (std::int32_t cell = fetch_cells(entity); cell >= 0) + { + auto cell_coeff + = c.subspan((e / estride) * cstride + offset, space_dim); + pack<-1>(cell_coeff, cell, bs, v, cell_info, dofmap, transformation); + } } break; } @@ -1013,7 +1056,7 @@ allocate_coefficient_storage(const Form& form) /// @param[in] form The Form /// @param[in] integral_type Type of integral /// @param[in] id The id of the integration domain -/// @param[in] c The coefficient array +/// @param[in,out] c The coefficient array /// @param[in] cstride The coefficient stride template void pack_coefficients(const Form& form, IntegralType integral_type, @@ -1024,17 +1067,43 @@ void pack_coefficients(const Form& form, IntegralType integral_type, = form.coefficients(); const std::vector offsets = form.coefficient_offsets(); + // Indicator for packing coefficients + std::vector active_coefficient(coefficients.size(), 0); if (!coefficients.empty()) { switch (integral_type) { case IntegralType::cell: { + // Get indicator for all coefficients that are active in cell + // integrals + for (std::size_t i = 0; i < form.num_integrals(IntegralType::cell); ++i) + { + for (auto idx : form.active_coeffs(IntegralType::cell, i)) + active_coefficient[idx] = 1; + } + // Iterate over coefficients for (std::size_t coeff = 0; coeff < coefficients.size(); ++coeff) { + if (!active_coefficient[coeff]) + continue; + + // Get coefficient mesh auto mesh = coefficients[coeff]->function_space()->mesh(); assert(mesh); + + // Other integrals in the form might have coefficients defined over + // entities of codim > 0, which don't make sense for cell integrals, so + // don't pack them. + if (const int codim + = form.mesh()->topology()->dim() - mesh->topology()->dim(); + codim > 0) + { + throw std::runtime_error("Should not be packing coefficients with " + "codim>0 in a cell integral"); + } + std::vector cells = form.domain(IntegralType::cell, id, *mesh); std::span cell_info @@ -1047,9 +1116,21 @@ void pack_coefficients(const Form& form, IntegralType integral_type, } case IntegralType::exterior_facet: { + // Get indicator for all coefficients that are active in exterior + // facet integrals + for (std::size_t i = 0; + i < form.num_integrals(IntegralType::exterior_facet); ++i) + { + for (auto idx : form.active_coeffs(IntegralType::exterior_facet, i)) + active_coefficient[idx] = 1; + } + // Iterate over coefficients for (std::size_t coeff = 0; coeff < coefficients.size(); ++coeff) { + if (!active_coefficient[coeff]) + continue; + auto mesh = coefficients[coeff]->function_space()->mesh(); std::vector facets = form.domain(IntegralType::exterior_facet, id, *mesh); @@ -1063,12 +1144,25 @@ void pack_coefficients(const Form& form, IntegralType integral_type, } case IntegralType::interior_facet: { + // Get indicator for all coefficients that are active in interior + // facet integrals + for (std::size_t i = 0; + i < form.num_integrals(IntegralType::interior_facet); ++i) + { + for (auto idx : form.active_coeffs(IntegralType::interior_facet, i)) + active_coefficient[idx] = 1; + } + // Iterate over coefficients for (std::size_t coeff = 0; coeff < coefficients.size(); ++coeff) { + if (!active_coefficient[coeff]) + continue; + auto mesh = coefficients[coeff]->function_space()->mesh(); std::vector facets = form.domain(IntegralType::interior_facet, id, *mesh); + std::span cell_info = impl::get_cell_orientation_info(*coefficients[coeff]); @@ -1076,6 +1170,7 @@ void pack_coefficients(const Form& form, IntegralType integral_type, impl::pack_coefficient_entity( c, 2 * cstride, *coefficients[coeff], cell_info, facets, 4, [](auto entity) { return entity[0]; }, 2 * offsets[coeff]); + // Pack coefficient ['-'] impl::pack_coefficient_entity( c, 2 * cstride, *coefficients[coeff], cell_info, facets, 4, @@ -1116,6 +1211,7 @@ Expression create_expression( tabulate_tensor = nullptr; if constexpr (std::is_same_v) tabulate_tensor = e.tabulate_tensor_float32; +#ifndef DOLFINX_NO_STDC_COMPLEX_KERNELS else if constexpr (std::is_same_v>) { tabulate_tensor = reinterpret_cast create_expression( const typename scalar_value_type::value_type*, const int*, const unsigned char*)>(e.tabulate_tensor_complex64); } +#endif // DOLFINX_NO_STDC_COMPLEX_KERNELS else if constexpr (std::is_same_v) tabulate_tensor = e.tabulate_tensor_float64; +#ifndef DOLFINX_NO_STDC_COMPLEX_KERNELS else if constexpr (std::is_same_v>) { tabulate_tensor = reinterpret_cast create_expression( const typename scalar_value_type::value_type*, const int*, const unsigned char*)>(e.tabulate_tensor_complex128); } +#endif // DOLFINX_NO_STDC_COMPLEX_KERNELS else throw std::runtime_error("Type not supported."); @@ -1263,8 +1362,7 @@ std::vector pack_constants(const U& u) for (auto& constant : constants) { const std::vector& value = constant->value; - std::copy(value.begin(), value.end(), - std::next(constant_values.begin(), offset)); + std::ranges::copy(value, std::next(constant_values.begin(), offset)); offset += value.size(); } diff --git a/cpp/dolfinx/geometry/BoundingBoxTree.h b/cpp/dolfinx/geometry/BoundingBoxTree.h index 26b219539eb..64ede45a057 100644 --- a/cpp/dolfinx/geometry/BoundingBoxTree.h +++ b/cpp/dolfinx/geometry/BoundingBoxTree.h @@ -164,16 +164,15 @@ _build_from_point(std::span, std::int32_t>> points, } // Compute bounding box of all points - auto minmax = std::minmax_element(points.begin(), points.end()); - std::array b0 = minmax.first->first; - std::array b1 = minmax.second->first; + auto [min, max] = std::ranges::minmax_element(points); + std::array b0 = min->first; + std::array b1 = max->first; // Sort bounding boxes along longest axis std::array b_diff; - std::transform(b1.begin(), b1.end(), b0.begin(), b_diff.begin(), - std::minus()); - const std::size_t axis = std::distance( - b_diff.begin(), std::max_element(b_diff.begin(), b_diff.end())); + std::ranges::transform(b1, b0, b_diff.begin(), std::minus()); + const std::size_t axis + = std::distance(b_diff.begin(), std::ranges::max_element(b_diff)); auto middle = std::next(points.begin(), points.size() / 2); std::nth_element(points.begin(), middle, points.end(), @@ -257,8 +256,8 @@ class BoundingBoxTree std::tie(_bboxes, _bbox_coordinates) = impl_bb::build_from_leaf(leaf_bboxes); - LOG(INFO) << "Computed bounding box tree with " << num_bboxes() - << " nodes for " << entities.size() << " entities."; + spdlog::info("Computed bounding box tree with {} nodes for {} entities", + num_bboxes(), entities.size()); } /// Constructor @@ -286,8 +285,8 @@ class BoundingBoxTree impl_bb::_build_from_point(std::span(points), _bboxes, _bbox_coordinates); } - LOG(INFO) << "Computed bounding box tree with " << num_bboxes() - << " nodes for " << points.size() << " points."; + spdlog::info("Computed bounding box tree with {} nodes for {} points.", + num_bboxes(), points.size()); } /// Move constructor @@ -351,8 +350,8 @@ class BoundingBoxTree BoundingBoxTree global_tree(std::move(global_bboxes), std::move(global_coords)); - LOG(INFO) << "Computed global bounding box tree with " - << global_tree.num_bboxes() << " boxes."; + spdlog::info("Computed global bounding box tree with {} boxes.", + global_tree.num_bboxes()); return global_tree; } @@ -405,7 +404,8 @@ class BoundingBoxTree for (std::size_t k = 0; k < 3; ++k) s << _bbox_coordinates[6 * i + j * 3 + k] << " "; if (j == 0) - s << "]->" << "["; + s << "]->" + << "["; } s << "]\n"; diff --git a/cpp/dolfinx/geometry/gjk.h b/cpp/dolfinx/geometry/gjk.h index a7f1b0cc01e..793c406b500 100644 --- a/cpp/dolfinx/geometry/gjk.h +++ b/cpp/dolfinx/geometry/gjk.h @@ -6,6 +6,7 @@ #pragma once +#include #include #include #include @@ -148,13 +149,13 @@ nearest_simplex(std::span s) qnorm += v[k] * v[k]; if (qnorm < qmin) { - std::copy(v.begin(), v.end(), vmin.begin()); + std::ranges::copy(v, vmin.begin()); qmin = qnorm; smin.resize(2 * 3); std::span smin0(smin.data(), 3); - std::copy(s0.begin(), s0.end(), smin0.begin()); + std::ranges::copy(s0, smin0.begin()); std::span smin1(smin.data() + 3, 3); - std::copy(s1.begin(), s1.end(), smin1.begin()); + std::ranges::copy(s1, smin1.begin()); } } } diff --git a/cpp/dolfinx/geometry/utils.h b/cpp/dolfinx/geometry/utils.h index 406b67d3fbc..0f643e62643 100644 --- a/cpp/dolfinx/geometry/utils.h +++ b/cpp/dolfinx/geometry/utils.h @@ -8,6 +8,7 @@ #include "BoundingBoxTree.h" #include "gjk.h" +#include #include #include #include @@ -21,6 +22,21 @@ namespace dolfinx::geometry { +/// @brief Information on the ownership of points distributed across +/// processes. +/// @tparam T Mesh geometry floating type. +template +struct PointOwnershipData +{ + std::vector src_owner; ///< Ranks owning each point sent into ownership + ///< determination for current process + std::vector + dest_owners; ///< Ranks that sent `dest_points` to current process + std::vector dest_points; ///< Points that are owned by current process + std::vector + dest_cells; ///< Cell indices (local to process) where each entry of + ///< `dest_points` is located +}; /// @brief Compute the shortest vector from a mesh entity to a point. /// @@ -410,8 +426,8 @@ template BoundingBoxTree create_midpoint_tree(const mesh::Mesh& mesh, int tdim, std::span entities) { - LOG(INFO) << "Building point search tree to accelerate distance queries for " - "a given topological dimension and subset of entities."; + spdlog::info("Building point search tree to accelerate distance queries for " + "a given topological dimension and subset of entities."); const std::vector midpoints = mesh::compute_midpoints(mesh, tdim, entities); @@ -491,15 +507,15 @@ compute_collisions(const BoundingBoxTree& tree, std::span points) /// -1 is returned. /// /// @note `cells` can for instance be found by using -/// `dolfinx::geometry::compute_collisions` between a bounding box tree for the +/// geometry::compute_collisions between a bounding box tree for the /// cells of the mesh and the point. /// -/// @param[in] mesh The mesh -/// @param[in] cells The candidate cells -/// @param[in] point The point (`shape=(3,)`) +/// @param[in] mesh The mesh. +/// @param[in] cells Candidate cells. +/// @param[in] point The point (`shape=(3,)`). /// @param[in] tol Tolerance for accepting a collision (in the squared -/// distance) -/// @return The local cell index, -1 if not found +/// distance). +/// @return Local cell index, -1 if not found. template std::int32_t compute_first_colliding_cell(const mesh::Mesh& mesh, std::span cells, @@ -600,8 +616,8 @@ compute_closest_entity(const BoundingBoxTree& tree, /// details. /// /// @note `candidate_cells` can for instance be found by using -/// `dolfinx::geometry::compute_collisions` between a bounding box tree and the -/// set of points. +/// geometry::compute_collisions between a bounding box tree and the set +/// of points. /// /// @param[in] mesh The mesh /// @param[in] candidate_cells List of candidate colliding cells for the @@ -656,7 +672,6 @@ graph::AdjacencyList compute_colliding_cells( /// points. dest_owner is a list of ranks corresponding to dest_points, /// the points that this process owns. dest_cells contains the /// corresponding cell for each entry in dest_points. - /// /// @note `dest_owner` is sorted /// @note Returns -1 if no colliding process is found @@ -668,10 +683,9 @@ graph::AdjacencyList compute_colliding_cells( /// one has to determine the closest cell among all processes with an /// intersecting bounding box, which is an expensive operation to perform. template -std::tuple, std::vector, std::vector, - std::vector> -determine_point_ownership(const mesh::Mesh& mesh, std::span points, - T padding) +PointOwnershipData determine_point_ownership(const mesh::Mesh& mesh, + std::span points, + T padding) { MPI_Comm comm = mesh.comm(); @@ -692,12 +706,13 @@ determine_point_ownership(const mesh::Mesh& mesh, std::span points, // Get unique list of outgoing ranks std::vector out_ranks = collisions.array(); - std::sort(out_ranks.begin(), out_ranks.end()); - out_ranks.erase(std::unique(out_ranks.begin(), out_ranks.end()), - out_ranks.end()); + std::ranges::sort(out_ranks); + auto [unique_end, range_end] = std::ranges::unique(out_ranks); + out_ranks.erase(unique_end, range_end); + // Compute incoming edges (source processes) std::vector in_ranks = dolfinx::MPI::compute_graph_edges_nbx(comm, out_ranks); - std::sort(in_ranks.begin(), in_ranks.end()); + std::ranges::sort(in_ranks); // Create neighborhood communicator in forward direction MPI_Comm forward_comm; @@ -800,10 +815,7 @@ determine_point_ownership(const mesh::Mesh& mesh, std::span points, // but divide by three { auto rescale = [](auto& x) - { - std::transform(x.cbegin(), x.cend(), x.begin(), - [](auto e) { return (e / 3); }); - }; + { std::ranges::transform(x, x.begin(), [](auto e) { return (e / 3); }); }; rescale(recv_sizes); rescale(recv_offsets); rescale(send_sizes); @@ -820,7 +832,7 @@ determine_point_ownership(const mesh::Mesh& mesh, std::span points, recv_sizes.data(), recv_offsets.data(), MPI_INT32_T, reverse_comm); - std::vector point_owners(points.size() / 3, -1); + std::vector point_owners(points.size() / 3, -1); for (std::size_t i = 0; i < unpack_map.size(); i++) { const std::int32_t pos = unpack_map[i]; @@ -921,7 +933,7 @@ determine_point_ownership(const mesh::Mesh& mesh, std::span points, // Pack ownership data std::vector send_owners(send_offsets.back()); - std::fill(counter.begin(), counter.end(), 0); + std::ranges::fill(counter, 0); for (std::size_t i = 0; i < points.size() / 3; ++i) { for (auto p : collisions.links(i)) @@ -940,7 +952,7 @@ determine_point_ownership(const mesh::Mesh& mesh, std::span points, forward_comm); // Unpack dest ranks if point owner is this rank - std::vector owned_recv_ranks; + std::vector owned_recv_ranks; owned_recv_ranks.reserve(recv_offsets.back()); std::vector owned_recv_points; std::vector owned_recv_cells; @@ -961,9 +973,10 @@ determine_point_ownership(const mesh::Mesh& mesh, std::span points, MPI_Comm_free(&forward_comm); MPI_Comm_free(&reverse_comm); - - return std::make_tuple(point_owners, owned_recv_ranks, owned_recv_points, - owned_recv_cells); + return PointOwnershipData{.src_owner = std::move(point_owners), + .dest_owners = std::move(owned_recv_ranks), + .dest_points = std::move(owned_recv_points), + .dest_cells = std::move(owned_recv_cells)}; } } // namespace dolfinx::geometry diff --git a/cpp/dolfinx/graph/AdjacencyList.h b/cpp/dolfinx/graph/AdjacencyList.h index 0053404c1d8..b66a1450ea5 100644 --- a/cpp/dolfinx/graph/AdjacencyList.h +++ b/cpp/dolfinx/graph/AdjacencyList.h @@ -17,7 +17,6 @@ namespace dolfinx::graph { - /// This class provides a static adjacency list data structure. It is /// commonly used to store directed graphs. For each node in the /// contiguous list of nodes [0, 1, 2, ..., n) it stores the connected diff --git a/cpp/dolfinx/graph/ordering.cpp b/cpp/dolfinx/graph/ordering.cpp index 1988dafc29b..df41220546b 100644 --- a/cpp/dolfinx/graph/ordering.cpp +++ b/cpp/dolfinx/graph/ordering.cpp @@ -5,11 +5,11 @@ // SPDX-License-Identifier: LGPL-3.0-or-later #include "ordering.h" +#include "AdjacencyList.h" #include #include #include #include -#include #include #include @@ -66,9 +66,9 @@ residual_graph_components(const graph::AdjacencyList& graph, it = std::find(it, labelled.end(), false); } - std::sort(rgc.begin(), rgc.end(), - [](const std::vector& a, const std::vector& b) - { return (a.size() > b.size()); }); + std::ranges::sort(rgc, + [](const std::vector& a, const std::vector& b) + { return (a.size() > b.size()); }); return rgc; } @@ -201,7 +201,7 @@ gps_reorder_unlabelled(const graph::AdjacencyList& graph, assert(lv.num_nodes() == lu.num_nodes()); const int k = lv.num_nodes(); - LOG(INFO) << "GPS pseudo-diameter:(" << k << ") " << u << "-" << v; + spdlog::info("GPS pseudo-diameter:({}) {}-{}", k, u, v); // ALGORITHM II. Minimizing level width. @@ -241,24 +241,24 @@ gps_reorder_unlabelled(const graph::AdjacencyList& graph, std::vector wn(k), wh(k), wl(k); for (const std::vector& r : rgc) { - std::transform(ls.begin(), ls.end(), wn.begin(), - [](const std::vector& vec) { return vec.size(); }); - std::copy(wn.begin(), wn.end(), wh.begin()); - std::copy(wn.begin(), wn.end(), wl.begin()); + std::ranges::transform(ls, wn.begin(), [](const std::vector& vec) + { return vec.size(); }); + std::ranges::copy(wn, wh.begin()); + std::ranges::copy(wn, wl.begin()); for (int w : r) { ++wh[lvp[w][0]]; ++wl[lvp[w][1]]; } // Zero any entries which did not increase - std::transform(wh.begin(), wh.end(), wn.begin(), wh.begin(), - [](int vh, int vn) { return (vh > vn) ? vh : 0; }); - std::transform(wl.begin(), wl.end(), wn.begin(), wl.begin(), - [](int vl, int vn) { return (vl > vn) ? vl : 0; }); + std::ranges::transform(wh, wn, wh.begin(), + [](int vh, int vn) { return (vh > vn) ? vh : 0; }); + std::ranges::transform(wl, wn, wl.begin(), + [](int vl, int vn) { return (vl > vn) ? vl : 0; }); // Find maximum of those that did increase - int h0 = *std::max_element(wh.begin(), wh.end()); - int l0 = *std::max_element(wl.begin(), wl.end()); + int h0 = *std::ranges::max_element(wh); + int l0 = *std::ranges::max_element(wl); // Choose which side to use int side = h0 < l0 ? 0 : 1; @@ -317,13 +317,13 @@ gps_reorder_unlabelled(const graph::AdjacencyList& graph, } // Add nodes to rv in order of increasing degree - std::sort(nbr.begin(), nbr.end(), cmp_degree); + std::ranges::sort(nbr, cmp_degree); rv.insert(rv.end(), nbr.begin(), nbr.end()); for (int w : nbr) labelled[w] = true; // Save nodes for next level to a separate list, rv_next - std::sort(nbr_next.begin(), nbr_next.end(), cmp_degree); + std::ranges::sort(nbr_next, cmp_degree); rv_next.insert(rv_next.end(), nbr_next.begin(), nbr_next.end()); for (int w : nbr_next) labelled[w] = true; @@ -341,7 +341,7 @@ gps_reorder_unlabelled(const graph::AdjacencyList& graph, if (nrem.size() == 0) break; - std::sort(nrem.begin(), nrem.end(), cmp_degree); + std::ranges::sort(nrem, cmp_degree); rv.push_back(nrem.front()); labelled[nrem.front()] = true; } diff --git a/cpp/dolfinx/graph/partition.cpp b/cpp/dolfinx/graph/partition.cpp index 4379f7de04b..d6080643003 100644 --- a/cpp/dolfinx/graph/partition.cpp +++ b/cpp/dolfinx/graph/partition.cpp @@ -5,12 +5,12 @@ // SPDX-License-Identifier: LGPL-3.0-or-later #include "partition.h" +#include "AdjacencyList.h" #include "partitioners.h" #include #include #include #include -#include #include #include @@ -70,10 +70,11 @@ graph::build::distribute(MPI_Comm comm, for (std::int32_t i = 0; i < destinations.num_nodes(); ++i) { auto di = destinations.links(i); - for (auto d : di) - dest_to_index.push_back({d, i, di[0]}); + std::ranges::transform(di, std::back_inserter(dest_to_index), + [i, d0 = di.front()](auto d) -> std::array + { return {d, i, d0}; }); } - std::sort(dest_to_index.begin(), dest_to_index.end()); + std::ranges::sort(dest_to_index); // Build list of unique dest ranks and count number of rows to send to // each dest (by neighbourhood rank) @@ -84,10 +85,10 @@ graph::build::distribute(MPI_Comm comm, while (it != dest_to_index.end()) { // Store global rank and find iterator to next global rank - dest.push_back((*it)[0]); + dest.push_back(it->front()); auto it1 = std::find_if(it, dest_to_index.end(), - [r = dest.back()](auto& idx) { return idx[0] != r; }); + [r = dest.back()](auto idx) { return idx[0] != r; }); // Store number of items for current rank num_items_per_dest.push_back(std::distance(it, it1)); @@ -100,7 +101,7 @@ graph::build::distribute(MPI_Comm comm, // Determine source ranks. Sort ranks to make distribution // deterministic. std::vector src = dolfinx::MPI::compute_graph_edges_nbx(comm, dest); - std::sort(src.begin(), src.end()); + std::ranges::sort(src); // Create neighbourhood communicator MPI_Comm neigh_comm; @@ -133,7 +134,7 @@ graph::build::distribute(MPI_Comm comm, std::span b(send_buffer.data() + i * buffer_shape1, buffer_shape1); auto row = list.links(pos); - std::copy(row.begin(), row.end(), b.begin()); + std::ranges::copy(row, b.begin()); auto info = b.last(3); info[0] = row.size(); // Number of edges for node @@ -161,20 +162,20 @@ graph::build::distribute(MPI_Comm comm, MPI_Comm_free(&neigh_comm); // Unpack receive buffer - std::vector src_ranks0, src_ranks1, ghost_index_owner; - src_ranks0.reserve(recv_disp.back()); + std::vector src_ranks, src_ranks1, ghost_index_owner; + src_ranks.reserve(recv_disp.back()); src_ranks1.reserve(recv_disp.back()); - std::vector data0, data1; - data0.reserve((buffer_shape1 - 3) * recv_disp.back()); + std::vector data, data1; + data.reserve((buffer_shape1 - 3) * recv_disp.back()); data1.reserve((buffer_shape1 - 3) * recv_disp.back()); - std::vector offsets0{0}, offsets1{0}; - offsets0.reserve(recv_disp.back()); + std::vector offsets{0}, offsets1{0}; + offsets.reserve(recv_disp.back()); offsets1.reserve(recv_disp.back()); - std::vector global_indices0, global_indices1; - global_indices0.reserve(recv_disp.back()); + std::vector global_indices, global_indices1; + global_indices.reserve(recv_disp.back()); global_indices1.reserve(recv_disp.back()); for (std::size_t p = 0; p < recv_disp.size() - 1; ++p) { @@ -184,15 +185,14 @@ graph::build::distribute(MPI_Comm comm, std::span row(recv_buffer.data() + i * buffer_shape1, buffer_shape1); auto info = row.last(3); std::size_t num_edges = info[0]; - int owner = info[1]; std::int64_t orig_global_index = info[2]; auto edges = row.first(num_edges); - if (owner == rank) + if (int owner = info[1]; owner == rank) { - data0.insert(data0.end(), edges.begin(), edges.end()); - offsets0.push_back(offsets0.back() + num_edges); - src_ranks0.push_back(src_rank); - global_indices0.push_back(orig_global_index); + data.insert(data.end(), edges.begin(), edges.end()); + offsets.push_back(offsets.back() + num_edges); + src_ranks.push_back(src_rank); + global_indices.push_back(orig_global_index); } else { @@ -200,28 +200,194 @@ graph::build::distribute(MPI_Comm comm, offsets1.push_back(offsets1.back() + info[0]); src_ranks1.push_back(src_rank); global_indices1.push_back(orig_global_index); - ghost_index_owner.push_back(info[1]); } } } - std::transform(offsets1.begin(), offsets1.end(), offsets1.begin(), - [off = offsets0.back()](auto x) { return x + off; }); - data0.insert(data0.end(), data1.begin(), data1.end()); - offsets0.insert(offsets0.end(), std::next(offsets1.begin()), offsets1.end()); - src_ranks0.insert(src_ranks0.end(), src_ranks1.begin(), src_ranks1.end()); - global_indices0.insert(global_indices0.end(), global_indices1.begin(), - global_indices1.end()); - - data0.shrink_to_fit(); - offsets0.shrink_to_fit(); - src_ranks0.shrink_to_fit(); - global_indices0.shrink_to_fit(); + std::ranges::transform(offsets1, offsets1.begin(), + [off = offsets.back()](auto x) { return x + off; }); + data.insert(data.end(), data1.begin(), data1.end()); + offsets.insert(offsets.end(), std::next(offsets1.begin()), offsets1.end()); + src_ranks.insert(src_ranks.end(), src_ranks1.begin(), src_ranks1.end()); + global_indices.insert(global_indices.end(), global_indices1.begin(), + global_indices1.end()); + + data.shrink_to_fit(); + offsets.shrink_to_fit(); + src_ranks.shrink_to_fit(); + global_indices.shrink_to_fit(); + ghost_index_owner.shrink_to_fit(); + + return { + graph::AdjacencyList(std::move(data), std::move(offsets)), + std::move(src_ranks), std::move(global_indices), + std::move(ghost_index_owner)}; +} +//----------------------------------------------------------------------------- +std::tuple, std::vector, + std::vector, std::vector> +graph::build::distribute(MPI_Comm comm, std::span list, + std::array shape, + const graph::AdjacencyList& destinations) +{ + common::Timer timer( + "Distribute fixed-degree adjacency list to destination ranks"); + + assert(list.size() == shape[0] * shape[1]); + assert(destinations.num_nodes() == (std::int32_t)shape[0]); + int rank = dolfinx::MPI::rank(comm); + std::int64_t num_owned = destinations.num_nodes(); + + // Get global offset for converting local index to global index for + // nodes in 'list' + std::int64_t offset_global = 0; + MPI_Exscan(&num_owned, &offset_global, 1, MPI_INT64_T, MPI_SUM, comm); + + // Buffer size (max number of edges + 2 for owning rank, + // and node global index) + const std::size_t buffer_shape1 = shape[1] + 2; + + // Build (dest, index, owning rank) list and sort + std::vector> dest_to_index; + dest_to_index.reserve(destinations.array().size()); + for (std::int32_t i = 0; i < destinations.num_nodes(); ++i) + { + auto di = destinations.links(i); + std::ranges::transform(di, std::back_inserter(dest_to_index), + [i, d0 = di.front()](auto d) -> std::array + { return {d, i, d0}; }); + } + std::ranges::sort(dest_to_index); + + // Build list of unique dest ranks and count number of rows to send to + // each dest (by neighbourhood rank) + std::vector dest; + std::vector num_items_per_dest; + { + auto it = dest_to_index.begin(); + while (it != dest_to_index.end()) + { + // Store global rank and find iterator to next global rank + dest.push_back(it->front()); + auto it1 + = std::find_if(it, dest_to_index.end(), + [r = dest.back()](auto& idx) { return idx[0] != r; }); + + // Store number of items for current rank + num_items_per_dest.push_back(std::distance(it, it1)); + + // Advance iterator + it = it1; + } + } + + // Determine source ranks. Sort ranks to make distribution + // deterministic. + std::vector src = dolfinx::MPI::compute_graph_edges_nbx(comm, dest); + std::ranges::sort(src); + + // Create neighbourhood communicator + MPI_Comm neigh_comm; + MPI_Dist_graph_create_adjacent(comm, src.size(), src.data(), MPI_UNWEIGHTED, + dest.size(), dest.data(), MPI_UNWEIGHTED, + MPI_INFO_NULL, false, &neigh_comm); + + // Send number of nodes to receivers + std::vector num_items_recv(src.size()); + num_items_per_dest.reserve(1); + num_items_recv.reserve(1); + MPI_Request request_size; + MPI_Ineighbor_alltoall(num_items_per_dest.data(), 1, MPI_INT, + num_items_recv.data(), 1, MPI_INT, neigh_comm, + &request_size); + + // Compute send displacements + std::vector send_disp(num_items_per_dest.size() + 1, 0); + std::partial_sum(num_items_per_dest.begin(), num_items_per_dest.end(), + std::next(send_disp.begin())); + + // Pack send buffer + std::vector send_buffer(buffer_shape1 * send_disp.back(), -1); + { + assert(send_disp.back() == (std::int32_t)dest_to_index.size()); + for (std::size_t i = 0; i < dest_to_index.size(); ++i) + { + std::array dest_data = dest_to_index[i]; + const std::size_t pos = dest_data[1]; + + std::span b(send_buffer.data() + i * buffer_shape1, buffer_shape1); + std::span row(list.data() + pos * shape[1], shape[1]); + std::ranges::copy(row, b.begin()); + + auto info = b.last(2); + info[0] = dest_data[2]; // Owning rank + info[1] = pos + offset_global; // Original global index + } + } + + // Prepare receive displacement + MPI_Wait(&request_size, MPI_STATUS_IGNORE); + std::vector recv_disp(num_items_recv.size() + 1, 0); + std::partial_sum(num_items_recv.begin(), num_items_recv.end(), + std::next(recv_disp.begin())); + + // Send/receive data facet + MPI_Datatype compound_type; + MPI_Type_contiguous(buffer_shape1, MPI_INT64_T, &compound_type); + MPI_Type_commit(&compound_type); + std::vector recv_buffer(buffer_shape1 * recv_disp.back()); + MPI_Neighbor_alltoallv(send_buffer.data(), num_items_per_dest.data(), + send_disp.data(), compound_type, recv_buffer.data(), + num_items_recv.data(), recv_disp.data(), compound_type, + neigh_comm); + MPI_Type_free(&compound_type); + MPI_Comm_free(&neigh_comm); + + spdlog::debug("Received {} data on {} [{}]", recv_disp.back(), rank, + shape[1]); + + // Unpack receive buffer + std::vector data, data1; + std::vector ghost_index_owner; + std::vector global_indices, global_indices1; + std::vector src_ranks, src_ranks1; + for (std::size_t p = 0; p < recv_disp.size() - 1; ++p) + { + int src_rank = src[p]; + for (std::int32_t q = recv_disp[p]; q < recv_disp[p + 1]; ++q) + { + std::span row(recv_buffer.data() + q * buffer_shape1, buffer_shape1); + auto info = row.last(2); + std::int64_t orig_global_index = info[1]; + auto edges = row.first(shape[1]); + if (int owner = info[0]; owner == rank) + { + data.insert(data.end(), edges.begin(), edges.end()); + global_indices.push_back(orig_global_index); + src_ranks.push_back(src_rank); + } + else + { + data1.insert(data1.end(), edges.begin(), edges.end()); + global_indices1.push_back(orig_global_index); + ghost_index_owner.push_back(owner); + src_ranks1.push_back(src_rank); + } + } + } + + data.insert(data.end(), data1.begin(), data1.end()); + data.shrink_to_fit(); + global_indices.insert(global_indices.end(), global_indices1.begin(), + global_indices1.end()); + global_indices.shrink_to_fit(); + src_ranks.insert(src_ranks.end(), src_ranks1.begin(), src_ranks1.end()); + src_ranks.shrink_to_fit(); ghost_index_owner.shrink_to_fit(); - return {graph::AdjacencyList(data0, offsets0), src_ranks0, - global_indices0, ghost_index_owner}; + return {std::move(data), std::move(src_ranks), std::move(global_indices), + std::move(ghost_index_owner)}; } //----------------------------------------------------------------------------- std::vector @@ -230,7 +396,7 @@ graph::build::compute_ghost_indices(MPI_Comm comm, std::span ghost_indices, std::span ghost_owners) { - LOG(INFO) << "Compute ghost indices"; + spdlog::info("Compute ghost indices"); // Get number of local cells determine global offset std::int64_t offset_local = 0; @@ -243,22 +409,17 @@ graph::build::compute_ghost_indices(MPI_Comm comm, std::vector ghost_index_count; std::vector neighbors; std::map proc_to_neighbor; + for (int p : ghost_owners) { - int np = 0; - [[maybe_unused]] int mpi_rank = dolfinx::MPI::rank(comm); - for (int p : ghost_owners) + assert(p != dolfinx::MPI::rank(comm)); + auto [it, insert] = proc_to_neighbor.insert({p, neighbors.size()}); + if (insert) { - assert(p != mpi_rank); - auto [it, insert] = proc_to_neighbor.insert({p, np}); - if (insert) - { - // New neighbor found - neighbors.push_back(p); - ghost_index_count.push_back(0); - ++np; - } - ++ghost_index_count[it->second]; + // New neighbor found + neighbors.push_back(p); + ghost_index_count.push_back(0); } + ++ghost_index_count[it->second]; } MPI_Comm neighbor_comm_fwd, neighbor_comm_rev; @@ -326,19 +487,18 @@ graph::build::compute_ghost_indices(MPI_Comm comm, old_to_new.push_back( {idx, static_cast(offset_local + old_to_new.size())}); } - std::sort(old_to_new.begin(), old_to_new.end()); + std::ranges::sort(old_to_new); // Replace values in recv_data with new_index and send back - std::transform(recv_data.begin(), recv_data.end(), recv_data.begin(), - [&old_to_new](auto r) - { - auto it = std::lower_bound( - old_to_new.begin(), old_to_new.end(), - std::array{r, 0}, - [](auto& a, auto& b) { return a[0] < b[0]; }); - assert(it != old_to_new.end() and (*it)[0] == r); - return (*it)[1]; - }); + std::ranges::transform(recv_data, recv_data.begin(), + [&old_to_new](auto r) + { + auto it = std::ranges::lower_bound( + old_to_new, r, std::ranges::less(), + [](auto e) { return e[0]; }); + assert(it != old_to_new.end() and it->front() == r); + return (*it)[1]; + }); std::vector new_recv(send_data.size()); MPI_Neighbor_alltoallv(recv_data.data(), recv_sizes.data(), @@ -350,25 +510,22 @@ graph::build::compute_ghost_indices(MPI_Comm comm, // Build (old id, new id) pairs std::vector> old_to_new1(send_data.size()); - std::transform(send_data.begin(), send_data.end(), new_recv.begin(), - old_to_new1.begin(), - [](auto idx_old, auto idx_new) -> - typename decltype(old_to_new1)::value_type - { return {idx_old, idx_new}; }); - std::sort(old_to_new1.begin(), old_to_new1.end()); + std::ranges::transform(send_data, new_recv, old_to_new1.begin(), + [](auto idx_old, auto idx_new) -> + typename decltype(old_to_new1)::value_type + { return {idx_old, idx_new}; }); + std::ranges::sort(old_to_new1); std::vector ghost_global_indices(ghost_indices.size()); - std::transform( - ghost_indices.begin(), ghost_indices.end(), ghost_global_indices.begin(), - [&old_to_new1](auto q) - { - auto it - = std::lower_bound(old_to_new1.begin(), old_to_new1.end(), - std::array{q, 0}, - [](auto& a, auto& b) { return a[0] < b[0]; }); - assert(it != old_to_new1.end() and (*it)[0] == q); - return (*it)[1]; - }); + std::ranges::transform(ghost_indices, ghost_global_indices.begin(), + [&old_to_new1](auto q) + { + auto it = std::ranges::lower_bound( + old_to_new1, std::array{q, 0}, + [](auto a, auto b) { return a[0] < b[0]; }); + assert(it != old_to_new1.end() and it->front() == q); + return (*it)[1]; + }); return ghost_global_indices; } @@ -379,13 +536,13 @@ graph::build::compute_local_to_global(std::span global, { common::Timer timer( "Compute-local-to-global links for global/local adjacency list"); + if (global.empty() and local.empty()) return std::vector(); - if (global.size() != local.size()) throw std::runtime_error("Data size mismatch."); - std::int32_t max_local_idx = *std::max_element(local.begin(), local.end()); + std::int32_t max_local_idx = *std::ranges::max_element(local); std::vector local_to_global_list(max_local_idx + 1, -1); for (std::size_t i = 0; i < local.size(); ++i) { @@ -408,22 +565,21 @@ std::vector graph::build::compute_local_to_local( global_to_local1.reserve(local1_to_global.size()); for (auto idx_global : local1_to_global) global_to_local1.push_back({idx_global, global_to_local1.size()}); - std::sort(global_to_local1.begin(), global_to_local1.end()); + std::ranges::sort(global_to_local1); // Compute inverse map for local0_to_local1 std::vector local0_to_local1; local0_to_local1.reserve(local0_to_global.size()); - std::transform(local0_to_global.begin(), local0_to_global.end(), - std::back_inserter(local0_to_local1), - [&global_to_local1](auto l2g) - { - auto it = std::lower_bound( - global_to_local1.begin(), global_to_local1.end(), - typename decltype(global_to_local1)::value_type(l2g, 0), - [](auto& a, auto& b) { return a.first < b.first; }); - assert(it != global_to_local1.end() and it->first == l2g); - return it->second; - }); + std::ranges::transform( + local0_to_global, std::back_inserter(local0_to_local1), + [&global_to_local1](auto l2g) + { + auto it = std::ranges::lower_bound(global_to_local1, l2g, + std::ranges::less(), + [](auto e) { return e.first; }); + assert(it != global_to_local1.end() and it->first == l2g); + return it->second; + }); return local0_to_local1; } diff --git a/cpp/dolfinx/graph/partition.h b/cpp/dolfinx/graph/partition.h index d28668e1c37..95971a93538 100644 --- a/cpp/dolfinx/graph/partition.h +++ b/cpp/dolfinx/graph/partition.h @@ -6,9 +6,9 @@ #pragma once +#include "AdjacencyList.h" #include #include -#include #include #include #include @@ -19,7 +19,6 @@ namespace dolfinx::graph { - /// @brief Signature of functions for computing the parallel /// partitioning of a distributed graph. /// @param[in] comm MPI Communicator that the graph is distributed @@ -64,12 +63,35 @@ namespace build /// 1. Received adjacency list for this process /// 2. Source ranks for each node in the adjacency list /// 3. Original global index for each node in the adjacency list -/// 4. Owner rank of ghost nodes +/// 4. Owning rank of ghost nodes. std::tuple, std::vector, std::vector, std::vector> distribute(MPI_Comm comm, const graph::AdjacencyList& list, const graph::AdjacencyList& destinations); +/// @brief Distribute fixed size nodes to destination ranks. +/// +/// The global index of each node is assumed to be the local index plus +/// the offset for this rank. +/// +/// @param[in] comm MPI Communicator +/// @param[in] list Constant degree (valency) adjacency list. The array +/// shape is (num_nodes, degree). Storage is row-major. +/// @param[in] shape Shape `(num_nodes, degree)` of `list`. +/// @param[in] destinations Destination ranks for the ith node (row) of +/// `list`. The first rank is the 'owner' of the node. +/// @return +/// 1. Received adjacency list on this process. The array shape is +/// (num_nodes, degree). Storage is row-major. +/// 2. Source rank for each received node. +/// 3. Original global index for each received node. +/// 4. Owning rank of ghost nodes. +std::tuple, std::vector, + std::vector, std::vector> +distribute(MPI_Comm comm, std::span list, + std::array shape, + const graph::AdjacencyList& destinations); + /// @brief Take a set of distributed input global indices, including /// ghosts, and determine the new global indices after remapping. /// diff --git a/cpp/dolfinx/graph/partitioners.cpp b/cpp/dolfinx/graph/partitioners.cpp index fe4be35306b..68bc0a57847 100644 --- a/cpp/dolfinx/graph/partitioners.cpp +++ b/cpp/dolfinx/graph/partitioners.cpp @@ -5,6 +5,7 @@ // SPDX-License-Identifier: LGPL-3.0-or-later #include "partitioners.h" +#include #include #include #include @@ -70,7 +71,7 @@ graph::AdjacencyList compute_destination_ranks( { if (node1 < range0 or node1 >= range1) { - auto it = std::upper_bound(node_disp.begin(), node_disp.end(), node1); + auto it = std::ranges::upper_bound(node_disp, node1); int remote_rank = std::distance(node_disp.begin(), it) - 1; node_to_dest.push_back( {remote_rank, node1, static_cast(part[node0])}); @@ -80,9 +81,10 @@ graph::AdjacencyList compute_destination_ranks( {rank, node1, static_cast(part[node0])}); } } - std::sort(node_to_dest.begin(), node_to_dest.end()); - node_to_dest.erase(std::unique(node_to_dest.begin(), node_to_dest.end()), - node_to_dest.end()); + + std::ranges::sort(node_to_dest); + auto [unique_end, range_end] = std::ranges::unique(node_to_dest); + node_to_dest.erase(unique_end, range_end); // Build send data and buffer std::vector dest, send_sizes; @@ -163,11 +165,12 @@ graph::AdjacencyList compute_destination_ranks( std::int32_t idx_local = idx - range0; local_node_to_dest.push_back({idx_local, d}); } - std::sort(local_node_to_dest.begin(), local_node_to_dest.end()); - local_node_to_dest.erase( - std::unique(local_node_to_dest.begin(), local_node_to_dest.end()), - local_node_to_dest.end()); + { + std::ranges::sort(local_node_to_dest); + auto [unique_end, range_end] = std::ranges::unique(local_node_to_dest); + local_node_to_dest.erase(unique_end, range_end); + } // Compute offsets std::vector offsets(graph.num_nodes() + 1, 0); { @@ -238,7 +241,7 @@ std::vector adaptive_repartition(MPI_Comm comm, std::vector ubvec(ncon, 1.05); // Call ParMETIS to repartition graph - int err = ParMETIS_V3_AdaptiveRepart( + [[maybe_unused]] int err = ParMETIS_V3_AdaptiveRepart( adj_graph.node_distribution().data(), adj_graph.nodes().data(), adj_graph.edges().data(), elmwgt, nullptr, vsize.data(), &wgtflag, &numflag, &ncon, &nparts, tpwgts.data(), ubvec.data(), &_itr, options, @@ -288,7 +291,7 @@ std::vector refine(MPI_Comm comm, const graph::AdjacencyList& adj_graph) // Call ParMETIS to partition graph common::Timer timer1("ParMETIS: call ParMETIS_V3_RefineKway"); - int err = ParMETIS_V3_RefineKway( + [[maybe_unused]] int err = ParMETIS_V3_RefineKway( adj_graph.node_distribution().data(), adj_graph.nodes().data(), adj_graph.edges().data(), elmwgt, nullptr, &wgtflag, &numflag, &ncon, &nparts, tpwgts.data(), ubvec.data(), options, &edgecut, part.data(), @@ -312,7 +315,7 @@ graph::partition_fn graph::scotch::partitioner(graph::scotch::strategy strategy, const AdjacencyList& graph, bool ghosting) { - LOG(INFO) << "Compute graph partition using PT-SCOTCH"; + spdlog::info("Compute graph partition using PT-SCOTCH"); common::Timer timer("Compute graph partition (SCOTCH)"); std::int64_t offset_global = 0; @@ -413,8 +416,8 @@ graph::partition_fn graph::scotch::partitioner(graph::scotch::strategy strategy, std::copy_if(graph.array().begin(), graph.array().end(), std::back_inserter(ghost_edges), [range](auto e) { return e < range[0] or e >= range[1]; }); - std::sort(ghost_edges.begin(), ghost_edges.end()); - auto it = std::unique(ghost_edges.begin(), ghost_edges.end()); + std::ranges::sort(ghost_edges); + auto it = std::ranges::unique(ghost_edges).begin(); num_ghost_nodes = std::distance(ghost_edges.begin(), it); } @@ -517,7 +520,7 @@ graph::partition_fn graph::parmetis::partitioner(double imbalance, const graph::AdjacencyList& graph, bool ghosting) { - LOG(INFO) << "Compute graph partition using ParMETIS"; + spdlog::info("Compute graph partition using ParMETIS"); common::Timer timer("Compute graph partition (ParMETIS)"); if (nparts == 1 and dolfinx::MPI::size(comm) == 1) @@ -613,7 +616,7 @@ graph::partition_fn graph::kahip::partitioner(int mode, int seed, MPI_Comm comm, int nparts, const graph::AdjacencyList& graph, bool ghosting) { - LOG(INFO) << "Compute graph partition using (parallel) KaHIP"; + spdlog::info("Compute graph partition using (parallel) KaHIP"); // KaHIP integer type using T = unsigned long long; diff --git a/cpp/dolfinx/graph/partitioners.h b/cpp/dolfinx/graph/partitioners.h index 65f7cf8dd59..aba51a8430a 100644 --- a/cpp/dolfinx/graph/partitioners.h +++ b/cpp/dolfinx/graph/partitioners.h @@ -7,6 +7,7 @@ #pragma once #include "partition.h" +#include namespace dolfinx::graph { @@ -79,4 +80,4 @@ graph::partition_fn partitioner(int mode = 1, int seed = 1, #endif } // namespace kahip -} // namespace dolfinx::graph \ No newline at end of file +} // namespace dolfinx::graph diff --git a/cpp/dolfinx/io/ADIOS2Writers.h b/cpp/dolfinx/io/ADIOS2Writers.h index 63f30b3fed3..ab4030c1885 100644 --- a/cpp/dolfinx/io/ADIOS2Writers.h +++ b/cpp/dolfinx/io/ADIOS2Writers.h @@ -10,6 +10,7 @@ #include "vtk_utils.h" #include +#include #include #include #include @@ -201,8 +202,8 @@ void initialize_function_attributes(adios2::IO& io, !assc) { std::vector u_type; - std::transform(u_data.cbegin(), u_data.cend(), std::back_inserter(u_type), - [](auto& f) { return f[1]; }); + std::ranges::transform(u_data, std::back_inserter(u_type), + [](auto f) { return f[1]; }); io.DefineAttribute("Fides_Variable_Associations", u_type.data(), u_type.size()); } @@ -213,8 +214,8 @@ void initialize_function_attributes(adios2::IO& io, !fields) { std::vector names; - std::transform(u_data.cbegin(), u_data.cend(), std::back_inserter(names), - [](auto& f) { return f[0]; }); + std::ranges::transform(u_data, std::back_inserter(names), + [](auto f) { return f[0]; }); io.DefineAttribute("Fides_Variable_List", names.data(), names.size()); } @@ -249,8 +250,17 @@ std::vector pack_function_data(const fem::Function& u) std::uint32_t num_vertices = vertex_map->size_local() + vertex_map->num_ghosts(); - int rank = V->value_shape().size(); - std::uint32_t num_components = std::pow(3, rank); + std::span value_shape = u.function_space()->value_shape(); + int rank = value_shape.size(); + int num_components = std::reduce(value_shape.begin(), value_shape.end(), 1, + std::multiplies{}); + if (num_components < std::pow(3, rank)) + num_components = std::pow(3, rank); + else if (num_components > std::pow(3, rank)) + { + throw std::runtime_error( + "Fides does not support tensors larger than pow(3, rank)"); + } // Get dof array and pack into array (padded where appropriate) auto dofmap_x = geometry.dofmap(); @@ -289,11 +299,24 @@ void write_data(adios2::IO& io, adios2::Engine& engine, assert(dofmap); auto mesh = V->mesh(); assert(mesh); - const int gdim = mesh->geometry().dim(); - // Vectors and tensor need padding in gdim < 3 - int rank = V->value_shape().size(); - bool need_padding = rank > 0 and gdim != 3 ? true : false; + // Pad to 3D if vector/tensor is product of dimensions is smaller than + // 3**rank to ensure that we can visualize them correctly in Paraview + std::span value_shape = u.function_space()->value_shape(); + int rank = value_shape.size(); + int num_components = std::reduce(value_shape.begin(), value_shape.end(), 1, + std::multiplies{}); + bool need_padding = false; + if (num_components < std::pow(3, rank)) + { + num_components = std::pow(3, rank); + need_padding = true; + } + else if (num_components > std::pow(3, rank)) + { + throw std::runtime_error( + "Fides does not support tensors larger than pow(3, rank)"); + } // Get vertex data. If the mesh and function dofmaps are the same we // can work directly with the dof array. @@ -320,7 +343,6 @@ void write_data(adios2::IO& io, adios2::Engine& engine, = vertex_map->size_local() + vertex_map->num_ghosts(); // Write each real and imaginary part of the function - std::uint32_t num_components = std::pow(3, rank); assert(data.size() % num_components == 0); if constexpr (std::is_scalar_v) { @@ -342,15 +364,15 @@ void write_data(adios2::IO& io, adios2::Engine& engine, adios2::Variable local_output_r = impl_adios2::define_variable( io, u.name + impl_adios2::field_ext[0], {}, {}, {num_vertices, num_components}); - std::transform(data.begin(), data.end(), data_real.begin(), - [](auto x) -> X { return std::real(x); }); + std::ranges::transform(data, data_real.begin(), + [](auto x) -> X { return std::real(x); }); engine.Put(local_output_r, data_real.data()); adios2::Variable local_output_c = impl_adios2::define_variable( io, u.name + impl_adios2::field_ext[1], {}, {}, {num_vertices, num_components}); - std::transform(data.begin(), data.end(), data_imag.begin(), - [](auto x) -> X { return std::imag(x); }); + std::ranges::transform(data, data_imag.begin(), + [](auto x) -> X { return std::imag(x); }); engine.Put(local_output_c, data_imag.data()); engine.PerformPuts(); } @@ -657,8 +679,16 @@ void vtx_write_data(adios2::IO& io, adios2::Engine& engine, // Get function data array and information about layout assert(u.x()); std::span u_vector = u.x()->array(); - int rank = u.function_space()->value_shape().size(); - std::uint32_t num_comp = std::pow(3, rank); + + // Pad to 3D if vector/tensor is product of dimensions is smaller than + // 3**rank to ensure that we can visualize them correctly in Paraview + std::span value_shape = u.function_space()->value_shape(); + int rank = value_shape.size(); + int num_comp = std::reduce(value_shape.begin(), value_shape.end(), 1, + std::multiplies{}); + if (num_comp < std::pow(3, rank)) + num_comp = std::pow(3, rank); + std::shared_ptr dofmap = u.function_space()->dofmap(); assert(dofmap); std::shared_ptr index_map = dofmap->index_map; @@ -694,7 +724,7 @@ void vtx_write_data(adios2::IO& io, adios2::Engine& engine, io, u.name + impl_adios2::field_ext[0], {}, {}, {num_dofs, num_comp}); engine.Put(output_real, data.data(), adios2::Mode::Sync); - std::fill(data.begin(), data.end(), 0); + std::ranges::fill(data, 0); for (std::size_t i = 0; i < num_dofs; ++i) for (int j = 0; j < index_map_bs; ++j) data[i * num_comp + j] = std::imag(u_vector[i * index_map_bs + j]); @@ -750,7 +780,7 @@ void vtx_write_mesh(adios2::IO& io, adios2::Engine& engine, { std::span vtkcell(vtkcells.data() + c * shape[1], shape[1]); std::span cell(cells.data() + c * (shape[1] + 1), shape[1] + 1); - std::copy(vtkcell.begin(), vtkcell.end(), std::next(cell.begin())); + std::ranges::copy(vtkcell, std::next(cell.begin())); } // Put topology (nodes) @@ -806,7 +836,7 @@ vtx_write_mesh_from_space(adios2::IO& io, adios2::Engine& engine, { std::span vtkcell(vtk.data() + c * vtkshape[1], vtkshape[1]); std::span cell(cells.data() + c * (vtkshape[1] + 1), vtkshape[1] + 1); - std::copy(vtkcell.begin(), vtkcell.end(), std::next(cell.begin())); + std::ranges::copy(vtkcell, std::next(cell.begin())); } // Define ADIOS2 variables for geometry, topology, celltypes and diff --git a/cpp/dolfinx/io/HDF5Interface.cpp b/cpp/dolfinx/io/HDF5Interface.cpp index 12f8dfb5274..e4a90c2a458 100644 --- a/cpp/dolfinx/io/HDF5Interface.cpp +++ b/cpp/dolfinx/io/HDF5Interface.cpp @@ -72,20 +72,21 @@ hid_t io::hdf5::open_file(MPI_Comm comm, const std::filesystem::path& filename, { if (auto d = filename.parent_path(); !d.empty()) std::filesystem::create_directories(d); - file_id = H5Fcreate(filename.c_str(), H5F_ACC_TRUNC, H5P_DEFAULT, plist_id); + file_id = H5Fcreate(filename.string().c_str(), H5F_ACC_TRUNC, H5P_DEFAULT, + plist_id); if (file_id < 0) throw std::runtime_error("Failed to create HDF5 file."); } else if (mode == "a") // Open file to append, creating if does not exist { if (std::filesystem::exists(filename)) - file_id = H5Fopen(filename.c_str(), H5F_ACC_RDWR, plist_id); + file_id = H5Fopen(filename.string().c_str(), H5F_ACC_RDWR, plist_id); else { if (auto d = filename.parent_path(); !d.empty()) std::filesystem::create_directories(d); - file_id - = H5Fcreate(filename.c_str(), H5F_ACC_EXCL, H5P_DEFAULT, plist_id); + file_id = H5Fcreate(filename.string().c_str(), H5F_ACC_EXCL, H5P_DEFAULT, + plist_id); } if (file_id < 0) @@ -98,14 +99,14 @@ hid_t io::hdf5::open_file(MPI_Comm comm, const std::filesystem::path& filename, { if (std::filesystem::exists(filename)) { - file_id = H5Fopen(filename.c_str(), H5F_ACC_RDONLY, plist_id); + file_id = H5Fopen(filename.string().c_str(), H5F_ACC_RDONLY, plist_id); if (file_id < 0) throw std::runtime_error("Failed to open HDF5 file."); } else { throw std::runtime_error("Unable to open HDF5 file. File " - + filename.native() + " does not exist."); + + filename.string() + " does not exist."); } } diff --git a/cpp/dolfinx/io/HDF5Interface.h b/cpp/dolfinx/io/HDF5Interface.h index 9f653d263bb..2d559a77677 100644 --- a/cpp/dolfinx/io/HDF5Interface.h +++ b/cpp/dolfinx/io/HDF5Interface.h @@ -283,7 +283,7 @@ std::vector read_dataset(hid_t dset_id, std::array range, if (rank < 1) throw std::runtime_error("Failed to get rank of data space."); else if (rank > 2) - LOG(WARNING) << "io::hdf5::read_dataset untested for rank > 2."; + spdlog::warn("io::hdf5::read_dataset untested for rank > 2."); // Allocate data for shape std::vector shape(rank); @@ -345,7 +345,7 @@ std::vector read_dataset(hid_t dset_id, std::array range, auto timer_end = std::chrono::system_clock::now(); std::chrono::duration dt = (timer_end - timer_start); double data_rate = data.size() * sizeof(T) / (1e6 * dt.count()); - LOG(INFO) << "HDF5 Read data rate: " << data_rate << "MB/s"; + spdlog::info("HDF5 Read data rate: {} MB/s", data_rate); return data; } diff --git a/cpp/dolfinx/io/VTKFile.cpp b/cpp/dolfinx/io/VTKFile.cpp index ac1f60f7a04..0f9b7d59b14 100644 --- a/cpp/dolfinx/io/VTKFile.cpp +++ b/cpp/dolfinx/io/VTKFile.cpp @@ -8,6 +8,7 @@ #include "cells.h" #include "vtk_utils.h" #include "xdmf_utils.h" +#include #include #include #include @@ -62,7 +63,7 @@ std::stringstream container_to_string(const T& x, int precision) { std::stringstream s; s.precision(precision); - std::for_each(x.begin(), x.end(), [&s](auto e) { s << e << " "; }); + std::ranges::for_each(x, [&s](auto e) { s << e << " "; }); return s; } //---------------------------------------------------------------------------- @@ -110,12 +111,12 @@ void add_pvtu_mesh(pugi::xml_node& node) /// Add float data to a pugixml node /// @param[in] name The name of the data array -/// @param[in] rank The rank of the field, e.g. 1 for a vector and 2 for -/// a tensor +/// @param[in] num_components An array indicating the value shape of `values` /// @param[in] values The data array to add /// @param[in,out] data_node The XML node to add data to template -void add_data_float(const std::string& name, int rank, +void add_data_float(const std::string& name, + std::span num_components, std::span values, pugi::xml_node& node) { static_assert(std::is_floating_point_v, "Scalar must be a float"); @@ -127,11 +128,9 @@ void add_data_float(const std::string& name, int rank, field_node.append_attribute("type") = type.c_str(); field_node.append_attribute("Name") = name.c_str(); field_node.append_attribute("format") = "ascii"; + if (!num_components.empty()) + field_node.append_attribute("NumberOfComponents") = num_components.front(); - if (rank == 1) - field_node.append_attribute("NumberOfComponents") = 3; - else if (rank == 2) - field_node.append_attribute("NumberOfComponents") = 9; field_node.append_child(pugi::node_pcdata) .set_value(container_to_string(values, 16).str().c_str()); } @@ -142,26 +141,26 @@ void add_data_float(const std::string& name, int rank, /// real and one complex), with suffixes from `field_ext` added to the /// name /// @param[in] name The name of the data array -/// @param[in] rank The rank of the field, e.g. 1 for a vector and 2 for -/// a tensor +/// @param[in] num_components An array indicating the value shape of `values` /// @param[in] values The data array to add /// @param[in,out] data_node The XML node to add data to template -void add_data(const std::string& name, int rank, std::span values, - pugi::xml_node& node) +void add_data(const std::string& name, + std::span num_components, + std::span values, pugi::xml_node& node) { if constexpr (std::is_scalar_v) - add_data_float(name, rank, values, node); + add_data_float(name, num_components, values, node); else { using U = typename T::value_type; std::vector v(values.size()); - std::transform(values.begin(), values.end(), v.begin(), - [](auto x) { return x.real(); }); - add_data_float(name + field_ext[0], rank, std::span(v), node); - std::transform(values.begin(), values.end(), v.begin(), - [](auto x) { return x.imag(); }); - add_data_float(name + field_ext[1], rank, std::span(v), node); + std::ranges::transform(values, v.begin(), [](auto x) { return x.real(); }); + add_data_float(name + field_ext[0], num_components, std::span(v), + node); + std::ranges::transform(values, v.begin(), [](auto x) { return x.imag(); }); + add_data_float(name + field_ext[1], num_components, std::span(v), + node); } } //---------------------------------------------------------------------------- @@ -206,8 +205,7 @@ void add_mesh(std::span x, std::array /*xshape*/, connectivity_node.append_attribute("format") = "ascii"; { std::stringstream ss; - std::for_each(cells.begin(), cells.end(), - [&ss](auto& v) { ss << v << " "; }); + std::ranges::for_each(cells, [&ss](auto& v) { ss << v << " "; }); connectivity_node.append_child(pugi::node_pcdata) .set_value(ss.str().c_str()); } @@ -265,8 +263,8 @@ void add_mesh(std::span x, std::array /*xshape*/, const std::int64_t cell_offset = cellmap.local_range()[0]; for (std::int32_t c = 0; c < cellmap.size_local(); ++c) ss << cell_offset + c << " "; - std::for_each(cellmap.ghosts().begin(), cellmap.ghosts().end(), - [&ss](auto& idx) { ss << idx << " "; }); + std::ranges::for_each(cellmap.ghosts(), + [&ss](auto& idx) { ss << idx << " "; }); cell_id_node.append_child(pugi::node_pcdata).set_value(ss.str().c_str()); } @@ -275,9 +273,9 @@ void add_mesh(std::span x, std::array /*xshape*/, if (!cellmap.ghosts().empty()) { std::span ghosts = cellmap.ghosts(); - auto minmax = std::minmax_element(ghosts.begin(), ghosts.end()); - min_idx = std::min(min_idx, *minmax.first); - max_idx = std::max(max_idx, *minmax.second); + auto [min, max] = std::ranges::minmax_element(ghosts); + min_idx = std::min(min_idx, *min); + max_idx = std::max(max_idx, *max); } cell_id_node.append_attribute("RangeMin") = min_idx; cell_id_node.append_attribute("RangeMax") = max_idx; @@ -292,15 +290,14 @@ void add_mesh(std::span x, std::array /*xshape*/, point_id_node.append_attribute("format") = "ascii"; { std::stringstream ss; - std::for_each(x_id.begin(), x_id.end(), - [&ss](auto idx) { ss << idx << " "; }); + std::ranges::for_each(x_id, [&ss](auto idx) { ss << idx << " "; }); point_id_node.append_child(pugi::node_pcdata).set_value(ss.str().c_str()); } if (!x_id.empty()) { - auto minmax = std::minmax_element(x_id.begin(), x_id.end()); - point_id_node.append_attribute("RangeMin") = *minmax.first; - point_id_node.append_attribute("RangeMax") = *minmax.second; + auto [min, max] = std::ranges::minmax_element(x_id); + point_id_node.append_attribute("RangeMin") = *min; + point_id_node.append_attribute("RangeMax") = *max; } // Point ghosts @@ -310,16 +307,15 @@ void add_mesh(std::span x, std::array /*xshape*/, point_ghost_node.append_attribute("format") = "ascii"; { std::stringstream ss; - std::for_each(x_ghost.begin(), x_ghost.end(), - [&ss](int ghost) { ss << ghost << " "; }); + std::ranges::for_each(x_ghost, [&ss](int ghost) { ss << ghost << " "; }); point_ghost_node.append_child(pugi::node_pcdata) .set_value(ss.str().c_str()); } if (!x_ghost.empty()) { - auto minmax = std::minmax_element(x_ghost.begin(), x_ghost.end()); - point_ghost_node.append_attribute("RangeMin") = *minmax.first; - point_ghost_node.append_attribute("RangeMax") = *minmax.second; + auto [min, max] = std::ranges::minmax_element(x_ghost); + point_ghost_node.append_attribute("RangeMin") = *min; + point_ghost_node.append_attribute("RangeMax") = *max; } } //---------------------------------------------------------------------------- @@ -477,8 +473,20 @@ void write_function( auto V = _u.get().function_space(); auto e = V->element(); assert(e); - int rank = V->value_shape().size(); - std::int32_t num_comp = std::pow(3, rank); + + // Pad to 3D if vector/tensor is product of dimensions is smaller than + // 3**rank to ensure that we can visualize them correctly in Paraview + std::span value_shape = V->value_shape(); + int rank = value_shape.size(); + int num_components = std::reduce(value_shape.begin(), value_shape.end(), 1, + std::multiplies{}); + if (num_components < std::pow(3, rank)) + num_components = std::pow(3, rank); + // Create array to store number of (padded) components in field + std::vector component_vector(int(rank > 0 ? 1 : 0)); + if (rank > 0) + component_vector[0] = num_components; + if (is_cellwise(*e)) { // -- Cell-wise data @@ -487,17 +495,18 @@ void write_function( assert(!data_node.empty()); auto dofmap = V->dofmap(); int bs = dofmap->bs(); - std::vector data(cshape[0] * num_comp, 0); + std::vector data(cshape[0] * num_components, 0); auto u_vector = _u.get().x()->array(); for (std::size_t c = 0; c < cshape[0]; ++c) { auto dofs = dofmap->cell_dofs(c); for (std::size_t i = 0; i < dofs.size(); ++i) for (int k = 0; k < bs; ++k) - data[num_comp * c + k] = u_vector[bs * dofs[i] + k]; + data[num_components * c + k] = u_vector[bs * dofs[i] + k]; } - add_data(_u.get().name, rank, std::span(data), data_node); + add_data(_u.get().name, std::span(component_vector), + std::span(data), data_node); } else { @@ -508,7 +517,7 @@ void write_function( // Function to pack data to 3D with 'zero' padding, typically when // a Function is 2D - auto pad_data = [num_comp](auto&& V, auto u) + auto pad_data = [num_components](auto&& V, auto u) { auto dofmap = V.dofmap(); int bs = dofmap->bs(); @@ -516,11 +525,11 @@ void write_function( int map_bs = dofmap->index_map_bs(); std::int32_t num_dofs_block = map_bs * (map->size_local() + map->num_ghosts()) / bs; - std::vector data(num_dofs_block * num_comp, 0); + std::vector data(num_dofs_block * num_components, 0); for (int i = 0; i < num_dofs_block; ++i) { std::copy_n(std::next(u.begin(), i * map_bs), map_bs, - std::next(data.begin(), i * num_comp)); + std::next(data.begin(), i * num_components)); } return data; }; @@ -529,12 +538,16 @@ void write_function( { // -- Identical spaces if (mesh0->geometry().dim() == 3) - add_data(_u.get().name, rank, _u.get().x()->array(), data_node); + add_data(_u.get().name, + std::span(component_vector), + _u.get().x()->array(), data_node); else { // Pad with zeros and then add auto data = pad_data(*V, _u.get().x()->array()); - add_data(_u.get().name, rank, std::span(data), data_node); + add_data(_u.get().name, + std::span(component_vector), + std::span(data), data_node); } } else if (*e == *element0) @@ -568,12 +581,16 @@ void write_function( // Pack/add data if (mesh0->geometry().dim() == 3) - add_data(_u.get().name, rank, std::span(u), data_node); + add_data(_u.get().name, + std::span(component_vector), + std::span(u), data_node); else { // Pad with zeros and then add auto data = pad_data(*V, _u.get().x()->array()); - add_data(_u.get().name, rank, std::span(data), data_node); + add_data(_u.get().name, + std::span(component_vector), + std::span(data), data_node); } } else @@ -640,8 +657,15 @@ void write_function( assert(e); std::string d_type = is_cellwise(*e) ? "PCellData" : "PPointData"; pugi::xml_node data_pnode = grid_node.child(d_type.c_str()); - const int rank = V->value_shape().size(); - constexpr std::array ncomps = {0, 3, 9}; + + // Pad to 3D if vector/tensor is product of dimensions is smaller than + // 3**rank to ensure that we can visualize them correctly in Paraview + std::span value_shape = V->value_shape(); + int rank = value_shape.size(); + int num_components = std::reduce(value_shape.begin(), value_shape.end(), + 1, std::multiplies{}); + if (num_components < std::pow(3, rank)) + num_components = std::pow(3, rank); auto add_field = [&](const std::string& name, int size) { @@ -649,7 +673,7 @@ void write_function( pugi::xml_node data_node = data_pnode.append_child("PDataArray"); data_node.append_attribute("type") = type.c_str(); data_node.append_attribute("Name") = name.c_str(); - data_node.append_attribute("NumberOfComponents") = ncomps[rank]; + data_node.append_attribute("NumberOfComponents") = num_components; }; if constexpr (std::is_scalar_v) diff --git a/cpp/dolfinx/io/XDMFFile.cpp b/cpp/dolfinx/io/XDMFFile.cpp index ed5bc127b24..25226a01669 100644 --- a/cpp/dolfinx/io/XDMFFile.cpp +++ b/cpp/dolfinx/io/XDMFFile.cpp @@ -45,7 +45,7 @@ XDMFFile::XDMFFile(MPI_Comm comm, const std::filesystem::path& filename, _h5_id = io::hdf5::open_file(_comm.comm(), hdf5_filename, file_mode, mpi_io); assert(_h5_id > 0); - LOG(INFO) << "Opened HDF5 file with id \"" << _h5_id << "\""; + spdlog::info("Opened HDF5 file with id \"{}\"", _h5_id); } else { @@ -68,6 +68,12 @@ XDMFFile::XDMFFile(MPI_Comm comm, const std::filesystem::path& filename, } else if (_file_mode == "w") { + if (_encoding == Encoding::ASCII and dolfinx::MPI::size(_comm.comm()) > 1) + { + throw std::runtime_error( + "ASCII encoding is not supported for writing files in parallel."); + } + _xml_doc->reset(); // Add XDMF node and version attribute @@ -84,6 +90,12 @@ XDMFFile::XDMFFile(MPI_Comm comm, const std::filesystem::path& filename, } else if (_file_mode == "a") { + if (_encoding == Encoding::ASCII and dolfinx::MPI::size(_comm.comm()) > 1) + { + throw std::runtime_error("ASCII encoding is not supported for appending " + "to files in parallel."); + } + if (std::filesystem::exists(_filename)) { // Load XML doc from file @@ -196,7 +208,7 @@ XDMFFile::read_topology_data(std::string name, std::string xpath) const if (!grid_node) throw std::runtime_error(" with name '" + name + "' not found."); - LOG(INFO) << "Read topology data \"" << name << "\" at \"" << xpath << "\""; + spdlog::info("Read topology data \"{}\" at {}", name, xpath); return xdmf_mesh::read_topology_data(_comm.comm(), _h5_id, grid_node); } //----------------------------------------------------------------------------- @@ -213,7 +225,7 @@ XDMFFile::read_geometry_data(std::string name, std::string xpath) const if (!grid_node) throw std::runtime_error(" with name '" + name + "' not found."); - LOG(INFO) << "Read geometry data \"" << name << "\" at \"" << xpath << "\""; + spdlog::info("Read geometry data \"{}\" at {}", name, xpath); return xdmf_mesh::read_geometry_data(_comm.comm(), _h5_id, grid_node); } //----------------------------------------------------------------------------- @@ -247,8 +259,8 @@ void XDMFFile::write_function(const fem::Function& u, double t, pugi::xml_node mesh_node = _xml_doc->select_node(mesh_xpath.c_str()).node(); if (!mesh_node) { - LOG(WARNING) << "No mesh found at '" << mesh_xpath - << "'. Write mesh before function!"; + spdlog::warn("No mesh found at '{}'. Write mesh before function!", + mesh_xpath); } const std::string ref_path @@ -325,7 +337,7 @@ mesh::MeshTags XDMFFile::read_meshtags(const mesh::Mesh& mesh, std::string name, std::string xpath) { - LOG(INFO) << "XDMF read meshtags (" << name << ")"; + spdlog::info("XDMF read meshtags ({})", name); pugi::xml_node node = _xml_doc->select_node(xpath.c_str()).node(); if (!node) throw std::runtime_error("XML node '" + xpath + "' not found."); @@ -360,7 +372,7 @@ XDMFFile::read_meshtags(const mesh::Mesh& mesh, std::string name, mesh.geometry().cmap().create_dof_layout(), mesh.geometry().dofmap(), mesh::cell_dim(cell_type), entities_span, values); - LOG(INFO) << "XDMF create meshtags"; + spdlog::info("XDMF create meshtags"); std::size_t num_vertices_per_entity = mesh::cell_num_entities( mesh::cell_entity_type(mesh.topology()->cell_type(), mesh::cell_dim(cell_type), 0), diff --git a/cpp/dolfinx/io/cells.cpp b/cpp/dolfinx/io/cells.cpp index fa209344dc0..0dfa3673f5b 100644 --- a/cpp/dolfinx/io/cells.cpp +++ b/cpp/dolfinx/io/cells.cpp @@ -667,7 +667,7 @@ io::cells::apply_permutation(std::span cells, assert(cells.size() == shape[0] * shape[1]); assert(shape[1] == p.size()); - LOG(INFO) << "IO permuting cells"; + spdlog::info("IO permuting cells"); std::vector cells_new(cells.size()); for (std::size_t c = 0; c < shape[0]; ++c) { diff --git a/cpp/dolfinx/io/cells.h b/cpp/dolfinx/io/cells.h index 16f7ddd015a..271b00eb45d 100644 --- a/cpp/dolfinx/io/cells.h +++ b/cpp/dolfinx/io/cells.h @@ -118,27 +118,27 @@ Pyramid: Pyramid13: namespace dolfinx::io::cells { -/// Permutation array to map from VTK to DOLFINx node ordering +/// @brief Permutation array to map from VTK to DOLFINx node ordering. /// /// @param[in] type The cell shape /// @param[in] num_nodes The number of cell 'nodes' /// @return Permutation array @p for permuting from VTK ordering to -/// DOLFINx ordering, i.e. `a_dolfin[i] = a_vtk[p[i]] +/// DOLFINx ordering, i.e. `a_dolfin[i] = a_vtk[p[i]]`. /// @details If `p = [0, 2, 1, 3]` and `a = [10, 3, 4, 7]`, then `a_p /// =[a[p[0]], a[p[1]], a[p[2]], a[p[3]]] = [10, 4, 3, 7]` std::vector perm_vtk(mesh::CellType type, int num_nodes); -/// Permutation array to map from Gmsh to DOLFINx node ordering +/// @brief Permutation array to map from Gmsh to DOLFINx node ordering. /// /// @param[in] type The cell shape /// @param[in] num_nodes /// @return Permutation array @p for permuting from Gmsh ordering to -/// DOLFINx ordering, i.e. `a_dolfin[i] = a_gmsh[p[i]] +/// DOLFINx ordering, i.e. `a_dolfin[i] = a_gmsh[p[i]]`. /// @details If `p = [0, 2, 1, 3]` and `a = [10, 3, 4, 7]`, then `a_p /// =[a[p[0]], a[p[1]], a[p[2]], a[p[3]]] = [10, 4, 3, 7]` std::vector perm_gmsh(mesh::CellType type, int num_nodes); -/// Compute the transpose of a re-ordering map +/// @brief Compute the transpose of a re-ordering map. /// /// @param[in] map A re-ordering map /// @return Transpose of the `map`. E.g., is `map = {1, 2, 3, 0}`, the diff --git a/cpp/dolfinx/io/vtk_utils.h b/cpp/dolfinx/io/vtk_utils.h index 4e4e01f480e..5bcb7d368da 100644 --- a/cpp/dolfinx/io/vtk_utils.h +++ b/cpp/dolfinx/io/vtk_utils.h @@ -8,6 +8,7 @@ #include "cells.h" #include "vtk_utils.h" +#include #include #include #include @@ -159,7 +160,7 @@ tabulate_lagrange_dof_coordinates(const fem::FunctionSpace& V) std::int32_t size_local = range[1] - range[0]; std::iota(x_id.begin(), std::next(x_id.begin(), size_local), range[0]); std::span ghosts = map_dofs->ghosts(); - std::copy(ghosts.begin(), ghosts.end(), std::next(x_id.begin(), size_local)); + std::ranges::copy(ghosts, std::next(x_id.begin(), size_local)); // Ghosts std::vector id_ghost(num_nodes, 0); diff --git a/cpp/dolfinx/io/xdmf_function.cpp b/cpp/dolfinx/io/xdmf_function.cpp index 90cbef31003..47ce57ddd6e 100644 --- a/cpp/dolfinx/io/xdmf_function.cpp +++ b/cpp/dolfinx/io/xdmf_function.cpp @@ -47,7 +47,7 @@ void xdmf_function::add_function(MPI_Comm comm, const fem::Function& u, double t, pugi::xml_node& xml_node, hid_t h5_id) { - LOG(INFO) << "Adding function to node \"" << xml_node.path('/') << "\""; + spdlog::info("Adding function to node \"{}\"", xml_node.path('/')); assert(u.function_space()); auto mesh = u.function_space()->mesh(); @@ -75,13 +75,14 @@ void xdmf_function::add_function(MPI_Comm comm, const fem::Function& u, assert(dofmap); const int bs = dofmap->bs(); + // Pad to 3D if vector/tensor is product of dimensions is smaller than 3**rank + // to ensure that we can visualize them correctly in Paraview std::span value_shape = u.function_space()->value_shape(); + int rank = value_shape.size(); int num_components = std::reduce(value_shape.begin(), value_shape.end(), 1, std::multiplies{}); - // Pad to 3D if vector is 1 or 2D, to ensure that we can visualize them - // correctly in Paraview - if (value_shape.size() == 1 && value_shape.front() < 3) - num_components = 3; + if (num_components < std::pow(3, rank)) + num_components = std::pow(3, rank); // Get fem::Function data values and shape std::vector data_values; @@ -190,13 +191,13 @@ void xdmf_function::add_function(MPI_Comm comm, const fem::Function& u, _data.resize(data_values.size()); if (component == "real_") { - std::transform(data_values.begin(), data_values.end(), _data.begin(), - [](auto x) { return x.real(); }); + std::ranges::transform(data_values, _data.begin(), + [](auto x) { return x.real(); }); } else if (component == "imag_") { - std::transform(data_values.begin(), data_values.end(), _data.begin(), - [](auto x) { return x.imag(); }); + std::ranges::transform(data_values, _data.begin(), + [](auto x) { return x.imag(); }); } u = std::span>(_data); } diff --git a/cpp/dolfinx/io/xdmf_mesh.cpp b/cpp/dolfinx/io/xdmf_mesh.cpp index 7ea1f063012..d6a228ce167 100644 --- a/cpp/dolfinx/io/xdmf_mesh.cpp +++ b/cpp/dolfinx/io/xdmf_mesh.cpp @@ -26,7 +26,7 @@ void xdmf_mesh::add_topology_data(MPI_Comm comm, pugi::xml_node& xml_node, const mesh::Geometry& geometry, int dim, std::span entities) { - LOG(INFO) << "Adding topology data to node \"" << xml_node.path('/') << "\""; + spdlog::info("Adding topology data to node {}", xml_node.path('/')); const int tdim = topology.dim(); @@ -161,7 +161,7 @@ void xdmf_mesh::add_geometry_data(MPI_Comm comm, pugi::xml_node& xml_node, hid_t h5_id, std::string path_prefix, const mesh::Geometry& geometry) { - LOG(INFO) << "Adding geometry data to node \"" << xml_node.path('/') << "\""; + spdlog::info("Adding geometry data to node \"{}\"", xml_node.path('/')); auto map = geometry.index_map(); assert(map); @@ -214,7 +214,7 @@ template void xdmf_mesh::add_mesh(MPI_Comm comm, pugi::xml_node& xml_node, hid_t h5_id, const mesh::Mesh& mesh, const std::string& name) { - LOG(INFO) << "Adding mesh to node \"" << xml_node.path('/') << "\""; + spdlog::info("Adding mesh to node \"{}\"", xml_node.path('/')); // Add grid node and attributes pugi::xml_node grid_node = xml_node.append_child("Grid"); diff --git a/cpp/dolfinx/io/xdmf_mesh.h b/cpp/dolfinx/io/xdmf_mesh.h index 8fb43e80b59..804f0ee5a35 100644 --- a/cpp/dolfinx/io/xdmf_mesh.h +++ b/cpp/dolfinx/io/xdmf_mesh.h @@ -7,6 +7,7 @@ #pragma once #include "xdmf_utils.h" +#include #include #include #include @@ -97,7 +98,7 @@ void add_meshtags(MPI_Comm comm, const mesh::MeshTags& meshtags, const mesh::Geometry& geometry, pugi::xml_node& xml_node, hid_t h5_id, const std::string& name) { - LOG(INFO) << "XDMF: add meshtags (" << name << ")"; + spdlog::info("XDMF: add meshtags ({})", name.c_str()); // Get mesh const int dim = meshtags.dim(); std::shared_ptr entity_map @@ -110,8 +111,7 @@ void add_meshtags(MPI_Comm comm, const mesh::MeshTags& meshtags, const std::int32_t num_local_entities = entity_map->size_local(); // Find number of tagged entities in local range - auto it = std::lower_bound(meshtags.indices().begin(), - meshtags.indices().end(), num_local_entities); + auto it = std::ranges::lower_bound(meshtags.indices(), num_local_entities); const int num_active_entities = std::distance(meshtags.indices().begin(), it); const std::string path_prefix = "/MeshTags/" + name; diff --git a/cpp/dolfinx/io/xdmf_utils.cpp b/cpp/dolfinx/io/xdmf_utils.cpp index 313479ddca0..3cedcc06d8c 100644 --- a/cpp/dolfinx/io/xdmf_utils.cpp +++ b/cpp/dolfinx/io/xdmf_utils.cpp @@ -5,6 +5,7 @@ // SPDX-License-Identifier: LGPL-3.0-or-later #include "xdmf_utils.h" +#include #include #include #include @@ -56,12 +57,13 @@ xdmf_utils::get_cell_type(const pugi::xml_node& topology_node) {"quadrilateral_9", {"quadrilateral", 2}}, {"quadrilateral_16", {"quadrilateral", 3}}, {"hexahedron", {"hexahedron", 1}}, + {"wedge", {"prism", 1}}, {"hexahedron_27", {"hexahedron", 2}}}; // Convert XDMF cell type string to DOLFINx cell type string std::string cell_type = type_attr.as_string(); - std::transform(cell_type.begin(), cell_type.end(), cell_type.begin(), - [](unsigned char c) { return std::tolower(c); }); + std::ranges::transform(cell_type, cell_type.begin(), + [](auto c) { return std::tolower(c); }); auto it = xdmf_to_dolfin.find(cell_type); if (it == xdmf_to_dolfin.end()) { @@ -227,7 +229,7 @@ xdmf_utils::distribute_entity_data( std::span data) { assert(entities.extent(0) == data.size()); - LOG(INFO) << "XDMF distribute entity data"; + spdlog::info("XDMF distribute entity data"); mesh::CellType cell_type = topology.cell_type(); // Get layout of dofs on 0th cell entity of dimension entity_dim @@ -270,7 +272,7 @@ xdmf_utils::distribute_entity_data( std::span entity(entities_v.data() + e * num_vert_per_e, num_vert_per_e); for (std::size_t i = 0; i < num_vert_per_e; ++i) entity[i] = entities(e, entity_vertex_dofs[i]); - std::sort(entity.begin(), entity.end()); + std::ranges::sort(entity); } std::array shape{entities.extent(0), num_vert_per_e}; @@ -301,8 +303,8 @@ xdmf_utils::distribute_entity_data( } std::vector perm(dest0.size()); std::iota(perm.begin(), perm.end(), 0); - std::sort(perm.begin(), perm.end(), - [&dest0](auto x0, auto x1) { return dest0[x0] < dest0[x1]; }); + std::ranges::sort(perm, [&dest0](auto x0, auto x1) + { return dest0[x0] < dest0[x1]; }); // Note: dest[perm[i]] is ordered with increasing i // Build list of neighbour dest ranks and count number of entities to @@ -330,7 +332,7 @@ xdmf_utils::distribute_entity_data( // Determine src ranks. Sort ranks so that ownership determination is // deterministic for a given number of ranks. std::vector src = dolfinx::MPI::compute_graph_edges_nbx(comm, dest); - std::sort(src.begin(), src.end()); + std::ranges::sort(src); // Create neighbourhood communicator for sending data to post // offices @@ -399,12 +401,12 @@ xdmf_utils::distribute_entity_data( { int size = dolfinx::MPI::size(comm); std::vector> dest_to_index; - std::transform( - indices.begin(), indices.end(), std::back_inserter(dest_to_index), + std::ranges::transform( + indices, std::back_inserter(dest_to_index), [size, num_nodes](auto n) { return std::pair(dolfinx::MPI::index_owner(size, n, num_nodes), n); }); - std::sort(dest_to_index.begin(), dest_to_index.end()); + std::ranges::sort(dest_to_index); // Build list of neighbour dest ranks and count number of indices to // send to each post office @@ -431,7 +433,7 @@ xdmf_utils::distribute_entity_data( // Determine src ranks. Sort ranks so that ownership determination is // deterministic for a given number of ranks. std::vector src = dolfinx::MPI::compute_graph_edges_nbx(comm, dest); - std::sort(src.begin(), src.end()); + std::ranges::sort(src); // Create neighbourhood communicator for sending data to post offices MPI_Comm comm0; @@ -457,9 +459,8 @@ xdmf_utils::distribute_entity_data( // Prepare send buffer std::vector send_buffer; send_buffer.reserve(indices.size()); - std::transform(dest_to_index.begin(), dest_to_index.end(), - std::back_inserter(send_buffer), - [](auto x) { return x.second; }); + std::ranges::transform(dest_to_index, std::back_inserter(send_buffer), + [](auto x) { return x.second; }); std::vector recv_buffer(recv_disp.back()); err = MPI_Neighbor_alltoallv(send_buffer.data(), num_items_send.data(), @@ -586,7 +587,7 @@ xdmf_utils::distribute_entity_data( std::span cell_vertex_dofs, auto entities_data, std::span entities_values) { - LOG(INFO) << "XDMF build map"; + spdlog::info("XDMF build map"); auto c_to_v = topology.connectivity(topology.dim(), 0); if (!c_to_v) throw std::runtime_error("Missing cell-vertex connectivity."); @@ -699,4 +700,4 @@ xdmf_utils::distribute_entity_data( MDSPAN_IMPL_STANDARD_NAMESPACE::dextents>, std::span>); -/// @endcond \ No newline at end of file +/// @endcond diff --git a/cpp/dolfinx/la/MatrixCSR.h b/cpp/dolfinx/la/MatrixCSR.h index e3260c2a735..bd1181532f6 100644 --- a/cpp/dolfinx/la/MatrixCSR.h +++ b/cpp/dolfinx/la/MatrixCSR.h @@ -8,6 +8,7 @@ #include "SparsityPattern.h" #include "matrix_csr_impl.h" +#include #include #include #include @@ -29,7 +30,7 @@ enum class BlockMode : int /// matrix has a block size of (1, 1). }; -/// Distributed sparse matrix +/// @brief Distributed sparse matrix. /// /// The matrix storage format is compressed sparse row. The matrix is /// partitioned row-wise across MPI ranks. @@ -182,9 +183,10 @@ class MatrixCSR /// @brief Set all non-zero local entries to a value including entries /// in ghost rows. /// @param[in] x The value to set non-zero matrix entries to - void set(value_type x) { std::fill(_data.begin(), _data.end(), x); } + void set(value_type x) { std::ranges::fill(_data, x); } /// @brief Set values in the matrix. + /// /// @note Only entries included in the sparsity pattern used to /// initialize the matrix can be set. /// @note All indices are local to the calling MPI rank and entries @@ -473,8 +475,9 @@ MatrixCSR::MatrixCSR(const SparsityPattern& p, BlockMode mode) // Compute off-diagonal offset for each row (compact) std::span num_diag_nnz = p.off_diagonal_offsets(); _off_diagonal_offset.reserve(num_diag_nnz.size()); - std::transform(num_diag_nnz.begin(), num_diag_nnz.end(), _row_ptr.begin(), - std::back_inserter(_off_diagonal_offset), std::plus{}); + std::ranges::transform(num_diag_nnz, _row_ptr, + std::back_inserter(_off_diagonal_offset), + std::plus{}); } // Some short-hand @@ -501,7 +504,7 @@ MatrixCSR::MatrixCSR(const SparsityPattern& p, BlockMode mode) _ghost_row_to_rank.reserve(_index_maps[0]->owners().size()); for (int r : _index_maps[0]->owners()) { - auto it = std::lower_bound(src_ranks.begin(), src_ranks.end(), r); + auto it = std::ranges::lower_bound(src_ranks, r); assert(it != src_ranks.end() and *it == r); int pos = std::distance(src_ranks.begin(), it); _ghost_row_to_rank.push_back(pos); @@ -551,9 +554,8 @@ MatrixCSR::MatrixCSR(const SparsityPattern& p, BlockMode mode) std::vector recv_disp; { std::vector send_sizes; - std::transform(data_per_proc.begin(), data_per_proc.end(), - std::back_inserter(send_sizes), - [](auto x) { return 2 * x; }); + std::ranges::transform(data_per_proc, std::back_inserter(send_sizes), + [](auto x) { return 2 * x; }); std::vector recv_sizes(dest_ranks.size()); send_sizes.reserve(1); @@ -580,17 +582,17 @@ MatrixCSR::MatrixCSR(const SparsityPattern& p, BlockMode mode) // data values _val_recv_disp.resize(recv_disp.size()); const int bs2 = _bs[0] * _bs[1]; - std::transform(recv_disp.begin(), recv_disp.end(), _val_recv_disp.begin(), - [&bs2](auto d) { return bs2 * d / 2; }); - std::transform(_val_send_disp.begin(), _val_send_disp.end(), - _val_send_disp.begin(), [&bs2](auto d) { return d * bs2; }); + std::ranges::transform(recv_disp, _val_recv_disp.begin(), + [&bs2](auto d) { return bs2 * d / 2; }); + std::ranges::transform(_val_send_disp, _val_send_disp.begin(), + [&bs2](auto d) { return d * bs2; }); // Global-to-local map for ghost columns std::vector> global_to_local; global_to_local.reserve(ghosts1.size()); for (std::int64_t idx : ghosts1) global_to_local.push_back({idx, global_to_local.size() + local_size[1]}); - std::sort(global_to_local.begin(), global_to_local.end()); + std::ranges::sort(global_to_local); // Compute location in which data for each index should be stored // when received @@ -604,10 +606,9 @@ MatrixCSR::MatrixCSR(const SparsityPattern& p, BlockMode mode) std::int32_t local_col = ghost_index_array[i + 1] - local_range[1][0]; if (local_col < 0 or local_col >= local_size[1]) { - auto it = std::lower_bound(global_to_local.begin(), global_to_local.end(), - std::pair(ghost_index_array[i + 1], -1), - [](auto& a, auto& b) - { return a.first < b.first; }); + auto it = std::ranges::lower_bound( + global_to_local, std::pair(ghost_index_array[i + 1], -1), + [](auto& a, auto& b) { return a.first < b.first; }); assert(it != global_to_local.end() and it->first == ghost_index_array[i + 1]); local_col = it->second; @@ -629,15 +630,17 @@ std::vector::value_type> MatrixCSR::to_dense() const { const std::size_t nrows = num_all_rows(); - const std::size_t ncols - = _index_maps[1]->size_local() + _index_maps[1]->num_ghosts(); + const std::size_t ncols = _index_maps[1]->size_global(); std::vector A(nrows * ncols * _bs[0] * _bs[1], 0.0); for (std::size_t r = 0; r < nrows; ++r) for (std::int32_t j = _row_ptr[r]; j < _row_ptr[r + 1]; ++j) for (int i0 = 0; i0 < _bs[0]; ++i0) for (int i1 = 0; i1 < _bs[1]; ++i1) { - A[(r * _bs[1] + i0) * ncols * _bs[0] + _cols[j] * _bs[1] + i1] + std::array local_col {_cols[j]}; + std::array global_col{0}; + _index_maps[1]->local_to_global(local_col, global_col); + A[(r * _bs[1] + i0) * ncols * _bs[0] + global_col[0] * _bs[1] + i1] = _data[j * _bs[0] * _bs[1] + i0 * _bs[1] + i1]; } diff --git a/cpp/dolfinx/la/SparsityPattern.cpp b/cpp/dolfinx/la/SparsityPattern.cpp index 4e253b37088..ac7ee1b6efc 100644 --- a/cpp/dolfinx/la/SparsityPattern.cpp +++ b/cpp/dolfinx/la/SparsityPattern.cpp @@ -140,6 +140,27 @@ SparsityPattern::SparsityPattern( } } //----------------------------------------------------------------------------- +void SparsityPattern::insert(std::int32_t row, std::int32_t col) +{ + if (!_offsets.empty()) + { + throw std::runtime_error( + "Cannot insert into sparsity pattern. It has already been finalized"); + } + + assert(_index_maps[0]); + const std::int32_t max_row + = _index_maps[0]->size_local() + _index_maps[0]->num_ghosts() - 1; + + if (row > max_row or row < 0) + { + throw std::runtime_error( + "Cannot insert rows that do not exist in the IndexMap."); + } + + _row_cache[row].push_back(col); +} +//----------------------------------------------------------------------------- void SparsityPattern::insert(std::span rows, std::span cols) { @@ -160,7 +181,6 @@ void SparsityPattern::insert(std::span rows, throw std::runtime_error( "Cannot insert rows that do not exist in the IndexMap."); } - _row_cache[row].insert(_row_cache[row].end(), cols.begin(), cols.end()); } } @@ -205,8 +225,7 @@ std::vector SparsityPattern::column_indices() const const std::int32_t num_ghosts = _col_ghosts.size(); std::vector global(local_size + num_ghosts); std::iota(global.begin(), std::next(global.begin(), local_size), range[0]); - std::copy(_col_ghosts.begin(), _col_ghosts.end(), - global.begin() + local_size); + std::ranges::copy(_col_ghosts, global.begin() + local_size); return global; } //----------------------------------------------------------------------------- @@ -250,7 +269,7 @@ void SparsityPattern::finalize() std::vector send_sizes(src0.size(), 0); for (std::size_t i = 0; i < owners0.size(); ++i) { - auto it = std::lower_bound(src0.begin(), src0.end(), owners0[i]); + auto it = std::ranges::lower_bound(src0, owners0[i]); assert(it != src0.end() and *it == owners0[i]); const int neighbour_rank = std::distance(src0.begin(), it); send_sizes[neighbour_rank] += 3 * _row_cache[i + local_size0].size(); @@ -268,7 +287,7 @@ void SparsityPattern::finalize() const int rank = dolfinx::MPI::rank(_comm.comm()); for (std::size_t i = 0; i < owners0.size(); ++i) { - auto it = std::lower_bound(src0.begin(), src0.end(), owners0[i]); + auto it = std::ranges::lower_bound(src0, owners0[i]); assert(it != src0.end() and *it == owners0[i]); const int neighbour_rank = std::distance(src0.begin(), it); @@ -362,8 +381,8 @@ void SparsityPattern::finalize() for (std::size_t i = 0; i < local_size0 + owners0.size(); ++i) { std::vector& row = _row_cache[i]; - std::sort(row.begin(), row.end()); - auto it_end = std::unique(row.begin(), row.end()); + std::ranges::sort(row); + auto it_end = std::ranges::unique(row).begin(); // Find position of first "off-diagonal" column _off_diagonal_offsets[i] = std::distance( @@ -382,8 +401,8 @@ void SparsityPattern::finalize() _edges.shrink_to_fit(); // Column count increased due to received rows from other processes - LOG(INFO) << "Column ghost size increased from " - << _index_maps[1]->ghosts().size() << " to " << _col_ghosts.size(); + spdlog::info("Column ghost size increased from {} to {}", + _index_maps[1]->ghosts().size(), _col_ghosts.size()); } //----------------------------------------------------------------------------- std::int64_t SparsityPattern::num_nonzeros() const diff --git a/cpp/dolfinx/la/SparsityPattern.h b/cpp/dolfinx/la/SparsityPattern.h index 33632d72813..726455a0c39 100644 --- a/cpp/dolfinx/la/SparsityPattern.h +++ b/cpp/dolfinx/la/SparsityPattern.h @@ -67,6 +67,19 @@ class SparsityPattern /// @brief Insert non-zero locations using local (process-wise) /// indices. + /// @param[in] row local row index + /// @param[in] col local column index + void insert(std::int32_t row, std::int32_t col); + + /// @brief Insert non-zero locations using local (process-wise) + /// indices. + /// + /// This routine inserts non-zero locations at the outer product of rows and + /// cols into the sparsity pattern, i.e. adds the matrix entries at + /// A[row[i], col[j]] for all i, j. + /// + /// @param[in] rows list of the local row indices + /// @param[in] cols list of the local column indices void insert(std::span rows, std::span cols); diff --git a/cpp/dolfinx/la/Vector.h b/cpp/dolfinx/la/Vector.h index 130883d2c81..9b69e4670fb 100644 --- a/cpp/dolfinx/la/Vector.h +++ b/cpp/dolfinx/la/Vector.h @@ -7,6 +7,7 @@ #pragma once #include "utils.h" +#include #include #include #include @@ -78,7 +79,7 @@ class Vector /// Set all entries (including ghosts) /// @param[in] v The value to set all entries to (on calling rank) - void set(value_type v) { std::fill(_x.begin(), _x.end(), v); } + void set(value_type v) { std::ranges::fill(_x, v); } /// Begin scatter of local data from owner to ghosts on other ranks /// @note Collective MPI operation @@ -288,8 +289,8 @@ auto norm(const V& x, Norm type = Norm::l2) { std::int32_t size_local = x.bs() * x.index_map()->size_local(); std::span data = x.array().subspan(0, size_local); - auto max_pos = std::max_element(data.begin(), data.end(), [](T a, T b) - { return std::norm(a) < std::norm(b); }); + auto max_pos = std::ranges::max_element( + data, [](T a, T b) { return std::norm(a) < std::norm(b); }); auto local_linf = std::abs(*max_pos); decltype(local_linf) linf = 0; MPI_Allreduce(&local_linf, &linf, 1, MPI::mpi_type(), @@ -324,9 +325,9 @@ void orthonormalize(std::vector> basis) // basis_i <- basis_i - dot_ij basis_j auto dot_ij = inner_product(bi, bj); - std::transform(bj.array().begin(), bj.array().end(), bi.array().begin(), - bi.mutable_array().begin(), - [dot_ij](auto xj, auto xi) { return xi - dot_ij * xj; }); + std::ranges::transform(bj.array(), bi.array(), bi.mutable_array().begin(), + [dot_ij](auto xj, auto xi) + { return xi - dot_ij * xj; }); } // Normalise basis function @@ -336,29 +337,34 @@ void orthonormalize(std::vector> basis) throw std::runtime_error( "Linear dependency detected. Cannot orthogonalize."); } - std::transform(bi.array().begin(), bi.array().end(), - bi.mutable_array().begin(), - [norm](auto x) { return x / norm; }); + std::ranges::transform(bi.array(), bi.mutable_array().begin(), + [norm](auto x) { return x / norm; }); } } /// @brief Test if basis is orthonormal. -/// @param[in] basis The set of vectors to check. +/// +/// Returns true if ||x_i - x_j|| - delta_{ij} < eps fro all i, j, and +/// otherwise false. +/// +/// @param[in] basis Set of vectors to check. +/// @param[in] eps Tolerance. /// @return True is basis is orthonormal, otherwise false. template -bool is_orthonormal(std::vector> basis) +bool is_orthonormal( + std::vector> basis, + dolfinx::scalar_value_type_t eps + = std::numeric_limits< + dolfinx::scalar_value_type_t>::epsilon()) { using T = typename V::value_type; - using U = typename dolfinx::scalar_value_type_t; - - // auto tol = std::sqrt(T(std::numeric_limits::epsilon())); for (std::size_t i = 0; i < basis.size(); i++) { for (std::size_t j = i; j < basis.size(); j++) { T delta_ij = (i == j) ? T(1) : T(0); auto dot_ij = inner_product(basis[i].get(), basis[j].get()); - if (std::norm(delta_ij - dot_ij) > std::numeric_limits::epsilon()) + if (std::norm(delta_ij - dot_ij) > eps) return false; } } diff --git a/cpp/dolfinx/la/petsc.cpp b/cpp/dolfinx/la/petsc.cpp index 8fb9e744fce..e596e0e22c7 100644 --- a/cpp/dolfinx/la/petsc.cpp +++ b/cpp/dolfinx/la/petsc.cpp @@ -11,6 +11,7 @@ #include "SparsityPattern.h" #include "Vector.h" #include "utils.h" +#include #include #include #include @@ -38,9 +39,9 @@ void la::petsc::error(int error_code, std::string filename, PetscErrorMessage(error_code, &desc, nullptr); // Log detailed error info - DLOG(INFO) << "PETSc error in '" << filename.c_str() << "', '" - << petsc_function.c_str() << "'"; - DLOG(INFO) << "PETSc error code '" << error_code << "' (" << desc << "."; + spdlog::info("PETSc error in '{}', '{}'", filename.c_str(), + petsc_function.c_str()); + spdlog::info("PETSc error code '{}' '{}'", error_code, desc); throw std::runtime_error("Failed to successfully call PETSc function '" + petsc_function + "'. PETSc error code is: " + std ::to_string(error_code) + ", " @@ -57,7 +58,7 @@ la::petsc::create_vectors(MPI_Comm comm, VecCreateMPI(comm, x[i].size(), PETSC_DETERMINE, &v[i]); PetscScalar* data; VecGetArray(v[i], &data); - std::copy(x[i].begin(), x[i].end(), data); + std::ranges::copy(x[i], data); VecRestoreArray(v[i], &data); } @@ -549,8 +550,9 @@ Vec petsc::Operator::create_vector(std::size_t dim) const } else { - LOG(ERROR) << "Cannot initialize PETSc vector to match PETSc matrix. " - << "Dimension must be 0 or 1, not " << dim; + spdlog::error("Cannot initialize PETSc vector to match PETSc matrix. " + "Dimension must be 0 or 1, not {}", + dim); throw std::runtime_error("Invalid dimension"); } @@ -697,7 +699,7 @@ int petsc::KrylovSolver::solve(Vec x, const Vec b, bool transpose) const PetscErrorCode ierr; // Solve linear system - LOG(INFO) << "PETSc Krylov solver starting to solve system."; + spdlog::info("PETSc Krylov solver starting to solve system."); // Solve system if (!transpose) diff --git a/cpp/dolfinx/la/petsc.h b/cpp/dolfinx/la/petsc.h index 011d4cf4d1c..07e146a0c98 100644 --- a/cpp/dolfinx/la/petsc.h +++ b/cpp/dolfinx/la/petsc.h @@ -87,17 +87,18 @@ Vec create_vector_wrap(const la::Vector& x) return create_vector_wrap(*x.index_map(), x.bs(), x.array()); } -/// @todo This function could take just the local sizes +/// @brief Compute PETSc IndexSets (IS) for a stack of index maps. /// -/// Compute PETSc IndexSets (IS) for a stack of index maps. E.g., if -/// `map[0] = {0, 1, 2, 3, 4, 5, 6}` and `map[1] = {0, 1, 2, 4}` (in -/// local indices) then `IS[0] = {0, 1, 2, 3, 4, 5, 6}` and `IS[1] = {7, -/// 8, 9, 10}`. +/// If `map[0] = {0, 1, 2, 3, 4, 5, 6}` and `map[1] = {0, 1, 2, 4}` (in +/// local indices) then `IS[0] = {0, 1, 2, 3, 4, 5, 6}` and +/// `IS[1] = {7, 8, 9, 10}`. +/// +/// @todo This function could take just the local sizes. /// /// @note The caller is responsible for destruction of each IS. /// /// @param[in] maps Vector of IndexMaps and corresponding block sizes -/// @returns Vector of PETSc Index Sets, created on` PETSC_COMM_SELF` +/// @return Vector of PETSc Index Sets, created on` PETSC_COMM_SELF` std::vector create_index_sets( const std::vector< std::pair, int>>& maps); @@ -297,9 +298,8 @@ class Matrix : public Operator PetscErrorCode ierr; #ifdef PETSC_USE_64BIT_INDICES cache.resize(rows.size() + cols.size()); - std::copy(rows.begin(), rows.end(), cache.begin()); - std::copy(cols.begin(), cols.end(), - std::next(cache.begin(), rows.size())); + std::ranges::copy(rows, cache.begin()); + std::ranges::copy(cols, std::next(cache.begin(), rows.size())); const PetscInt* _rows = cache.data(); const PetscInt* _cols = cache.data() + rows.size(); ierr = MatSetValuesLocal(A, rows.size(), _rows, cols.size(), _cols, @@ -332,9 +332,8 @@ class Matrix : public Operator PetscErrorCode ierr; #ifdef PETSC_USE_64BIT_INDICES cache.resize(rows.size() + cols.size()); - std::copy(rows.begin(), rows.end(), cache.begin()); - std::copy(cols.begin(), cols.end(), - std::next(cache.begin(), rows.size())); + std::ranges::copy(rows, cache.begin()); + std::ranges::copy(cols, std::next(cache.begin(), rows.size())); const PetscInt* _rows = cache.data(); const PetscInt* _cols = cache.data() + rows.size(); ierr = MatSetValuesBlockedLocal(A, rows.size(), _rows, cols.size(), _cols, diff --git a/cpp/dolfinx/la/slepc.cpp b/cpp/dolfinx/la/slepc.cpp index 78e8bc99966..11ba6ea0be5 100644 --- a/cpp/dolfinx/la/slepc.cpp +++ b/cpp/dolfinx/la/slepc.cpp @@ -97,7 +97,7 @@ void SLEPcEigenSolver::solve(std::int64_t n) EPSConvergedReason reason; EPSGetConvergedReason(_eps, &reason); if (reason < 0) - LOG(WARNING) << "Eigenvalue solver did not converge"; + spdlog::warn("Eigenvalue solver did not converge"); // Report solver status PetscInt num_iterations = 0; @@ -105,8 +105,8 @@ void SLEPcEigenSolver::solve(std::int64_t n) EPSType eps_type = nullptr; EPSGetType(_eps, &eps_type); - LOG(INFO) << "Eigenvalue solver (" << eps_type << ") converged in " - << num_iterations << " iterations."; + spdlog::info("Eigenvalue solver ({}) converged in {} iterations.", eps_type, + num_iterations); } //----------------------------------------------------------------------------- std::complex SLEPcEigenSolver::get_eigenvalue(int i) const diff --git a/cpp/dolfinx/mesh/Geometry.h b/cpp/dolfinx/mesh/Geometry.h index 9713492f1c0..30784a72858 100644 --- a/cpp/dolfinx/mesh/Geometry.h +++ b/cpp/dolfinx/mesh/Geometry.h @@ -7,6 +7,7 @@ #pragma once #include "Topology.h" +#include #include #include #include @@ -112,7 +113,7 @@ class Geometry /// Move Assignment Geometry& operator=(Geometry&&) = default; - /// Return Euclidean dimension of coordinate system + /// Return dimension of the Euclidean coordinate system int dim() const { return _dim; } /// @brief DofMap for the geometry @@ -270,9 +271,9 @@ create_geometry( reorder_fn = nullptr) { - LOG(INFO) << "Create Geometry (multiple)"; + spdlog::info("Create Geometry (multiple)"); - assert(std::is_sorted(nodes.begin(), nodes.end())); + assert(std::ranges::is_sorted(nodes)); using T = typename std::remove_reference_t; // Check elements match cell types in topology @@ -285,7 +286,7 @@ create_geometry( for (const auto& el : elements) dof_layouts.push_back(el.create_dof_layout()); - LOG(INFO) << "Got " << dof_layouts.size() << " dof layouts"; + spdlog::info("Got {} dof layouts", dof_layouts.size()); // Build 'geometry' dofmap on the topology auto [_dof_index_map, bs, dofmaps] @@ -309,14 +310,14 @@ create_geometry( } } - LOG(INFO) << "Calling compute_local_to_global"; + spdlog::info("Calling compute_local_to_global"); // Compute local-to-global map from local indices in dofmap to the // corresponding global indices in cells, and pass to function to // compute local (dof) to local (position in coords) map from (i) // local-to-global for dofs and (ii) local-to-global for entries in // coords - LOG(INFO) << "xdofs.size = " << xdofs.size(); + spdlog::info("xdofs.size = {}", xdofs.size()); std::vector all_dofmaps; std::stringstream s; for (auto q : dofmaps) @@ -324,17 +325,17 @@ create_geometry( s << q.size() << " "; all_dofmaps.insert(all_dofmaps.end(), q.begin(), q.end()); } - LOG(INFO) << "dofmap sizes = " << s.str(); - LOG(INFO) << "all_dofmaps.size = " << all_dofmaps.size(); - LOG(INFO) << "nodes.size = " << nodes.size(); + spdlog::info("dofmap sizes = {}", s.str()); + spdlog::info("all_dofmaps.size = {}", all_dofmaps.size()); + spdlog::info("nodes.size = {}", nodes.size()); const std::vector l2l = graph::build::compute_local_to_local( graph::build::compute_local_to_global(xdofs, all_dofmaps), nodes); // Allocate space for input global indices and copy data std::vector igi(nodes.size()); - std::transform(l2l.cbegin(), l2l.cend(), igi.begin(), - [&nodes](auto index) { return nodes[index]; }); + std::ranges::transform(l2l, igi.begin(), + [&nodes](auto index) { return nodes[index]; }); // Build coordinate dof array, copying coordinates to correct position assert(x.size() % dim == 0); @@ -347,7 +348,7 @@ create_geometry( std::next(xg.begin(), 3 * i)); } - LOG(INFO) << "Creating geometry with " << dofmaps.size() << " dofmaps"; + spdlog::info("Creating geometry with {} dofmaps", dof_layouts.size()); return Geometry(dof_index_map, std::move(dofmaps), elements, std::move(xg), dim, std::move(igi)); @@ -388,7 +389,7 @@ create_geometry( reorder_fn = nullptr) { - assert(std::is_sorted(nodes.begin(), nodes.end())); + assert(std::ranges::is_sorted(nodes)); using T = typename std::remove_reference_t; fem::ElementDofLayout dof_layout = element.create_dof_layout(); @@ -425,8 +426,8 @@ create_geometry( // Allocate space for input global indices and copy data std::vector igi(nodes.size()); - std::transform(l2l.cbegin(), l2l.cend(), igi.begin(), - [&nodes](auto index) { return nodes[index]; }); + std::ranges::transform(l2l, igi.begin(), + [&nodes](auto index) { return nodes[index]; }); // Build coordinate dof array, copying coordinates to correct position assert(x.size() % dim == 0); @@ -443,120 +444,4 @@ create_geometry( std::move(xg), dim, std::move(igi)); } -/// @brief Create a sub-geometry for a subset of entities. -/// @param topology Full mesh topology. -/// @param geometry Full mesh geometry. -/// @param dim Topological dimension of the sub-topology. -/// @param subentity_to_entity Map from sub-topology entity to the -/// entity in the parent topology. -/// @return A sub-geometry and a map from sub-geometry coordinate -/// degree-of-freedom to the coordinate degree-of-freedom in `geometry`. -template -std::pair, std::vector> -create_subgeometry(const Topology& topology, const Geometry& geometry, - int dim, std::span subentity_to_entity) -{ - // Get the geometry dofs in the sub-geometry based on the entities in - // sub-geometry - const fem::ElementDofLayout layout = geometry.cmap().create_dof_layout(); - // NOTE: Unclear what this return for prisms - const std::size_t num_entity_dofs = layout.num_entity_closure_dofs(dim); - - std::vector x_indices; - x_indices.reserve(num_entity_dofs * subentity_to_entity.size()); - { - auto xdofs = geometry.dofmap(); - const int tdim = topology.dim(); - - // Fetch connectivities required to get entity dofs - const std::vector>>& closure_dofs - = layout.entity_closure_dofs_all(); - auto e_to_c = topology.connectivity(dim, tdim); - assert(e_to_c); - auto c_to_e = topology.connectivity(tdim, dim); - assert(c_to_e); - for (std::size_t i = 0; i < subentity_to_entity.size(); ++i) - { - const std::int32_t idx = subentity_to_entity[i]; - assert(!e_to_c->links(idx).empty()); - // Always pick the last cell to be consistent with the e_to_v connectivity - const std::int32_t cell = e_to_c->links(idx).back(); - auto cell_entities = c_to_e->links(cell); - auto it = std::find(cell_entities.begin(), cell_entities.end(), idx); - assert(it != cell_entities.end()); - std::size_t local_entity = std::distance(cell_entities.begin(), it); - - auto xc = MDSPAN_IMPL_STANDARD_NAMESPACE::submdspan( - xdofs, cell, MDSPAN_IMPL_STANDARD_NAMESPACE::full_extent); - for (std::int32_t entity_dof : closure_dofs[dim][local_entity]) - x_indices.push_back(xc[entity_dof]); - } - } - - std::vector sub_x_dofs = x_indices; - std::sort(sub_x_dofs.begin(), sub_x_dofs.end()); - sub_x_dofs.erase(std::unique(sub_x_dofs.begin(), sub_x_dofs.end()), - sub_x_dofs.end()); - - // Get the sub-geometry dofs owned by this process - auto x_index_map = geometry.index_map(); - assert(x_index_map); - - std::shared_ptr sub_x_dof_index_map; - std::vector subx_to_x_dofmap; - { - auto [map, new_to_old] = common::create_sub_index_map( - *x_index_map, sub_x_dofs, common::IndexMapOrder::any, true); - sub_x_dof_index_map = std::make_shared(std::move(map)); - subx_to_x_dofmap = std::move(new_to_old); - } - - // Create sub-geometry coordinates - std::span x = geometry.x(); - std::int32_t sub_num_x_dofs = subx_to_x_dofmap.size(); - std::vector sub_x(3 * sub_num_x_dofs); - for (int i = 0; i < sub_num_x_dofs; ++i) - { - std::copy_n(std::next(x.begin(), 3 * subx_to_x_dofmap[i]), 3, - std::next(sub_x.begin(), 3 * i)); - } - - // Create geometry to sub-geometry map - std::vector x_to_subx_dof_map( - x_index_map->size_local() + x_index_map->num_ghosts(), -1); - for (std::size_t i = 0; i < subx_to_x_dofmap.size(); ++i) - x_to_subx_dof_map[subx_to_x_dofmap[i]] = i; - - // Create sub-geometry dofmap - std::vector sub_x_dofmap; - sub_x_dofmap.reserve(x_indices.size()); - std::transform(x_indices.cbegin(), x_indices.cend(), - std::back_inserter(sub_x_dofmap), - [&x_to_subx_dof_map](auto x_dof) - { - assert(x_to_subx_dof_map[x_dof] != -1); - return x_to_subx_dof_map[x_dof]; - }); - - // Create sub-geometry coordinate element - CellType sub_coord_cell - = cell_entity_type(geometry.cmap().cell_shape(), dim, 0); - fem::CoordinateElement sub_cmap(sub_coord_cell, geometry.cmap().degree(), - geometry.cmap().variant()); - - // Sub-geometry input_global_indices - // TODO: Check this - const std::vector& igi = geometry.input_global_indices(); - std::vector sub_igi; - sub_igi.reserve(subx_to_x_dofmap.size()); - std::transform(subx_to_x_dofmap.begin(), subx_to_x_dofmap.end(), - std::back_inserter(sub_igi), - [&igi](std::int32_t sub_x_dof) { return igi[sub_x_dof]; }); - - // Create geometry - return {Geometry(sub_x_dof_index_map, std::move(sub_x_dofmap), {sub_cmap}, - std::move(sub_x), geometry.dim(), std::move(sub_igi)), - std::move(subx_to_x_dofmap)}; -} - } // namespace dolfinx::mesh diff --git a/cpp/dolfinx/mesh/MeshTags.h b/cpp/dolfinx/mesh/MeshTags.h index db27c81bb0e..3026c258158 100644 --- a/cpp/dolfinx/mesh/MeshTags.h +++ b/cpp/dolfinx/mesh/MeshTags.h @@ -58,7 +58,7 @@ class MeshTags "Indices and values arrays must have same size."); } #ifndef NDEBUG - if (!std::is_sorted(_indices.begin(), _indices.end())) + if (!std::ranges::is_sorted(_indices)) throw std::runtime_error("MeshTag data is not sorted"); if (std::adjacent_find(_indices.begin(), _indices.end()) != _indices.end()) throw std::runtime_error("MeshTag data has duplicates"); @@ -127,8 +127,8 @@ class MeshTags }; /// @brief Create MeshTags from arrays -/// @param[in] topology Mesh topology that the tags are associated with -/// @param[in] dim Topological dimension of tagged entities +/// @param[in] topology Mesh topology that the tags are associated with. +/// @param[in] dim Topological dimension of tagged entities. /// @param[in] entities Local vertex indices for tagged entities. /// @param[in] values Tag values for each entity in `entities`. The /// length of `values` must be equal to number of rows in `entities`. @@ -139,8 +139,8 @@ MeshTags create_meshtags(std::shared_ptr topology, int dim, const graph::AdjacencyList& entities, std::span values) { - LOG(INFO) - << "Building MeshTags object from tagged entities (defined by vertices)."; + spdlog::info( + "Building MeshTags object from tagged entities (defined by vertices)."); // Compute the indices of the topology entities (index is set to -1 if // it can't be found) @@ -157,7 +157,7 @@ MeshTags create_meshtags(std::shared_ptr topology, int dim, auto [indices_sorted, values_sorted] = common::sort_unique(indices, values); // Remove any entities that were not found (these have an index of -1) - auto it0 = std::lower_bound(indices_sorted.begin(), indices_sorted.end(), 0); + auto it0 = std::ranges::lower_bound(indices_sorted, 0); std::size_t pos0 = std::distance(indices_sorted.begin(), it0); indices_sorted.erase(indices_sorted.begin(), it0); values_sorted.erase(values_sorted.begin(), diff --git a/cpp/dolfinx/mesh/Topology.cpp b/cpp/dolfinx/mesh/Topology.cpp index ac1669bb3de..c1bed136587 100644 --- a/cpp/dolfinx/mesh/Topology.cpp +++ b/cpp/dolfinx/mesh/Topology.cpp @@ -49,8 +49,7 @@ determine_sharing_ranks(MPI_Comm comm, std::span indices) std::int64_t global_range = 0; { std::int64_t max_index - = indices.empty() ? 0 - : *std::max_element(indices.begin(), indices.end()); + = indices.empty() ? 0 : *std::ranges::max_element(indices); MPI_Allreduce(&max_index, &global_range, 1, MPI_INT64_T, MPI_MAX, comm); global_range += 1; } @@ -64,7 +63,7 @@ determine_sharing_ranks(MPI_Comm comm, std::span indices) int dest = dolfinx::MPI::index_owner(size, idx, global_range); dest_to_index.push_back({dest, static_cast(dest_to_index.size())}); } - std::sort(dest_to_index.begin(), dest_to_index.end()); + std::ranges::sort(dest_to_index); } // Build list of neighbour dest ranks and count number of indices to @@ -92,7 +91,7 @@ determine_sharing_ranks(MPI_Comm comm, std::span indices) // Determine src ranks. Sort ranks so that ownership determination is // deterministic for a given number of ranks. std::vector src = dolfinx::MPI::compute_graph_edges_nbx(comm, dest); - std::sort(src.begin(), src.end()); + std::ranges::sort(src); // Create neighbourhood communicator for sending data to post offices MPI_Comm neigh_comm0; @@ -141,7 +140,7 @@ determine_sharing_ranks(MPI_Comm comm, std::span indices) for (std::int32_t i = recv_disp0[p]; i < recv_disp0[p + 1]; ++i) indices_list.push_back({recv_buffer0[i], i, int(p)}); } - std::sort(indices_list.begin(), indices_list.end()); + std::ranges::sort(indices_list); // Find which ranks have each index std::vector num_items_per_dest1(recv_disp0.size() - 1, 0); @@ -304,11 +303,12 @@ std::array, 2> vertex_ownership_groups( [](std::size_t s, auto& v) { return s + v.size(); })); for (auto c : cells_owned) local_vertex_set.insert(local_vertex_set.end(), c.begin(), c.end()); - dolfinx::radix_sort(std::span(local_vertex_set)); - local_vertex_set.erase( - std::unique(local_vertex_set.begin(), local_vertex_set.end()), - local_vertex_set.end()); + { + dolfinx::radix_sort(local_vertex_set); + auto [unique_end, range_end] = std::ranges::unique(local_vertex_set); + local_vertex_set.erase(unique_end, range_end); + } // Build set of ghost cell vertices (attached to a ghost cell) std::vector ghost_vertex_set; ghost_vertex_set.reserve( @@ -316,32 +316,30 @@ std::array, 2> vertex_ownership_groups( [](std::size_t s, auto& v) { return s + v.size(); })); for (auto c : cells_ghost) ghost_vertex_set.insert(ghost_vertex_set.end(), c.begin(), c.end()); - dolfinx::radix_sort(std::span(ghost_vertex_set)); - ghost_vertex_set.erase( - std::unique(ghost_vertex_set.begin(), ghost_vertex_set.end()), - ghost_vertex_set.end()); + { + dolfinx::radix_sort(ghost_vertex_set); + auto [unique_end, range_end] = std::ranges::unique(ghost_vertex_set); + ghost_vertex_set.erase(unique_end, range_end); + } // Build difference 1: Vertices attached only to owned cells, and // therefore owned by this rank std::vector owned_vertices; - std::set_difference(local_vertex_set.begin(), local_vertex_set.end(), - boundary_vertices.begin(), boundary_vertices.end(), - std::back_inserter(owned_vertices)); + std::ranges::set_difference(local_vertex_set, boundary_vertices, + std::back_inserter(owned_vertices)); // Build difference 2: Vertices attached only to ghost cells, and // therefore not owned by this rank std::vector unowned_vertices; - std::set_difference(ghost_vertex_set.begin(), ghost_vertex_set.end(), - local_vertex_set.begin(), local_vertex_set.end(), - std::back_inserter(unowned_vertices)); + std::ranges::set_difference(ghost_vertex_set, local_vertex_set, + std::back_inserter(unowned_vertices)); // TODO Check this in debug mode only? // Sanity check // No vertices in unowned should also be in boundary... std::vector unowned_vertices_in_error; - std::set_intersection(unowned_vertices.begin(), unowned_vertices.end(), - boundary_vertices.begin(), boundary_vertices.end(), - std::back_inserter(unowned_vertices_in_error)); + std::ranges::set_intersection(unowned_vertices, boundary_vertices, + std::back_inserter(unowned_vertices_in_error)); if (!unowned_vertices_in_error.empty()) { @@ -389,10 +387,18 @@ exchange_indexing(MPI_Comm comm, std::span indices, else src.push_back(ranks.front()); } - std::sort(src.begin(), src.end()); - src.erase(std::unique(src.begin(), src.end()), src.end()); - std::sort(dest.begin(), dest.end()); - dest.erase(std::unique(dest.begin(), dest.end()), dest.end()); + + { + std::ranges::sort(src); + auto [unique_end, range_end] = std::ranges::unique(src); + src.erase(unique_end, range_end); + } + + { + std::ranges::sort(dest); + auto [unique_end, range_end] = std::ranges::unique(dest); + dest.erase(unique_end, range_end); + } // Pack send data. Use std::vector> since size will be // modest (equal to number of neighbour ranks) @@ -406,8 +412,7 @@ exchange_indexing(MPI_Comm comm, std::span indices, { // Get local vertex index std::int64_t idx_old = indices[i]; - auto local_it = std::lower_bound(global_indices.begin(), - global_indices.end(), idx_old); + auto local_it = std::ranges::lower_bound(global_indices, idx_old); assert(local_it != global_indices.end() and *local_it == idx_old); std::size_t pos = std::distance(global_indices.begin(), local_it); std::int64_t idx_new = local_indices[pos] + offset; @@ -417,7 +422,7 @@ exchange_indexing(MPI_Comm comm, std::span indices, for (std::size_t j = 1; j < ranks.size(); ++j) { // Find rank on the neighborhood comm - auto it = std::lower_bound(dest.begin(), dest.end(), ranks[j]); + auto it = std::ranges::lower_bound(dest, ranks[j]); assert(it != dest.end() and *it == ranks[j]); int neighbor = std::distance(dest.begin(), it); @@ -440,9 +445,8 @@ exchange_indexing(MPI_Comm comm, std::span indices, // Prepare send sizes and send displacements std::vector send_sizes; send_sizes.reserve(dest.size()); - std::transform(send_buffer.begin(), send_buffer.end(), - std::back_inserter(send_sizes), - [](auto& x) { return x.size(); }); + std::ranges::transform(send_buffer, std::back_inserter(send_sizes), + [](auto& x) { return x.size(); }); std::vector send_disp(dest.size() + 1, 0); std::partial_sum(send_sizes.begin(), send_sizes.end(), std::next(send_disp.begin())); @@ -539,19 +543,18 @@ std::vector> exchange_ghost_indexing( // Build list of (owner rank, index) pairs for each ghost index, and // sort std::vector> owner_to_ghost; - std::transform(map0.ghosts().begin(), map0.ghosts().end(), - map0.owners().begin(), std::back_inserter(owner_to_ghost), - [](auto idx, auto r) -> std::pair - { return {r, idx}; }); - std::sort(owner_to_ghost.begin(), owner_to_ghost.end()); + std::ranges::transform(map0.ghosts(), map0.owners(), + std::back_inserter(owner_to_ghost), + [](auto idx, auto r) -> std::pair + { return {r, idx}; }); + std::ranges::sort(owner_to_ghost); // Build send buffer (the second component of each pair in // owner_to_ghost) to send to rank that owns the index std::vector send_buffer; send_buffer.reserve(owner_to_ghost.size()); - std::transform(owner_to_ghost.begin(), owner_to_ghost.end(), - std::back_inserter(send_buffer), - [](auto x) { return x.second; }); + std::ranges::transform(owner_to_ghost, std::back_inserter(send_buffer), + [](auto x) { return x.second; }); // Compute send sizes and displacements std::vector send_sizes, send_disp{0}; @@ -602,17 +605,16 @@ std::vector> exchange_ghost_indexing( vertices.end()); } - std::sort(shared_vertices.begin(), shared_vertices.end()); - shared_vertices.erase( - std::unique(shared_vertices.begin(), shared_vertices.end()), - shared_vertices.end()); + std::ranges::sort(shared_vertices); + auto [unique_end, range_end] = std::ranges::unique(shared_vertices); + shared_vertices.erase(unique_end, range_end); } } // Compute send sizes and offsets std::vector send_sizes(dest.size()); - std::transform(shared_vertices_fwd.begin(), shared_vertices_fwd.end(), - send_sizes.begin(), [](auto& x) { return 3 * x.size(); }); + std::ranges::transform(shared_vertices_fwd, send_sizes.begin(), + [](auto& x) { return 3 * x.size(); }); std::vector send_disp(dest.size() + 1); std::partial_sum(send_sizes.begin(), send_sizes.end(), std::next(send_disp.begin())); @@ -637,8 +639,8 @@ std::vector> exchange_ghost_indexing( for (auto vertex_old : vertices_old) { // Find new vertex index and determine owning rank - auto it = std::lower_bound( - global_local_entities1.begin(), global_local_entities1.end(), + auto it = std::ranges::lower_bound( + global_local_entities1, std::pair(vertex_old, 0), [](auto& a, auto& b) { return a.first < b.first; }); assert(it != global_local_entities1.end()); @@ -672,8 +674,9 @@ std::vector> exchange_ghost_indexing( data.reserve(recv_buffer.size() / 3); for (std::size_t i = 0; i < recv_buffer.size(); i += 3) data.push_back({recv_buffer[i], recv_buffer[i + 1], recv_buffer[i + 2]}); - std::sort(data.begin(), data.end()); - data.erase(std::unique(data.begin(), data.end()), data.end()); + std::ranges::sort(data); + auto [unique_end, range_end] = std::ranges::unique(data); + data.erase(unique_end, range_end); MPI_Comm_free(&comm); @@ -697,10 +700,9 @@ std::vector convert_to_local_indexing( std::transform(g.begin(), std::next(g.begin(), data.size()), data.begin(), [&global_to_local](auto i) { - auto it = std::lower_bound( - global_to_local.begin(), global_to_local.end(), - std::pair(i, 0), - [](auto& a, auto& b) { return a.first < b.first; }); + auto it = std::ranges::lower_bound( + global_to_local, i, std::ranges::less(), + [](auto& e) { return e.first; }); assert(it != global_to_local.end()); assert(it->first == i); return it->second; @@ -747,7 +749,7 @@ Topology::Topology(MPI_Comm comm, const std::vector& cell_types) assert(!cell_types.empty()); std::int8_t tdim = cell_dim(cell_types[0]); assert(tdim > 0); - for (auto ct : cell_types) + for ([[maybe_unused]] auto ct : cell_types) assert(cell_dim(ct) == tdim); _interprocess_facets.resize(1); @@ -857,7 +859,7 @@ std::int32_t Topology::create_entities(int dim) // Store boundary facets if (dim == this->dim() - 1) { - std::sort(interprocess_entities.begin(), interprocess_entities.end()); + std::ranges::sort(interprocess_entities); assert(index < _interprocess_facets.size()); _interprocess_facets[index] = std::move(interprocess_entities); } @@ -1041,7 +1043,7 @@ Topology mesh::create_topology( assert(ghost_owners.size() == cells.size()); assert(original_cell_index.size() == cells.size()); - LOG(INFO) << "Create topology (generalised)"; + spdlog::info("Create topology (generalised)"); // Check cell data consistency and compile spans of owned and ghost cells std::vector num_local_cells(cell_type.size()); std::vector> owned_cells; @@ -1092,12 +1094,12 @@ Topology mesh::create_topology( else unowned_vertices.push_back(global_index); } - dolfinx::radix_sort(std::span(unowned_vertices)); + dolfinx::radix_sort(unowned_vertices); // Add owned but shared vertices to owned_vertices, and sort owned_vertices.insert(owned_vertices.end(), owned_shared_vertices.begin(), owned_shared_vertices.end()); - dolfinx::radix_sort(std::span(owned_vertices)); + dolfinx::radix_sort(owned_vertices); } // Number all owned vertices, iterating over vertices cell-wise @@ -1108,8 +1110,7 @@ Topology mesh::create_topology( { for (auto vtx : cells[i]) { - auto it = std::lower_bound(owned_vertices.begin(), owned_vertices.end(), - vtx); + auto it = std::ranges::lower_bound(owned_vertices, vtx); if (it != owned_vertices.end() and *it == vtx) { std::size_t pos = std::distance(owned_vertices.begin(), it); @@ -1161,8 +1162,7 @@ Topology mesh::create_topology( for (std::size_t i = 0; i < unowned_vertex_data.size(); i += 3) { const std::int64_t idx_global = unowned_vertex_data[i]; - auto it = std::lower_bound(unowned_vertices.begin(), - unowned_vertices.end(), idx_global); + auto it = std::ranges::lower_bound(unowned_vertices, idx_global); assert(it != unowned_vertices.end() and *it == idx_global); std::size_t pos = std::distance(unowned_vertices.begin(), it); assert(local_vertex_indices_unowned[pos] < 0); @@ -1177,20 +1177,15 @@ Topology mesh::create_topology( global_to_local_vertices; global_to_local_vertices.reserve(owned_vertices.size() + unowned_vertices.size()); - std::transform(owned_vertices.begin(), owned_vertices.end(), - local_vertex_indices.begin(), - std::back_inserter(global_to_local_vertices), - [](auto idx0, auto idx1) { - return std::pair(idx0, idx1); - }); - std::transform(unowned_vertices.begin(), unowned_vertices.end(), - local_vertex_indices_unowned.begin(), - std::back_inserter(global_to_local_vertices), - [](auto idx0, auto idx1) { - return std::pair(idx0, idx1); - }); - std::sort(global_to_local_vertices.begin(), - global_to_local_vertices.end()); + std::ranges::transform( + owned_vertices, local_vertex_indices, + std::back_inserter(global_to_local_vertices), [](auto idx0, auto idx1) + { return std::pair(idx0, idx1); }); + std::ranges::transform( + unowned_vertices, local_vertex_indices_unowned, + std::back_inserter(global_to_local_vertices), [](auto idx0, auto idx1) + { return std::pair(idx0, idx1); }); + std::ranges::sort(global_to_local_vertices); // Send (from the ghost cell owner) and receive global indices for // ghost vertices that are not on the process boundary. Data is @@ -1215,8 +1210,7 @@ Topology mesh::create_topology( for (auto& data : recv_data) { std::int64_t global_idx_old = data[0]; - auto it0 = std::lower_bound(unowned_vertices.begin(), - unowned_vertices.end(), global_idx_old); + auto it0 = std::ranges::lower_bound(unowned_vertices, global_idx_old); if (it0 != unowned_vertices.end() and *it0 == global_idx_old) { if (std::size_t pos = std::distance(unowned_vertices.begin(), it0); @@ -1237,19 +1231,17 @@ Topology mesh::create_topology( std::vector> global_to_local_vertices; global_to_local_vertices.reserve(owned_vertices.size() + unowned_vertices.size()); - std::transform( - owned_vertices.begin(), owned_vertices.end(), - local_vertex_indices.begin(), + std::ranges::transform( + owned_vertices, local_vertex_indices, std::back_inserter(global_to_local_vertices), [](auto idx0, auto idx1) -> std::pair { return {idx0, idx1}; }); - std::transform( - unowned_vertices.begin(), unowned_vertices.end(), - local_vertex_indices_unowned.begin(), + std::ranges::transform( + unowned_vertices, local_vertex_indices_unowned, std::back_inserter(global_to_local_vertices), [](auto idx0, auto idx1) -> std::pair { return {idx0, idx1}; }); - std::sort(global_to_local_vertices.begin(), global_to_local_vertices.end()); + std::ranges::sort(global_to_local_vertices); std::vector> _cells_local_idx(cells.size()); for (std::size_t i = 0; i < cell_type.size(); ++i) @@ -1281,8 +1273,10 @@ Topology mesh::create_topology( // Build list of ranks that own vertices that are ghosted by this // rank (out edges) std::vector src = ghost_vertex_owners; - dolfinx::radix_sort(std::span(src)); - src.erase(std::unique(src.begin(), src.end()), src.end()); + dolfinx::radix_sort(src); + auto [unique_end, range_end] = std::ranges::unique(src); + src.erase(unique_end, range_end); + dest = dolfinx::MPI::compute_graph_edges_nbx(comm, src); } @@ -1327,7 +1321,7 @@ mesh::create_topology(MPI_Comm comm, std::span cells, std::span ghost_owners, CellType cell_type, std::span boundary_vertices) { - LOG(INFO) << "Create topology (single cell type)"; + spdlog::info("Create topology (single cell type)"); return create_topology(comm, {cell_type}, {cells}, {original_cell_index}, {ghost_owners}, boundary_vertices); @@ -1348,9 +1342,10 @@ mesh::create_subtopology(const Topology& topology, int dim, { // FIXME Make this an input requirement? std::vector _entities(entities.begin(), entities.end()); - std::sort(_entities.begin(), _entities.end()); - _entities.erase(std::unique(_entities.begin(), _entities.end()), - _entities.end()); + std::ranges::sort(_entities); + auto [unique_end, range_end] = std::ranges::unique(_entities); + _entities.erase(unique_end, range_end); + auto [_submap, _subentities] = common::create_sub_index_map(*topology.index_map(dim), _entities); submap = std::make_shared(std::move(_submap)); @@ -1430,7 +1425,7 @@ std::vector mesh::entities_to_index(const Topology& topology, int dim, std::span entities) { - LOG(INFO) << "Build list of mesh entity indices from the entity vertices."; + spdlog::info("Build list of mesh entity indices from the entity vertices."); // Tagged entity topological dimension auto map_e = topology.index_map(dim); @@ -1454,8 +1449,8 @@ mesh::entities_to_index(const Topology& topology, int dim, for (int e = 0; e < num_entities_mesh; ++e) { auto vertices = e_to_v->links(e); - std::copy(vertices.begin(), vertices.end(), key.begin()); - std::sort(key.begin(), key.end()); + std::ranges::copy(vertices, key.begin()); + std::ranges::sort(key); auto ins = entity_key_to_index.insert({key, e}); if (!ins.second) throw std::runtime_error("Duplicate mesh entity detected."); @@ -1470,8 +1465,8 @@ mesh::entities_to_index(const Topology& topology, int dim, for (std::size_t e = 0; e < entities.size(); e += num_vertices_per_entity) { auto v = entities.subspan(e, num_vertices_per_entity); - std::copy(v.begin(), v.end(), vertices.begin()); - std::sort(vertices.begin(), vertices.end()); + std::ranges::copy(v, vertices.begin()); + std::ranges::sort(vertices); if (auto it = entity_key_to_index.find(vertices); it != entity_key_to_index.end()) { diff --git a/cpp/dolfinx/mesh/Topology.h b/cpp/dolfinx/mesh/Topology.h index 01a2d8c1749..e3f5cfdb1a2 100644 --- a/cpp/dolfinx/mesh/Topology.h +++ b/cpp/dolfinx/mesh/Topology.h @@ -151,16 +151,18 @@ class Topology /// @brief Returns the permutation information const std::vector& get_cell_permutation_info() const; - /// @brief Get the permutation number to apply to a facet. + /// @brief Get the numbers that encode the number of permutations to apply to + /// facets. /// - /// The permutations are numbered so that: + /// The permutations are encoded so that: /// /// - `n % 2` gives the number of reflections to apply /// - `n // 2` gives the number of rotations to apply /// - /// Each column of the returned array represents a cell, and each row - /// a facet of that cell. - /// @return The permutation number + /// The data is stored in a flattened 2D array, so that `data[cell_index * + /// facets_per_cell + facet_index]` contains the facet with index + /// `facet_index` of the cell with index `cell_index`. + /// @return The encoded permutation info /// @note An exception is raised if the permutations have not been /// computed const std::vector& get_facet_permutations() const; @@ -189,13 +191,19 @@ class Topology /// @brief Compute entity permutations and reflections. void create_entity_permutations(); - /// @brief List of inter-process facets, if facet topology has been - /// computed. + /// @brief List of inter-process facets. + /// + /// "Inter-process" facets are facets that are connected (1) to a cell + /// that is owned by the calling process (rank) and (2) to a cell that + /// is owned by another process. + /// + /// @pre Inter-process facets are available only if facet topology has + /// been computed. const std::vector& interprocess_facets() const; /// @brief List of inter-process facets, if facet topology has been - /// computed, for the facet type in `Topology::entity_types` identified by - /// index + /// computed, for the facet type in `Topology::entity_types` + /// identified by index. /// @param index Index of facet type const std::vector& interprocess_facets(std::int8_t index) const; diff --git a/cpp/dolfinx/mesh/generation.h b/cpp/dolfinx/mesh/generation.h index d0abe1e0396..ba2fb2fd187 100644 --- a/cpp/dolfinx/mesh/generation.h +++ b/cpp/dolfinx/mesh/generation.h @@ -1,4 +1,4 @@ -// Copyright (C) 2005-2023 Anders Logg and Garth N. Wells +// Copyright (C) 2005-2024 Anders Logg and Garth N. Wells // // This file is part of DOLFINx (https://www.fenicsproject.org) // @@ -9,12 +9,16 @@ #include "Mesh.h" #include "cell_types.h" #include "utils.h" +#include #include -#include +#include #include #include +#include #include #include +#include +#include #include namespace dolfinx::mesh @@ -33,37 +37,40 @@ enum class DiagonalType namespace impl { template -Mesh build_tri(MPI_Comm comm, std::array, 2> p, - std::array n, +std::tuple, std::vector> +create_interval_cells(std::array p, std::int64_t n); + +template +Mesh build_tri(MPI_Comm comm, std::array, 2> p, + std::array n, const CellPartitionFunction& partitioner, DiagonalType diagonal); template -Mesh build_quad(MPI_Comm comm, const std::array, 2> p, - std::array n, +Mesh build_quad(MPI_Comm comm, const std::array, 2> p, + std::array n, const CellPartitionFunction& partitioner); template -std::vector create_geom(MPI_Comm comm, - std::array, 2> p, - std::array n); +std::vector create_geom(MPI_Comm comm, std::array, 2> p, + std::array n); template Mesh build_tet(MPI_Comm comm, MPI_Comm subcomm, - std::array, 2> p, - std::array n, + std::array, 2> p, + std::array n, const CellPartitionFunction& partitioner); template Mesh build_hex(MPI_Comm comm, MPI_Comm subcomm, - std::array, 2> p, - std::array n, + std::array, 2> p, + std::array n, const CellPartitionFunction& partitioner); template Mesh build_prism(MPI_Comm comm, MPI_Comm subcomm, - std::array, 2> p, - std::array n, + std::array, 2> p, + std::array n, const CellPartitionFunction& partitioner); } // namespace impl @@ -89,10 +96,19 @@ Mesh build_prism(MPI_Comm comm, MPI_Comm subcomm, /// @return Mesh template Mesh create_box(MPI_Comm comm, MPI_Comm subcomm, - std::array, 2> p, - std::array n, CellType celltype, + std::array, 2> p, + std::array n, CellType celltype, CellPartitionFunction partitioner = nullptr) { + if (std::ranges::any_of(n, [](auto e) { return e < 1; })) + throw std::runtime_error("At least one cell per dimension is required"); + + for (int32_t i = 0; i < 3; i++) + { + if (p[0][i] >= p[1][i]) + throw std::runtime_error("It must hold p[0] < p[1]."); + } + if (!partitioner and dolfinx::MPI::size(comm) > 1) partitioner = create_cell_partitioner(); @@ -126,8 +142,8 @@ Mesh create_box(MPI_Comm comm, MPI_Comm subcomm, /// across MPI ranks. /// @return Mesh template -Mesh create_box(MPI_Comm comm, std::array, 2> p, - std::array n, CellType celltype, +Mesh create_box(MPI_Comm comm, std::array, 2> p, + std::array n, CellType celltype, const CellPartitionFunction& partitioner = nullptr) { return create_box(comm, comm, p, n, celltype, partitioner); @@ -150,11 +166,20 @@ Mesh create_box(MPI_Comm comm, std::array, 2> p, /// @param[in] diagonal Direction of diagonals /// @return Mesh template -Mesh create_rectangle(MPI_Comm comm, std::array, 2> p, - std::array n, CellType celltype, +Mesh create_rectangle(MPI_Comm comm, std::array, 2> p, + std::array n, CellType celltype, CellPartitionFunction partitioner, DiagonalType diagonal = DiagonalType::right) { + if (std::ranges::any_of(n, [](auto e) { return e < 1; })) + throw std::runtime_error("At least one cell per dimension is required"); + + for (int32_t i = 0; i < 2; i++) + { + if (p[0][i] >= p[1][i]) + throw std::runtime_error("It must hold p[0] < p[1]."); + } + if (!partitioner and dolfinx::MPI::size(comm) > 1) partitioner = create_cell_partitioner(); @@ -184,8 +209,8 @@ Mesh create_rectangle(MPI_Comm comm, std::array, 2> p, /// @param[in] diagonal Direction of diagonals /// @return Mesh template -Mesh create_rectangle(MPI_Comm comm, std::array, 2> p, - std::array n, CellType celltype, +Mesh create_rectangle(MPI_Comm comm, std::array, 2> p, + std::array n, CellType celltype, DiagonalType diagonal = DiagonalType::right) { return create_rectangle(comm, p, n, celltype, nullptr, diagonal); @@ -194,131 +219,120 @@ Mesh create_rectangle(MPI_Comm comm, std::array, 2> p, /// @brief Interval mesh of the 1D line `[a, b]`. /// /// Given `n` cells in the axial direction, the total number of -/// intervals will be `n` and the total number of vertices will be `n + -/// 1`. +/// intervals will be `n` and the total number of vertices will be +/// `n + 1`. /// -/// @param[in] comm MPI communicator to build the mesh on -/// @param[in] nx Number of cells -/// @param[in] p End points of the interval +/// @param[in] comm MPI communicator to build the mesh on. +/// @param[in] n Number of cells. +/// @param[in] p End points of the interval. +/// @param[in] ghost_mode ghost mode of the created mesh, defaults to none /// @param[in] partitioner Partitioning function for distributing cells /// across MPI ranks. -/// @return A mesh +/// @return A mesh. template -Mesh create_interval(MPI_Comm comm, std::size_t nx, std::array p, +Mesh create_interval(MPI_Comm comm, std::int64_t n, std::array p, + mesh::GhostMode ghost_mode = mesh::GhostMode::none, CellPartitionFunction partitioner = nullptr) { + if (n < 1) + throw std::runtime_error("At least one cell is required."); + + const auto [a, b] = p; + if (a >= b) + throw std::runtime_error("It must hold p[0] < p[1]."); + if (std::abs(a - b) < std::numeric_limits::epsilon()) + { + throw std::runtime_error( + "Length of interval is zero. Check your dimensions."); + } + if (!partitioner and dolfinx::MPI::size(comm) > 1) - partitioner = create_cell_partitioner(); + partitioner = create_cell_partitioner(ghost_mode); fem::CoordinateElement element(CellType::interval, 1); - std::vector x; - std::vector cells; if (dolfinx::MPI::rank(comm) == 0) { - const T a = p[0]; - const T b = p[1]; - const T ab = (b - a) / static_cast(nx); - - if (std::abs(a - b) < std::numeric_limits::epsilon()) - { - throw std::runtime_error( - "Length of interval is zero. Check your dimensions."); - } - - if (b < a) - { - throw std::runtime_error( - "Interval length is negative. Check order of arguments."); - } - - if (nx < 1) - throw std::runtime_error( - "Number of points on interval must be at least 1"); - - // Create vertices - x.resize(nx + 1); - for (std::size_t ix = 0; ix <= nx; ix++) - x[ix] = a + ab * static_cast(ix); - - // Create intervals - cells.resize(nx * 2); - for (std::size_t ix = 0; ix < nx; ++ix) - for (std::size_t j = 0; j < 2; ++j) - cells[2 * ix + j] = ix + j; - + auto [x, cells] = impl::create_interval_cells(p, n); return create_mesh(comm, MPI_COMM_SELF, cells, element, MPI_COMM_SELF, x, {x.size(), 1}, partitioner); } else { - return create_mesh(comm, MPI_COMM_NULL, {}, element, MPI_COMM_NULL, x, - {x.size(), 1}, partitioner); + return create_mesh(comm, MPI_COMM_NULL, {}, element, MPI_COMM_NULL, + std::vector{}, {0, 1}, partitioner); } } namespace impl { + template -std::vector create_geom(MPI_Comm comm, - std::array, 2> p, - std::array n) +std::tuple, std::vector> +create_interval_cells(std::array p, std::int64_t n) { - // Extract data - const std::array p0 = p[0]; - const std::array p1 = p[1]; - std::int64_t nx = n[0]; - std::int64_t ny = n[1]; - std::int64_t nz = n[2]; + const auto [a, b] = p; - const std::int64_t n_points = (nx + 1) * (ny + 1) * (nz + 1); - std::array range_p = dolfinx::MPI::local_range( - dolfinx::MPI::rank(comm), n_points, dolfinx::MPI::size(comm)); + const T h = (b - a) / static_cast(n); + + // Create vertices + std::vector x(n + 1); + std::ranges::generate(x, [i = std::int64_t(0), a, h]() mutable + { return a + h * static_cast(i++); }); - // Extract minimum and maximum coordinates - const double x0 = std::min(p0[0], p1[0]); - const double x1 = std::max(p0[0], p1[0]); - const double y0 = std::min(p0[1], p1[1]); - const double y1 = std::max(p0[1], p1[1]); - const double z0 = std::min(p0[2], p1[2]); - const double z1 = std::max(p0[2], p1[2]); - - const T a = x0; - const T b = x1; - const T ab = (b - a) / static_cast(nx); - const T c = y0; - const T d = y1; - const T cd = (d - c) / static_cast(ny); - const T e = z0; - const T f = z1; - const T ef = (f - e) / static_cast(nz); - - if (std::abs(x0 - x1) < 2.0 * std::numeric_limits::epsilon() - or std::abs(y0 - y1) < 2.0 * std::numeric_limits::epsilon() - or std::abs(z0 - z1) < 2.0 * std::numeric_limits::epsilon()) + // Create intervals -> cells=[0, 1, 1, ..., n-1, n-1, n] + std::vector cells(2 * n); + for (std::size_t ix = 0; ix < cells.size() / 2; ++ix) { - throw std::runtime_error( - "Box seems to have zero width, height or depth. Check dimensions"); + cells[2 * ix] = ix; + cells[2 * ix + 1] = ix + 1; } - if (nx < 1 or ny < 1 or nz < 1) + return {std::move(x), std::move(cells)}; +} + +template +std::vector create_geom(MPI_Comm comm, std::array, 2> p, + std::array n) +{ + // Extract data + auto [p0, p1] = p; + const auto [nx, ny, nz] = n; + + assert(std::ranges::all_of(n, [](auto e) { return e >= 1; })); + for (std::int64_t i = 0; i < 3; i++) + assert(p0[i] < p1[i]); + + // Structured grid cuboid extents + const std::array extents = { + (p1[0] - p0[0]) / static_cast(nx), + (p1[1] - p0[1]) / static_cast(ny), + (p1[2] - p0[2]) / static_cast(nz), + }; + + if (std::ranges::any_of( + extents, [](auto e) + { return std::abs(e) < 2.0 * std::numeric_limits::epsilon(); })) { throw std::runtime_error( - "BoxMesh has non-positive number of vertices in some dimension"); + "Box seems to have zero width, height or depth. Check dimensions"); } + const std::int64_t n_points = (nx + 1) * (ny + 1) * (nz + 1); + const auto [range_begin, range_end] = dolfinx::MPI::local_range( + dolfinx::MPI::rank(comm), n_points, dolfinx::MPI::size(comm)); + std::vector geom; - geom.reserve((range_p[1] - range_p[0]) * 3); + geom.reserve((range_end - range_begin) * 3); const std::int64_t sqxy = (nx + 1) * (ny + 1); - for (std::int64_t v = range_p[0]; v < range_p[1]; ++v) + for (std::int64_t v = range_begin; v < range_end; ++v) { - const std::int64_t iz = v / sqxy; + // lexiographic index to spatial index const std::int64_t p = v % sqxy; - const std::int64_t iy = p / (nx + 1); - const std::int64_t ix = p % (nx + 1); - const T z = e + ef * static_cast(iz); - const T y = c + cd * static_cast(iy); - const T x = a + ab * static_cast(ix); - geom.insert(geom.end(), {x, y, z}); + std::array idx = {p % (nx + 1), p / (nx + 1), v / sqxy}; + + // vertex = p0 + idx * extents (elementwise) + for (std::size_t i = 0; i < idx.size(); i++) + geom.push_back(p0[i] + static_cast(idx[i]) * extents[i]); } return geom; @@ -326,20 +340,19 @@ std::vector create_geom(MPI_Comm comm, template Mesh build_tet(MPI_Comm comm, MPI_Comm subcomm, - std::array, 2> p, - std::array n, + std::array, 2> p, + std::array n, const CellPartitionFunction& partitioner) { common::Timer timer("Build BoxMesh (tetrahedra)"); std::vector x; std::vector cells; + fem::CoordinateElement element(CellType::tetrahedron, 1); if (subcomm != MPI_COMM_NULL) { x = create_geom(subcomm, p, n); - const std::int64_t nx = n[0]; - const std::int64_t ny = n[1]; - const std::int64_t nz = n[2]; + const auto [nx, ny, nz] = n; const std::int64_t n_cells = nx * ny * nz; std::array range_c = dolfinx::MPI::local_range( @@ -349,10 +362,10 @@ Mesh build_tet(MPI_Comm comm, MPI_Comm subcomm, // Create tetrahedra for (std::int64_t i = range_c[0]; i < range_c[1]; ++i) { - const std::size_t iz = i / (nx * ny); - const std::size_t j = i % (nx * ny); - const std::size_t iy = j / nx; - const std::size_t ix = j % nx; + const std::int64_t iz = i / (nx * ny); + const std::int64_t j = i % (nx * ny); + const std::int64_t iy = j / nx; + const std::int64_t ix = j % nx; const std::int64_t v0 = iz * (nx + 1) * (ny + 1) + iy * (nx + 1) + ix; const std::int64_t v1 = v0 + 1; const std::int64_t v2 = v0 + (nx + 1); @@ -369,28 +382,26 @@ Mesh build_tet(MPI_Comm comm, MPI_Comm subcomm, } } - fem::CoordinateElement element(CellType::tetrahedron, 1); return create_mesh(comm, subcomm, cells, element, subcomm, x, {x.size() / 3, 3}, partitioner); } template mesh::Mesh build_hex(MPI_Comm comm, MPI_Comm subcomm, - std::array, 2> p, - std::array n, + std::array, 2> p, + std::array n, const CellPartitionFunction& partitioner) { common::Timer timer("Build BoxMesh (hexahedra)"); std::vector x; std::vector cells; + fem::CoordinateElement element(CellType::hexahedron, 1); if (subcomm != MPI_COMM_NULL) { x = create_geom(subcomm, p, n); // Create cuboids - const std::int64_t nx = n[0]; - const std::int64_t ny = n[1]; - const std::int64_t nz = n[2]; + const auto [nx, ny, nz] = n; const std::int64_t n_cells = nx * ny * nz; std::array range_c = dolfinx::MPI::local_range( dolfinx::MPI::rank(subcomm), n_cells, dolfinx::MPI::size(subcomm)); @@ -414,19 +425,19 @@ mesh::Mesh build_hex(MPI_Comm comm, MPI_Comm subcomm, } } - fem::CoordinateElement element(CellType::hexahedron, 1); return create_mesh(comm, subcomm, cells, element, subcomm, x, {x.size() / 3, 3}, partitioner); } template Mesh build_prism(MPI_Comm comm, MPI_Comm subcomm, - std::array, 2> p, - std::array n, + std::array, 2> p, + std::array n, const CellPartitionFunction& partitioner) { std::vector x; std::vector cells; + fem::CoordinateElement element(CellType::prism, 1); if (subcomm != MPI_COMM_NULL) { x = create_geom(subcomm, p, n); @@ -437,10 +448,9 @@ Mesh build_prism(MPI_Comm comm, MPI_Comm subcomm, const std::int64_t n_cells = nx * ny * nz; std::array range_c = dolfinx::MPI::local_range( dolfinx::MPI::rank(comm), n_cells, dolfinx::MPI::size(comm)); - const std::size_t cell_range = range_c[1] - range_c[0]; + const std::int64_t cell_range = range_c[1] - range_c[0]; // Create cuboids - cells.reserve(2 * cell_range * 6); for (std::int64_t i = range_c[0]; i < range_c[1]; ++i) { @@ -462,57 +472,36 @@ Mesh build_prism(MPI_Comm comm, MPI_Comm subcomm, } } - fem::CoordinateElement element(CellType::prism, 1); return create_mesh(comm, subcomm, cells, element, subcomm, x, {x.size() / 3, 3}, partitioner); } template -Mesh build_tri(MPI_Comm comm, std::array, 2> p, - std::array n, +Mesh build_tri(MPI_Comm comm, std::array, 2> p, + std::array n, const CellPartitionFunction& partitioner, DiagonalType diagonal) { fem::CoordinateElement element(CellType::triangle, 1); - std::vector x; - std::vector cells; if (dolfinx::MPI::rank(comm) == 0) { - const std::array p0 = p[0]; - const std::array p1 = p[1]; - - const std::size_t nx = n[0]; - const std::size_t ny = n[1]; + const auto [p0, p1] = p; + const auto [nx, ny] = n; - // Extract minimum and maximum coordinates - const T x0 = std::min(p0[0], p1[0]); - const T x1 = std::max(p0[0], p1[0]); - const T y0 = std::min(p0[1], p1[1]); - const T y1 = std::max(p0[1], p1[1]); + const auto [a, c] = p0; + const auto [b, d] = p1; - const T a = x0; - const T b = x1; const T ab = (b - a) / static_cast(nx); - const T c = y0; - const T d = y1; const T cd = (d - c) / static_cast(ny); - - if (std::abs(x0 - x1) < std::numeric_limits::epsilon() - or std::abs(y0 - y1) < std::numeric_limits::epsilon()) + if (std::abs(b - a) < std::numeric_limits::epsilon() + or std::abs(d - c) < std::numeric_limits::epsilon()) { throw std::runtime_error("Rectangle seems to have zero width, height or " "depth. Check dimensions"); } - if (nx < 1 or ny < 1) - { - throw std::runtime_error( - "Rectangle has non-positive number of vertices in some dimension: " - "number of vertices must be at least 1 in each dimension"); - } - // Create vertices and cells - std::size_t nv, nc; + std::int64_t nv, nc; switch (diagonal) { case DiagonalType::crossed: @@ -524,15 +513,17 @@ Mesh build_tri(MPI_Comm comm, std::array, 2> p, nc = 2 * nx * ny; } + std::vector x; x.reserve(nv * 2); + std::vector cells; cells.reserve(nc * 3); // Create main vertices - std::size_t vertex = 0; - for (std::size_t iy = 0; iy <= ny; iy++) + std::int64_t vertex = 0; + for (std::int64_t iy = 0; iy <= ny; iy++) { - const T x1 = c + cd * static_cast(iy); - for (std::size_t ix = 0; ix <= nx; ix++) + T x1 = c + cd * static_cast(iy); + for (std::int64_t ix = 0; ix <= nx; ix++) x.insert(x.end(), {a + ab * static_cast(ix), x1}); } @@ -540,11 +531,14 @@ Mesh build_tri(MPI_Comm comm, std::array, 2> p, switch (diagonal) { case DiagonalType::crossed: - for (std::size_t iy = 0; iy < ny; iy++) + for (std::int64_t iy = 0; iy < ny; iy++) { - const T x1 = c + cd * (static_cast(iy) + 0.5); - for (std::size_t ix = 0; ix < nx; ix++) - x.insert(x.end(), {a + ab * (static_cast(ix) + 0.5), x1}); + T x1 = c + cd * (static_cast(iy) + 0.5); + for (std::int64_t ix = 0; ix < nx; ix++) + { + T x0 = a + ab * (static_cast(ix) + 0.5); + x.insert(x.end(), {x0, x1}); + } } break; default: @@ -556,15 +550,15 @@ Mesh build_tri(MPI_Comm comm, std::array, 2> p, { case DiagonalType::crossed: { - for (std::size_t iy = 0; iy < ny; iy++) + for (std::int64_t iy = 0; iy < ny; iy++) { - for (std::size_t ix = 0; ix < nx; ix++) + for (std::int64_t ix = 0; ix < nx; ix++) { - const std::size_t v0 = iy * (nx + 1) + ix; - const std::size_t v1 = v0 + 1; - const std::size_t v2 = v0 + (nx + 1); - const std::size_t v3 = v1 + (nx + 1); - const std::size_t vmid = (nx + 1) * (ny + 1) + iy * nx + ix; + std::int64_t v0 = iy * (nx + 1) + ix; + std::int64_t v1 = v0 + 1; + std::int64_t v2 = v0 + (nx + 1); + std::int64_t v3 = v1 + (nx + 1); + std::int64_t vmid = (nx + 1) * (ny + 1) + iy * nx + ix; // Note that v0 < v1 < v2 < v3 < vmid cells.insert(cells.end(), {v0, v1, vmid, v0, v2, vmid, v1, v3, vmid, @@ -576,7 +570,7 @@ Mesh build_tri(MPI_Comm comm, std::array, 2> p, default: { DiagonalType local_diagonal = diagonal; - for (std::size_t iy = 0; iy < ny; iy++) + for (std::int64_t iy = 0; iy < ny; iy++) { // Set up alternating diagonal switch (diagonal) @@ -596,14 +590,12 @@ Mesh build_tri(MPI_Comm comm, std::array, 2> p, default: break; } - for (std::size_t ix = 0; ix < nx; ix++) + for (std::int64_t ix = 0; ix < nx; ix++) { - const std::size_t v0 = iy * (nx + 1) + ix; - const std::size_t v1 = v0 + 1; - const std::size_t v2 = v0 + (nx + 1); - const std::size_t v3 = v1 + (nx + 1); - - std::size_t offset = iy * nx + ix; + std::int64_t v0 = iy * (nx + 1) + ix; + std::int64_t v1 = v0 + 1; + std::int64_t v2 = v0 + (nx + 1); + std::int64_t v3 = v1 + (nx + 1); switch (local_diagonal) { case DiagonalType::left: @@ -636,47 +628,45 @@ Mesh build_tri(MPI_Comm comm, std::array, 2> p, } else { - return create_mesh(comm, MPI_COMM_NULL, cells, element, MPI_COMM_NULL, x, - {x.size() / 2, 2}, partitioner); + return create_mesh(comm, MPI_COMM_NULL, {}, element, MPI_COMM_NULL, + std::vector{}, {0, 2}, partitioner); } } template -Mesh build_quad(MPI_Comm comm, const std::array, 2> p, - std::array n, +Mesh build_quad(MPI_Comm comm, const std::array, 2> p, + std::array n, const CellPartitionFunction& partitioner) { fem::CoordinateElement element(CellType::quadrilateral, 1); - std::vector cells; - std::vector x; if (dolfinx::MPI::rank(comm) == 0) { - const std::size_t nx = n[0]; - const std::size_t ny = n[1]; - const T a = p[0][0]; - const T b = p[1][0]; + const auto [nx, ny] = n; + const auto [a, c] = p[0]; + const auto [b, d] = p[1]; + const T ab = (b - a) / static_cast(nx); - const T c = p[0][1]; - const T d = p[1][1]; const T cd = (d - c) / static_cast(ny); // Create vertices + std::vector x; x.reserve((nx + 1) * (ny + 1) * 2); - std::size_t vertex = 0; - for (std::size_t ix = 0; ix <= nx; ix++) + std::int64_t vertex = 0; + for (std::int64_t ix = 0; ix <= nx; ix++) { T x0 = a + ab * static_cast(ix); - for (std::size_t iy = 0; iy <= ny; iy++) + for (std::int64_t iy = 0; iy <= ny; iy++) x.insert(x.end(), {x0, c + cd * static_cast(iy)}); } // Create rectangles + std::vector cells; cells.reserve(nx * ny * 4); - for (std::size_t ix = 0; ix < nx; ix++) + for (std::int64_t ix = 0; ix < nx; ix++) { - for (std::size_t iy = 0; iy < ny; iy++) + for (std::int64_t iy = 0; iy < ny; iy++) { - std::size_t i0 = ix * (ny + 1); + std::int64_t i0 = ix * (ny + 1); cells.insert(cells.end(), {i0 + iy, i0 + iy + 1, i0 + iy + ny + 1, i0 + iy + ny + 2}); } @@ -687,8 +677,8 @@ Mesh build_quad(MPI_Comm comm, const std::array, 2> p, } else { - return create_mesh(comm, MPI_COMM_NULL, cells, element, MPI_COMM_NULL, x, - {x.size() / 2, 2}, partitioner); + return create_mesh(comm, MPI_COMM_NULL, {}, element, MPI_COMM_NULL, + std::vector{}, {0, 2}, partitioner); } } } // namespace impl diff --git a/cpp/dolfinx/mesh/graphbuild.cpp b/cpp/dolfinx/mesh/graphbuild.cpp index 5c06b55d460..9229c686eb5 100644 --- a/cpp/dolfinx/mesh/graphbuild.cpp +++ b/cpp/dolfinx/mesh/graphbuild.cpp @@ -48,7 +48,7 @@ graph::AdjacencyList compute_nonlocal_dual_graph( std::size_t shape1, std::span cells, const graph::AdjacencyList& local_graph) { - LOG(INFO) << "Build nonlocal part of mesh dual graph"; + spdlog::info("Build nonlocal part of mesh dual graph"); common::Timer timer("Compute non-local part of mesh dual graph"); // TODO: Two possible straightforward optimisations: @@ -107,7 +107,7 @@ graph::AdjacencyList compute_nonlocal_dual_graph( fshape1 = recv_buffer_r[0]; vrange = {-recv_buffer_r[1], recv_buffer_r[2] + 1}; - LOG(2) << "Max. vertices per facet=" << fshape1 << "\n"; + spdlog::debug("Max. vertices per facet={}", fshape1); } const std::int32_t buffer_shape1 = fshape1 + 1; @@ -127,7 +127,7 @@ graph::AdjacencyList compute_nonlocal_dual_graph( dest_to_index.push_back({dolfinx::MPI::index_owner(num_ranks, v0, range), static_cast(i)}); } - std::sort(dest_to_index.begin(), dest_to_index.end()); + std::ranges::sort(dest_to_index); // Build list of dest ranks and count number of items (facets) to // send to each dest post office (by neighbourhood rank) @@ -162,11 +162,12 @@ graph::AdjacencyList compute_nonlocal_dual_graph( // Determine source ranks const std::vector src = dolfinx::MPI::compute_graph_edges_nbx(comm, dest); - LOG(INFO) << "Number of destination and source ranks in non-local dual graph " - "construction, and ratio to total number of ranks: " - << dest.size() << ", " << src.size() << ", " - << static_cast(dest.size()) / num_ranks << ", " - << static_cast(src.size()) / num_ranks; + spdlog::info("Number of destination and source ranks in non-local dual graph " + "construction, and ratio to total number of ranks: {}, {}, " + "{}, {}", + dest.size(), src.size(), + static_cast(dest.size()) / num_ranks, + static_cast(src.size()) / num_ranks); // Create neighbourhood communicator for sending data to // post offices @@ -234,14 +235,15 @@ graph::AdjacencyList compute_nonlocal_dual_graph( // Compute sort permutation for received data std::vector sort_order(recv_buffer.size() / buffer_shape1); std::iota(sort_order.begin(), sort_order.end(), 0); - std::sort(sort_order.begin(), sort_order.end(), - [&recv_buffer, buffer_shape1, fshape1](auto f0, auto f1) - { - auto it0 = std::next(recv_buffer.begin(), f0 * buffer_shape1); - auto it1 = std::next(recv_buffer.begin(), f1 * buffer_shape1); - return std::lexicographical_compare( - it0, std::next(it0, fshape1), it1, std::next(it1, fshape1)); - }); + std::ranges::sort( + sort_order, + [&recv_buffer, buffer_shape1, fshape1](auto f0, auto f1) + { + auto it0 = std::next(recv_buffer.begin(), f0 * buffer_shape1); + auto it1 = std::next(recv_buffer.begin(), f1 * buffer_shape1); + return std::lexicographical_compare(it0, std::next(it0, fshape1), it1, + std::next(it1, fshape1)); + }); auto it = sort_order.begin(); while (it != sort_order.end()) @@ -326,8 +328,8 @@ graph::AdjacencyList compute_nonlocal_dual_graph( { auto e = local_graph.links(i); disp[i] += e.size(); - std::transform(e.begin(), e.end(), std::next(data.begin(), offsets[i]), - [cell_offset](auto x) { return x + cell_offset; }); + std::ranges::transform(e, std::next(data.begin(), offsets[i]), + [cell_offset](auto x) { return x + cell_offset; }); } // Add non-local data @@ -346,59 +348,86 @@ graph::AdjacencyList compute_nonlocal_dual_graph( } //----------------------------------------------------------------------------- } // namespace - //----------------------------------------------------------------------------- std::tuple, std::vector, std::size_t, std::vector> -mesh::build_local_dual_graph(CellType celltype, - std::span cells) +mesh::build_local_dual_graph( + std::span celltypes, + const std::vector>& cells) { - LOG(INFO) << "Build local part of mesh dual graph"; - common::Timer timer("Compute local part of mesh dual graph"); + spdlog::info("Build local part of mesh dual graph (mixed)"); + common::Timer timer("Compute local part of mesh dual graph (mixed)"); + + std::size_t ncells_local + = std::accumulate(cells.begin(), cells.end(), 0, + [](std::size_t s, std::span c) + { return s + c.size(); }); - if (cells.empty()) + if (ncells_local == 0) { // Empty mesh on this process return {graph::AdjacencyList(0), std::vector(), 0, std::vector()}; } - int tdim = mesh::cell_dim(celltype); - int num_cell_vertices = mesh::cell_num_entities(celltype, 0); - const std::int32_t num_cells = cells.size() / num_cell_vertices; - - // Note: only meshes with a single cell type are supported - const graph::AdjacencyList cell_facets - = mesh::get_entity_vertices(celltype, tdim - 1); + if (cells.size() != celltypes.size()) + { + throw std::runtime_error( + "Number of cell types must match number of cell arrays."); + }; - // Determine maximum number of vertices for facet + // Create indexing offset for each cell type + // and determine max number of vertices per facet + std::vector cell_offsets = {0}; int max_vertices_per_facet = 0; - for (int i = 0; i < cell_facets.num_nodes(); ++i) + int facet_count = 0; + int tdim = mesh::cell_dim(celltypes.front()); + for (std::size_t j = 0; j < cells.size(); ++j) { - max_vertices_per_facet - = std::max(max_vertices_per_facet, cell_facets.num_links(i)); + assert(tdim == mesh::cell_dim(celltypes[j])); + int num_cell_vertices = mesh::cell_num_entities(celltypes[j], 0); + std::int32_t num_cells = cells[j].size() / num_cell_vertices; + cell_offsets.push_back(cell_offsets.back() + num_cells); + facet_count += mesh::cell_num_entities(celltypes[j], tdim - 1) * num_cells; + + graph::AdjacencyList cell_facets + = mesh::get_entity_vertices(celltypes[j], tdim - 1); + + // Determine maximum number of vertices for facet + for (std::int32_t i = 0; i < cell_facets.num_nodes(); ++i) + { + max_vertices_per_facet + = std::max(max_vertices_per_facet, cell_facets.num_links(i)); + } } const int shape1 = max_vertices_per_facet + 1; - - // Build a list of facets, defined by sorted vertices, with the connected - // cell index after the vertices std::vector facets; - facets.reserve(num_cells * cell_facets.num_nodes() * shape1); - for (std::int32_t c = 0; c < num_cells; ++c) + facets.reserve(facet_count * shape1); + + for (std::size_t j = 0; j < cells.size(); ++j) { - // Loop over cell facets - auto v = cells.subspan(num_cell_vertices * c, num_cell_vertices); - for (int f = 0; f < cell_facets.num_nodes(); ++f) + // Build a list of facets, defined by sorted vertices, with the connected + // cell index after the vertices + int num_cell_vertices = mesh::cell_num_entities(celltypes[j], 0); + std::int32_t num_cells = cells[j].size() / num_cell_vertices; + graph::AdjacencyList cell_facets + = mesh::get_entity_vertices(celltypes[j], tdim - 1); + + for (std::int32_t c = 0; c < num_cells; ++c) { - auto facet_vertices = cell_facets.links(f); - std::transform(facet_vertices.begin(), facet_vertices.end(), - std::back_inserter(facets), - [v](auto idx) { return v[idx]; }); - std::sort(std::prev(facets.end(), facet_vertices.size()), facets.end()); - facets.insert(facets.end(), - max_vertices_per_facet - facet_vertices.size(), -1); - facets.push_back(c); + // Loop over cell facets + auto v = cells[j].subspan(num_cell_vertices * c, num_cell_vertices); + for (int f = 0; f < cell_facets.num_nodes(); ++f) + { + auto facet_vertices = cell_facets.links(f); + std::ranges::transform(facet_vertices, std::back_inserter(facets), + [v](auto idx) { return v[idx]; }); + std::sort(std::prev(facets.end(), facet_vertices.size()), facets.end()); + facets.insert(facets.end(), + max_vertices_per_facet - facet_vertices.size(), -1); + facets.push_back(c + cell_offsets[j]); + } } } @@ -460,7 +489,7 @@ mesh::build_local_dual_graph(CellType celltype, // -- Build adjacency list data - std::vector sizes(num_cells, 0); + std::vector sizes(cell_offsets.back(), 0); for (auto e : edges) { ++sizes[e[0]]; @@ -485,24 +514,23 @@ mesh::build_local_dual_graph(CellType celltype, } //----------------------------------------------------------------------------- graph::AdjacencyList -mesh::build_dual_graph(MPI_Comm comm, CellType celltype, - const graph::AdjacencyList& cells) +mesh::build_dual_graph(MPI_Comm comm, std::span celltypes, + const std::vector>& cells) { - LOG(INFO) << "Building mesh dual graph"; + spdlog::info("Building mesh dual graph"); // Compute local part of dual graph (cells are graph nodes, and edges // are connections by facet) auto [local_graph, facets, shape1, fcells] - = mesh::build_local_dual_graph(celltype, cells.array()); - assert(local_graph.num_nodes() == cells.num_nodes()); + = mesh::build_local_dual_graph(celltypes, cells); // Extend with nonlocal edges and convert to global indices graph::AdjacencyList graph = compute_nonlocal_dual_graph(comm, facets, shape1, fcells, local_graph); - LOG(INFO) << "Graph edges (local: " << local_graph.offsets().back() - << ", non-local: " - << graph.offsets().back() - local_graph.offsets().back() << ")"; + spdlog::info("Graph edges (local: {}, non-local: {})", + local_graph.offsets().back(), + graph.offsets().back() - local_graph.offsets().back()); return graph; } diff --git a/cpp/dolfinx/mesh/graphbuild.h b/cpp/dolfinx/mesh/graphbuild.h index 13e202228eb..471604bc294 100644 --- a/cpp/dolfinx/mesh/graphbuild.h +++ b/cpp/dolfinx/mesh/graphbuild.h @@ -23,14 +23,15 @@ namespace dolfinx::mesh enum class CellType; /// @brief Compute the local part of the dual graph (cell-cell -/// connections via facets) and facet with only one attached cell. +/// connections via facets) and facets with only one attached cell. /// -/// @param[in] celltype Cell type. -/// @param[in] cells Cell vertices (stored as as a flattened list). +/// @param[in] celltypes List of cell types. +/// @param[in] cells Lists of cell vertices (stored as flattened lists, one for +/// each cell type). /// @return /// 1. Local dual graph /// 2. Facets, defined by their vertices, that are shared by only one -/// cell on this rank. The logically 2D is array flattened (row-major). +/// cell on this rank. The logically 2D array is flattened (row-major). /// 3. The number of columns for the facet data array (2). /// 4. The attached cell (local index) to each returned facet in (2). /// @@ -38,11 +39,14 @@ enum class CellType; /// x]`, where `v_i` is a vertex global index, `x` is a padding value /// (all padding values will be equal). /// -/// @note The return data will likely change once we support mixed -/// topology meshes. +/// @note The cells of each cell type are numbered locally consecutively, +/// i.e. if there are `n` cells of type `0` and `m` cells of type `1`, then +/// cells of type `0` are numbered `0..(n-1)` and cells of type `1` are numbered +/// `n..(n+m-1)` respectively, in the returned dual graph. std::tuple, std::vector, std::size_t, std::vector> -build_local_dual_graph(CellType celltype, std::span cells); +build_local_dual_graph(std::span celltypes, + const std::vector>& cells); /// @brief Build distributed mesh dual graph (cell-cell connections via /// facets) from minimal mesh data. @@ -52,12 +56,14 @@ build_local_dual_graph(CellType celltype, std::span cells); /// @note Collective function /// /// @param[in] comm The MPI communicator -/// @param[in] celltype The cell type -/// @param[in] cells Collection of cells, defined by the cell vertices -/// from which to build the dual graph +/// @param[in] celltypes List of cell types +/// @param[in] cells Collections of cells, defined by the cell vertices +/// from which to build the dual graph, as flattened arrays for each cell type +/// in `celltypes`. +/// @note `cells` and `celltypes` must have the same size. /// @return The dual graph graph::AdjacencyList -build_dual_graph(MPI_Comm comm, CellType celltype, - const graph::AdjacencyList& cells); +build_dual_graph(MPI_Comm comm, std::span celltypes, + const std::vector>& cells); } // namespace dolfinx::mesh diff --git a/cpp/dolfinx/mesh/permutationcomputation.cpp b/cpp/dolfinx/mesh/permutationcomputation.cpp index be372c4ce07..9376adc240b 100644 --- a/cpp/dolfinx/mesh/permutationcomputation.cpp +++ b/cpp/dolfinx/mesh/permutationcomputation.cpp @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include namespace @@ -21,205 +23,189 @@ using namespace dolfinx; namespace { -//----------------------------------------------------------------------------- -template -std::vector> compute_face_permutations_simplex( - const graph::AdjacencyList& c_to_v, - const graph::AdjacencyList& c_to_f, - const graph::AdjacencyList& f_to_v, int faces_per_cell, - const common::IndexMap& im) +std::pair +compute_triangle_rot_reflect(const std::vector& e_vertices, + const std::vector& vertices) { - const std::int32_t num_cells = c_to_v.num_nodes(); - std::vector> face_perm(num_cells, 0); - std::vector cell_vertices, vertices; - for (int c = 0; c < num_cells; ++c) - { - cell_vertices.resize(c_to_v.links(c).size()); - im.local_to_global(c_to_v.links(c), cell_vertices); - auto cell_faces = c_to_f.links(c); - for (int i = 0; i < faces_per_cell; ++i) - { - // Get the face - const int face = cell_faces[i]; - vertices.resize(f_to_v.links(face).size()); - im.local_to_global(f_to_v.links(face), vertices); - // Orient that triangle so the lowest numbered vertex is the - // origin, and the next vertex anticlockwise from the lowest has a - // lower number than the next vertex clockwise. Find the index of - // the lowest numbered vertex + // Number of rotations + std::uint8_t min_v + = std::distance(e_vertices.begin(), std::ranges::min_element(e_vertices)); - // Store local vertex indices here - std::array e_vertices; + // pre is the (local) number of the next vertex clockwise from the lowest + // numbered vertex + const int pre = e_vertices[(min_v + 2) % 3]; - // Find iterators pointing to cell vertex given a vertex on facet - for (int j = 0; j < 3; ++j) - { - auto it = std::find(cell_vertices.begin(), cell_vertices.end(), - vertices[j]); - // Get the actual local vertex indices - e_vertices[j] = std::distance(cell_vertices.begin(), it); - } + // post is the (local) number of the next vertex anticlockwise from the + // lowest numbered vertex + const int post = e_vertices[(min_v + 1) % 3]; - // Number of rotations - std::uint8_t min_v = 0; - for (int v = 1; v < 3; ++v) - if (e_vertices[v] < e_vertices[min_v]) - min_v = v; - - // pre is the number of the next vertex clockwise from the lowest - // numbered vertex - const int pre = min_v == 0 ? e_vertices[3 - 1] : e_vertices[min_v - 1]; - - // post is the number of the next vertex anticlockwise from the - // lowest numbered vertex - const int post = min_v == 3 - 1 ? e_vertices[0] : e_vertices[min_v + 1]; - - std::uint8_t g_min_v = 0; - for (int v = 1; v < 3; ++v) - if (vertices[v] < vertices[g_min_v]) - g_min_v = v; - - // pre is the number of the next vertex clockwise from the lowest - // numbered vertex - const int g_pre = g_min_v == 0 ? vertices[3 - 1] : vertices[g_min_v - 1]; - - // post is the number of the next vertex anticlockwise from the - // lowest numbered vertex - const int g_post = g_min_v == 3 - 1 ? vertices[0] : vertices[g_min_v + 1]; - - std::uint8_t rots = 0; - if (g_post > g_pre) - rots = min_v <= g_min_v ? g_min_v - min_v : g_min_v + 3 - min_v; - else - rots = g_min_v <= min_v ? min_v - g_min_v : min_v + 3 - g_min_v; - - face_perm[c][3 * i] = (post > pre) == (g_post < g_pre); - face_perm[c][3 * i + 1] = rots % 2; - face_perm[c][3 * i + 2] = rots / 2; - } - } + std::uint8_t g_min_v + = std::distance(vertices.begin(), std::ranges::min_element(vertices)); - return face_perm; + // g_pre is the (global) number of the next vertex clockwise from the lowest + // numbered vertex + const int g_pre = vertices[(g_min_v + 2) % 3]; + + // g_post is the (global) number of the next vertex anticlockwise from the + // lowest numbered vertex + const int g_post = vertices[(g_min_v + 1) % 3]; + + std::uint8_t rots = 0; + if (g_post > g_pre) + rots = (g_min_v + 3 - min_v) % 3; + else + rots = (min_v + 3 - g_min_v) % 3; + + return {(post > pre) == (g_post < g_pre), rots}; +} +//----------------------------------------------------------------------------- +std::pair +compute_quad_rot_reflect(const std::vector& e_vertices, + const std::vector& vertices) +{ + // Find minimum local cell vertex on facet + std::uint8_t min_v + = std::distance(e_vertices.begin(), std::ranges::min_element(e_vertices)); + + // Table of next and previous vertices + // 0 - 2 + // | | + // 1 - 3 + const std::array prev = {2, 0, 3, 1}; + + // pre is the (local) number of the next vertex clockwise from the + // lowest numbered vertex + std::int32_t pre = e_vertices[prev[min_v]]; + + // post is the (local) number of the next vertex anticlockwise + // from the lowest numbered vertex + std::int32_t post = e_vertices[prev[3 - min_v]]; + + // If min_v is 2 or 3, swap: + // 0 - 2 0 - 3 + // | | | | + // 1 - 3 1 - 2 + // Because of the dolfinx ordering (left), in order to compute the number of + // anti-clockwise rotations required correctly, min_v is altered to give the + // ordering on the right. + if (min_v == 2 or min_v == 3) + min_v = 5 - min_v; + + // Find minimum global vertex in facet + std::uint8_t g_min_v + = std::distance(vertices.begin(), std::ranges::min_element(vertices)); + + // rots is the number of rotations to get the lowest numbered + // vertex to the origin + + // g_pre is the (global) number of the next vertex clockwise from the + // lowest numbered vertex + std::int64_t g_pre = vertices[prev[g_min_v]]; + + // g_post is the (global) number of the next vertex anticlockwise + // from the lowest numbered vertex + std::int64_t g_post = vertices[prev[3 - g_min_v]]; + + if (g_min_v == 2 or g_min_v == 3) + g_min_v = 5 - g_min_v; + + std::uint8_t rots = 0; + if (g_post > g_pre) + rots = (g_min_v - min_v + 4) % 4; + else + rots = (min_v - g_min_v + 4) % 4; + return {(post > pre) == (g_post < g_pre), rots}; } //----------------------------------------------------------------------------- template std::vector> -compute_face_permutations_tp(const graph::AdjacencyList& c_to_v, - const graph::AdjacencyList& c_to_f, - const graph::AdjacencyList& f_to_v, - int faces_per_cell, const common::IndexMap& im) +compute_triangle_quad_face_permutations(const mesh::Topology& topology, + int cell_index) { - const std::int32_t num_cells = c_to_v.num_nodes(); - std::vector> face_perm(num_cells, 0); - std::vector cell_vertices, vertices; - for (int c = 0; c < num_cells; ++c) + std::vector cell_types = topology.entity_types(3); + mesh::CellType cell_type = cell_types.at(cell_index); + + // Get face types of the cell and mesh + std::vector mesh_face_types = topology.entity_types(2); + std::vector cell_face_types( + mesh::cell_num_entities(cell_type, 2)); + for (std::size_t i = 0; i < cell_face_types.size(); ++i) + cell_face_types[i] = mesh::cell_facet_type(cell_type, i); + + // Connectivity for each face type + std::vector>> c_to_f; + std::vector>> f_to_v; + + // Create mapping for each face type to cell-local face index + int tdim = topology.dim(); + std::vector> face_type_indices(mesh_face_types.size()); + for (std::size_t i = 0; i < mesh_face_types.size(); ++i) { - cell_vertices.resize(c_to_v.links(c).size()); - im.local_to_global(c_to_v.links(c), cell_vertices); - - auto cell_faces = c_to_f.links(c); - for (int i = 0; i < faces_per_cell; ++i) + for (std::size_t j = 0; j < cell_face_types.size(); ++j) { - // Get the face - const int face = cell_faces[i]; - vertices.resize(f_to_v.links(face).size()); - im.local_to_global(f_to_v.links(face), vertices); - - // Orient that triangle so the lowest numbered vertex is the - // origin, and the next vertex anticlockwise from the lowest has a - // lower number than the next vertex clockwise. Find the index of - // the lowest numbered vertex - - // Store local vertex indices here - std::array e_vertices; - - // Find iterators pointing to cell vertex given a vertex on facet - for (int j = 0; j < 4; ++j) - { - auto it = std::find(cell_vertices.begin(), cell_vertices.end(), - vertices[j]); - // Get the actual local vertex indices - e_vertices[j] = std::distance(cell_vertices.begin(), it); - } - - // Number of rotations - std::uint8_t min_v = 0; - for (int v = 1; v < 4; ++v) - if (e_vertices[v] < e_vertices[min_v]) - min_v = v; - - // pre is the (local) number of the next vertex clockwise from the - // lowest numbered vertex - int pre = 2; - - // post is the (local) number of the next vertex anticlockwise - // from the lowest numbered vertex - int post = 1; - - assert(min_v < 4); - switch (min_v) - { - case 1: - pre = 0; - post = 3; - break; - case 2: - pre = 3; - post = 0; - min_v = 3; - break; - case 3: - pre = 1; - post = 2; - min_v = 2; - break; - } - - std::uint8_t g_min_v = 0; - for (int v = 1; v < 4; ++v) - if (vertices[v] < vertices[g_min_v]) - g_min_v = v; + if (mesh_face_types[i] == cell_face_types[j]) + face_type_indices[i].push_back(j); + } + c_to_f.push_back(topology.connectivity({tdim, cell_index}, {2, i})); + f_to_v.push_back(topology.connectivity({2, i}, {0, 0})); + } - // rots is the number of rotations to get the lowest numbered - // vertex to the origin - // pre is the (local) number of the next vertex clockwise from the - // lowest numbered vertex - int g_pre = 2; + auto c_to_v = topology.connectivity({tdim, cell_index}, {0, 0}); + assert(c_to_v); - // post is the (local) number of the next vertex anticlockwise - // from the lowest numbered vertex - int g_post = 1; + const std::int32_t num_cells = c_to_v->num_nodes(); + std::vector> face_perm(num_cells, 0); + std::vector cell_vertices, vertices; + std::vector e_vertices; + auto im = topology.index_map(0); - assert(g_min_v < 4); - switch (g_min_v) + for (std::size_t t = 0; t < face_type_indices.size(); ++t) + { + spdlog::info("Computing permutations for face type {}", t); + if (!face_type_indices[t].empty()) + { + auto compute_refl_rots = (mesh_face_types[t] == mesh::CellType::triangle) + ? compute_triangle_rot_reflect + : compute_quad_rot_reflect; + for (int c = 0; c < num_cells; ++c) { - case 1: - g_pre = 0; - g_post = 3; - break; - case 2: - g_pre = 3; - g_post = 0; - g_min_v = 3; - break; - case 3: - g_pre = 1; - g_post = 2; - g_min_v = 2; - break; + cell_vertices.resize(c_to_v->links(c).size()); + im->local_to_global(c_to_v->links(c), cell_vertices); + + auto cell_faces = c_to_f[t]->links(c); + for (std::size_t i = 0; i < cell_faces.size(); ++i) + { + // Get the face + const int face = cell_faces[i]; + e_vertices.resize(f_to_v[t]->num_links(face)); + vertices.resize(f_to_v[t]->num_links(face)); + im->local_to_global(f_to_v[t]->links(face), vertices); + + // Orient that triangle or quadrilateral so the lowest numbered + // vertex is the origin, and the next vertex anticlockwise from + // the lowest has a lower number than the next vertex clockwise. + // Find the index of the lowest numbered vertex. + + // Find iterators pointing to cell vertex given a vertex on facet + for (std::size_t j = 0; j < vertices.size(); ++j) + { + auto it = std::find(cell_vertices.begin(), cell_vertices.end(), + vertices[j]); + // Get the actual local vertex indices + e_vertices[j] = std::distance(cell_vertices.begin(), it); + } + + // Compute reflections and rotations for this face type + auto [refl, rots] = compute_refl_rots(e_vertices, vertices); + + // Store bits for this face + int fi = face_type_indices[t][i]; + face_perm[c][3 * fi] = refl; + face_perm[c][3 * fi + 1] = rots % 2; + face_perm[c][3 * fi + 2] = rots / 2; + } } - - std::uint8_t rots = 0; - if (vertices[g_post] > vertices[g_pre]) - rots = min_v <= g_min_v ? g_min_v - min_v : g_min_v + 4 - min_v; - else - rots = g_min_v <= min_v ? min_v - g_min_v : min_v + 4 - g_min_v; - - face_perm[c][3 * i] = (e_vertices[post] > e_vertices[pre]) - == (vertices[g_post] < vertices[g_pre]); - face_perm[c][3 * i + 1] = rots % 2; - face_perm[c][3 * i + 2] = rots / 2; } } @@ -230,7 +216,6 @@ template std::vector> compute_edge_reflections(const mesh::Topology& topology) { - mesh::CellType cell_type = topology.cell_type(); const int tdim = topology.dim(); const int edges_per_cell = cell_num_entities(cell_type, 1); @@ -251,7 +236,7 @@ compute_edge_reflections(const mesh::Topology& topology) std::vector cell_vertices, vertices; for (int c = 0; c < c_to_v->num_nodes(); ++c) { - cell_vertices.resize(c_to_v->links(c).size()); + cell_vertices.resize(c_to_v->num_links(c)); im->local_to_global(c_to_v->links(c), cell_vertices); auto cell_edges = c_to_e->links(c); for (int i = 0; i < edges_per_cell; ++i) @@ -281,35 +266,19 @@ template std::vector> compute_face_permutations(const mesh::Topology& topology) { - const int tdim = topology.dim(); + if (topology.entity_types(3).size() > 1) + { + throw std::runtime_error( + "Cannot compute permutations for mixed topology mesh."); + } + + [[maybe_unused]] const int tdim = topology.dim(); assert(tdim > 2); if (!topology.index_map(2)) throw std::runtime_error("Faces have not been computed."); - // If faces have been computed, the below should exist - auto c_to_v = topology.connectivity(tdim, 0); - assert(c_to_v); - auto c_to_f = topology.connectivity(tdim, 2); - assert(c_to_f); - auto f_to_v = topology.connectivity(2, 0); - assert(f_to_v); - - auto im = topology.index_map(0); - assert(im); - - mesh::CellType cell_type = topology.cell_type(); - const int faces_per_cell = cell_num_entities(cell_type, 2); - if (cell_type == mesh::CellType::triangle - or cell_type == mesh::CellType::tetrahedron) - { - return compute_face_permutations_simplex( - *c_to_v, *c_to_f, *f_to_v, faces_per_cell, *im); - } - else - { - return compute_face_permutations_tp(*c_to_v, *c_to_f, *f_to_v, - faces_per_cell, *im); - } + // Compute face permutations for first cell type in the topology + return compute_triangle_quad_face_permutations(topology, 0); } //----------------------------------------------------------------------------- } // namespace @@ -318,6 +287,7 @@ compute_face_permutations(const mesh::Topology& topology) std::pair, std::vector> mesh::compute_entity_permutations(const mesh::Topology& topology) { + common::Timer t_perm("Compute entity permutations"); const int tdim = topology.dim(); CellType cell_type = topology.cell_type(); const std::int32_t num_cells = topology.connectivity(tdim, 0)->num_nodes(); @@ -328,6 +298,7 @@ mesh::compute_entity_permutations(const mesh::Topology& topology) std::int32_t used_bits = 0; if (tdim > 2) { + spdlog::info("Compute face permutations"); const int faces_per_cell = cell_num_entities(cell_type, 2); const auto face_perm = compute_face_permutations<_BITSETSIZE>(topology); for (int c = 0; c < num_cells; ++c) @@ -349,6 +320,7 @@ mesh::compute_entity_permutations(const mesh::Topology& topology) if (tdim > 1) { + spdlog::info("Compute edge permutations"); const int edges_per_cell = cell_num_entities(cell_type, 1); const auto edge_perm = compute_edge_reflections<_BITSETSIZE>(topology); for (int c = 0; c < num_cells; ++c) diff --git a/cpp/dolfinx/mesh/permutationcomputation.h b/cpp/dolfinx/mesh/permutationcomputation.h index 159a86a7b61..1c38d09d2c8 100644 --- a/cpp/dolfinx/mesh/permutationcomputation.h +++ b/cpp/dolfinx/mesh/permutationcomputation.h @@ -15,23 +15,23 @@ namespace dolfinx::mesh class Topology; /// Compute (1) facet rotation and reflection data, and (2) cell -/// permutation data. This information is used assemble of (1) facet -/// inetgrals and (2) vector elements. +/// permutation data. This information is used in the assembly of (1) facet +/// integrals and (2) most non-Lagrange elements. /// -/// 1. Get the permutation numbers to apply to facets. The -/// permutations are numbered so that: +/// 1. The facet rotation and reflection data is encoded so that: /// /// - `n % 2` gives the number of reflections to apply /// - `n // 2` gives the number of rotations to apply /// -/// Each column of the returned array represents a cell, and each -/// row a facet of that cell. This data is used to permute the -/// quadrature point on facet integrals when data from the cells on -/// both sides of the facet is used. +/// The data is stored in a flattened 2D array, so that `data[cell_index * +/// facets_per_cell + facet_index]` contains the facet with index +/// `facet_index` of the cell with index `cell_index`. This data passed to +/// FFCx kernels, where it is used to permute the quadrature points on facet +/// integrals when data from the cells on both sides of the facet is used. /// -/// 2. Get the permutation information about the entities of each +/// 2. The cell permutation data contains information about the entities of each /// cell, relative to a low-to-high ordering. This data is packed -/// so that a 32 bit int is used for each cell. For 2D cells, one +/// so that a 32-bit int is used for each cell. For 2D cells, one /// bit is used for each edge, to represent whether or not the edge /// is reversed: the least significant bit is for edge 0, the next /// for edge 1, etc. For 3D cells, three bits are used for each diff --git a/cpp/dolfinx/mesh/topologycomputation.cpp b/cpp/dolfinx/mesh/topologycomputation.cpp index 3f5ff9ed4b0..edefdbd1e26 100644 --- a/cpp/dolfinx/mesh/topologycomputation.cpp +++ b/cpp/dolfinx/mesh/topologycomputation.cpp @@ -39,13 +39,14 @@ namespace template graph::AdjacencyList create_adj_list(U& data, std::int32_t size) { - std::sort(data.begin(), data.end()); - data.erase(std::unique(data.begin(), data.end()), data.end()); + std::ranges::sort(data); + auto [unique_end, range_end] = std::ranges::unique(data); + data.erase(unique_end, range_end); std::vector array; array.reserve(data.size()); - std::transform(data.begin(), data.end(), std::back_inserter(array), - [](auto x) { return x.second; }); + std::ranges::transform(data, std::back_inserter(array), + [](auto x) { return x.second; }); std::vector offsets{0}; offsets.reserve(size + 1); @@ -94,7 +95,7 @@ int get_ownership(const U& processes, const V& vertices) /// @param[in] num_entities_per_cell Number of entities per cell /// @param[in] entity_index Initial numbering for each row in /// entity_list -/// @returns Local indices and index map +/// @returns Local indices, the index map and shared entities std::tuple, common::IndexMap, std::vector> get_local_indexing(MPI_Comm comm, const common::IndexMap& vertex_map, std::span entity_list, @@ -114,7 +115,7 @@ get_local_indexing(MPI_Comm comm, const common::IndexMap& vertex_map, // Find the maximum entity index, hence the number of entities std::int32_t entity_count = 0; - if (auto mx = std::max_element(entity_index.begin(), entity_index.end()); + if (auto mx = std::ranges::max_element(entity_index); mx != entity_index.end()) { entity_count = *mx + 1; @@ -129,8 +130,9 @@ get_local_indexing(MPI_Comm comm, const common::IndexMap& vertex_map, // Create unique list of ranks that share vertices (owners of) std::vector ranks(vertex_ranks.array().begin(), vertex_ranks.array().end()); - std::sort(ranks.begin(), ranks.end()); - ranks.erase(std::unique(ranks.begin(), ranks.end()), ranks.end()); + std::ranges::sort(ranks); + auto [unique_end, range_end] = std::ranges::unique(ranks); + ranks.erase(unique_end, range_end); MPI_Comm neighbor_comm; MPI_Dist_graph_create_adjacent( @@ -168,7 +170,7 @@ get_local_indexing(MPI_Comm comm, const common::IndexMap& vertex_map, entity_ranks.insert(entity_ranks.end(), vertex_ranks.links(v).begin(), vertex_ranks.links(v).end()); } - std::sort(entity_ranks.begin(), entity_ranks.end()); + std::ranges::sort(entity_ranks); // If the number of vertices shared with a rank is // 'num_vertices_per_e', then add entity data to the send buffer @@ -180,7 +182,7 @@ get_local_indexing(MPI_Comm comm, const common::IndexMap& vertex_map, if (std::distance(it, it1) == num_vertices_per_e) { vertex_map.local_to_global(entity, vglobal); - std::sort(vglobal.begin(), vglobal.end()); + std::ranges::sort(vglobal); entity_to_local_idx.insert(entity_to_local_idx.end(), vglobal.begin(), vglobal.end()); entity_to_local_idx.push_back(*entity_idx); @@ -188,7 +190,7 @@ get_local_indexing(MPI_Comm comm, const common::IndexMap& vertex_map, // Only send entities that are not known to be ghosts if (ghost_status[*entity_idx] != 1) { - auto itr_local = std::lower_bound(ranks.begin(), ranks.end(), *it); + auto itr_local = std::ranges::lower_bound(ranks, *it); assert(itr_local != ranks.end() and *itr_local == *it); const int r = std::distance(ranks.begin(), itr_local); @@ -205,24 +207,20 @@ get_local_indexing(MPI_Comm comm, const common::IndexMap& vertex_map, perm.resize(entity_to_local_idx.size() / (num_vertices_per_e + 1)); std::iota(perm.begin(), perm.end(), 0); - std::sort(perm.begin(), perm.end(), - [&entities = entity_to_local_idx, - shape = num_vertices_per_e + 1](auto e0, auto e1) - { - auto it0 = std::next(entities.begin(), e0 * shape); - auto it1 = std::next(entities.begin(), e1 * shape); - return std::lexicographical_compare(it0, std::next(it0, shape), - it1, std::next(it1, shape)); - }); - perm.erase(std::unique(perm.begin(), perm.end(), - [&entities = entity_to_local_idx, - shape = num_vertices_per_e + 1](auto e0, auto e1) - { - auto it0 = std::next(entities.begin(), e0 * shape); - auto it1 = std::next(entities.begin(), e1 * shape); - return std::equal(it0, std::next(it0, shape), it1); - }), - perm.end()); + + auto range_by_index = [&, shape = num_vertices_per_e + 1](auto e) + { + auto begin = std::next(entity_to_local_idx.begin(), e * shape); + return std::ranges::subrange(begin, std::next(begin, shape)); + }; + + std::ranges::sort(perm, std::ranges::lexicographical_compare, + range_by_index); + + auto [unique_end, range_end] + = std::ranges::unique(perm, std::ranges::equal, range_by_index); + + perm.erase(unique_end, range_end); } // Get shared entities of this dimension, and also match up an index @@ -306,9 +304,8 @@ get_local_indexing(MPI_Comm comm, const common::IndexMap& vertex_map, shared_entities_data.push_back({idx, ranks[r]}); shared_entities_data.push_back({idx, mpi_rank}); recv_index.push_back(idx); - std::transform( - entity.begin(), entity.end(), - std::back_inserter(shared_entity_to_global_vertices_data), + std::ranges::transform( + entity, std::back_inserter(shared_entity_to_global_vertices_data), [idx](auto v) -> std::pair { return {idx, v}; }); } @@ -363,15 +360,13 @@ get_local_indexing(MPI_Comm comm, const common::IndexMap& vertex_map, } num_local = c; - std::transform(local_index.cbegin(), local_index.cend(), - local_index.begin(), - [&c](auto index) { return index == -1 ? c++ : index; }); + std::ranges::transform(local_index, local_index.begin(), [&c](auto index) + { return index == -1 ? c++ : index; }); assert(c == entity_count); // Convert interprocess entities to local_index - std::transform(interprocess_entities.cbegin(), interprocess_entities.cend(), - interprocess_entities.begin(), - [&local_index](std::int32_t i) { return local_index[i]; }); + std::ranges::transform(interprocess_entities, interprocess_entities.begin(), + [&local_index](auto i) { return local_index[i]; }); } //--------- @@ -389,25 +384,20 @@ get_local_indexing(MPI_Comm comm, const common::IndexMap& vertex_map, std::vector send_global_index_data; for (const auto& indices : send_index) { - std::transform(indices.cbegin(), indices.cend(), - std::back_inserter(send_global_index_data), - [&local_index, size = num_local, - offset = local_offset](auto idx) -> std::int64_t - { - // If not in our local range, send -1. - return local_index[idx] < size - ? offset + local_index[idx] - : -1; - }); + std::ranges::transform( + indices, std::back_inserter(send_global_index_data), + [&local_index, size = num_local, + offset = local_offset](auto idx) -> std::int64_t + { + // If not in our local range, send -1. + return local_index[idx] < size ? offset + local_index[idx] : -1; + }); } // Transform send/receive sizes and displacements for scalar send for (auto x : {&send_sizes, &send_disp, &recv_sizes, &recv_disp}) - { - std::transform(x->begin(), x->end(), x->begin(), - [num_vertices_per_e](auto a) - { return a / num_vertices_per_e; }); - } + std::ranges::transform(*x, x->begin(), [num_vertices_per_e](auto a) + { return a / num_vertices_per_e; }); recv_data.resize(recv_disp.back()); MPI_Neighbor_alltoallv(send_global_index_data.data(), send_sizes.data(), @@ -440,9 +430,9 @@ get_local_indexing(MPI_Comm comm, const common::IndexMap& vertex_map, // Create map from initial numbering to new local indices std::vector new_entity_index(entity_index.size()); - std::transform(entity_index.begin(), entity_index.end(), - new_entity_index.begin(), - [&local_index](auto index) { return local_index[index]; }); + std::ranges::transform(entity_index, new_entity_index.begin(), + [&local_index](auto index) + { return local_index[index]; }); return {std::move(new_entity_index), std::move(index_map), std::move(interprocess_entities)}; @@ -531,10 +521,42 @@ compute_entities_by_key_matching( // Get entity vertices. Padded with -1 if fewer than // max_vertices_per_entity + // NOTE Entity orientation is determined by vertex ordering. The + // orientation of an entity with respect to the cell may differ from its + // global mesh orientation. Hence, we reorder the vertices so that + // each entity's orientation agrees with their global orientation. + // FIXME This might be better below when the entity to vertex + // connectivity is computed + std::vector entity_vertices(ev.size()); + for (std::size_t j = 0; j < ev.size(); ++j) + entity_vertices[j] = vertices[ev[j]]; + + // Orient the entities. Simply sort according to global vertex index + // for simplices + std::vector global_vertices(entity_vertices.size()); + vertex_index_map.local_to_global(entity_vertices, global_vertices); + + std::vector perm(global_vertices.size()); + std::iota(perm.begin(), perm.end(), 0); + std::ranges::sort(perm, + [&global_vertices](std::size_t i0, std::size_t i1) { + return global_vertices[i0] < global_vertices[i1]; + }); + // For quadrilaterals, the vertex opposite the lowest vertex should + // be last + if (entity_type == mesh::CellType::quadrilateral) + { + std::size_t min_vertex_idx = perm[0]; + std::size_t opposite_vertex_index = 3 - min_vertex_idx; + auto it = std::find(perm.begin(), perm.end(), opposite_vertex_index); + assert(it != perm.end()); + std::rotate(it, it + 1, perm.end()); + } + for (std::size_t j = 0; j < ev.size(); ++j) entity_list[(cell_type_offsets[k] + idx) * num_vertices_per_entity + j] - = vertices[ev[j]]; + = entity_vertices[perm[j]]; } } } @@ -595,7 +617,7 @@ compute_entities_by_key_matching( for (std::size_t k = 0; k < cell_lists.size(); ++k) { auto cells = std::get<1>(cell_lists[k]); - const std::size_t num_cells = cells->num_nodes(); + [[maybe_unused]] const std::size_t num_cells = cells->num_nodes(); auto cell_map = std::get<2>(cell_lists[k]); int num_entities_per_cell = cell_type_entities[k].size(); assert(cell_map->size_local() + cell_map->num_ghosts() == (int)num_cells); @@ -726,7 +748,7 @@ compute_from_map(const graph::AdjacencyList& c_d0_0, auto v = vref->links(i); for (int j = 0; j < 2; ++j) key[j] = e0[v[j]]; - std::sort(key.begin(), key.end()); + std::ranges::sort(key); auto it = edge_to_index.find(key); assert(it != edge_to_index.end()); connections.push_back(it->second); @@ -745,7 +767,7 @@ std::tuple>>, mesh::compute_entities(MPI_Comm comm, const Topology& topology, int dim, int index) { - LOG(INFO) << "Computing mesh entities of dimension " << dim; + spdlog::info("Computing mesh entities of dimension {}", dim); const int tdim = topology.dim(); // Vertices must always exist @@ -782,13 +804,13 @@ mesh::compute_entities(MPI_Comm comm, const Topology& topology, int dim, cell_lists[i] = {cell_types[i], cells, cell_map}; } - auto [d0, d1, im, interprocess_facets] = compute_entities_by_key_matching( + auto [d0, d1, im, interprocess_entities] = compute_entities_by_key_matching( comm, cell_lists, *vertex_map, entity_type, dim); return {d0, std::make_shared>(std::move(d1)), std::make_shared(std::move(im)), - std::move(interprocess_facets)}; + std::move(interprocess_entities)}; } //----------------------------------------------------------------------------- std::array>, 2> @@ -796,9 +818,9 @@ mesh::compute_connectivity(const Topology& topology, std::pair d0, std::pair d1) { - LOG(INFO) << "Requesting connectivity (" << std::to_string(d0.first) << "," - << std::to_string(d0.second) << ") - (" << std::to_string(d1.first) - << "," << std::to_string(d1.second) << ")"; + spdlog::info("Requesting connectivity ({}, {}) - ({}, {})", + std::to_string(d0.first), std::to_string(d0.second), + std::to_string(d1.first), std::to_string(d1.second)); // Return if connectivity has already been computed if (topology.connectivity(d0, d1)) @@ -856,8 +878,8 @@ mesh::compute_connectivity(const Topology& topology, auto c_d1_d0 = std::make_shared>( compute_from_map(*c_d1_0, *c_d0_0)); - LOG(INFO) << "Computing mesh connectivity " << d0.first << " - " - << d1.first << " from transpose."; + spdlog::info("Computing mesh connectivity {}-{} from transpose.", + d0.first, d1.first); auto c_d0_d1 = std::make_shared>( compute_from_transpose(*c_d1_d0, c_d0_0->num_nodes())); return {c_d0_d1, c_d1_d0}; @@ -867,8 +889,8 @@ mesh::compute_connectivity(const Topology& topology, assert(c_d0_0); assert(topology.connectivity(d1, d0)); - LOG(INFO) << "Computing mesh connectivity " << std::to_string(d0.first) - << " - " << std::to_string(d1.first) << " from transpose."; + spdlog::info("Computing mesh connectivity {}-{} from transpose.", + std::to_string(d0.first), std::to_string(d1.first)); auto c_d0_d1 = std::make_shared>( compute_from_transpose(*topology.connectivity(d1, d0), c_d0_0->num_nodes())); diff --git a/cpp/dolfinx/mesh/topologycomputation.h b/cpp/dolfinx/mesh/topologycomputation.h index 5ff507a308d..a5e8a98f2f7 100644 --- a/cpp/dolfinx/mesh/topologycomputation.h +++ b/cpp/dolfinx/mesh/topologycomputation.h @@ -29,8 +29,11 @@ namespace dolfinx::mesh class Topology; /// @brief Compute mesh entities of given topological dimension by -/// computing entity-to-vertex connectivity (dim, 0), and cell-to-entity -/// connectivity (tdim, dim). +/// computing entity-to-vertex connectivity `(dim, 0)`, and cell-to-entity +/// connectivity `(tdim, dim)`. +/// +/// Computed entities are oriented such that their +/// local (to the process) orientation agrees with their global orientation /// @param[in] comm MPI Communicator /// @param[in] topology Mesh topology /// @param[in] dim The dimension of the entities to create @@ -38,9 +41,9 @@ class Topology; /// `Topology::entity_types(dim)`. /// @return Tuple of (cell-entity connectivity, entity-vertex /// connectivity, index map, list of interprocess entities). -/// Interprocess entities lie on the "true" boundary between owned cells of each -/// process. If the entities already exist, then {nullptr, nullptr, nullptr, -/// std::vector()} is returned. +/// Interprocess entities lie on the "true" boundary between owned cells +/// of each process. If the entities already exists, then {nullptr, +/// nullptr, nullptr, std::vector()} is returned. std::tuple>>, std::shared_ptr>, std::shared_ptr, std::vector> diff --git a/cpp/dolfinx/mesh/utils.cpp b/cpp/dolfinx/mesh/utils.cpp index b80f3a584f7..b067a321b60 100644 --- a/cpp/dolfinx/mesh/utils.cpp +++ b/cpp/dolfinx/mesh/utils.cpp @@ -58,14 +58,15 @@ mesh::extract_topology(CellType cell_type, const fem::ElementDofLayout& layout, std::vector mesh::exterior_facet_indices(const Topology& topology) { const int tdim = topology.dim(); - auto facet_map = topology.index_map(tdim - 1); - if (!facet_map) - throw std::runtime_error("Facets have not been computed."); - + auto f_to_c = topology.connectivity(tdim - 1, tdim); + if (!f_to_c) + { + throw std::runtime_error( + "Facet to cell connectivity has not been computed."); + } // Find all owned facets (not ghost) with only one attached cell + auto facet_map = topology.index_map(tdim - 1); const int num_facets = facet_map->size_local(); - auto f_to_c = topology.connectivity(tdim - 1, tdim); - assert(f_to_c); std::vector facets; for (std::int32_t f = 0; f < num_facets; ++f) { @@ -74,12 +75,9 @@ std::vector mesh::exterior_facet_indices(const Topology& topology) } // Remove facets on internal inter-process boundary - const std::vector& interprocess_facets - = topology.interprocess_facets(); std::vector ext_facets; - std::set_difference(facets.begin(), facets.end(), interprocess_facets.begin(), - interprocess_facets.end(), - std::back_inserter(ext_facets)); + std::ranges::set_difference(facets, topology.interprocess_facets(), + std::back_inserter(ext_facets)); return ext_facets; } //------------------------------------------------------------------------------ @@ -87,15 +85,16 @@ mesh::CellPartitionFunction mesh::create_cell_partitioner(mesh::GhostMode ghost_mode, const graph::partition_fn& partfn) { - return [partfn, ghost_mode](MPI_Comm comm, int nparts, CellType cell_type, - const graph::AdjacencyList& cells) + return [partfn, ghost_mode]( + MPI_Comm comm, int nparts, const std::vector& cell_types, + const std::vector>& cells) -> graph::AdjacencyList { - LOG(INFO) << "Compute partition of cells across ranks"; + spdlog::info("Compute partition of cells across ranks"); // Compute distributed dual graph (for the cells on this process) const graph::AdjacencyList dual_graph - = build_dual_graph(comm, cell_type, cells); + = build_dual_graph(comm, cell_types, cells); // Just flag any kind of ghosting for now bool ghosting = (ghost_mode != GhostMode::none); @@ -138,9 +137,10 @@ mesh::compute_incident_entities(const Topology& topology, entities1.insert(entities1.end(), e.begin(), e.end()); } - std::sort(entities1.begin(), entities1.end()); - entities1.erase(std::unique(entities1.begin(), entities1.end()), - entities1.end()); + std::ranges::sort(entities1); + auto [unique_end, range_end] = std::ranges::unique(entities1); + entities1.erase(unique_end, range_end); + return entities1; } //----------------------------------------------------------------------------- diff --git a/cpp/dolfinx/mesh/utils.h b/cpp/dolfinx/mesh/utils.h index fb425923959..1fa5724e39d 100644 --- a/cpp/dolfinx/mesh/utils.h +++ b/cpp/dolfinx/mesh/utils.h @@ -9,6 +9,7 @@ #include "Mesh.h" #include "Topology.h" #include "graphbuild.h" +#include #include #include #include @@ -54,7 +55,7 @@ void reorder_list(std::span list, std::span nodemap) { auto links_old = std::span(orig.data() + n * degree, degree); auto links_new = list.subspan(nodemap[n] * degree, degree); - std::copy(links_old.begin(), links_old.end(), links_new.begin()); + std::ranges::copy(links_old, links_new.begin()); } } @@ -103,12 +104,17 @@ compute_vertex_coords_boundary(const mesh::Mesh& mesh, int dim, } // Build vector of boundary vertices - std::sort(vertices.begin(), vertices.end()); - vertices.erase(std::unique(vertices.begin(), vertices.end()), - vertices.end()); - std::sort(entities.begin(), entities.end()); - entities.erase(std::unique(entities.begin(), entities.end()), - entities.end()); + { + std::ranges::sort(vertices); + auto [unique_end, range_end] = std::ranges::unique(vertices); + vertices.erase(unique_end, range_end); + } + + { + std::ranges::sort(entities); + auto [unique_end, range_end] = std::ranges::unique(entities); + entities.erase(unique_end, range_end); + } } // Get geometry data @@ -166,18 +172,19 @@ std::vector exterior_facet_indices(const Topology& topology); /// /// @param[in] comm MPI Communicator /// @param[in] nparts Number of partitions -/// @param[in] cell_type Type of cell in mesh -/// @param[in] cells Cells on this process. The ith entry in list -/// contains the global indices for the cell vertices. Each cell can -/// appear only once across all processes. The cell vertex indices are -/// not necessarily contiguous globally, i.e. the maximum index across -/// all processes can be greater than the number of vertices. High-order -/// 'nodes', e.g. mid-side points, should not be included. +/// @param[in] cell_types Cell types in the mesh +/// @param[in] cells Lists of cells of each cell type. cells[i] is a flattened +/// row major 2D array of shape (num_cells, num_cell_vertices) for cell_types[i] +/// on this process, containing the global indices for the cell vertices. Each +/// cell can appear only once across all processes. The cell vertex indices are +/// not necessarily contiguous globally, i.e. the maximum index across all +/// processes can be greater than the number of vertices. High-order 'nodes', +/// e.g. mid-side points, should not be included. /// @return Destination ranks for each cell on this process /// @note Cells can have multiple destination ranks, when ghosted. using CellPartitionFunction = std::function( - MPI_Comm comm, int nparts, CellType cell_type, - const graph::AdjacencyList& cells)>; + MPI_Comm comm, int nparts, const std::vector& cell_types, + const std::vector>& cells)>; /// @brief Extract topology from cell data, i.e. extract cell vertices. /// @param[in] cell_type The cell shape @@ -272,14 +279,8 @@ std::vector cell_normals(const Mesh& mesh, int dim, // Find geometry nodes for topology entities std::span x = mesh.geometry().x(); - - // Orient cells if they are tetrahedron - bool orient = false; - if (topology->cell_type() == CellType::tetrahedron) - orient = true; - std::vector geometry_entities - = entities_to_geometry(mesh, dim, entities, orient); + = entities_to_geometry(mesh, dim, entities, false); const std::size_t shape1 = geometry_entities.size() / entities.size(); std::vector n(entities.size() * 3); @@ -299,8 +300,8 @@ std::vector cell_normals(const Mesh& mesh, int dim, // Define normal by rotating tangent counter-clockwise std::array t; - std::transform(p[1].begin(), p[1].end(), p[0].begin(), t.begin(), - [](auto x, auto y) { return x - y; }); + std::ranges::transform(p[1], p[0], t.begin(), + [](auto x, auto y) { return x - y; }); T norm = std::sqrt(t[0] * t[0] + t[1] * t[1]); std::span ni(n.data() + 3 * i, 3); @@ -324,16 +325,16 @@ std::vector cell_normals(const Mesh& mesh, int dim, // Compute (p1 - p0) and (p2 - p0) std::array dp1, dp2; - std::transform(p[1].begin(), p[1].end(), p[0].begin(), dp1.begin(), - [](auto x, auto y) { return x - y; }); - std::transform(p[2].begin(), p[2].end(), p[0].begin(), dp2.begin(), - [](auto x, auto y) { return x - y; }); + std::ranges::transform(p[1], p[0], dp1.begin(), + [](auto x, auto y) { return x - y; }); + std::ranges::transform(p[2], p[0], dp2.begin(), + [](auto x, auto y) { return x - y; }); // Define cell normal via cross product of first two edges std::array ni = math::cross(dp1, dp2); T norm = std::sqrt(ni[0] * ni[0] + ni[1] * ni[1] + ni[2] * ni[2]); - std::transform(ni.begin(), ni.end(), std::next(n.begin(), 3 * i), - [norm](auto x) { return x / norm; }); + std::ranges::transform(ni, std::next(n.begin(), 3 * i), + [norm](auto x) { return x / norm; }); } return n; @@ -353,16 +354,16 @@ std::vector cell_normals(const Mesh& mesh, int dim, // Compute (p1 - p0) and (p2 - p0) std::array dp1, dp2; - std::transform(p[1].begin(), p[1].end(), p[0].begin(), dp1.begin(), - [](auto x, auto y) { return x - y; }); - std::transform(p[2].begin(), p[2].end(), p[0].begin(), dp2.begin(), - [](auto x, auto y) { return x - y; }); + std::ranges::transform(p[1], p[0], dp1.begin(), + [](auto x, auto y) { return x - y; }); + std::ranges::transform(p[2], p[0], dp2.begin(), + [](auto x, auto y) { return x - y; }); // Define cell normal via cross product of first two edges std::array ni = math::cross(dp1, dp2); T norm = std::sqrt(ni[0] * ni[0] + ni[1] * ni[1] + ni[2] * ni[2]); - std::transform(ni.begin(), ni.end(), std::next(n.begin(), 3 * i), - [norm](auto x) { return x / norm; }); + std::ranges::transform(ni, std::next(n.begin(), 3 * i), + [norm](auto x) { return x / norm; }); } return n; @@ -386,7 +387,6 @@ std::vector compute_midpoints(const Mesh& mesh, int dim, std::span x = mesh.geometry().x(); // Build map from entity -> geometry dof - // FIXME: This assumes a linear geometry. const std::vector e_to_g = entities_to_geometry(mesh, dim, entities, false); std::size_t shape1 = e_to_g.size() / entities.size(); @@ -399,9 +399,9 @@ std::vector compute_midpoints(const Mesh& mesh, int dim, for (auto row : rows) { std::span xg(x.data() + 3 * row, 3); - std::transform(p.begin(), p.end(), xg.begin(), p.begin(), - [size = rows.size()](auto x, auto y) - { return x + y / size; }); + std::ranges::transform(p, xg, p.begin(), + [size = rows.size()](auto x, auto y) + { return x + y / size; }); } } @@ -610,125 +610,123 @@ std::vector locate_entities_boundary(const Mesh& mesh, int dim, return entities; } -/// @brief Determine the indices in the geometry data for each vertex of -/// the given mesh entities. -/// -/// @warning This function should not be used unless there is no -/// alternative. It may be removed in the future. +/// @brief Compute the geometry degrees of freedom associated with +/// the closure of a given set of cell entities. /// /// @param[in] mesh The mesh. /// @param[in] dim Topological dimension of the entities of interest. -/// @param[in] entities Entity indices (local) to compute the vertex -/// geometry indices for. -/// @param[in] orient If true, in 3D, reorients facets to have -/// consistent normal direction. -/// @return Indices in the geometry array for the entity vertices. The -/// shape is `(num_entities, num_vertices_per_entity)` and the storage -/// is row-major. The index `indices[i, j]` is the position in the -/// geometry array of the `j`-th vertex of the `entity[i]`. +/// @param[in] entities Entity indices (local to process). +/// @param[in] permute If `true`, permute the DOFs such that they are +/// consistent with the orientation of `dim`-dimensional mesh entities. +/// This requires `create_entity_permutations` to be called first. +/// @return The geometry DOFs associated with the closure of each entity +/// in `entities`. The shape is `(num_entities, num_xdofs_per_entity)` +/// and the storage is row-major. The index `indices[i, j]` is the +/// position in the geometry array of the `j`-th vertex of the +/// `entity[i]`. +/// +/// @pre The mesh connectivities `dim -> mesh.topology().dim()` and +/// `mesh.topology().dim() -> dim` must have been computed. Otherwise an +/// exception is thrown. template std::vector entities_to_geometry(const Mesh& mesh, int dim, - std::span entities, bool orient) + std::span entities, + bool permute = false) { auto topology = mesh.topology(); assert(topology); - CellType cell_type = topology->cell_type(); if (cell_type == CellType::prism and dim == 2) throw std::runtime_error("More work needed for prism cells"); - if (orient and (cell_type != CellType::tetrahedron or dim != 2)) - throw std::runtime_error("Can only orient facets of a tetrahedral mesh"); + const int tdim = topology->dim(); const Geometry& geometry = mesh.geometry(); - auto x = geometry.x(); + auto xdofs = geometry.dofmap(); - const int tdim = topology->dim(); - mesh.topology_mutable()->create_entities(dim); - mesh.topology_mutable()->create_connectivity(dim, tdim); - mesh.topology_mutable()->create_connectivity(dim, 0); - mesh.topology_mutable()->create_connectivity(tdim, 0); + // Get the DOF layout and the number of DOFs per entity + const fem::CoordinateElement& coord_ele = geometry.cmap(); + const fem::ElementDofLayout layout = coord_ele.create_dof_layout(); + const std::size_t num_entity_dofs = layout.num_entity_closure_dofs(dim); + std::vector entity_xdofs; + entity_xdofs.reserve(entities.size() * num_entity_dofs); - auto xdofs = geometry.dofmap(); - auto e_to_c = topology->connectivity(dim, tdim); - if (!e_to_c) + // Get the element's closure DOFs + const std::vector>>& closure_dofs_all + = layout.entity_closure_dofs_all(); + + // Special case when dim == tdim (cells) + if (dim == tdim) { - throw std::runtime_error( - "Entity-to-cell connectivity has not been computed."); + for (std::size_t i = 0; i < entities.size(); ++i) + { + const std::int32_t c = entities[i]; + // Extract degrees of freedom + auto x_c = MDSPAN_IMPL_STANDARD_NAMESPACE::submdspan( + xdofs, c, MDSPAN_IMPL_STANDARD_NAMESPACE::full_extent); + for (std::int32_t entity_dof : closure_dofs_all[tdim][0]) + entity_xdofs.push_back(x_c[entity_dof]); + } + return entity_xdofs; } - auto e_to_v = topology->connectivity(dim, 0); - if (!e_to_v) + assert(dim != tdim); + + auto e_to_c = topology->connectivity(dim, tdim); + if (!e_to_c) { throw std::runtime_error( - "Entity-to-vertex connectivity has not been computed."); + "Entity-to-cell connectivity has not been computed. Missing dims " + + std::to_string(dim) + "->" + std::to_string(tdim)); } - auto c_to_v = topology->connectivity(tdim, 0); - if (!e_to_v) + auto c_to_e = topology->connectivity(tdim, dim); + if (!c_to_e) { throw std::runtime_error( - "Cell-to-vertex connectivity has not been computed."); + "Cell-to-entity connectivity has not been computed. Missing dims " + + std::to_string(tdim) + "->" + std::to_string(dim)); } - const std::size_t num_vertices - = num_cell_vertices(cell_entity_type(cell_type, dim, 0)); - std::vector geometry_idx(entities.size() * num_vertices); + // Get the cell info, which is needed to permute the closure dofs + std::span cell_info; + if (permute) + cell_info = std::span(mesh.topology()->get_cell_permutation_info()); + for (std::size_t i = 0; i < entities.size(); ++i) { - const std::int32_t idx = entities[i]; - // Always pick the second cell to be consistent with the e_to_v connectivity - const std::int32_t cell = e_to_c->links(idx).back(); - auto ev = e_to_v->links(idx); - assert(ev.size() == num_vertices); - auto cv = c_to_v->links(cell); - std::span xc( - xdofs.data_handle() + xdofs.extent(1) * cell, xdofs.extent(1)); - for (std::size_t j = 0; j < num_vertices; ++j) - { - int k = std::distance(cv.begin(), std::find(cv.begin(), cv.end(), ev[j])); - assert(k < (int)cv.size()); - geometry_idx[i * num_vertices + j] = xc[k]; - } + const std::int32_t e = entities[i]; + + // Get a cell connected to the entity + assert(!e_to_c->links(e).empty()); + std::int32_t c = e_to_c->links(e).front(); - if (orient) + // Get the local index of the entity + std::span cell_entities = c_to_e->links(c); + auto it = std::find(cell_entities.begin(), cell_entities.end(), e); + assert(it != cell_entities.end()); + std::size_t local_entity = std::distance(cell_entities.begin(), it); + + std::vector closure_dofs(closure_dofs_all[dim][local_entity]); + + // Cell sub-entities must be permuted so that their local orientation agrees + // with their global orientation + if (permute) { - // Compute cell midpoint - std::array midpoint = {0, 0, 0}; - for (std::int32_t j : xc) - for (int k = 0; k < 3; ++k) - midpoint[k] += x[3 * j + k]; - std::transform(midpoint.begin(), midpoint.end(), midpoint.begin(), - [size = xc.size()](auto x) { return x / size; }); - - // Compute vector triple product of two edges and vector to midpoint - std::array p0, p1, p2; - std::copy_n(std::next(x.begin(), 3 * geometry_idx[i * num_vertices + 0]), - 3, p0.begin()); - std::copy_n(std::next(x.begin(), 3 * geometry_idx[i * num_vertices + 1]), - 3, p1.begin()); - std::copy_n(std::next(x.begin(), 3 * geometry_idx[i * num_vertices + 2]), - 3, p2.begin()); - - std::array a; - std::transform(midpoint.begin(), midpoint.end(), p0.begin(), a.begin(), - [](auto x, auto y) { return x - y; }); - std::transform(p1.begin(), p1.end(), p0.begin(), std::next(a.begin(), 3), - [](auto x, auto y) { return x - y; }); - std::transform(p2.begin(), p2.end(), p0.begin(), std::next(a.begin(), 6), - [](auto x, auto y) { return x - y; }); - - // Midpoint direction should be opposite to normal, hence this - // should be negative. Switch points if not. - if (math::det(a.data(), {3, 3}) > 0.0) - { - std::swap(geometry_idx[i * num_vertices + 1], - geometry_idx[i * num_vertices + 2]); - } + mesh::CellType entity_type + = mesh::cell_entity_type(cell_type, dim, local_entity); + coord_ele.permute_subentity_closure(closure_dofs, cell_info[c], + entity_type, local_entity); } + + // Extract degrees of freedom + auto x_c = MDSPAN_IMPL_STANDARD_NAMESPACE::submdspan( + xdofs, c, MDSPAN_IMPL_STANDARD_NAMESPACE::full_extent); + for (std::int32_t entity_dof : closure_dofs) + entity_xdofs.push_back(x_c[entity_dof]); } - return geometry_idx; + return entity_xdofs; } /// Create a function that computes destination rank for mesh cells in @@ -772,7 +770,7 @@ compute_incident_entities(const Topology& topology, /// 'nodes' will be included. See dolfinx::io::cells for examples of the /// Basix ordering. /// @param[in] element Coordinate element for the cells. -/// @param[in] commg +/// @param[in] commg Communicator for geometry /// @param[in] x Geometry data ('node' coordinates). Row-major storage. /// The global index of the `i`th node (row) in `x` is taken as `i` plus /// the process offset on`comm`, The offset is the sum of `x` rows on @@ -793,7 +791,7 @@ Mesh> create_mesh( const fem::ElementDofLayout doflayout = element.create_dof_layout(); const int num_cell_vertices = mesh::num_cell_vertices(element.cell_shape()); - const int num_cell_nodes = doflayout.num_dofs(); + std::size_t num_cell_nodes = doflayout.num_dofs(); // Note: `extract_topology` extracts topology data, i.e. just the // vertices. For P1 geometry this should just be the identity @@ -803,44 +801,47 @@ Mesh> create_mesh( // `extract_topology` could be skipped for 'P1 geometry' elements // -- Partition topology across ranks of comm - graph::AdjacencyList cells1(0); - // std::vector cells1; + std::vector cells1; std::vector original_idx1; std::vector ghost_owners; if (partitioner) { + spdlog::info("Using partitioner with {} cell data", cells.size()); graph::AdjacencyList dest(0); if (commt != MPI_COMM_NULL) { int size = dolfinx::MPI::size(comm); - auto t = graph::regular_adjacency_list( - extract_topology(element.cell_shape(), doflayout, cells), - num_cell_vertices); - dest = partitioner(commt, size, celltype, t); + auto t = extract_topology(element.cell_shape(), doflayout, cells); + dest = partitioner(commt, size, {celltype}, {t}); } // Distribute cells (topology, includes higher-order 'nodes') to // destination rank - std::vector src; - auto _cells = graph::regular_adjacency_list( - std::vector(cells.begin(), cells.end()), num_cell_nodes); - std::tie(cells1, src, original_idx1, ghost_owners) - = graph::build::distribute(comm, _cells, dest); + assert(cells.size() % num_cell_nodes == 0); + std::size_t num_cells = cells.size() / num_cell_nodes; + std::vector src_ranks; + std::tie(cells1, src_ranks, original_idx1, ghost_owners) + = graph::build::distribute(comm, cells, {num_cells, num_cell_nodes}, + dest); + spdlog::debug("Got {} cells from distribution", cells1.size()); } else { - cells1 = graph::regular_adjacency_list( - std::vector(cells.begin(), cells.end()), num_cell_nodes); - std::int64_t offset(0), num_owned(cells1.num_nodes()); + cells1 = std::vector(cells.begin(), cells.end()); + assert(cells1.size() % num_cell_nodes == 0); + std::int64_t offset = 0; + std::int64_t num_owned = cells1.size() / num_cell_nodes; MPI_Exscan(&num_owned, &offset, 1, MPI_INT64_T, MPI_SUM, comm); - original_idx1.resize(cells1.num_nodes()); + original_idx1.resize(num_owned); std::iota(original_idx1.begin(), original_idx1.end(), offset); } // Extract cell 'topology', i.e. extract the vertices for each cell // and discard any 'higher-order' nodes std::vector cells1_v - = extract_topology(celltype, doflayout, cells1.array()); + = extract_topology(celltype, doflayout, cells1); + spdlog::info("Extract basic topology: {}->{}", cells1.size(), + cells1_v.size()); // Build local dual graph for owned cells to (i) get list of vertices // on the process boundary and (ii) apply re-ordering to cells for @@ -852,10 +853,11 @@ Mesh> create_mesh( std::vector cell_offsets(num_owned_cells + 1, 0); for (std::size_t i = 1; i < cell_offsets.size(); ++i) cell_offsets[i] = cell_offsets[i - 1] + num_cell_vertices; + spdlog::info("Build local dual graph"); auto [graph, unmatched_facets, max_v, facet_attached_cells] = build_local_dual_graph( - celltype, - std::span(cells1_v.data(), num_owned_cells * num_cell_vertices)); + std::vector{celltype}, + {std::span(cells1_v.data(), num_owned_cells * num_cell_vertices)}); const std::vector remap = graph::reorder_gps(graph); // Create re-ordered cell lists (leaves ghosts unchanged) @@ -867,15 +869,15 @@ Mesh> create_mesh( std::next(_original_idx.begin(), num_owned_cells)); impl::reorder_list( std::span(cells1_v.data(), remap.size() * num_cell_vertices), remap); - impl::reorder_list( - std::span(cells1.array().data(), remap.size() * num_cell_nodes), remap); + impl::reorder_list(std::span(cells1.data(), remap.size() * num_cell_nodes), + remap); original_idx1 = _original_idx; // Boundary vertices are marked as 'unknown' boundary_v = unmatched_facets; - std::sort(boundary_v.begin(), boundary_v.end()); - boundary_v.erase(std::unique(boundary_v.begin(), boundary_v.end()), - boundary_v.end()); + std::ranges::sort(boundary_v); + auto [unique_end, range_end] = std::ranges::unique(boundary_v); + boundary_v.erase(unique_end, range_end); // Remove -1 if it occurs in boundary vertices (may occur in mixed // topology) @@ -897,15 +899,246 @@ Mesh> create_mesh( // Build list of unique (global) node indices from cells1 and // distribute coordinate data - std::vector nodes1 = cells1.array(); - dolfinx::radix_sort(std::span(nodes1)); - nodes1.erase(std::unique(nodes1.begin(), nodes1.end()), nodes1.end()); + std::vector nodes1 = cells1; + dolfinx::radix_sort(nodes1); + auto [unique_end, range_end] = std::ranges::unique(nodes1); + nodes1.erase(unique_end, range_end); + + std::vector coords + = dolfinx::MPI::distribute_data(comm, nodes1, commg, x, xshape[1]); + + // Create geometry object + Geometry geometry + = create_geometry(topology, element, nodes1, cells1, coords, xshape[1]); + + return Mesh(comm, std::make_shared(std::move(topology)), + std::move(geometry)); +} + +/// @brief Create a distributed mixed-topology mesh from mesh data using a +/// provided graph partitioning function for determining the parallel +/// distribution of the mesh. +/// +/// From mesh input data that is distributed across processes, a +/// distributed mesh::Mesh is created. If the partitioning function is +/// not callable, i.e. it does not store a callable function, no +/// re-distribution of cells is done. +/// +/// @note This is an experimental specialised version of `create_mesh` for mixed +/// topology meshes, and does not include cell reordering. +/// +/// @param[in] comm Communicator to build the mesh on. +/// @param[in] commt Communicator that the topology data (`cells`) is +/// distributed on. This should be `MPI_COMM_NULL` for ranks that should +/// not participate in computing the topology partitioning. +/// @param[in] cells Cells on the calling process, as a list of lists, +/// one list for each cell type (or an empty list if there are no cells of that +/// type on this process). The cells are defined by their 'nodes' (using global +/// indices) following the Basix ordering, and concatenated to form a flattened +/// list. For lowest order cells this will be just the cell vertices. For +/// higher-order cells, other cells 'nodes' will be included. See +/// dolfinx::io::cells for examples of the Basix ordering. +/// @param[in] elements Coordinate elements for the cells, one for each cell +/// type in the mesh. In parallel, these must be the same on all processes. +/// @param[in] commg Communicator for geometry +/// @param[in] x Geometry data ('node' coordinates). Row-major storage. +/// The global index of the `i`th node (row) in `x` is taken as `i` plus +/// the process offset on`comm`, The offset is the sum of `x` rows on +/// all processed with a lower rank than the caller. +/// @param[in] xshape Shape of the `x` data. +/// @param[in] partitioner Graph partitioner that computes the owning +/// rank for each cell. If not callable, cells are not redistributed. +/// @return A mesh distributed on the communicator `comm`. +template +Mesh> create_mesh( + MPI_Comm comm, MPI_Comm commt, + const std::vector>& cells, + const std::vector>>& elements, + MPI_Comm commg, const U& x, std::array xshape, + const CellPartitionFunction& partitioner) +{ + assert(cells.size() == elements.size()); + std::int32_t num_cell_types = cells.size(); + std::vector celltypes; + std::ranges::transform(elements, std::back_inserter(celltypes), + [](auto e) { return e.cell_shape(); }); + std::vector doflayouts; + std::ranges::transform(elements, std::back_inserter(doflayouts), + [](auto e) { return e.create_dof_layout(); }); + + // Note: `extract_topology` extracts topology data, i.e. just the + // vertices. For P1 geometry this should just be the identity + // operator. For other elements the filtered lists may have 'gaps', + // i.e. the indices might not be contiguous. + // + // `extract_topology` could be skipped for 'P1 geometry' elements + + // -- Partition topology across ranks of comm + std::vector> cells1(num_cell_types); + std::vector> original_idx1(num_cell_types); + std::vector> ghost_owners(num_cell_types); + if (partitioner) + { + spdlog::info("Using partitioner with cell data ({} cell types)", + num_cell_types); + graph::AdjacencyList dest(0); + if (commt != MPI_COMM_NULL) + { + int size = dolfinx::MPI::size(comm); + std::vector> t(num_cell_types); + std::vector> tspan(num_cell_types); + for (std::int32_t i = 0; i < num_cell_types; ++i) + { + t[i] = extract_topology(celltypes[i], doflayouts[i], cells[i]); + tspan[i] = std::span(t[i]); + } + dest = partitioner(commt, size, celltypes, tspan); + } + + std::int32_t cell_offset = 0; + for (std::int32_t i = 0; i < num_cell_types; ++i) + { + std::size_t num_cell_nodes = doflayouts[i].num_dofs(); + assert(cells[i].size() % num_cell_nodes == 0); + std::size_t num_cells = cells[i].size() / num_cell_nodes; + + // Extract destination AdjacencyList for this cell type + std::vector offsets_i( + std::next(dest.offsets().begin(), cell_offset), + std::next(dest.offsets().begin(), cell_offset + num_cells + 1)); + std::vector data_i( + std::next(dest.array().begin(), offsets_i.front()), + std::next(dest.array().begin(), offsets_i.back())); + std::int32_t offset_0 = offsets_i.front(); + std::ranges::for_each(offsets_i, + [&offset_0](std::int32_t& j) { j -= offset_0; }); + graph::AdjacencyList dest_i(data_i, offsets_i); + cell_offset += num_cells; + + // Distribute cells (topology, includes higher-order 'nodes') to + // destination rank + std::vector src_ranks; + std::tie(cells1[i], src_ranks, original_idx1[i], ghost_owners[i]) + = graph::build::distribute(comm, cells[i], + {num_cells, num_cell_nodes}, dest_i); + spdlog::debug("Got {} cells from distribution", cells1[i].size()); + } + } + else + { + // No partitioning, construct a global index + std::int64_t num_owned = 0; + for (std::int32_t i = 0; i < num_cell_types; ++i) + { + cells1[i] = std::vector(cells[i].begin(), cells[i].end()); + std::int32_t num_cell_nodes = doflayouts[i].num_dofs(); + assert(cells1[i].size() % num_cell_nodes == 0); + original_idx1[i].resize(cells1[i].size() / num_cell_nodes); + num_owned += original_idx1[i].size(); + } + // Add on global offset + std::int64_t global_offset = 0; + MPI_Exscan(&num_owned, &global_offset, 1, MPI_INT64_T, MPI_SUM, comm); + for (std::int32_t i = 0; i < num_cell_types; ++i) + { + std::iota(original_idx1[i].begin(), original_idx1[i].end(), + global_offset); + global_offset += original_idx1[i].size(); + } + } + + // Extract cell 'topology', i.e. extract the vertices for each cell + // and discard any 'higher-order' nodes + std::vector> cells1_v(num_cell_types); + for (std::int32_t i = 0; i < num_cell_types; ++i) + { + cells1_v[i] = extract_topology(celltypes[i], doflayouts[i], cells1[i]); + spdlog::info("Extract basic topology: {}->{}", cells1[i].size(), + cells1_v[i].size()); + } + + // Build local dual graph for owned cells to (i) get list of vertices + // on the process boundary and (ii) apply re-ordering to cells for + // locality + std::vector boundary_v; + { + std::vector> cells1_v_local_cells; + + for (std::int32_t i = 0; i < num_cell_types; ++i) + { + std::int32_t num_cell_vertices = mesh::num_cell_vertices(celltypes[i]); + std::int32_t num_owned_cells + = cells1_v[i].size() / num_cell_vertices - ghost_owners[i].size(); + cells1_v_local_cells.push_back( + std::span(cells1_v[i].data(), num_owned_cells * num_cell_vertices)); + } + spdlog::info("Build local dual graph"); + auto [graph, unmatched_facets, max_v, facet_attached_cells] + = build_local_dual_graph(celltypes, cells1_v_local_cells); + + // TODO: in the original create_mesh(), cell reordering is done here. + // This needs reworking for mixed cell topology + + // Boundary vertices are marked as 'unknown' + boundary_v = unmatched_facets; + std::ranges::sort(boundary_v); + auto [unique_end, range_end] = std::ranges::unique(boundary_v); + boundary_v.erase(unique_end, range_end); + + // Remove -1 if it occurs in boundary vertices (may occur in mixed + // topology) + if (!boundary_v.empty() > 0 and boundary_v[0] == -1) + boundary_v.erase(boundary_v.begin()); + } + + spdlog::debug("Got {} boundary vertices", boundary_v.size()); + + // Create Topology + + std::vector> cells1_v_span; + std::ranges::transform(cells1_v, std::back_inserter(cells1_v_span), + [](auto& c) { return std::span(c); }); + std::vector> original_idx1_span; + std::ranges::transform(original_idx1, std::back_inserter(original_idx1_span), + [](auto& c) { return std::span(c); }); + std::vector> ghost_owners_span; + std::ranges::transform(ghost_owners, std::back_inserter(ghost_owners_span), + [](auto& c) { return std::span(c); }); + + Topology topology + = create_topology(comm, celltypes, cells1_v_span, original_idx1_span, + ghost_owners_span, boundary_v); + + // Create connectivities required higher-order geometries for creating + // a Geometry object + for (int i = 0; i < num_cell_types; ++i) + { + for (int e = 1; e < topology.dim(); ++e) + if (doflayouts[i].num_entity_dofs(e) > 0) + topology.create_entities(e); + if (elements[i].needs_dof_permutations()) + topology.create_entity_permutations(); + } + + // Build list of unique (global) node indices from cells1 and + // distribute coordinate data + std::vector nodes1, nodes2; + for (std::vector& c : cells1) + nodes1.insert(nodes1.end(), c.begin(), c.end()); + for (std::vector& c : cells1) + nodes2.insert(nodes2.end(), c.begin(), c.end()); + + dolfinx::radix_sort(nodes1); + auto [unique_end, range_end] = std::ranges::unique(nodes1); + nodes1.erase(unique_end, range_end); + std::vector coords = dolfinx::MPI::distribute_data(comm, nodes1, commg, x, xshape[1]); // Create geometry object - Geometry geometry = create_geometry(topology, element, nodes1, cells1.array(), - coords, xshape[1]); + Geometry geometry + = create_geometry(topology, elements, nodes1, nodes2, coords, xshape[1]); return Mesh(comm, std::make_shared(std::move(topology)), std::move(geometry)); @@ -945,6 +1178,99 @@ create_mesh(MPI_Comm comm, std::span cells, } } +/// @brief Create a sub-geometry from a mesh and a subset of mesh entities to +/// be included. A sub-geometry is simply a `Geometry` object containing only +/// the geometric information for the subset of entities. The entities may +/// differ in topological dimension from the original mesh. +/// +/// @param[in] mesh The full mesh. +/// @param[in] dim Topological dimension of the sub-topology. +/// @param[in] subentity_to_entity Map from sub-topology entity to the +/// entity in the parent topology. +/// @return A sub-geometry and a map from sub-geometry coordinate +/// degree-of-freedom to the coordinate degree-of-freedom in `geometry`. +template +std::pair, std::vector> +create_subgeometry(const Mesh& mesh, int dim, + std::span subentity_to_entity) +{ + const Geometry& geometry = mesh.geometry(); + + // Get the geometry dofs in the sub-geometry based on the entities in + // sub-geometry + const fem::ElementDofLayout layout = geometry.cmap().create_dof_layout(); + + std::vector x_indices + = entities_to_geometry(mesh, dim, subentity_to_entity, true); + + std::vector sub_x_dofs = x_indices; + std::ranges::sort(sub_x_dofs); + auto [unique_end, range_end] = std::ranges::unique(sub_x_dofs); + sub_x_dofs.erase(unique_end, range_end); + + // Get the sub-geometry dofs owned by this process + auto x_index_map = geometry.index_map(); + assert(x_index_map); + + std::shared_ptr sub_x_dof_index_map; + std::vector subx_to_x_dofmap; + { + auto [map, new_to_old] = common::create_sub_index_map( + *x_index_map, sub_x_dofs, common::IndexMapOrder::any, true); + sub_x_dof_index_map = std::make_shared(std::move(map)); + subx_to_x_dofmap = std::move(new_to_old); + } + + // Create sub-geometry coordinates + std::span x = geometry.x(); + std::int32_t sub_num_x_dofs = subx_to_x_dofmap.size(); + std::vector sub_x(3 * sub_num_x_dofs); + for (int i = 0; i < sub_num_x_dofs; ++i) + { + std::copy_n(std::next(x.begin(), 3 * subx_to_x_dofmap[i]), 3, + std::next(sub_x.begin(), 3 * i)); + } + + // Create geometry to sub-geometry map + std::vector x_to_subx_dof_map( + x_index_map->size_local() + x_index_map->num_ghosts(), -1); + for (std::size_t i = 0; i < subx_to_x_dofmap.size(); ++i) + x_to_subx_dof_map[subx_to_x_dofmap[i]] = i; + + // Create sub-geometry dofmap + std::vector sub_x_dofmap; + sub_x_dofmap.reserve(x_indices.size()); + std::ranges::transform(x_indices, std::back_inserter(sub_x_dofmap), + [&x_to_subx_dof_map](auto x_dof) + { + assert(x_to_subx_dof_map[x_dof] != -1); + return x_to_subx_dof_map[x_dof]; + }); + + // Create sub-geometry coordinate element + CellType sub_coord_cell + = cell_entity_type(geometry.cmap().cell_shape(), dim, 0); + // Special handling if point meshes, as they only support constant basis + // functions + int degree = geometry.cmap().degree(); + if (sub_coord_cell == CellType::point) + degree = 0; + fem::CoordinateElement sub_cmap(sub_coord_cell, degree, + geometry.cmap().variant()); + + // Sub-geometry input_global_indices + const std::vector& igi = geometry.input_global_indices(); + std::vector sub_igi; + sub_igi.reserve(subx_to_x_dofmap.size()); + std::ranges::transform(subx_to_x_dofmap, std::back_inserter(sub_igi), + [&igi](auto sub_x_dof) { return igi[sub_x_dof]; }); + + // Create geometry + return {Geometry(sub_x_dof_index_map, std::move(sub_x_dofmap), {sub_cmap}, + std::move(sub_x), geometry.dim(), std::move(sub_igi)), + std::move(subx_to_x_dofmap)}; +} + /// @brief Create a new mesh consisting of a subset of entities in a /// mesh. /// @param[in] mesh The mesh @@ -969,8 +1295,9 @@ create_submesh(const Mesh& mesh, int dim, mesh.topology_mutable()->create_entities(dim); mesh.topology_mutable()->create_connectivity(dim, tdim); mesh.topology_mutable()->create_connectivity(tdim, dim); - auto [geometry, subx_to_x_dofmap] = mesh::create_subgeometry( - *mesh.topology(), mesh.geometry(), dim, subentity_to_entity); + mesh.topology_mutable()->create_entity_permutations(); + auto [geometry, subx_to_x_dofmap] + = mesh::create_subgeometry(mesh, dim, subentity_to_entity); return {Mesh(mesh.comm(), std::make_shared(std::move(topology)), std::move(geometry)), diff --git a/cpp/dolfinx/nls/NewtonSolver.cpp b/cpp/dolfinx/nls/NewtonSolver.cpp index 1921016c554..bc2b8f455df 100644 --- a/cpp/dolfinx/nls/NewtonSolver.cpp +++ b/cpp/dolfinx/nls/NewtonSolver.cpp @@ -35,10 +35,10 @@ std::pair converged(const nls::petsc::NewtonSolver& solver, // Output iteration number and residual if (solver.report and dolfinx::MPI::rank(solver.comm()) == 0) { - LOG(INFO) << "Newton iteration " << solver.iteration() - << ": r (abs) = " << residual << " (tol = " << solver.atol - << ") r (rel) = " << relative_residual << "(tol = " << solver.rtol - << ")"; + spdlog::info("Newton iteration {}" + ": r (abs) = {} (tol = {}), r (rel) = {} (tol = {})", + solver.iteration(), residual, solver.atol, relative_residual, + solver.rtol); } // Return true if convergence criterion is met @@ -249,9 +249,9 @@ std::pair nls::petsc::NewtonSolver::solve(Vec x) { if (dolfinx::MPI::rank(_comm.comm()) == 0) { - LOG(INFO) << "Newton solver finished in " << _iteration - << " iterations and " << _krylov_iterations - << " linear solver iterations."; + spdlog::info("Newton solver finished in {} iterations and {} linear " + "solver iterations.", + _iteration, _krylov_iterations); } } else @@ -267,7 +267,7 @@ std::pair nls::petsc::NewtonSolver::solve(Vec x) throw std::runtime_error("Newton solver did not converge"); } else - LOG(WARNING) << "Newton solver did not converge."; + spdlog::warn("Newton solver did not converge."); } return {_iteration, newton_converged}; diff --git a/cpp/dolfinx/refinement/CMakeLists.txt b/cpp/dolfinx/refinement/CMakeLists.txt index 097500018a6..9560e45c823 100644 --- a/cpp/dolfinx/refinement/CMakeLists.txt +++ b/cpp/dolfinx/refinement/CMakeLists.txt @@ -1,7 +1,10 @@ set(HEADERS_refinement ${CMAKE_CURRENT_SOURCE_DIR}/dolfinx_refinement.h - ${CMAKE_CURRENT_SOURCE_DIR}/plaza.h ${CMAKE_CURRENT_SOURCE_DIR}/refine.h + ${CMAKE_CURRENT_SOURCE_DIR}/interval.h + ${CMAKE_CURRENT_SOURCE_DIR}/plaza.h + ${CMAKE_CURRENT_SOURCE_DIR}/refine.h ${CMAKE_CURRENT_SOURCE_DIR}/utils.h + ${CMAKE_CURRENT_SOURCE_DIR}/option.h PARENT_SCOPE ) diff --git a/cpp/dolfinx/refinement/dolfinx_refinement.h b/cpp/dolfinx/refinement/dolfinx_refinement.h index a49ece85944..b4e1bea6f16 100644 --- a/cpp/dolfinx/refinement/dolfinx_refinement.h +++ b/cpp/dolfinx/refinement/dolfinx_refinement.h @@ -11,3 +11,4 @@ namespace dolfinx::refinement // DOLFINx refinement interface #include +#include diff --git a/cpp/dolfinx/refinement/interval.h b/cpp/dolfinx/refinement/interval.h new file mode 100644 index 00000000000..a8c633b183d --- /dev/null +++ b/cpp/dolfinx/refinement/interval.h @@ -0,0 +1,190 @@ +// Copyright (C) 2024 Paul T. Kühner +// +// This file is part of DOLFINX (https://www.fenicsproject.org) +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include "dolfinx/mesh/Mesh.h" +#include "dolfinx/mesh/cell_types.h" +#include "dolfinx/mesh/utils.h" +#include "dolfinx/refinement/plaza.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dolfinx/refinement/option.h" +#include "dolfinx/refinement/utils.h" + +namespace dolfinx::refinement::interval +{ +/// @brief Refine with markers returning new mesh data. +/// +/// @param[in] mesh Input mesh to be refined +/// @param[in] cells Indices of the cells that are marked for refinement +/// @param[in] option Refinement option indicating if parent cells +/// and/or facets are to be computed. +/// @return New mesh data: cell topology, vertex coordinates and parent +/// cell indices. +template +std::tuple, std::vector, + std::array, std::optional>, + std::optional>> +compute_refinement_data(const mesh::Mesh& mesh, + std::optional> cells, + Option option) +{ + bool compute_parent_facet = option_parent_facet(option); + bool compute_parent_cell = option_parent_cell(option); + + if (compute_parent_facet) + throw std::runtime_error("Parent facet computation not yet supported!"); + + auto topology = mesh.topology(); + assert(topology); + assert(topology->dim() == 1); + auto map_c = topology->index_map(1); + assert(map_c); + + // TODO: creation of sharing ranks in external function? Also same + // code in use for plaza + // Get sharing ranks for each cell + graph::AdjacencyList cell_ranks = map_c->index_to_dest_ranks(); + + // Create unique list of ranks that share cells (owners of ghosts plus + // ranks that ghost owned indices) + std::vector ranks = cell_ranks.array(); + std::ranges::sort(ranks); + auto to_remove = std::ranges::unique(ranks); + ranks.erase(to_remove.begin(), to_remove.end()); + + // Convert cell_ranks from global rank to to neighbourhood ranks + std::ranges::transform(cell_ranks.array(), cell_ranks.array().begin(), + [&ranks](auto r) + { + auto it = std::lower_bound(ranks.begin(), + ranks.end(), r); + assert(it != ranks.end() and *it == r); + return std::distance(ranks.begin(), it); + }); + + // Create refinement flag for cells + std::vector refinement_marker( + map_c->size_local() + map_c->num_ghosts(), !cells.has_value()); + + // Mark cells for refinement + std::vector> marked_for_update(ranks.size()); + if (cells.has_value()) + { + std::ranges::for_each(cells.value(), + [&](auto cell) + { + if (!refinement_marker[cell]) + { + refinement_marker[cell] = true; + for (int rank : cell_ranks.links(cell)) + marked_for_update[rank].push_back(cell); + } + }); + } + + // Create neighborhood communicator for vertex creation + MPI_Comm neighbor_comm; + MPI_Dist_graph_create_adjacent( + mesh.comm(), ranks.size(), ranks.data(), MPI_UNWEIGHTED, ranks.size(), + ranks.data(), MPI_UNWEIGHTED, MPI_INFO_NULL, false, &neighbor_comm); + + // Communicate ghost cells that might have been marked. This is not + // necessary for a uniform refinement. + if (cells.has_value()) + { + update_logical_edgefunction(neighbor_comm, marked_for_update, + refinement_marker, *map_c); + } + + // Construct the new vertices + const auto [new_vertex_map, new_vertex_coords, xshape] + = create_new_vertices(neighbor_comm, cell_ranks, mesh, refinement_marker); + MPI_Comm_free(&neighbor_comm); + + auto c_to_v = mesh.topology()->connectivity(1, 0); + assert(c_to_v); + + // Get the count of cells to refine, note: we only consider non-ghost + // cells + const std::int32_t number_of_refined_cells + = std::count(refinement_marker.begin(), + std::next(refinement_marker.begin(), + mesh.topology()->index_map(1)->size_local()), + true); + + // Produce local global indices, by padding out the previous index map + std::vector global_indices + = adjust_indices(*mesh.topology()->index_map(0), number_of_refined_cells); + + // Build the topology on the new vertices + const std::int32_t refined_cell_count + = mesh.topology()->index_map(1)->size_local() + number_of_refined_cells; + + std::vector cell_topology; + cell_topology.reserve(refined_cell_count * 2); + + std::optional> parent_cell(std::nullopt); + if (compute_parent_cell) + { + parent_cell.emplace(); + parent_cell->reserve(refined_cell_count); + } + + for (std::int32_t cell = 0; cell < map_c->size_local(); ++cell) + { + const auto& vertices = c_to_v->links(cell); + assert(vertices.size() == 2); + + // We consider a cell (defined by global vertices) + // a ----------- b + const std::int64_t a = global_indices[vertices[0]]; + const std::int64_t b = global_indices[vertices[1]]; + if (refinement_marker[cell]) + { + // Find (global) index of new midpoint vertex: + // a --- c --- b + auto it = new_vertex_map.find(cell); + assert(it != new_vertex_map.end()); + const std::int64_t c = it->second; + + // Add new cells/edges to refined topology + cell_topology.insert(cell_topology.end(), {a, c, c, b}); + + if (compute_parent_cell) + parent_cell->insert(parent_cell->end(), {cell, cell}); + } + else + { + // Copy the previous cell + cell_topology.insert(cell_topology.end(), {a, b}); + + if (compute_parent_cell) + parent_cell->push_back(cell); + } + } + + assert(cell_topology.size() == 2 * refined_cell_count); + assert(parent_cell->size() == (compute_parent_cell ? refined_cell_count : 0)); + + std::vector offsets(refined_cell_count + 1); + std::ranges::generate(offsets, [i = 0]() mutable { return 2 * i++; }); + + graph::AdjacencyList cell_adj(std::move(cell_topology), std::move(offsets)); + + return {std::move(cell_adj), std::move(new_vertex_coords), xshape, + std::move(parent_cell), std::nullopt}; +} + +} // namespace dolfinx::refinement::interval diff --git a/cpp/dolfinx/refinement/option.h b/cpp/dolfinx/refinement/option.h new file mode 100644 index 00000000000..5c21cbd6654 --- /dev/null +++ b/cpp/dolfinx/refinement/option.h @@ -0,0 +1,52 @@ +// Copyright (C) 2024 Paul T. Kühner +// +// This file is part of DOLFINX (https://www.fenicsproject.org) +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include +#include + +namespace dolfinx::refinement +{ +/// @brief Options for data to compute during mesh refinement. +enum class Option : std::uint8_t +{ + none = 0b00, /*!< No extra data */ + parent_facet = 0b01, /*!< Compute list of the cell-local facet indices in the + parent cell of each facet in each new cell (or -1 if no match) */ + parent_cell + = 0b10, /*!< Compute list with the parent cell index for each new cell */ + parent_cell_and_facet = 0b11 /*< Both cell and facet parent data */ +}; + +/// @brief Combine two refinement options into one, both flags will be +/// set for the resulting option. +inline constexpr Option operator|(Option a, Option b) +{ + using bitmask_t = std::underlying_type_t