From 5dc2c7345919dae642209f14b2d36e9788665ddc Mon Sep 17 00:00:00 2001 From: Sarbani-Roy Date: Thu, 13 Nov 2025 11:43:24 +0100 Subject: [PATCH 01/10] Set up Docker-based workflow for macOS --- .gitignore | 31 ++++++++++++- .../linear-elastic-plate-with-hole/Snakefile | 8 +++- .../fenics/Snakefile | 20 ++++++-- .../kratos/Snakefile | 18 +++++--- .../kratos/environment_simulation.yml | 4 +- dockerfiles/Dockerfile | 46 +++++++++++++++++++ dockerfiles/Dockerfile_base_env_ready | 40 ++++++++++++++++ 7 files changed, 153 insertions(+), 14 deletions(-) create mode 100644 dockerfiles/Dockerfile create mode 100644 dockerfiles/Dockerfile_base_env_ready diff --git a/.gitignore b/.gitignore index f684468..c32b731 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,31 @@ .snakemake -site \ No newline at end of file +site + +# macOS system files +.DS_Store + +# Python cache files +__pycache__/ +*.py[cod] + +# Benchmark and workflow output files +benchmarks/linear-elastic-plate-with-hole/element_size_vs_stress.pdf +benchmarks/linear-elastic-plate-with-hole/metadata4ing_provenance.zip +benchmarks/linear-elastic-plate-with-hole/workflow_config.json + +# Generated folders and outputs +benchmarks/linear-elastic-plate-with-hole/metadata4ing_provenance/ +benchmarks/linear-elastic-plate-with-hole/snakemake_results/ + +# (Optional) ignore all ZIP and PDF files in benchmarks if they are always generated +# benchmarks/**/*.zip +# benchmarks/**/*.pdf + +# VS Code / IDE settings (optional, if you use editors) +.vscode/ +.idea/ + +# Conda and environment files (optional safety) +*.lock +*.log +.env diff --git a/benchmarks/linear-elastic-plate-with-hole/Snakefile b/benchmarks/linear-elastic-plate-with-hole/Snakefile index 12414e1..69b170c 100644 --- a/benchmarks/linear-elastic-plate-with-hole/Snakefile +++ b/benchmarks/linear-elastic-plate-with-hole/Snakefile @@ -21,9 +21,11 @@ rule create_mesh: parameters = lambda wildcards: configuration_to_parameter_file[wildcards.configuration], output: mesh = f"{result_dir}/mesh/mesh_{{configuration}}.msh", - conda: "environment_mesh.yml" + # conda: "environment_mesh.yml" shell: """ + source /opt/miniforge/etc/profile.d/conda.sh + conda activate mesh python3 {input.script} --input_parameter_file {input.parameters} --output_mesh_file {output.mesh} """ @@ -54,9 +56,11 @@ rule summary: ), output: summary_json = f"{result_dir}/{{tool}}/summary.json", - conda: "environment_postprocessing.yml", + # conda: "environment_postprocessing.yml", shell: """ + source /opt/miniforge/etc/profile.d/conda.sh + conda activate postprocessing python3 {input.script} \ --input_configuration {configurations} \ --input_parameter_file {input.parameters} \ diff --git a/benchmarks/linear-elastic-plate-with-hole/fenics/Snakefile b/benchmarks/linear-elastic-plate-with-hole/fenics/Snakefile index d82cd75..5e83a9b 100644 --- a/benchmarks/linear-elastic-plate-with-hole/fenics/Snakefile +++ b/benchmarks/linear-elastic-plate-with-hole/fenics/Snakefile @@ -15,9 +15,23 @@ rule run_fenics_simulation: output: zip = f"{result_dir}/{{tool}}/solution_field_data_{{configuration}}.zip", metrics = f"{result_dir}/{{tool}}/solution_metrics_{{configuration}}.json", - conda: - "environment_simulation.yml", + # conda: + # "environment_simulation.yml", shell: """ - python3 {input.script} --input_parameter_file {input.parameters} --input_mesh_file {input.mesh} --output_solution_file_zip {output.zip} --output_metrics_file {output.metrics} + # Disable 'unbound variable' errors to avoid ADDR2LINE issues + set +u + # Load conda + source /opt/miniforge/etc/profile.d/conda.sh + # Activate the FEniCS environment + conda activate fenics-sim + # Prevent OpenMP crash under qemu (macOS/arm64 emulation) + export OMP_NUM_THREADS=1 + + # Run the actual simulation + python3 {input.script} \ + --input_parameter_file {input.parameters} \ + --input_mesh_file {input.mesh} \ + --output_solution_file_zip {output.zip} \ + --output_metrics_file {output.metrics} """ \ No newline at end of file diff --git a/benchmarks/linear-elastic-plate-with-hole/kratos/Snakefile b/benchmarks/linear-elastic-plate-with-hole/kratos/Snakefile index 38054c6..0b64ece 100644 --- a/benchmarks/linear-elastic-plate-with-hole/kratos/Snakefile +++ b/benchmarks/linear-elastic-plate-with-hole/kratos/Snakefile @@ -16,10 +16,12 @@ rule mesh_to_mdpa: script = f"{tool}/msh_to_mdpa.py", output: mdpa = f"{result_dir}/{tool}/mesh_{{configuration}}.mdpa", - conda: - "environment_simulation.yml", + # conda: + # "environment_simulation.yml", shell: """ + source /opt/miniforge/etc/profile.d/conda.sh + conda activate kratos-sim python3 {input.script} \ --input_parameter_file {input.parameters} \ --input_mesh_file {input.mesh} \ @@ -44,10 +46,12 @@ rule create_kratos_input_and_run_simulation: kratos_inputfile = f"{result_dir}/{tool}/ProjectParameters_{{configuration}}.json", kratos_materialfile = f"{result_dir}/{tool}/MaterialParameters_{{configuration}}.json", result_vtk = f"{result_dir}/{tool}/{{configuration}}/Structure_0_1.vtk", - conda: - "environment_simulation.yml", + # conda: + # "environment_simulation.yml", shell: """ + source /opt/miniforge/etc/profile.d/conda.sh + conda activate kratos-sim python3 {input.script_create_kratos_input} \ --input_parameter_file {input.parameters} \ --input_mdpa_file {input.mdpa} \ @@ -70,10 +74,12 @@ rule postprocess_kratos_results: output: zip = f"{result_dir}/{tool}/solution_field_data_{{configuration}}.zip", metrics = f"{result_dir}/{tool}/solution_metrics_{{configuration}}.json", - conda: - "environment_simulation.yml", + # conda: + # "environment_simulation.yml", shell: """ + source /opt/miniforge/etc/profile.d/conda.sh + conda activate kratos-sim python3 {input.script} \ --input_parameter_file {input.parameters} \ --input_result_vtk {input.result_vtk} \ diff --git a/benchmarks/linear-elastic-plate-with-hole/kratos/environment_simulation.yml b/benchmarks/linear-elastic-plate-with-hole/kratos/environment_simulation.yml index fda7aa8..1324f13 100644 --- a/benchmarks/linear-elastic-plate-with-hole/kratos/environment_simulation.yml +++ b/benchmarks/linear-elastic-plate-with-hole/kratos/environment_simulation.yml @@ -7,7 +7,7 @@ dependencies: - python-gmsh - pint - pyvista + - sympy - pip - pip: - - KratosMultiphysics-all - - sympy + - KratosMultiphysics-all==9.0.2 diff --git a/dockerfiles/Dockerfile b/dockerfiles/Dockerfile new file mode 100644 index 0000000..54070d7 --- /dev/null +++ b/dockerfiles/Dockerfile @@ -0,0 +1,46 @@ +# ----------------------------- +# Dockerfile for NFDI_Benchmark +# ----------------------------- + +# Base image similar to GitHub Actions ubuntu-latest +FROM --platform=linux/amd64 ubuntu:22.04 + +# Avoid interactive prompts during apt installs +ENV DEBIAN_FRONTEND=noninteractive + +# Install basic dependencies +RUN apt-get update && apt-get install -y \ + wget curl git unzip zip bzip2 libbz2-dev python3 python3-pip \ + build-essential && \ + rm -rf /var/lib/apt/lists/* + +# Install Miniforge3 for amd64 architecture +RUN wget https://github.com/conda-forge/miniforge/releases/download/24.11.0-0/Miniforge3-24.11.0-0-Linux-x86_64.sh && \ + bash Miniforge3-24.11.0-0-Linux-x86_64.sh -b -p /opt/miniforge && \ + rm Miniforge3-24.11.0-0-Linux-x86_64.sh + +# Add conda/mamba to PATH +ENV PATH="/opt/miniforge/bin:$PATH" + +ENV OMP_NUM_THREADS=1 +ENV KMP_AFFINITY=disabled +ENV OMP_PROC_BIND=FALSE + +# Set working directory inside container +WORKDIR /NFDI_Benchmark + +# Copy only environment yaml first to leverage Docker caching +COPY environment_benchmarks.yml /NFDI_Benchmark/ + +# Create model-validation environment +RUN mamba env create -n model-validation -f environment_benchmarks.yml && \ + echo "conda activate model-validation" >> ~/.bashrc + +# Copy the rest of the repo into container +COPY . /NFDI_Benchmark + +# Use bash login shell by default +SHELL ["bash", "-l", "-c"] + +# Drop into interactive bash shell when container starts +CMD ["/bin/bash"] \ No newline at end of file diff --git a/dockerfiles/Dockerfile_base_env_ready b/dockerfiles/Dockerfile_base_env_ready new file mode 100644 index 0000000..b721937 --- /dev/null +++ b/dockerfiles/Dockerfile_base_env_ready @@ -0,0 +1,40 @@ +# Use Ubuntu base image under x86_64 emulation for Apple Silicon Macs +FROM --platform=linux/amd64 ubuntu:22.04 + +# Prevent interactive prompts during package installs +ENV DEBIAN_FRONTEND=noninteractive + +# Update system and install required packages +RUN apt-get update && apt-get install -y \ + wget git build-essential curl unzip \ + && rm -rf /var/lib/apt/lists/* + +# Install Miniforge (Conda for ARM/x86 compatible builds) +ENV CONDA_DIR=/opt/miniforge +RUN wget https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-Linux-x86_64.sh -O /tmp/miniforge.sh && \ + bash /tmp/miniforge.sh -b -p $CONDA_DIR && \ + rm /tmp/miniforge.sh +ENV PATH=$CONDA_DIR/bin:$PATH + +# Initialize Conda shell integration +RUN conda init bash + +# Create default working directory +WORKDIR /NFDI_Benchmark + +# Copy your repository into the image (optional; or mount it later) +# COPY . /NFDI_Benchmark + +# Pre-install mamba (faster environment management) +RUN conda install -y mamba -n base -c conda-forge + +# Create and activate a base environment for model validation +RUN mamba create -n model-validation python=3.10 -y && \ + echo "source $CONDA_DIR/etc/profile.d/conda.sh && conda activate model-validation" >> ~/.bashrc + +# Set environment variables for stability under emulation +ENV OMP_NUM_THREADS=1 +ENV KMP_AFFINITY=disabled + +# Default command starts bash with Conda activated +ENTRYPOINT ["/bin/bash", "-l"] \ No newline at end of file From ed5c123aaac278859a10fb1cd817013df356d397 Mon Sep 17 00:00:00 2001 From: Sarbani-Roy Date: Fri, 14 Nov 2025 10:40:29 +0100 Subject: [PATCH 02/10] documentation regarding docker setup added --- docker_setup.md | 170 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 docker_setup.md diff --git a/docker_setup.md b/docker_setup.md new file mode 100644 index 0000000..e7db44d --- /dev/null +++ b/docker_setup.md @@ -0,0 +1,170 @@ +# NFDI Benchmark Setup on macOS with Docker + +Kratos Multiphysics is **not natively supported on macOS**, so we use Docker to run it. Below is a step-by-step guide to set up and run the NFDI Benchmark workflow on a Mac with Apple Silicon (ARM) architecture. + +--- + +## 1. Choose a Base Docker Image + +Kratos is distributed only for **x86_64 (amd64) Linux**. Since Apple Silicon uses ARM, we need to emulate x86_64 in Docker: + +```dockerfile +FROM --platform=linux/amd64 ubuntu:22.04 +``` + +This `--platform=linux/amd64` flag ensures Docker emulates an x86_64 environment, allowing Kratos to run correctly. + +--- + +## 2. Build the Docker Image + +Navigate to the repository folder and build the Docker image: + +```bash +cd ~/NFDI_benchmark/NFDI4IngModelValidationPlatform +docker build -t nfdi_benchmark -f dockerfiles/Dockerfile +``` + +--- + +## 3. Run the Docker Container + +Run the container and mount the local repository inside it: +```bash +docker run --platform linux/amd64 -it \ + --name nfdi_benchmark_container \ + -v $(pwd):/NFDI_Benchmark \ + nfdi_benchmark:latest +``` + +This makes the local repository available inside the container and opens an interactive shell. + +To rerun the same container +```bash +docker start -ai +``` + +> **Note:** Docker containers do not start with an interactive login shell, so `conda activate` does not work immediately. We must initialize Conda first. + +```bash +# Initialize Conda +source /opt/miniforge/etc/profile.d/conda.sh + +# Activate the model-validation environment +conda activate model-validation + +# Navigate to the benchmark folder +cd benchmarks/linear-elastic-plate-with-hole +``` + +--- + +## 4. Generate Configuration and Run Workflow + +Generate configuration files: + +```bash +python generate_config.py +``` + +Run the Snakemake workflow: + +```bash +snakemake --use-conda --force --cores all +``` + +Snakemake will look for a `Snakefile` inside `linear-elastic-plate-with-hole` and execute it. + +--- + +## 5. Pre-build Conda Environments (Workaround for Architecture Issues) + +Snakemake’s automatic Conda environment creation fails under Docker emulation due to architecture mismatches. The solution is to pre-create the environments manually. + +### 5.1 Kratos Environment + +```bash +source /opt/miniforge/etc/profile.d/conda.sh +mamba env create -n kratos-sim -f kratos/environment_simulation.yml +conda activate kratos-sim + +# Test Kratos installation +python -c "import KratosMultiphysics; print(KratosMultiphysics.__version__)" +``` + +### 5.2 FEniCS Environment + +FEniCS can fail when running multiple threads under emulation, so we limit it to a single thread: + +```bash +mamba env create -f fenics/environment_simulation.yml -n fenics-sim +conda activate fenics-sim + +# Limit threads to avoid OpenMP/QEMU errors +export OMP_NUM_THREADS=1 +export KMP_AFFINITY=disabled + +# Test FEniCS installation +python -c "import dolfinx; print('FEniCSx imported OK')" +``` + +### 5.3 Mesh Environment + +```bash +conda deactivate +mamba env create -f environment_mesh.yml -n mesh +``` + +### 5.4 Postprocessing Environment + +```bash +mamba env create -f environment_postprocessing.yml -n postprocessing +``` + +> Once these environments are created, Snakemake will skip environment creation, significantly speeding up workflow execution. + +--- + +## 6. Re-run Snakemake Using Pre-built Environments + +```bash +snakemake --use-conda --cores all +``` + +--- + +## 7. Generate Metadata / Provenance Report + +```bash +snakemake --use-conda --force --cores all \ + --reporter metadata4ing \ + --report-metadata4ing-paramscript parameter_extractor.py \ + --report-metadata4ing-filename metadata4ing_provenance +``` + +This updates all metadata and provenance information without re-running the simulations. + +--- + +## 8. Extract Snakemake Outputs (Optional) + +```bash +mkdir -p ./metadata4ing_provenance +unzip -o ./metadata4ing_provenance.zip -d ./metadata4ing_provenance +``` + +--- + +## 9. Plot Results + +```bash +python plot_provenance.py ./metadata4ing_provenance +``` + +--- + +✅ **Notes / Tips:** +- Always initialize Conda inside the Docker container before activating environments. +- Limit FEniCS to a single thread on macOS with ARM to avoid OpenMP errors. +- Pre-building environments avoids repeated environment creation and reduces workflow runtime. + From f6a109d284e7d26fc7a2083c302dfddba13cf0e0 Mon Sep 17 00:00:00 2001 From: Sarbani-Roy Date: Thu, 20 Nov 2025 11:15:44 +0100 Subject: [PATCH 03/10] New workflowfile added --- .github/workflows/run-benchmark-docker.yml | 94 +++++++++++++++++ .github/workflows/run-benchmark.yml | 112 --------------------- 2 files changed, 94 insertions(+), 112 deletions(-) create mode 100644 .github/workflows/run-benchmark-docker.yml delete mode 100644 .github/workflows/run-benchmark.yml diff --git a/.github/workflows/run-benchmark-docker.yml b/.github/workflows/run-benchmark-docker.yml new file mode 100644 index 0000000..340b6ba --- /dev/null +++ b/.github/workflows/run-benchmark-docker.yml @@ -0,0 +1,94 @@ +name: Benchmarking platform in Docker CI + +on: + push: + branches: + - dumux_and_rotating_cylinder + pull_request: + branches: + - dumux_and_rotating_cylinder + workflow_dispatch: + +env: + CACHE_NUMBER: 1 + +jobs: + tests: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Set up QEMU for amd64 emulation + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build Docker image (amd64) + run: | + docker build \ + --platform linux/amd64 \ + -f dockerfiles/Dockerfile \ + -t nfdi_benchmark_docker_ci . + + - name: Run benchmarking platform inside Docker container + run: | + docker run --platform linux/amd64 \ + -v $GITHUB_WORKSPACE:/NFDI_Benchmark \ + nfdi_benchmark_docker_ci \ + bash -lc " + set -e + source /opt/miniforge/etc/profile.d/conda.sh + + echo '>>> Generating config' + cd /NFDI_Benchmark/benchmarks/linear-elastic-plate-with-hole + python generate_config.py + + echo '>>> Pre-building Conda environments' + + echo '>>> Building Kratos environment' + mamba env create -n kratos-sim -f /NFDI_Benchmark/benchmarks/linear-elastic-plate-with-hole/kratos/environment_simulation.yml + conda activate kratos-sim + python -c \"import KratosMultiphysics; print('Kratos version:', KratosMultiphysics.__version__)\" + + echo '>>> Building FEniCS environment' + mamba env create -n fenics-sim -f /NFDI_Benchmark/benchmarks/linear-elastic-plate-with-hole/fenics/environment_simulation.yml + conda activate fenics-sim + export OMP_NUM_THREADS=1 + export KMP_AFFINITY=disabled + python -c \"import dolfinx; print('FEniCSx imported OK')\" + + echo '>>> Building Mesh environment' + conda deactivate + mamba env create -n mesh -f /NFDI_Benchmark/benchmarks/linear-elastic-plate-with-hole/environment_mesh.yml + + echo '>>> Building Postprocessing environment' + mamba env create -n postprocessing -f /NFDI_Benchmark/benchmarks/linear-elastic-plate-with-hole/environment_postprocessing.yml + + echo '>>> Running Snakemake simulation' + cd /NFDI_Benchmark/benchmarks/linear-elastic-plate-with-hole + snakemake --use-conda --force --cores all + + echo '>>> Running metadata/provenance extraction' + snakemake --use-conda --force --cores all \ + --reporter metadata4ing \ + --report-metadata4ing-paramscript ../common/parameter_extractor.py \ + --report-metadata4ing-filename metadata4ing_provenance + + echo '>>> Unzipping provenance results' + unzip -o metadata4ing_provenance.zip -d metadata4ing_provenance + + echo '>>> Postprocessing' + conda activate postprocessing + python plot_provenance.py metadata4ing_provenance + " + + - name: Upload results (provenance + plot) + uses: actions/upload-artifact@v4 + with: + name: Benchmarking platform in Docker CI + path: | + benchmarks/linear-elastic-plate-with-hole/metadata4ing_provenance/ + benchmarks/linear-elastic-plate-with-hole/element_size_vs_stress.pdf \ No newline at end of file diff --git a/.github/workflows/run-benchmark.yml b/.github/workflows/run-benchmark.yml deleted file mode 100644 index b401593..0000000 --- a/.github/workflows/run-benchmark.yml +++ /dev/null @@ -1,112 +0,0 @@ -name: CI -on: - push: - - pull_request: - branches: [ main ] - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - - # Runs the workflow once per day at 3:15am - schedule: - - cron: '3 16 * * *' - -env: - CACHE_NUMBER: 1 # increase to reset cache manually - -jobs: - tests: - runs-on: ubuntu-latest - - - - steps: - - name: checkout repo content - uses: actions/checkout@v2 - - - name: Setup Mambaforge - uses: conda-incubator/setup-miniconda@v3 - with: - miniforge-version: latest - activate-environment: model-validation - use-mamba: true - - - name: Set strict channel priority - run: conda config --set channel_priority strict - - - name: Update environment - run: mamba env update -n model-validation -f environment_benchmarks.yml - - - name: generate-config-files - shell: bash -l {0} - run: | - cd $GITHUB_WORKSPACE/benchmarks/linear-elastic-plate-with-hole/ - python generate_config.py - - - name: run_linear-elastic-plate-with-hole-benchmarks_snakemake - shell: bash -l {0} - run: | - cd $GITHUB_WORKSPACE/benchmarks/linear-elastic-plate-with-hole/ - snakemake --use-conda --force --cores 'all' - snakemake --use-conda --force --cores all \ - --reporter metadata4ing \ - --report-metadata4ing-paramscript parameter_extractor.py \ - --report-metadata4ing-filename metadata4ing_provenance - - - name: run_linear-elastic-plate-with-hole-benchmarks_nextflow - shell: bash -l {0} - run: | - cd $GITHUB_WORKSPACE/benchmarks/linear-elastic-plate-with-hole/ - nextflow run main.nf -params-file workflow_config.json -plugins nf-prov@1.4.0 - - - name: Archive Linear Elastic plate with a hole benchmark data for snakemake - uses: actions/upload-artifact@v4 - with: - name: snakemake_results_linear-elastic-plate-with-hole - path: | - benchmarks/linear-elastic-plate-with-hole/metadata4ing_provenance.zip - - - name: Archive Linear Elastic plate with a hole benchmark data for nextflow - uses: actions/upload-artifact@v4 - with: - name: nextflow_results_linear-elastic-plate-with-hole - path: | - benchmarks/linear-elastic-plate-with-hole/nextflow_results/ - - process-artifacts: - runs-on: ubuntu-latest - needs: tests - steps: - - name: Checkout repo content - uses: actions/checkout@v2 - - - name: Download artifact - uses: actions/download-artifact@v4 - with: - name: snakemake_results_linear-elastic-plate-with-hole - path: ./artifact_files - - - name: Unzip metadata4ing_provenance.zip - run: | - mkdir -p ./metadata4ing_provenance - unzip -o ./artifact_files/metadata4ing_provenance.zip -d ./metadata4ing_provenance - - - name: Setup Mambaforge with postprocessing env - uses: conda-incubator/setup-miniconda@v3 - with: - miniforge-version: latest - activate-environment: postprocessing - use-mamba: true - environment-file: benchmarks/linear-elastic-plate-with-hole/environment_postprocessing.yml - - - name: Run plotting script - shell: bash -l {0} - run: | - python benchmarks/linear-elastic-plate-with-hole/plot_provenance.py ./metadata4ing_provenance - - - name: Upload PDF plot as artifact - uses: actions/upload-artifact@v4 - with: - name: element-size-vs-stress-plot - path: element_size_vs_stress.pdf \ No newline at end of file From 6ec6093af48d202ac042db3afad3b32f4884f888 Mon Sep 17 00:00:00 2001 From: Sarbani-Roy Date: Thu, 20 Nov 2025 11:32:45 +0100 Subject: [PATCH 04/10] workflowfilechanged for Kratos environment --- .github/workflows/run-benchmark-docker.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run-benchmark-docker.yml b/.github/workflows/run-benchmark-docker.yml index 340b6ba..c68b65b 100644 --- a/.github/workflows/run-benchmark-docker.yml +++ b/.github/workflows/run-benchmark-docker.yml @@ -13,7 +13,7 @@ env: CACHE_NUMBER: 1 jobs: - tests: + build-and-run: runs-on: ubuntu-latest steps: @@ -51,7 +51,7 @@ jobs: echo '>>> Building Kratos environment' mamba env create -n kratos-sim -f /NFDI_Benchmark/benchmarks/linear-elastic-plate-with-hole/kratos/environment_simulation.yml conda activate kratos-sim - python -c \"import KratosMultiphysics; print('Kratos version:', KratosMultiphysics.__version__)\" + python -c \"import KratosMultiphysics; print('Kratos imported OK')\" echo '>>> Building FEniCS environment' mamba env create -n fenics-sim -f /NFDI_Benchmark/benchmarks/linear-elastic-plate-with-hole/fenics/environment_simulation.yml From 8ca055095bbf80ab9944e5e21d765677f0a74c04 Mon Sep 17 00:00:00 2001 From: Sarbani-Roy Date: Thu, 20 Nov 2025 14:14:25 +0100 Subject: [PATCH 05/10] trying to modify the workflow file --- .github/workflows/run-benchmark-docker.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/run-benchmark-docker.yml b/.github/workflows/run-benchmark-docker.yml index c68b65b..719bc17 100644 --- a/.github/workflows/run-benchmark-docker.yml +++ b/.github/workflows/run-benchmark-docker.yml @@ -54,6 +54,7 @@ jobs: python -c \"import KratosMultiphysics; print('Kratos imported OK')\" echo '>>> Building FEniCS environment' + conda deactivate mamba env create -n fenics-sim -f /NFDI_Benchmark/benchmarks/linear-elastic-plate-with-hole/fenics/environment_simulation.yml conda activate fenics-sim export OMP_NUM_THREADS=1 @@ -67,7 +68,14 @@ jobs: echo '>>> Building Postprocessing environment' mamba env create -n postprocessing -f /NFDI_Benchmark/benchmarks/linear-elastic-plate-with-hole/environment_postprocessing.yml - echo '>>> Running Snakemake simulation' + echo '>>> Running simulation' + + # Check which conda environment is active + echo '>>> Current Conda environments:' + conda info --envs + echo '>>> Active Conda environment:' + echo $CONDA_DEFAULT_ENV + cd /NFDI_Benchmark/benchmarks/linear-elastic-plate-with-hole snakemake --use-conda --force --cores all From 736fd1b3005f1868c564cbd87a132a003f981835 Mon Sep 17 00:00:00 2001 From: Sarbani-Roy Date: Thu, 20 Nov 2025 14:16:59 +0100 Subject: [PATCH 06/10] modified the workflow file --- .github/workflows/run-benchmark-docker.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/run-benchmark-docker.yml b/.github/workflows/run-benchmark-docker.yml index 719bc17..f3a8903 100644 --- a/.github/workflows/run-benchmark-docker.yml +++ b/.github/workflows/run-benchmark-docker.yml @@ -69,13 +69,7 @@ jobs: mamba env create -n postprocessing -f /NFDI_Benchmark/benchmarks/linear-elastic-plate-with-hole/environment_postprocessing.yml echo '>>> Running simulation' - - # Check which conda environment is active - echo '>>> Current Conda environments:' - conda info --envs - echo '>>> Active Conda environment:' - echo $CONDA_DEFAULT_ENV - + conda activate model-validation cd /NFDI_Benchmark/benchmarks/linear-elastic-plate-with-hole snakemake --use-conda --force --cores all From dfeda1ff78cdd40403d8050fe6e8df9845d151c4 Mon Sep 17 00:00:00 2001 From: Sarbani-Roy Date: Thu, 27 Nov 2025 22:42:25 +0100 Subject: [PATCH 07/10] postprocessing muted for docker-based workfow and local workflowfile added --- .github/workflows/docker_ci.yml | 113 ++++++++++++ .github/workflows/run-benchmark-docker.yml | 96 ---------- .gitignore | 1 + docker_setup.md | 170 ------------------ dockerfiles/Dockerfile | 46 ----- dockerfiles/Dockerfile.base | 16 ++ dockerfiles/Dockerfile.fenics | 22 +++ dockerfiles/Dockerfile.kratos | 21 +++ dockerfiles/Dockerfile.mesh | 34 ++++ dockerfiles/Dockerfile.postprocessing | 6 + dockerfiles/Dockerfile_base_env_ready | 40 ----- local_workflow/build_all_images.sh | 17 ++ local_workflow/config.sh | 14 ++ local_workflow/local_workflow.sh | 24 +++ local_workflow/platform_utils.sh | 24 +++ local_workflow/run_all_benchmarks.sh | 29 +++ .../run_linear-elastic-plate-with-hole.sh | 127 +++++++++++++ 17 files changed, 448 insertions(+), 352 deletions(-) create mode 100644 .github/workflows/docker_ci.yml delete mode 100644 .github/workflows/run-benchmark-docker.yml delete mode 100644 docker_setup.md delete mode 100644 dockerfiles/Dockerfile create mode 100644 dockerfiles/Dockerfile.base create mode 100644 dockerfiles/Dockerfile.fenics create mode 100644 dockerfiles/Dockerfile.kratos create mode 100644 dockerfiles/Dockerfile.mesh create mode 100644 dockerfiles/Dockerfile.postprocessing delete mode 100644 dockerfiles/Dockerfile_base_env_ready create mode 100644 local_workflow/build_all_images.sh create mode 100644 local_workflow/config.sh create mode 100644 local_workflow/local_workflow.sh create mode 100644 local_workflow/platform_utils.sh create mode 100644 local_workflow/run_all_benchmarks.sh create mode 100644 local_workflow/run_linear-elastic-plate-with-hole.sh diff --git a/.github/workflows/docker_ci.yml b/.github/workflows/docker_ci.yml new file mode 100644 index 0000000..01df61f --- /dev/null +++ b/.github/workflows/docker_ci.yml @@ -0,0 +1,113 @@ +name: Docker-based CI + +on: + push: + branches: + - dumux_and_rotating_cylinder + pull_request: + branches: + - dumux_and_rotating_cylinder + workflow_dispatch: + schedule: + - cron: '3 16 * * *' + +jobs: + # ----------------------------- + # Build Docker images (Linux only) + # ----------------------------- + build-docker: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Build base image + id: base + run: | + docker build -t ci-base -f dockerfiles/Dockerfile.base . + + - name: Build Docker images + run: | + docker build -t ci-mesh -f dockerfiles/Dockerfile.mesh . + docker build -t ci-fenics -f dockerfiles/Dockerfile.fenics . + docker build -t ci-kratos -f dockerfiles/Dockerfile.kratos . + docker build -t ci-postprocessing -f dockerfiles/Dockerfile.postprocessing . + + - name: Generate config and mesh + run: | + docker run --rm -v ${{ github.workspace }}:/workspace -w /workspace/benchmarks/linear-elastic-plate-with-hole ci-mesh \ + python generate_config.py + + docker run --rm -v ${{ github.workspace }}:/workspace -w /workspace/benchmarks/linear-elastic-plate-with-hole ci-mesh \ + bash -c 'mkdir -p results/linear-elastic-plate-with-hole/mesh && \ + for f in parameters_*.json; do \ + python create_mesh.py --input_parameter_file "$f" \ + --output_mesh_file "results/linear-elastic-plate-with-hole/mesh/mesh_${f%.json}.msh"; \ + done' + + - name: Run Kratos simulations + run: | + docker run --rm -v ${{ github.workspace }}:/workspace -w /workspace/benchmarks/linear-elastic-plate-with-hole ci-kratos \ + bash -c 'mkdir -p results/linear-elastic-plate-with-hole/kratos && \ + for f in parameters_*.json; do \ + # Convert mesh to MDPA + python kratos/msh_to_mdpa.py \ + --input_parameter_file "$f" \ + --input_mesh_file "results/linear-elastic-plate-with-hole/mesh/mesh_${f%.json}.msh" \ + --output_mdpa_file "results/linear-elastic-plate-with-hole/kratos/mesh_${f%.json}.mdpa"; \ + \ + # Create Kratos input and run simulation + python kratos/create_kratos_input.py \ + --input_parameter_file "$f" \ + --input_mdpa_file "results/linear-elastic-plate-with-hole/kratos/mesh_${f%.json}.mdpa" \ + --input_kratos_input_template kratos/input_template.json \ + --input_material_template kratos/StructuralMaterials_template.json \ + --output_kratos_inputfile "results/linear-elastic-plate-with-hole/kratos/ProjectParameters_${f%.json}.json" \ + --output_kratos_materialfile "results/linear-elastic-plate-with-hole/kratos/MaterialParameters_${f%.json}.json"; \ + \ + python kratos/run_kratos_simulation.py \ + --input_parameter_file "$f" \ + --input_kratos_inputfile "results/linear-elastic-plate-with-hole/kratos/ProjectParameters_${f%.json}.json" \ + --input_kratos_materialfile "results/linear-elastic-plate-with-hole/kratos/MaterialParameters_${f%.json}.json"; \ + done' + + - name: Run fenics simulations + run: | + docker run --rm -v ${{ github.workspace }}:/workspace \ + -w /workspace/benchmarks/linear-elastic-plate-with-hole ci-fenics \ + bash -c 'mkdir -p results/linear-elastic-plate-with-hole/fenics && \ + for f in parameters_*.json; do \ + config_name="${f%.json}"; \ + python fenics/run_fenics_simulation.py \ + --input_parameter_file "$f" \ + --input_mesh_file "results/linear-elastic-plate-with-hole/mesh/mesh_${config_name}.msh" \ + --output_solution_file_zip "results/linear-elastic-plate-with-hole/fenics/solution_field_data_${config_name}.zip" \ + --output_metrics_file "results/linear-elastic-plate-with-hole/fenics/solution_metrics_${config_name}.json"; \ + done' + + # - name: Postprocessing (metadata + extraction + plot) + # run: | + # docker run --rm \ + # -v ${{ github.workspace }}:/workspace \ + # -w /workspace/benchmarks/linear-elastic-plate-with-hole \ + # ci-postprocessing \ + # bash -c ' + # echo "Generating metadata..."; + # python parameter_extractor.py ./results metadata4ing_provenance.zip; + + # echo "Extracting ZIP..."; + # mkdir -p ./metadata4ing_provenance; + # unzip -o metadata4ing_provenance.zip -d ./metadata4ing_provenance; + + # echo "Plotting provenance..."; + # python plot_provenance.py ./metadata4ing_provenance + # ' + + - name: Upload Artifacts + uses: actions/upload-artifact@v4 + with: + name: workflow-results + path: + benchmarks/linear-elastic-plate-with-hole/results + # benchmarks/linear-elastic-plate-with-hole/metadata4ing_provenance/ + # benchmarks/linear-elastic-plate-with-hole/metadata4ing_provenance.zip + # benchmarks/linear-elastic-plate-with-hole/*.pdf diff --git a/.github/workflows/run-benchmark-docker.yml b/.github/workflows/run-benchmark-docker.yml deleted file mode 100644 index f3a8903..0000000 --- a/.github/workflows/run-benchmark-docker.yml +++ /dev/null @@ -1,96 +0,0 @@ -name: Benchmarking platform in Docker CI - -on: - push: - branches: - - dumux_and_rotating_cylinder - pull_request: - branches: - - dumux_and_rotating_cylinder - workflow_dispatch: - -env: - CACHE_NUMBER: 1 - -jobs: - build-and-run: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - - name: Set up QEMU for amd64 emulation - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Build Docker image (amd64) - run: | - docker build \ - --platform linux/amd64 \ - -f dockerfiles/Dockerfile \ - -t nfdi_benchmark_docker_ci . - - - name: Run benchmarking platform inside Docker container - run: | - docker run --platform linux/amd64 \ - -v $GITHUB_WORKSPACE:/NFDI_Benchmark \ - nfdi_benchmark_docker_ci \ - bash -lc " - set -e - source /opt/miniforge/etc/profile.d/conda.sh - - echo '>>> Generating config' - cd /NFDI_Benchmark/benchmarks/linear-elastic-plate-with-hole - python generate_config.py - - echo '>>> Pre-building Conda environments' - - echo '>>> Building Kratos environment' - mamba env create -n kratos-sim -f /NFDI_Benchmark/benchmarks/linear-elastic-plate-with-hole/kratos/environment_simulation.yml - conda activate kratos-sim - python -c \"import KratosMultiphysics; print('Kratos imported OK')\" - - echo '>>> Building FEniCS environment' - conda deactivate - mamba env create -n fenics-sim -f /NFDI_Benchmark/benchmarks/linear-elastic-plate-with-hole/fenics/environment_simulation.yml - conda activate fenics-sim - export OMP_NUM_THREADS=1 - export KMP_AFFINITY=disabled - python -c \"import dolfinx; print('FEniCSx imported OK')\" - - echo '>>> Building Mesh environment' - conda deactivate - mamba env create -n mesh -f /NFDI_Benchmark/benchmarks/linear-elastic-plate-with-hole/environment_mesh.yml - - echo '>>> Building Postprocessing environment' - mamba env create -n postprocessing -f /NFDI_Benchmark/benchmarks/linear-elastic-plate-with-hole/environment_postprocessing.yml - - echo '>>> Running simulation' - conda activate model-validation - cd /NFDI_Benchmark/benchmarks/linear-elastic-plate-with-hole - snakemake --use-conda --force --cores all - - echo '>>> Running metadata/provenance extraction' - snakemake --use-conda --force --cores all \ - --reporter metadata4ing \ - --report-metadata4ing-paramscript ../common/parameter_extractor.py \ - --report-metadata4ing-filename metadata4ing_provenance - - echo '>>> Unzipping provenance results' - unzip -o metadata4ing_provenance.zip -d metadata4ing_provenance - - echo '>>> Postprocessing' - conda activate postprocessing - python plot_provenance.py metadata4ing_provenance - " - - - name: Upload results (provenance + plot) - uses: actions/upload-artifact@v4 - with: - name: Benchmarking platform in Docker CI - path: | - benchmarks/linear-elastic-plate-with-hole/metadata4ing_provenance/ - benchmarks/linear-elastic-plate-with-hole/element_size_vs_stress.pdf \ No newline at end of file diff --git a/.gitignore b/.gitignore index c32b731..8a22949 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ benchmarks/linear-elastic-plate-with-hole/workflow_config.json # Generated folders and outputs benchmarks/linear-elastic-plate-with-hole/metadata4ing_provenance/ benchmarks/linear-elastic-plate-with-hole/snakemake_results/ +benchmarks/linear-elastic-plate-with-hole/results/ # (Optional) ignore all ZIP and PDF files in benchmarks if they are always generated # benchmarks/**/*.zip diff --git a/docker_setup.md b/docker_setup.md deleted file mode 100644 index e7db44d..0000000 --- a/docker_setup.md +++ /dev/null @@ -1,170 +0,0 @@ -# NFDI Benchmark Setup on macOS with Docker - -Kratos Multiphysics is **not natively supported on macOS**, so we use Docker to run it. Below is a step-by-step guide to set up and run the NFDI Benchmark workflow on a Mac with Apple Silicon (ARM) architecture. - ---- - -## 1. Choose a Base Docker Image - -Kratos is distributed only for **x86_64 (amd64) Linux**. Since Apple Silicon uses ARM, we need to emulate x86_64 in Docker: - -```dockerfile -FROM --platform=linux/amd64 ubuntu:22.04 -``` - -This `--platform=linux/amd64` flag ensures Docker emulates an x86_64 environment, allowing Kratos to run correctly. - ---- - -## 2. Build the Docker Image - -Navigate to the repository folder and build the Docker image: - -```bash -cd ~/NFDI_benchmark/NFDI4IngModelValidationPlatform -docker build -t nfdi_benchmark -f dockerfiles/Dockerfile -``` - ---- - -## 3. Run the Docker Container - -Run the container and mount the local repository inside it: -```bash -docker run --platform linux/amd64 -it \ - --name nfdi_benchmark_container \ - -v $(pwd):/NFDI_Benchmark \ - nfdi_benchmark:latest -``` - -This makes the local repository available inside the container and opens an interactive shell. - -To rerun the same container -```bash -docker start -ai -``` - -> **Note:** Docker containers do not start with an interactive login shell, so `conda activate` does not work immediately. We must initialize Conda first. - -```bash -# Initialize Conda -source /opt/miniforge/etc/profile.d/conda.sh - -# Activate the model-validation environment -conda activate model-validation - -# Navigate to the benchmark folder -cd benchmarks/linear-elastic-plate-with-hole -``` - ---- - -## 4. Generate Configuration and Run Workflow - -Generate configuration files: - -```bash -python generate_config.py -``` - -Run the Snakemake workflow: - -```bash -snakemake --use-conda --force --cores all -``` - -Snakemake will look for a `Snakefile` inside `linear-elastic-plate-with-hole` and execute it. - ---- - -## 5. Pre-build Conda Environments (Workaround for Architecture Issues) - -Snakemake’s automatic Conda environment creation fails under Docker emulation due to architecture mismatches. The solution is to pre-create the environments manually. - -### 5.1 Kratos Environment - -```bash -source /opt/miniforge/etc/profile.d/conda.sh -mamba env create -n kratos-sim -f kratos/environment_simulation.yml -conda activate kratos-sim - -# Test Kratos installation -python -c "import KratosMultiphysics; print(KratosMultiphysics.__version__)" -``` - -### 5.2 FEniCS Environment - -FEniCS can fail when running multiple threads under emulation, so we limit it to a single thread: - -```bash -mamba env create -f fenics/environment_simulation.yml -n fenics-sim -conda activate fenics-sim - -# Limit threads to avoid OpenMP/QEMU errors -export OMP_NUM_THREADS=1 -export KMP_AFFINITY=disabled - -# Test FEniCS installation -python -c "import dolfinx; print('FEniCSx imported OK')" -``` - -### 5.3 Mesh Environment - -```bash -conda deactivate -mamba env create -f environment_mesh.yml -n mesh -``` - -### 5.4 Postprocessing Environment - -```bash -mamba env create -f environment_postprocessing.yml -n postprocessing -``` - -> Once these environments are created, Snakemake will skip environment creation, significantly speeding up workflow execution. - ---- - -## 6. Re-run Snakemake Using Pre-built Environments - -```bash -snakemake --use-conda --cores all -``` - ---- - -## 7. Generate Metadata / Provenance Report - -```bash -snakemake --use-conda --force --cores all \ - --reporter metadata4ing \ - --report-metadata4ing-paramscript parameter_extractor.py \ - --report-metadata4ing-filename metadata4ing_provenance -``` - -This updates all metadata and provenance information without re-running the simulations. - ---- - -## 8. Extract Snakemake Outputs (Optional) - -```bash -mkdir -p ./metadata4ing_provenance -unzip -o ./metadata4ing_provenance.zip -d ./metadata4ing_provenance -``` - ---- - -## 9. Plot Results - -```bash -python plot_provenance.py ./metadata4ing_provenance -``` - ---- - -✅ **Notes / Tips:** -- Always initialize Conda inside the Docker container before activating environments. -- Limit FEniCS to a single thread on macOS with ARM to avoid OpenMP errors. -- Pre-building environments avoids repeated environment creation and reduces workflow runtime. - diff --git a/dockerfiles/Dockerfile b/dockerfiles/Dockerfile deleted file mode 100644 index 54070d7..0000000 --- a/dockerfiles/Dockerfile +++ /dev/null @@ -1,46 +0,0 @@ -# ----------------------------- -# Dockerfile for NFDI_Benchmark -# ----------------------------- - -# Base image similar to GitHub Actions ubuntu-latest -FROM --platform=linux/amd64 ubuntu:22.04 - -# Avoid interactive prompts during apt installs -ENV DEBIAN_FRONTEND=noninteractive - -# Install basic dependencies -RUN apt-get update && apt-get install -y \ - wget curl git unzip zip bzip2 libbz2-dev python3 python3-pip \ - build-essential && \ - rm -rf /var/lib/apt/lists/* - -# Install Miniforge3 for amd64 architecture -RUN wget https://github.com/conda-forge/miniforge/releases/download/24.11.0-0/Miniforge3-24.11.0-0-Linux-x86_64.sh && \ - bash Miniforge3-24.11.0-0-Linux-x86_64.sh -b -p /opt/miniforge && \ - rm Miniforge3-24.11.0-0-Linux-x86_64.sh - -# Add conda/mamba to PATH -ENV PATH="/opt/miniforge/bin:$PATH" - -ENV OMP_NUM_THREADS=1 -ENV KMP_AFFINITY=disabled -ENV OMP_PROC_BIND=FALSE - -# Set working directory inside container -WORKDIR /NFDI_Benchmark - -# Copy only environment yaml first to leverage Docker caching -COPY environment_benchmarks.yml /NFDI_Benchmark/ - -# Create model-validation environment -RUN mamba env create -n model-validation -f environment_benchmarks.yml && \ - echo "conda activate model-validation" >> ~/.bashrc - -# Copy the rest of the repo into container -COPY . /NFDI_Benchmark - -# Use bash login shell by default -SHELL ["bash", "-l", "-c"] - -# Drop into interactive bash shell when container starts -CMD ["/bin/bash"] \ No newline at end of file diff --git a/dockerfiles/Dockerfile.base b/dockerfiles/Dockerfile.base new file mode 100644 index 0000000..4519526 --- /dev/null +++ b/dockerfiles/Dockerfile.base @@ -0,0 +1,16 @@ +# Base image for all other stages +FROM ubuntu:22.04 + +# Set environment +ENV DEBIAN_FRONTEND=noninteractive + +# Install basic packages +RUN apt-get update && apt-get install -y \ + python3 python3-pip python3-venv git unzip curl wget build-essential \ + && ln -s /usr/bin/python3 /usr/bin/python \ + && rm -rf /var/lib/apt/lists/* + +# Upgrade pip +RUN python3 -m pip install --upgrade pip + +WORKDIR /workspace diff --git a/dockerfiles/Dockerfile.fenics b/dockerfiles/Dockerfile.fenics new file mode 100644 index 0000000..a7cd6f1 --- /dev/null +++ b/dockerfiles/Dockerfile.fenics @@ -0,0 +1,22 @@ +FROM dolfinx/dolfinx:v0.9.0 + +# ----------------------------- +# Install system dependencies +# ----------------------------- +RUN apt-get update && apt-get install -y \ + cmake \ + gcc \ + g++ \ + git \ + gmsh \ + libgomp1 \ + libxrender1 \ + libxext6 \ + libglu1-mesa \ + && rm -rf /var/lib/apt/lists/* + +# Install Python dependencies missing in the image +RUN python3 -m pip install --upgrade pip +RUN python3 -m pip install pint sympy pyvista gmsh + +WORKDIR /workspace/benchmarks/linear-elastic-plate-with-hole diff --git a/dockerfiles/Dockerfile.kratos b/dockerfiles/Dockerfile.kratos new file mode 100644 index 0000000..8724cc9 --- /dev/null +++ b/dockerfiles/Dockerfile.kratos @@ -0,0 +1,21 @@ +FROM ci-base AS kratos + +# ----------------------------- +# Install system dependencies +# ----------------------------- +RUN apt-get update && apt-get install -y \ + cmake \ + gcc \ + g++ \ + git \ + gmsh \ + libgomp1 \ + libxrender1 \ + libxext6 \ + libglu1-mesa \ + && rm -rf /var/lib/apt/lists/* + +# Python dependencies for Kratos simulations +RUN pip install meshio pyvista pint sympy KratosMultiphysics-all gmsh + +WORKDIR /workspace/benchmarks/linear-elastic-plate-with-hole diff --git a/dockerfiles/Dockerfile.mesh b/dockerfiles/Dockerfile.mesh new file mode 100644 index 0000000..4e3d6ff --- /dev/null +++ b/dockerfiles/Dockerfile.mesh @@ -0,0 +1,34 @@ +# Use full Debian-based Python 3.10 image +FROM python:3.10 + +# ----------------------------- +# Install system dependencies +# ----------------------------- +RUN apt-get update && apt-get install -y \ + cmake \ + gcc \ + g++ \ + git \ + gmsh \ + libgomp1 \ + libxrender1 \ + libxext6 \ + libglu1-mesa \ + && rm -rf /var/lib/apt/lists/* + +# ----------------------------- +# Install Python dependencies +# ----------------------------- +# pint <0.25 ensures compatibility with Python 3.10 +# gmsh latest pip wheel works with Python 3.10 +RUN pip install --no-cache-dir "pint<0.25" gmsh + +# ----------------------------- +# Verify Gmsh installation (headless) +# ----------------------------- +RUN python -c "import gmsh; gmsh.initialize(); print('Gmsh Python API OK'); gmsh.finalize()" + +# ----------------------------- +# Set working directory +# ----------------------------- +WORKDIR /workspace/benchmarks/linear-elastic-plate-with-hole diff --git a/dockerfiles/Dockerfile.postprocessing b/dockerfiles/Dockerfile.postprocessing new file mode 100644 index 0000000..aec32d9 --- /dev/null +++ b/dockerfiles/Dockerfile.postprocessing @@ -0,0 +1,6 @@ +FROM ci-base AS postprocessing + +# Python dependencies for postprocessing +RUN pip install matplotlib pyvista pint rdflib + +WORKDIR /workspace/benchmarks/linear-elastic-plate-with-hole diff --git a/dockerfiles/Dockerfile_base_env_ready b/dockerfiles/Dockerfile_base_env_ready deleted file mode 100644 index b721937..0000000 --- a/dockerfiles/Dockerfile_base_env_ready +++ /dev/null @@ -1,40 +0,0 @@ -# Use Ubuntu base image under x86_64 emulation for Apple Silicon Macs -FROM --platform=linux/amd64 ubuntu:22.04 - -# Prevent interactive prompts during package installs -ENV DEBIAN_FRONTEND=noninteractive - -# Update system and install required packages -RUN apt-get update && apt-get install -y \ - wget git build-essential curl unzip \ - && rm -rf /var/lib/apt/lists/* - -# Install Miniforge (Conda for ARM/x86 compatible builds) -ENV CONDA_DIR=/opt/miniforge -RUN wget https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-Linux-x86_64.sh -O /tmp/miniforge.sh && \ - bash /tmp/miniforge.sh -b -p $CONDA_DIR && \ - rm /tmp/miniforge.sh -ENV PATH=$CONDA_DIR/bin:$PATH - -# Initialize Conda shell integration -RUN conda init bash - -# Create default working directory -WORKDIR /NFDI_Benchmark - -# Copy your repository into the image (optional; or mount it later) -# COPY . /NFDI_Benchmark - -# Pre-install mamba (faster environment management) -RUN conda install -y mamba -n base -c conda-forge - -# Create and activate a base environment for model validation -RUN mamba create -n model-validation python=3.10 -y && \ - echo "source $CONDA_DIR/etc/profile.d/conda.sh && conda activate model-validation" >> ~/.bashrc - -# Set environment variables for stability under emulation -ENV OMP_NUM_THREADS=1 -ENV KMP_AFFINITY=disabled - -# Default command starts bash with Conda activated -ENTRYPOINT ["/bin/bash", "-l"] \ No newline at end of file diff --git a/local_workflow/build_all_images.sh b/local_workflow/build_all_images.sh new file mode 100644 index 0000000..4e87e49 --- /dev/null +++ b/local_workflow/build_all_images.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -e + +source "$(dirname "$0")/platform_utils.sh" +source "$(dirname "$0")/config.sh" + +check_docker + +echo "Building Docker images..." + +docker build -t $IMG_BASE -f $DOCKERFILES_DIR/Dockerfile.base $PROJECT_ROOT +docker build -t $IMG_MESH -f $DOCKERFILES_DIR/Dockerfile.mesh $PROJECT_ROOT +docker build -t $IMG_FENICS -f $DOCKERFILES_DIR/Dockerfile.fenics $PROJECT_ROOT +docker build -t $IMG_KRATOS -f $DOCKERFILES_DIR/Dockerfile.kratos $PROJECT_ROOT +docker build -t $IMG_POST -f $DOCKERFILES_DIR/Dockerfile.postprocessing $PROJECT_ROOT + +echo "All images built." diff --git a/local_workflow/config.sh b/local_workflow/config.sh new file mode 100644 index 0000000..c715345 --- /dev/null +++ b/local_workflow/config.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +# Top-level project dir +PROJECT_ROOT=$(cd "$(dirname "$0")/.." && pwd) + +DOCKERFILES_DIR="$PROJECT_ROOT/dockerfiles" +BENCHMARKS_DIR="$PROJECT_ROOT/benchmarks" + +# Image tags +IMG_BASE="benchmarks-base" +IMG_MESH="benchmarks-mesh" +IMG_FENICS="benchmarks-fenics" +IMG_KRATOS="benchmarks-kratos" +IMG_POST="benchmarks-postprocessing" diff --git a/local_workflow/local_workflow.sh b/local_workflow/local_workflow.sh new file mode 100644 index 0000000..02d9316 --- /dev/null +++ b/local_workflow/local_workflow.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -e + +source "$(dirname "$0")/platform_utils.sh" +source "$(dirname "$0")/config.sh" + +echo "======================================" +echo " LOCAL BENCHMARK WORKFLOW" +echo " Platform: $PLATFORM" +echo " Root: $PROJECT_ROOT" +echo "======================================" + +# Make scripts executable +chmod +x local_workflow/*.sh + +# 1. Build all Docker images +bash "$(dirname "$0")/build_all_images.sh" + +# 2. Run all benchmarks +bash "$(dirname "$0")/run_all_benchmarks.sh" + +echo "======================================" +echo " WORKFLOW COMPLETE" +echo "======================================" diff --git a/local_workflow/platform_utils.sh b/local_workflow/platform_utils.sh new file mode 100644 index 0000000..c0342d1 --- /dev/null +++ b/local_workflow/platform_utils.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -e + +detect_platform() { + OS=$(uname -s) + case "$OS" in + Linux*) echo "linux" ;; + Darwin*) echo "macos" ;; + *) echo "unknown" ;; + esac +} + +check_docker() { + if ! command -v docker >/dev/null 2>&1; then + echo "Docker is not installed." + echo "macOS: Install Docker Desktop" + echo "Linux: sudo apt install docker.io" + exit 1 + fi +} + +export PLATFORM=$(detect_platform) +export -f detect_platform +export -f check_docker diff --git a/local_workflow/run_all_benchmarks.sh b/local_workflow/run_all_benchmarks.sh new file mode 100644 index 0000000..f863e31 --- /dev/null +++ b/local_workflow/run_all_benchmarks.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -e + +source "$(dirname "$0")/config.sh" + +WF_DIR="$(dirname "$0")" + +echo "Running ALL benchmarks..." + +for BENCH in $(ls "$BENCHMARKS_DIR"); do + BENCH_PATH="$BENCHMARKS_DIR/$BENCH" + + if [[ -d "$BENCH_PATH" ]]; then + SCRIPT="$WF_DIR/run_${BENCH}.sh" + + if [[ -f "$SCRIPT" ]]; then + echo "======================================" + echo "Running benchmark script for: $BENCH" + echo "$SCRIPT" + echo "======================================" + bash "$SCRIPT" + else + echo "No script found for benchmark: $BENCH" + echo "Expected: run_${BENCH}.sh" + fi + fi +done + +echo "All benchmark scripts executed." diff --git a/local_workflow/run_linear-elastic-plate-with-hole.sh b/local_workflow/run_linear-elastic-plate-with-hole.sh new file mode 100644 index 0000000..b6d8eb3 --- /dev/null +++ b/local_workflow/run_linear-elastic-plate-with-hole.sh @@ -0,0 +1,127 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Load config +source "$(dirname "$0")/config.sh" + +# Detect OS +OS_TYPE="$(uname -s)" +PLATFORM="" +if [[ "$OS_TYPE" == "Darwin" ]]; then + PLATFORM="--platform linux/amd64" +fi + +# Build Docker images if they don't exist +build_image_if_missing() { + local image_name=$1 + local dockerfile=$2 + + if ! docker image inspect "$image_name" > /dev/null 2>&1; then + echo "Building Docker image: $image_name" + if [[ -n "$PLATFORM" ]]; then + docker buildx build $PLATFORM --load -t "$image_name" -f "$dockerfile" . + else + docker build -t "$image_name" -f "$dockerfile" . + fi + else + echo "Docker image $image_name already exists" + fi +} + +# Ensure ci-base exists and built for correct platform +if ! docker image inspect ci-base:latest > /dev/null 2>&1; then + echo "Building local ci-base image from Dockerfile.base" + if [[ -n "$PLATFORM" ]]; then + docker buildx build $PLATFORM --load -t ci-base:latest -f "$PROJECT_ROOT/dockerfiles/Dockerfile.base" . + else + docker build -t ci-base:latest -f "$PROJECT_ROOT/dockerfiles/Dockerfile.base" . + fi +fi + +# Build benchmark images +build_image_if_missing "$IMG_MESH" "$PROJECT_ROOT/dockerfiles/Dockerfile.mesh" +build_image_if_missing "$IMG_FENICS" "$PROJECT_ROOT/dockerfiles/Dockerfile.fenics" +build_image_if_missing "$IMG_KRATOS" "$PROJECT_ROOT/dockerfiles/Dockerfile.kratos" +build_image_if_missing "$IMG_POST" "$PROJECT_ROOT/dockerfiles/Dockerfile.postprocessing" + +# Paths on host (for mkdir purposes) +OUTPUT_DIR="$BENCHMARKS_DIR/linear-elastic-plate-with-hole/results" +mkdir -p "$OUTPUT_DIR/mesh" "$OUTPUT_DIR/fenics" "$OUTPUT_DIR/kratos" + +echo "Running benchmark: linear-elastic-plate-with-hole" +echo "Benchmark path: $BENCHMARKS_DIR/linear-elastic-plate-with-hole" +echo "Output path: $OUTPUT_DIR" + +# Generate config files inside container +docker run --rm -v "$PROJECT_ROOT":/workspace -w "/workspace/benchmarks/linear-elastic-plate-with-hole" "$IMG_MESH" \ + python3 generate_config.py + +# Mesh generation for all parameter files inside container +docker run --rm -v "$PROJECT_ROOT":/workspace -w "/workspace/benchmarks/linear-elastic-plate-with-hole" "$IMG_MESH" \ + bash -c "for f in parameters_*.json; do \ + python3 create_mesh.py --input_parameter_file \"\$f\" \ + --output_mesh_file \"results/mesh/mesh_\${f%.json}.msh\"; \ + done" + + +echo "Running Kratos simulations for benchmark: linear-elastic-plate-with-hole" + +# Run Kratos simulations inside container +docker run --rm -v "$PROJECT_ROOT":/workspace -w "/workspace/benchmarks/linear-elastic-plate-with-hole" "$IMG_KRATOS" \ + bash -c "for f in parameters_*.json; do \ + # Convert mesh to MDPA + python kratos/msh_to_mdpa.py \ + --input_parameter_file \"\$f\" \ + --input_mesh_file \"results/mesh/mesh_\${f%.json}.msh\" \ + --output_mdpa_file \"results/kratos/mesh_\${f%.json}.mdpa\"; \ + \ + # Create Kratos input files + python kratos/create_kratos_input.py \ + --input_parameter_file \"\$f\" \ + --input_mdpa_file \"results/kratos/mesh_\${f%.json}.mdpa\" \ + --input_kratos_input_template kratos/input_template.json \ + --input_material_template kratos/StructuralMaterials_template.json \ + --output_kratos_inputfile \"results/kratos/ProjectParameters_\${f%.json}.json\" \ + --output_kratos_materialfile \"results/kratos/MaterialParameters_\${f%.json}.json\"; \ + \ + # Run Kratos simulation + python kratos/run_kratos_simulation.py \ + --input_parameter_file \"\$f\" \ + --input_kratos_inputfile \"results/kratos/ProjectParameters_\${f%.json}.json\" \ + --input_kratos_materialfile \"results/kratos/MaterialParameters_\${f%.json}.json\"; \ + done" + +echo "Running Fenics simulations for benchmark: linear-elastic-plate-with-hole" + +# Run Fenics simulations inside container +docker run --rm -v "$PROJECT_ROOT":/workspace -w "/workspace/benchmarks/linear-elastic-plate-with-hole" "$IMG_FENICS" \ + bash -c "for f in parameters_*.json; do \ + config_name=\"\${f%.json}\"; \ + python fenics/run_fenics_simulation.py \ + --input_parameter_file \"\$f\" \ + --input_mesh_file \"results/mesh/mesh_\${config_name}.msh\" \ + --output_solution_file_zip \"results/fenics/solution_field_data_\${config_name}.zip\" \ + --output_metrics_file \"results/fenics/solution_metrics_\${config_name}.json\"; \ + done" + +# echo "Running Postprocessing for benchmark: linear-elastic-plate-with-hole" + +# # Run postprocessing inside container +# docker run --rm \ +# -v "$(pwd)":/workspace \ +# -w /workspace/benchmarks "$IMG_POST" \ +# bash -c " +# echo 'Generating metadata...'; +# python common/parameter_extractor.py ./linear-elastic-plate-with-hole/results ./linear-elastic-plate-with-hole/metadata4ing_provenance.zip; + +# # echo 'Extracting ZIP...'; +# # mkdir -p linear-elastic-plate-with-hole/metadata4ing_provenance; +# # unzip -o linear-elastic-plate-with-hole/metadata4ing_provenance.zip -d linear-elastic-plate-with-hole/metadata4ing_provenance; + +# echo 'Plotting provenance...'; +# python linear-elastic-plate-with-hole/plot_metrics.py ./linear-elastic-plate-with-hole/metadata4ing_provenance +# " + +# echo "Postprocessing complete. Results are in: $OUTPUT_DIR" + +echo "Benchmark linear-elastic-plate-with-hole completed!" From 12895f8e559945b1627c47c983905e01823129e3 Mon Sep 17 00:00:00 2001 From: Sarbani-Roy Date: Thu, 27 Nov 2025 22:52:32 +0100 Subject: [PATCH 08/10] snakemake-based workflow deleted for this branch --- .github/workflows/run-benchmark.yml | 108 ---------------------------- 1 file changed, 108 deletions(-) delete mode 100644 .github/workflows/run-benchmark.yml diff --git a/.github/workflows/run-benchmark.yml b/.github/workflows/run-benchmark.yml deleted file mode 100644 index 04ea390..0000000 --- a/.github/workflows/run-benchmark.yml +++ /dev/null @@ -1,108 +0,0 @@ -name: CI -on: - push: - - pull_request: - branches: [ main ] - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - - # Runs the workflow once per day at 3:15am - schedule: - - cron: '3 16 * * *' - -env: - CACHE_NUMBER: 1 # increase to reset cache manually - -jobs: - tests: - runs-on: ubuntu-latest - - - - steps: - - name: checkout repo content - uses: actions/checkout@v2 - - - name: Setup Mambaforge - uses: conda-incubator/setup-miniconda@v3 - with: - miniforge-version: latest - activate-environment: model-validation - use-mamba: true - - - name: Set strict channel priority - run: conda config --set channel_priority strict - - - name: Update environment - run: mamba env update -n model-validation -f environment_benchmarks.yml - - - name: generate-config-files - shell: bash -l {0} - run: | - cd $GITHUB_WORKSPACE/benchmarks/linear-elastic-plate-with-hole/ - python generate_config.py - - - name: run_linear-elastic-plate-with-hole-benchmarks_snakemake - shell: bash -l {0} - run: | - cd $GITHUB_WORKSPACE/benchmarks/linear-elastic-plate-with-hole/ - snakemake --use-conda --force --cores 'all' - snakemake --use-conda --force --cores all \ - --reporter metadata4ing \ - --report-metadata4ing-paramscript ../common/parameter_extractor.py \ - --report-metadata4ing-filename snakemake_provenance - unzip snakemake_provenance -d snakemake_provenance - - - name: run_linear-elastic-plate-with-hole-benchmarks_nextflow - shell: bash -l {0} - run: | - cd $GITHUB_WORKSPACE/benchmarks/linear-elastic-plate-with-hole/ - nextflow run main.nf -params-file workflow_config.json -c ../common/nextflow.config -plugins nf-prov@1.4.0 - - - name: Archive Linear Elastic plate with a hole benchmark data for snakemake - uses: actions/upload-artifact@v4 - with: - name: snakemake_results_linear-elastic-plate-with-hole - path: | - benchmarks/linear-elastic-plate-with-hole/snakemake_provenance/ - - - name: Archive Linear Elastic plate with a hole benchmark data for nextflow - uses: actions/upload-artifact@v4 - with: - name: nextflow_results_linear-elastic-plate-with-hole - path: | - benchmarks/linear-elastic-plate-with-hole/nextflow_results/ - - process-artifacts: - runs-on: ubuntu-latest - needs: tests - steps: - - name: Checkout repo content - uses: actions/checkout@v2 - - - name: Download artifact - uses: actions/download-artifact@v4 - with: - name: snakemake_results_linear-elastic-plate-with-hole - path: ./snakemake_provenance - - - name: Setup Mambaforge with postprocessing env - uses: conda-incubator/setup-miniconda@v3 - with: - miniforge-version: latest - activate-environment: postprocessing - use-mamba: true - environment-file: benchmarks/linear-elastic-plate-with-hole/environment_postprocessing.yml - - - name: Run plotting script - shell: bash -l {0} - run: | - python benchmarks/linear-elastic-plate-with-hole/plot_metrics.py ./snakemake_provenance - - - name: Upload PDF plot as artifact - uses: actions/upload-artifact@v4 - with: - name: element-size-vs-stress-plot - path: element_size_vs_stress.pdf \ No newline at end of file From c0fb195a8dd71d5e170d54b1bf6bb2fd22bbcf25 Mon Sep 17 00:00:00 2001 From: Sarbani-Roy Date: Mon, 8 Dec 2025 10:53:27 +0100 Subject: [PATCH 09/10] simulation steps are moved inside the dockerfile --- .github/workflows/docker_ci.yml | 150 +++++++++++++++----------------- dockerfiles/Dockerfile.base | 76 +++++++++++++--- dockerfiles/Dockerfile.fenics | 62 +++++++++---- dockerfiles/Dockerfile.kratos | 50 +++++++---- dockerfiles/Dockerfile.mesh | 34 -------- 5 files changed, 215 insertions(+), 157 deletions(-) delete mode 100644 dockerfiles/Dockerfile.mesh diff --git a/.github/workflows/docker_ci.yml b/.github/workflows/docker_ci.yml index 01df61f..f26fd38 100644 --- a/.github/workflows/docker_ci.yml +++ b/.github/workflows/docker_ci.yml @@ -2,87 +2,91 @@ name: Docker-based CI on: push: - branches: - - dumux_and_rotating_cylinder + branches: [ main ] pull_request: - branches: - - dumux_and_rotating_cylinder + branches: [ main ] workflow_dispatch: - schedule: - - cron: '3 16 * * *' + # schedule: + # - cron: '3 16 * * *' + +permissions: + contents: write jobs: - # ----------------------------- - # Build Docker images (Linux only) - # ----------------------------- build-docker: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - - name: Build base image - id: base - run: | - docker build -t ci-base -f dockerfiles/Dockerfile.base . + # ----------------------------- + # Build Docker images + # ----------------------------- + - name: Build base image (mesh generation included) + run: docker build -t ci-base -f dockerfiles/Dockerfile.base . - - name: Build Docker images + - name: Build Fenics image (simulations included) + run: docker build -t ci-fenics -f dockerfiles/Dockerfile.fenics . + + - name: Build Kratos image (simulations included) + run: docker build -t ci-kratos -f dockerfiles/Dockerfile.kratos . + + # ----------------------------- + # Extract Fenics results + # ----------------------------- + - name: Extract Results from Fenics container run: | - docker build -t ci-mesh -f dockerfiles/Dockerfile.mesh . - docker build -t ci-fenics -f dockerfiles/Dockerfile.fenics . - docker build -t ci-kratos -f dockerfiles/Dockerfile.kratos . - docker build -t ci-postprocessing -f dockerfiles/Dockerfile.postprocessing . + docker create --name fenics_temp ci-fenics + # Copy the results folder as-is from container to host + docker cp fenics_temp:/sim/benchmarks/linear-elastic-plate-with-hole/results ./benchmarks/linear-elastic-plate-with-hole/ + docker rm fenics_temp - - name: Generate config and mesh + # ----------------------------- + # Extract Kratos results + # ----------------------------- + - name: Extract Kratos results run: | - docker run --rm -v ${{ github.workspace }}:/workspace -w /workspace/benchmarks/linear-elastic-plate-with-hole ci-mesh \ - python generate_config.py + docker create --name kratos_temp ci-kratos + # Copy the results folder as-is from container to host + docker cp kratos_temp:/sim/benchmarks/linear-elastic-plate-with-hole/results ./benchmarks/linear-elastic-plate-with-hole/ + docker rm kratos_temp - docker run --rm -v ${{ github.workspace }}:/workspace -w /workspace/benchmarks/linear-elastic-plate-with-hole ci-mesh \ - bash -c 'mkdir -p results/linear-elastic-plate-with-hole/mesh && \ - for f in parameters_*.json; do \ - python create_mesh.py --input_parameter_file "$f" \ - --output_mesh_file "results/linear-elastic-plate-with-hole/mesh/mesh_${f%.json}.msh"; \ - done' + # ----------------------------- + # Upload Artifacts + # ----------------------------- + - name: Upload Artifacts + uses: actions/upload-artifact@v4 + with: + name: workflow-results + path: | + benchmarks/linear-elastic-plate-with-hole/results/fenics + benchmarks/linear-elastic-plate-with-hole/results/kratos - - name: Run Kratos simulations - run: | - docker run --rm -v ${{ github.workspace }}:/workspace -w /workspace/benchmarks/linear-elastic-plate-with-hole ci-kratos \ - bash -c 'mkdir -p results/linear-elastic-plate-with-hole/kratos && \ - for f in parameters_*.json; do \ - # Convert mesh to MDPA - python kratos/msh_to_mdpa.py \ - --input_parameter_file "$f" \ - --input_mesh_file "results/linear-elastic-plate-with-hole/mesh/mesh_${f%.json}.msh" \ - --output_mdpa_file "results/linear-elastic-plate-with-hole/kratos/mesh_${f%.json}.mdpa"; \ - \ - # Create Kratos input and run simulation - python kratos/create_kratos_input.py \ - --input_parameter_file "$f" \ - --input_mdpa_file "results/linear-elastic-plate-with-hole/kratos/mesh_${f%.json}.mdpa" \ - --input_kratos_input_template kratos/input_template.json \ - --input_material_template kratos/StructuralMaterials_template.json \ - --output_kratos_inputfile "results/linear-elastic-plate-with-hole/kratos/ProjectParameters_${f%.json}.json" \ - --output_kratos_materialfile "results/linear-elastic-plate-with-hole/kratos/MaterialParameters_${f%.json}.json"; \ - \ - python kratos/run_kratos_simulation.py \ - --input_parameter_file "$f" \ - --input_kratos_inputfile "results/linear-elastic-plate-with-hole/kratos/ProjectParameters_${f%.json}.json" \ - --input_kratos_materialfile "results/linear-elastic-plate-with-hole/kratos/MaterialParameters_${f%.json}.json"; \ - done' + # # ----------------------------- + # # Optional: commit generated results to GitHub + # # ----------------------------- + # - name: Commit generated results + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # run: | + # git config --global user.name "github-actions[bot]" + # git config --global user.email "github-actions[bot]@users.noreply.github.com" - - name: Run fenics simulations - run: | - docker run --rm -v ${{ github.workspace }}:/workspace \ - -w /workspace/benchmarks/linear-elastic-plate-with-hole ci-fenics \ - bash -c 'mkdir -p results/linear-elastic-plate-with-hole/fenics && \ - for f in parameters_*.json; do \ - config_name="${f%.json}"; \ - python fenics/run_fenics_simulation.py \ - --input_parameter_file "$f" \ - --input_mesh_file "results/linear-elastic-plate-with-hole/mesh/mesh_${config_name}.msh" \ - --output_solution_file_zip "results/linear-elastic-plate-with-hole/fenics/solution_field_data_${config_name}.zip" \ - --output_metrics_file "results/linear-elastic-plate-with-hole/fenics/solution_metrics_${config_name}.json"; \ - done' + # # Create a flat zip of the simulation results + # cd benchmarks/linear-elastic-plate-with-hole + # tar -cJf simulation_results.tar.xz results + # cd ../.. + + # # Add only the zip + # git add -f benchmarks/linear-elastic-plate-with-hole/simulation_results.tar.xz + + # git commit -m "Add generated simulation results" || echo "No changes to commit" + # git push https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git HEAD:main + + # - name: Build Postprocessing image + # run: | + # docker build -t ci-postprocessing -f dockerfiles/Dockerfile.postprocessing . # - name: Postprocessing (metadata + extraction + plot) # run: | @@ -93,21 +97,11 @@ jobs: # bash -c ' # echo "Generating metadata..."; # python parameter_extractor.py ./results metadata4ing_provenance.zip; - + # # echo "Extracting ZIP..."; # mkdir -p ./metadata4ing_provenance; # unzip -o metadata4ing_provenance.zip -d ./metadata4ing_provenance; - + # # echo "Plotting provenance..."; # python plot_provenance.py ./metadata4ing_provenance - # ' - - - name: Upload Artifacts - uses: actions/upload-artifact@v4 - with: - name: workflow-results - path: - benchmarks/linear-elastic-plate-with-hole/results - # benchmarks/linear-elastic-plate-with-hole/metadata4ing_provenance/ - # benchmarks/linear-elastic-plate-with-hole/metadata4ing_provenance.zip - # benchmarks/linear-elastic-plate-with-hole/*.pdf + # ' \ No newline at end of file diff --git a/dockerfiles/Dockerfile.base b/dockerfiles/Dockerfile.base index 4519526..799d909 100644 --- a/dockerfiles/Dockerfile.base +++ b/dockerfiles/Dockerfile.base @@ -1,16 +1,70 @@ -# Base image for all other stages -FROM ubuntu:22.04 +# Dockerfile.base +# ci-base: ubuntu:22.04, installs system deps + python deps, generates config & meshes at build time -# Set environment -ENV DEBIAN_FRONTEND=noninteractive +FROM ubuntu:22.04 AS ci-base +ARG DEBIAN_FRONTEND=noninteractive -# Install basic packages -RUN apt-get update && apt-get install -y \ - python3 python3-pip python3-venv git unzip curl wget build-essential \ - && ln -s /usr/bin/python3 /usr/bin/python \ +# ----------------------------- +# 1) Install system packages (kept together for caching) +# ----------------------------- +RUN apt-get update && apt-get install -y --no-install-recommends \ + python3 python3-pip python3-venv \ + cmake gcc g++ git wget curl unzip build-essential \ + gmsh libgomp1 libxrender1 libxext6 libglu1-mesa \ + libgl1-mesa-glx libosmesa6 xvfb \ + libblas-dev liblapack-dev \ && rm -rf /var/lib/apt/lists/* -# Upgrade pip -RUN python3 -m pip install --upgrade pip +# ----------------------------- +# 2) Python & common pip packages (cache layer) +# ----------------------------- +RUN python3 -m pip install --upgrade pip setuptools wheel +# install packages needed by mesh generation (meshio, pyvista, etc.) +RUN python3 -m pip install --no-cache-dir meshio pyvista pint sympy gmsh -WORKDIR /workspace +# ----------------------------- +# 3) Create a non-root runtime user (do not use /sim as home to avoid overwrites) +# ----------------------------- +RUN useradd -m -s /bin/bash sim +# create workspace +RUN mkdir -p /sim && chown sim:sim /sim +WORKDIR /sim + +# optional shared mount +RUN mkdir -p /sim/shared +VOLUME /sim/shared + +# ----------------------------- +# 4) Copy only required files to optimize cache +# (adjust these paths to match the repo) +# ----------------------------- +COPY benchmarks/linear-elastic-plate-with-hole /sim/benchmarks/linear-elastic-plate-with-hole + +# if we have a requirements.txt for additional packages used by mesh scripts, copy it: +# COPY requirements_mesh.txt /sim/requirements_mesh.txt +# RUN python3 -m pip install --no-cache-dir -r /sim/requirements_mesh.txt + +# ensure ownership +RUN chown -R sim:sim /sim + +# ----------------------------- +# 5) Build-time mesh generation (runs as non-root) +# This will bake meshes into the image layers. +# ----------------------------- +USER sim +WORKDIR /sim/benchmarks/linear-elastic-plate-with-hole + +RUN cd /sim/benchmarks/linear-elastic-plate-with-hole && \ + ls && \ + python3 generate_config.py && \ + mkdir -p results/linear-elastic-plate-with-hole/mesh && \ + for f in parameters_*.json; do \ + python3 create_mesh.py --input_parameter_file "$f" \ + --output_mesh_file results/linear-elastic-plate-with-hole/mesh/mesh_${f%.json}.msh; \ + done + +# ----------------------------- +# 6) Final workdir and entrypoint +# ----------------------------- +WORKDIR /sim +ENTRYPOINT ["/bin/bash"] \ No newline at end of file diff --git a/dockerfiles/Dockerfile.fenics b/dockerfiles/Dockerfile.fenics index a7cd6f1..66706dd 100644 --- a/dockerfiles/Dockerfile.fenics +++ b/dockerfiles/Dockerfile.fenics @@ -1,22 +1,52 @@ +# Dockerfile.fenics +# Stage 1: reuse ci-base to get meshes & project +FROM ci-base AS mesh-stage + +# Stage 2: Fenics runtime image FROM dolfinx/dolfinx:v0.9.0 +ARG DEBIAN_FRONTEND=noninteractive + +# ----------------------------- +# 1) Copy project + generated meshes from ci-base +# ----------------------------- +# copy the whole /sim produced by ci-base to /sim in final image +COPY --from=mesh-stage /sim /sim + +# ----------------------------- +# 2) Install any Python packages missing from dolfinx that fenics scripts need +# ----------------------------- +RUN python3 -m pip install --upgrade pip setuptools wheel +# add packages required by fenics scripts (meshio, pyvista, pint, sympy, gmsh python API if needed) +RUN python3 -m pip install --no-cache-dir meshio pyvista pint sympy # ----------------------------- -# Install system dependencies +# 3) User + permissions (ensure sim exists) # ----------------------------- -RUN apt-get update && apt-get install -y \ - cmake \ - gcc \ - g++ \ - git \ - gmsh \ - libgomp1 \ - libxrender1 \ - libxext6 \ - libglu1-mesa \ - && rm -rf /var/lib/apt/lists/* +# attempt to create sim user only if not present; avoid overwriting /sim +RUN id -u sim >/dev/null 2>&1 || useradd -m -s /bin/bash sim +RUN chown -R sim:sim /sim -# Install Python dependencies missing in the image -RUN python3 -m pip install --upgrade pip -RUN python3 -m pip install pint sympy pyvista gmsh +WORKDIR /sim +VOLUME /sim/shared +USER sim -WORKDIR /workspace/benchmarks/linear-elastic-plate-with-hole +# ----------------------------- +# 4) Build-time Fenics simulations (bake results into image) +# ----------------------------- +WORKDIR /sim/benchmarks/linear-elastic-plate-with-hole + +RUN mkdir -p results/fenics && \ + for f in parameters_*.json; do \ + cfg="${f%.json}"; \ + python3 fenics/run_fenics_simulation.py \ + --input_parameter_file "$f" \ + --input_mesh_file results/linear-elastic-plate-with-hole/mesh/mesh_${cfg}.msh \ + --output_solution_file_zip results/fenics/solution_field_data_${cfg}.zip \ + --output_metrics_file results/fenics/solution_metrics_${cfg}.json; \ + done + +# ----------------------------- +# 5) Final +# ----------------------------- +WORKDIR /sim +ENTRYPOINT ["/bin/bash"] \ No newline at end of file diff --git a/dockerfiles/Dockerfile.kratos b/dockerfiles/Dockerfile.kratos index 8724cc9..9621f45 100644 --- a/dockerfiles/Dockerfile.kratos +++ b/dockerfiles/Dockerfile.kratos @@ -1,21 +1,35 @@ -FROM ci-base AS kratos +FROM ci-base -# ----------------------------- -# Install system dependencies -# ----------------------------- -RUN apt-get update && apt-get install -y \ - cmake \ - gcc \ - g++ \ - git \ - gmsh \ - libgomp1 \ - libxrender1 \ - libxext6 \ - libglu1-mesa \ - && rm -rf /var/lib/apt/lists/* +ARG DEBIAN_FRONTEND=noninteractive -# Python dependencies for Kratos simulations -RUN pip install meshio pyvista pint sympy KratosMultiphysics-all gmsh +# Only pip packages required +RUN python3 -m pip install --upgrade pip setuptools wheel && \ + python3 -m pip install --no-cache-dir KratosMultiphysics-all meshio pyvista pint sympy -WORKDIR /workspace/benchmarks/linear-elastic-plate-with-hole +# User (already exists in ci-base) +USER sim +WORKDIR /sim +VOLUME /sim/shared + +# Run Kratos simulations +WORKDIR /sim/benchmarks/linear-elastic-plate-with-hole +RUN mkdir -p results/kratos && \ + for f in parameters_*.json; do \ + python3 kratos/msh_to_mdpa.py \ + --input_parameter_file "$f" \ + --input_mesh_file results/linear-elastic-plate-with-hole/mesh/mesh_${f%.json}.msh \ + --output_mdpa_file results/kratos/mesh_${f%.json}.mdpa; \ + python3 kratos/create_kratos_input.py \ + --input_parameter_file "$f" \ + --input_mdpa_file results/kratos/mesh_${f%.json}.mdpa \ + --input_kratos_input_template kratos/input_template.json \ + --input_material_template kratos/StructuralMaterials_template.json \ + --output_kratos_inputfile results/kratos/ProjectParameters_${f%.json}.json \ + --output_kratos_materialfile results/kratos/MaterialParameters_${f%.json}.json; \ + python3 kratos/run_kratos_simulation.py \ + --input_parameter_file "$f" \ + --input_kratos_inputfile results/kratos/ProjectParameters_${f%.json}.json \ + --input_kratos_materialfile results/kratos/MaterialParameters_${f%.json}.json; \ + done + +ENTRYPOINT ["/bin/bash"] \ No newline at end of file diff --git a/dockerfiles/Dockerfile.mesh b/dockerfiles/Dockerfile.mesh deleted file mode 100644 index 4e3d6ff..0000000 --- a/dockerfiles/Dockerfile.mesh +++ /dev/null @@ -1,34 +0,0 @@ -# Use full Debian-based Python 3.10 image -FROM python:3.10 - -# ----------------------------- -# Install system dependencies -# ----------------------------- -RUN apt-get update && apt-get install -y \ - cmake \ - gcc \ - g++ \ - git \ - gmsh \ - libgomp1 \ - libxrender1 \ - libxext6 \ - libglu1-mesa \ - && rm -rf /var/lib/apt/lists/* - -# ----------------------------- -# Install Python dependencies -# ----------------------------- -# pint <0.25 ensures compatibility with Python 3.10 -# gmsh latest pip wheel works with Python 3.10 -RUN pip install --no-cache-dir "pint<0.25" gmsh - -# ----------------------------- -# Verify Gmsh installation (headless) -# ----------------------------- -RUN python -c "import gmsh; gmsh.initialize(); print('Gmsh Python API OK'); gmsh.finalize()" - -# ----------------------------- -# Set working directory -# ----------------------------- -WORKDIR /workspace/benchmarks/linear-elastic-plate-with-hole From 9f44aa8418bc027bee92593f35733b134b2b4d89 Mon Sep 17 00:00:00 2001 From: Sarbani-Roy Date: Thu, 11 Dec 2025 02:21:26 +0100 Subject: [PATCH 10/10] dockerfilr for dumux added --- .../rotating-cylinders/dumux/Dockerfile | 71 + .../dumux/dumuxModule/.gitignore | 65 + .../dumux/dumuxModule/CMakeLists.txt | 34 + .../dumuxModule/LICENSES/GPL-3.0-or-later.txt | 674 +++ .../dumux/dumuxModule/README.md | 55 + .../dumux/dumuxModule/cmake.opts | 68 + .../cmake/modules/AddInputFileLinks.cmake | 13 + .../dumuxModule/cmake/modules/CMakeLists.txt | 6 + .../cmake/modules/DumuxAddLibrary.cmake | 27 + .../cmake/modules/DumuxDoxygen.cmake | 18 + .../cmake/modules/DumuxModuleMacros.cmake | 14 + .../modules/DumuxMultithreadingBackend.cmake | 44 + .../cmake/modules/DumuxTestMacros.cmake | 308 ++ .../dumuxModule/cmake/modules/FindGmsh.cmake | 51 + .../dumuxModule/cmake/modules/FindGstat.cmake | 55 + .../dumux/dumuxModule/config.h.cmake | 45 + .../dumux/dumuxModule/doc/CMakeLists.txt | 1 + .../dumuxModule/doc/doxygen/CMakeLists.txt | 2 + .../dumux/dumuxModule/doc/doxygen/Doxylocal | 30 + .../dumux/assembly/cclocalresidual.hh | 110 + .../dumuxModule/dumux/assembly/coloring.hh | 303 ++ .../dumux/assembly/cvfelocalassembler.hh | 768 +++ .../dumux/assembly/cvfelocalresidual.hh | 217 + .../dumuxModule/dumux/assembly/diffmethod.hh | 31 + .../dumuxModule/dumux/assembly/entitycolor.hh | 45 + .../dumux/assembly/fclocalassembler.hh | 781 +++ .../dumux/assembly/fvlocalassemblerbase.hh | 330 ++ .../dumux/assembly/fvlocalresidual.hh | 501 ++ .../dumux/assembly/initialsolution.hh | 97 + .../dumux/assembly/jacobianpattern.hh | 251 + .../dumux/assembly/numericepsilon.hh | 58 + .../dumux/assembly/partialreassembler.hh | 510 ++ .../dumux/assembly/volvardeflectionhelper_.hh | 91 + .../dumux/common/balanceequationopts.hh | 40 + .../dumuxModule/dumux/common/boundaryflag.hh | 59 + .../dumuxModule/dumux/common/boundarytypes.hh | 374 ++ .../dumux/common/defaultmappertraits.hh | 30 + .../dumux/common/defaultusagemessage.hh | 57 + .../dumuxModule/dumux/common/dumuxmessage.hh | 423 ++ .../dumuxModule/dumux/common/entitymap.hh | 79 + .../dumuxModule/dumux/common/exceptions.hh | 66 + .../dumuxModule/dumux/common/fvproblem.hh | 552 +++ .../common/fvproblemwithspatialparams.hh | 76 + .../dumux/common/fvspatialparams.hh | 149 + .../dumux/common/gridcapabilities.hh | 79 + .../dumuxModule/dumux/common/indextraits.hh | 34 + .../dumuxModule/dumux/common/initialize.hh | 109 + .../dumux/common/loggingparametertree.hh | 558 +++ .../dumux/dumuxModule/dumux/common/math.hh | 955 ++++ .../dumuxModule/dumux/common/numeqvector.hh | 38 + .../dumux/common/numericdifferentiation.hh | 125 + .../dumuxModule/dumux/common/parameters.hh | 178 + .../dumuxModule/dumux/common/pdesolver.hh | 178 + .../dumuxModule/dumux/common/pointsource.hh | 348 ++ .../dumuxModule/dumux/common/properties.hh | 184 + .../dumux/common/properties/grid.hh | 48 + .../dumux/common/properties/model.hh | 77 + .../dumux/common/properties/propertysystem.hh | 360 ++ .../dumux/common/reservedblockvector.hh | 88 + .../dumux/dumuxModule/dumux/common/tag.hh | 69 + .../dumuxModule/dumux/common/timeloop.hh | 785 +++ .../dumux/common/typetraits/isvalid.hh | 86 + .../dumux/common/typetraits/matrix.hh | 44 + .../dumux/common/typetraits/periodic.hh | 29 + .../dumux/common/typetraits/problem.hh | 39 + .../dumux/common/typetraits/state.hh | 37 + .../dumux/common/typetraits/typetraits.hh | 41 + .../dumux/common/typetraits/utility.hh | 91 + .../dumux/common/typetraits/vector.hh | 30 + .../dumux/common/variablesbackend.hh | 257 + .../dumux/discretization/basegridgeometry.hh | 181 + .../dumux/discretization/basicgridgeometry.hh | 241 + .../dumuxModule/dumux/discretization/box.hh | 152 + .../discretization/box/boxgeometryhelper.hh | 607 +++ .../discretization/box/fvelementgeometry.hh | 495 ++ .../discretization/box/fvgridgeometry.hh | 679 +++ .../discretization/box/subcontrolvolume.hh | 151 + .../box/subcontrolvolumeface.hh | 199 + .../dumux/discretization/cctpfa.hh | 127 + .../cellcentered/connectivitymap.hh | 125 + .../cellcentered/elementboundarytypes.hh | 45 + .../cellcentered/elementsolution.hh | 156 + .../cellcentered/gridvolumevariables.hh | 139 + .../cellcentered/subcontrolvolume.hh | 121 + .../tpfa/computetransmissibility.hh | 84 + .../tpfa/elementfluxvariablescache.hh | 383 ++ .../tpfa/elementvolumevariables.hh | 402 ++ .../cellcentered/tpfa/fvelementgeometry.hh | 695 +++ .../cellcentered/tpfa/fvgridgeometry.hh | 611 +++ .../tpfa/gridfluxvariablescache.hh | 207 + .../cellcentered/tpfa/gridvolumevariables.hh | 52 + .../cellcentered/tpfa/subcontrolvolumeface.hh | 195 + .../dumux/discretization/checkoverlapsize.hh | 64 + .../cvfe/elementboundarytypes.hh | 113 + .../cvfe/elementfluxvariablescache.hh | 263 + .../discretization/cvfe/elementsolution.hh | 139 + .../cvfe/elementvolumevariables.hh | 211 + .../discretization/cvfe/fluxvariablescache.hh | 104 + .../cvfe/gridfluxvariablescache.hh | 164 + .../cvfe/gridvolumevariables.hh | 140 + .../discretization/defaultlocaloperator.hh | 31 + .../dumux/discretization/elementsolution.hh | 25 + .../dumux/discretization/evalgradients.hh | 150 + .../dumux/discretization/evalsolution.hh | 285 ++ .../dumux/discretization/extrusion.hh | 182 + .../facecentered/diamond/fvelementgeometry.hh | 211 + .../facecentered/diamond/fvgridgeometry.hh | 400 ++ .../facecentered/diamond/geometryhelper.hh | 425 ++ .../facecentered/diamond/subcontrolvolume.hh | 122 + .../diamond/subcontrolvolumeface.hh | 160 + .../staggered/consistentlyorientedgrid.hh | 46 + .../facecentered/staggered/elementsolution.hh | 161 + .../dumux/discretization/fcdiamond.hh | 158 + .../dumux/discretization/fluxstencil.hh | 116 + .../dumux/discretization/fvgridvariables.hh | 160 + .../dumux/discretization/fvproperties.hh | 80 + .../dumux/discretization/localdoftraits.hh | 22 + .../dumux/discretization/localview.hh | 31 + .../dumux/discretization/method.hh | 157 + .../discretization/nonconformingfecache.hh | 65 + .../dumux/discretization/pq1bubble.hh | 152 + .../pq1bubble/fvelementgeometry.hh | 237 + .../pq1bubble/fvgridgeometry.hh | 442 ++ .../pq1bubble/geometryhelper.hh | 443 ++ .../pq1bubble/pq1bubblefecache.hh | 65 + .../pq1bubble/pq1bubblelocalfiniteelement.hh | 346 ++ .../pq1bubble/subcontrolvolume.hh | 124 + .../pq1bubble/subcontrolvolumeface.hh | 170 + .../discretization/scvandscvfiterators.hh | 164 + .../staggered/elementsolution.hh | 72 + .../discretization/subcontrolvolumebase.hh | 84 + .../subcontrolvolumefacebase.hh | 101 + .../dumuxModule/dumux/flux/box/fourierslaw.hh | 93 + .../dumux/flux/ccmpfa/fourierslaw.hh | 196 + .../dumux/flux/cctpfa/fourierslaw.hh | 240 + .../dumux/flux/facetensoraverage.hh | 75 + .../dumux/flux/fluxvariablesbase.hh | 86 + .../dumux/flux/fluxvariablescaching.hh | 61 + .../dumuxModule/dumux/flux/fourierslaw.hh | 36 + .../dumuxModule/dumux/flux/fourierslaw_fwd.hh | 35 + .../dumux/flux/referencesystemformulation.hh | 61 + .../flux/staggered/freeflow/fourierslaw.hh | 104 + .../dumuxModule/dumux/flux/upwindscheme.hh | 201 + .../freeflow/navierstokes/energy/indices.hh | 34 + .../freeflow/navierstokes/energy/iofields.hh | 50 + .../freeflow/navierstokes/energy/model.hh | 63 + .../navierstokes/energy/volumevariables.hh | 157 + .../dumux/freeflow/navierstokes/iofields.hh | 135 + .../navierstokes/mass/1p/advectiveflux.hh | 57 + .../navierstokes/mass/1p/fluxvariables.hh | 81 + .../freeflow/navierstokes/mass/1p/indices.hh | 29 + .../navierstokes/mass/1p/localresidual.hh | 115 + .../freeflow/navierstokes/mass/1p/model.hh | 319 ++ .../navierstokes/mass/1p/volumevariables.hh | 150 + .../freeflow/navierstokes/mass/problem.hh | 151 + .../navierstokes/momentum/boundarytypes.hh | 108 + .../navierstokes/momentum/cvfe/flux.hh | 207 + .../navierstokes/momentum/cvfe/indices.hh | 61 + .../momentum/cvfe/localresidual.hh | 167 + .../navierstokes/momentum/cvfe/model.hh | 196 + .../momentum/cvfe/volumevariables.hh | 94 + .../freeflow/navierstokes/momentum/problem.hh | 843 ++++ .../momentum/velocityreconstruction.hh | 132 + .../navierstokes/scalarfluxvariables.hh | 145 + .../scalarfluxvariablescachefiller.hh | 151 + .../navierstokes/scalarvolumevariables.hh | 94 + .../freeflow/navierstokes/velocityoutput.hh | 112 + .../dumux/freeflow/nonisothermal/indices.hh | 34 + .../dumux/freeflow/nonisothermal/iofields.hh | 53 + .../dumux/freeflow/nonisothermal/model.hh | 60 + .../dumuxModule/dumux/freeflow/properties.hh | 48 + .../dumux/freeflow/spatialparams.hh | 125 + .../dumux/freeflow/turbulencemodel.hh | 94 + .../dumux/geometry/boundingboxtree.hh | 446 ++ .../dumuxModule/dumux/geometry/center.hh | 34 + .../dumux/geometry/geometricentityset.hh | 328 ++ .../dumux/geometry/geometryintersection.hh | 1808 +++++++ .../dumux/geometry/grahamconvexhull.hh | 206 + .../dumux/geometry/intersectingentities.hh | 458 ++ .../dumux/geometry/intersectspointgeometry.hh | 111 + .../dumux/geometry/intersectspointsimplex.hh | 230 + .../dumux/geometry/triangulation.hh | 377 ++ .../dumuxModule/dumux/geometry/volume.hh | 187 + .../dumux/dumuxModule/dumux/io/container.hh | 82 + .../dumuxModule/dumux/io/defaultiofields.hh | 42 + .../dumux/dumuxModule/dumux/io/format.hh | 53 + .../dumuxModule/dumux/io/format/fmt/core.h | 3327 +++++++++++++ .../dumuxModule/dumux/io/format/fmt/format.h | 4221 +++++++++++++++++ .../dumuxModule/dumux/io/format/fmt/ranges.h | 727 +++ .../dumux/io/grid/cakegridmanager.hh | 558 +++ .../dumux/io/grid/gmshgriddatahandle.hh | 197 + .../dumuxModule/dumux/io/grid/griddata.hh | 371 ++ .../dumux/io/grid/periodicgridtraits.hh | 134 + .../dumux/io/grid/vtkgriddatahandle.hh | 276 ++ .../dumux/dumuxModule/dumux/io/name.hh | 158 + .../dumuxModule/dumux/io/velocityoutput.hh | 81 + .../dumuxModule/dumux/io/vtk/fieldtype.hh | 28 + .../dumuxModule/dumux/io/vtk/function.hh | 427 ++ .../dumuxModule/dumux/io/vtk/precision.hh | 49 + .../dumuxModule/dumux/io/vtk/vtkreader.hh | 730 +++ .../dumuxModule/dumux/io/vtkoutputmodule.hh | 881 ++++ .../dumuxModule/dumux/io/xml/tinyxml2.cpp | 2828 +++++++++++ .../dumux/dumuxModule/dumux/io/xml/tinyxml2.h | 2253 +++++++++ .../dumuxModule/dumux/linear/dunevectors.hh | 73 + .../dumux/linear/istlsolverregistry.hh | 64 + .../dumuxModule/dumux/linear/istlsolvers.hh | 926 ++++ .../dumux/linear/linearalgebratraits.hh | 70 + .../dumux/linear/linearsolverparameters.hh | 151 + .../dumux/linear/linearsolvertraits.hh | 225 + .../dumux/linear/matrixconverter.hh | 247 + .../dumux/linear/parallelhelpers.hh | 934 ++++ .../dumux/linear/parallelmatrixadapter.hh | 106 + .../dumux/linear/preconditioners.hh | 680 +++ .../dumux/dumuxModule/dumux/linear/solver.hh | 141 + .../dumux/linear/solvercategory.hh | 46 + .../dumuxModule/dumux/linear/stokes_solver.hh | 517 ++ .../dumux/linear/symmetrize_constraints.hh | 142 + .../dumux/material/components/base.hh | 149 + .../material/components/componenttraits.hh | 49 + .../dumux/material/components/constant.hh | 392 ++ .../dumux/material/components/gas.hh | 156 + .../dumux/material/components/ion.hh | 42 + .../dumux/material/components/liquid.hh | 144 + .../dumux/material/components/solid.hh | 79 + .../dumuxModule/dumux/material/constants.hh | 74 + .../dumux/material/fluidstates/immiscible.hh | 351 ++ .../dumux/material/fluidsystems/1pliquid.hh | 327 ++ .../dumux/material/fluidsystems/base.hh | 449 ++ .../fluidsystems/nullparametercache.hh | 26 + .../fluidsystems/parametercachebase.hh | 144 + .../dumuxModule/dumux/material/idealgas.hh | 64 + .../dumux/multidomain/assemblerview.hh | 79 + .../multidomain/couplingjacobianpattern.hh | 206 + .../dumux/multidomain/couplingmanager.hh | 355 ++ .../multidomain/freeflow/couplingmanager.hh | 51 + .../freeflow/couplingmanager_cvfe.hh | 790 +++ .../freeflow/couplingmanager_staggered.hh | 703 +++ .../dumux/multidomain/freeflow/typetraits.hh | 23 + .../dumux/multidomain/fvassembler.hh | 690 +++ .../dumux/multidomain/newtonsolver.hh | 229 + .../multidomain/subdomaincclocalassembler.hh | 942 ++++ .../subdomaincvfelocalassembler.hh | 450 ++ .../multidomain/subdomainfclocalassembler.hh | 462 ++ .../subdomainstaggeredlocalassembler.hh | 1001 ++++ .../dumuxModule/dumux/multidomain/traits.hh | 228 + .../nonlinear/newtonconvergencewriter.hh | 140 + .../dumux/nonlinear/newtonsolver.hh | 1353 ++++++ .../nonlinear/primaryvariableswitchadapter.hh | 127 + .../dumux/parallel/multithreading.hh | 53 + .../dumux/parallel/parallel_for.hh | 169 + .../dumux/parallel/vectorcommdatahandle.hh | 127 + .../dumux/dumuxModule/dumuxModule.pc.in | 15 + .../dumux/dumuxModule/dune.module | 12 + .../dumux/dumuxModule/test/CMakeLists.txt | 1 + .../dumuxModule/test/freeflow/CMakeLists.txt | 1 + .../test/freeflow/navierstokes/CMakeLists.txt | 1 + .../rotatingcylinders/CMakeLists.txt | 17 + .../navierstokes/rotatingcylinders/main.cc | 200 + .../rotatingcylinders/params.input | 55 + .../navierstokes/rotatingcylinders/problem.hh | 166 + .../rotatingcylinders/properties.hh | 94 + .../dumux/install_dumuxModule.py | 105 + 262 files changed, 67552 insertions(+) create mode 100644 benchmarks/rotating-cylinders/dumux/Dockerfile create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/.gitignore create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/CMakeLists.txt create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/LICENSES/GPL-3.0-or-later.txt create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/README.md create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/cmake.opts create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/AddInputFileLinks.cmake create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/CMakeLists.txt create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/DumuxAddLibrary.cmake create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/DumuxDoxygen.cmake create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/DumuxModuleMacros.cmake create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/DumuxMultithreadingBackend.cmake create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/DumuxTestMacros.cmake create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/FindGmsh.cmake create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/FindGstat.cmake create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/config.h.cmake create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/doc/CMakeLists.txt create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/doc/doxygen/CMakeLists.txt create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/doc/doxygen/Doxylocal create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/cclocalresidual.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/coloring.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/cvfelocalassembler.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/cvfelocalresidual.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/diffmethod.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/entitycolor.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/fclocalassembler.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/fvlocalassemblerbase.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/fvlocalresidual.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/initialsolution.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/jacobianpattern.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/numericepsilon.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/partialreassembler.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/volvardeflectionhelper_.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/balanceequationopts.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/boundaryflag.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/boundarytypes.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/defaultmappertraits.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/defaultusagemessage.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/dumuxmessage.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/entitymap.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/exceptions.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/fvproblem.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/fvproblemwithspatialparams.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/fvspatialparams.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/gridcapabilities.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/indextraits.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/initialize.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/loggingparametertree.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/math.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/numeqvector.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/numericdifferentiation.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/parameters.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/pdesolver.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/pointsource.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/properties.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/properties/grid.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/properties/model.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/properties/propertysystem.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/reservedblockvector.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/tag.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/timeloop.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/typetraits/isvalid.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/typetraits/matrix.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/typetraits/periodic.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/typetraits/problem.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/typetraits/state.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/typetraits/typetraits.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/typetraits/utility.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/typetraits/vector.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/variablesbackend.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/basegridgeometry.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/basicgridgeometry.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/box.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/box/boxgeometryhelper.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/box/fvelementgeometry.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/box/fvgridgeometry.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/box/subcontrolvolume.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/box/subcontrolvolumeface.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cctpfa.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/connectivitymap.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/elementboundarytypes.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/elementsolution.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/gridvolumevariables.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/subcontrolvolume.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/tpfa/computetransmissibility.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/tpfa/elementfluxvariablescache.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/tpfa/elementvolumevariables.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/tpfa/fvelementgeometry.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/tpfa/fvgridgeometry.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/tpfa/gridfluxvariablescache.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/tpfa/gridvolumevariables.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/tpfa/subcontrolvolumeface.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/checkoverlapsize.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cvfe/elementboundarytypes.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cvfe/elementfluxvariablescache.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cvfe/elementsolution.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cvfe/elementvolumevariables.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cvfe/fluxvariablescache.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cvfe/gridfluxvariablescache.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cvfe/gridvolumevariables.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/defaultlocaloperator.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/elementsolution.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/evalgradients.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/evalsolution.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/extrusion.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/facecentered/diamond/fvelementgeometry.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/facecentered/diamond/fvgridgeometry.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/facecentered/diamond/geometryhelper.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/facecentered/diamond/subcontrolvolume.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/facecentered/diamond/subcontrolvolumeface.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/facecentered/staggered/consistentlyorientedgrid.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/facecentered/staggered/elementsolution.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/fcdiamond.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/fluxstencil.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/fvgridvariables.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/fvproperties.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/localdoftraits.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/localview.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/method.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/nonconformingfecache.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/pq1bubble.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/pq1bubble/fvelementgeometry.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/pq1bubble/fvgridgeometry.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/pq1bubble/geometryhelper.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/pq1bubble/pq1bubblefecache.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/pq1bubble/pq1bubblelocalfiniteelement.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/pq1bubble/subcontrolvolume.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/pq1bubble/subcontrolvolumeface.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/scvandscvfiterators.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/staggered/elementsolution.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/subcontrolvolumebase.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/subcontrolvolumefacebase.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/box/fourierslaw.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/ccmpfa/fourierslaw.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/cctpfa/fourierslaw.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/facetensoraverage.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/fluxvariablesbase.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/fluxvariablescaching.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/fourierslaw.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/fourierslaw_fwd.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/referencesystemformulation.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/staggered/freeflow/fourierslaw.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/upwindscheme.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/energy/indices.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/energy/iofields.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/energy/model.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/energy/volumevariables.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/iofields.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/mass/1p/advectiveflux.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/mass/1p/fluxvariables.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/mass/1p/indices.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/mass/1p/localresidual.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/mass/1p/model.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/mass/1p/volumevariables.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/mass/problem.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/momentum/boundarytypes.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/momentum/cvfe/flux.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/momentum/cvfe/indices.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/momentum/cvfe/localresidual.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/momentum/cvfe/model.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/momentum/cvfe/volumevariables.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/momentum/problem.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/momentum/velocityreconstruction.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/scalarfluxvariables.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/scalarfluxvariablescachefiller.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/scalarvolumevariables.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/velocityoutput.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/nonisothermal/indices.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/nonisothermal/iofields.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/nonisothermal/model.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/properties.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/spatialparams.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/turbulencemodel.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/boundingboxtree.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/center.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/geometricentityset.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/geometryintersection.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/grahamconvexhull.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/intersectingentities.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/intersectspointgeometry.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/intersectspointsimplex.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/triangulation.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/volume.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/container.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/defaultiofields.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/format.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/format/fmt/core.h create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/format/fmt/format.h create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/format/fmt/ranges.h create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/grid/cakegridmanager.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/grid/gmshgriddatahandle.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/grid/griddata.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/grid/periodicgridtraits.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/grid/vtkgriddatahandle.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/name.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/velocityoutput.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/vtk/fieldtype.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/vtk/function.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/vtk/precision.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/vtk/vtkreader.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/vtkoutputmodule.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/xml/tinyxml2.cpp create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/xml/tinyxml2.h create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/linear/dunevectors.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/linear/istlsolverregistry.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/linear/istlsolvers.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/linear/linearalgebratraits.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/linear/linearsolverparameters.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/linear/linearsolvertraits.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/linear/matrixconverter.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/linear/parallelhelpers.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/linear/parallelmatrixadapter.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/linear/preconditioners.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/linear/solver.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/linear/solvercategory.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/linear/stokes_solver.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/linear/symmetrize_constraints.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/material/components/base.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/material/components/componenttraits.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/material/components/constant.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/material/components/gas.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/material/components/ion.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/material/components/liquid.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/material/components/solid.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/material/constants.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/material/fluidstates/immiscible.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/material/fluidsystems/1pliquid.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/material/fluidsystems/base.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/material/fluidsystems/nullparametercache.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/material/fluidsystems/parametercachebase.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/material/idealgas.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/multidomain/assemblerview.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/multidomain/couplingjacobianpattern.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/multidomain/couplingmanager.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/multidomain/freeflow/couplingmanager.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/multidomain/freeflow/couplingmanager_cvfe.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/multidomain/freeflow/couplingmanager_staggered.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/multidomain/freeflow/typetraits.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/multidomain/fvassembler.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/multidomain/newtonsolver.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/multidomain/subdomaincclocalassembler.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/multidomain/subdomaincvfelocalassembler.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/multidomain/subdomainfclocalassembler.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/multidomain/subdomainstaggeredlocalassembler.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/multidomain/traits.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/nonlinear/newtonconvergencewriter.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/nonlinear/newtonsolver.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/nonlinear/primaryvariableswitchadapter.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/parallel/multithreading.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/parallel/parallel_for.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/parallel/vectorcommdatahandle.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dumuxModule.pc.in create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/dune.module create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/test/CMakeLists.txt create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/test/freeflow/CMakeLists.txt create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/test/freeflow/navierstokes/CMakeLists.txt create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/test/freeflow/navierstokes/rotatingcylinders/CMakeLists.txt create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/test/freeflow/navierstokes/rotatingcylinders/main.cc create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/test/freeflow/navierstokes/rotatingcylinders/params.input create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/test/freeflow/navierstokes/rotatingcylinders/problem.hh create mode 100644 benchmarks/rotating-cylinders/dumux/dumuxModule/test/freeflow/navierstokes/rotatingcylinders/properties.hh create mode 100755 benchmarks/rotating-cylinders/dumux/install_dumuxModule.py diff --git a/benchmarks/rotating-cylinders/dumux/Dockerfile b/benchmarks/rotating-cylinders/dumux/Dockerfile new file mode 100644 index 0000000..09680b4 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/Dockerfile @@ -0,0 +1,71 @@ +# SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +# SPDX-License-Identifier: GPL-3.0-or-later + +# dumuxModule docker container +# see https://github.com/phusion/baseimage-docker for information on the base image +# It is Ubuntu LTS customized for better Docker compatibility +FROM phusion/baseimage:jammy-1.0.4 +MAINTAINER sarbani.roy@simtech.uni-stuttgart.de + +# run Ubuntu update as advised on https://github.com/phusion/baseimage-docker +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update \ + && apt-get upgrade -y -o Dpkg::Options::="--force-confold" \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +# install the basic dependencies +RUN apt-get update \ + && apt-get install --no-install-recommends --yes \ + ca-certificates \ + vim \ + python3-dev \ + python3-pip \ + git \ + pkg-config \ + cmake \ + build-essential \ + gfortran \ + mpi-default-bin \ + mpi-default-dev \ + libsuitesparse-dev \ + libsuperlu-dev \ + libeigen3-dev \ + doxygen \ + wget \ + clang-15 \ + lld-15 \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +# set Clang 15 as default compiler +ENV CC=clang-15 +ENV CXX=clang++-15 + +# switch to the dumux user and set the working directory +# USER dumux +WORKDIR /dumux + +# create a shared volume communicating with the host +RUN mkdir /dumux/shared +VOLUME /dumux/shared + +# Copy dumuxModule from local build context into the container +COPY dumuxModule /dumux/dumuxmodule + +# Install the dumux module and its dependencies +# This expects the install script to do everything from clone to configure +COPY install_dumuxModule.py /dumux/install_dumuxModule.py +RUN python3 /dumux/install_dumuxModule.py && rm -f /dumux/install_dumuxModule.py + +# Build and run the specific DUMUX test +RUN cd /dumux/dumuxmodule/build-cmake/test/freeflow/navierstokes/rotatingcylinders \ + && make test_ff_navierstokes_rotatingcylinders \ + && ./test_ff_navierstokes_rotatingcylinders + +# set entry point like advised https://github.com/phusion/baseimage-docker +# this sets the permissions right, see above +ENTRYPOINT ["/sbin/my_init","--quiet","--","/bin/bash","-l","-c"] + +# start interactive shell +CMD ["/bin/bash","-i"] \ No newline at end of file diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/.gitignore b/benchmarks/rotating-cylinders/dumux/dumuxModule/.gitignore new file mode 100644 index 0000000..0889719 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/.gitignore @@ -0,0 +1,65 @@ +# SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +# SPDX-License-Identifier: CC0-1.0 + +# build system clutter +build-* +Testing +!bin/testing + +# auto-saved files +*~ + +# hidden files +.cproject +.project +.vscode + +# left overs from git rebase +*.orig +*.rej + +# latex clutter +*.pdf +*.aux +*.blg +*.log +*.bbl +*.dvi +*.idx +*.out +*.tdo +*.toc +*.synctex.gz + +# Python clutter +*.pyc +__pycache__ +.ipynb_checkpoints +*.bin +*.npy + +# Environments +.env +.venv +env/ +venv/ + +# macOS +.DS_Store + +# always consider files containing source code regardless of their name +!*.cc +!*.hh +!*.c +!*.h +!*.sh +!*.py + +# always consider reference solutions +!*reference.vtu + +# ignore files generated during python setup.py sdist +MANIFEST +_skbuild/ +dist +*.egg-info diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/CMakeLists.txt b/benchmarks/rotating-cylinders/dumux/dumuxModule/CMakeLists.txt new file mode 100644 index 0000000..7aeb491 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.16) +project(dumuxModule CXX) + +if(NOT (dune-common_DIR OR dune-common_ROOT OR + "${CMAKE_PREFIX_PATH}" MATCHES ".*dune-common.*")) + string(REPLACE ${PROJECT_NAME} dune-common dune-common_DIR + ${PROJECT_BINARY_DIR}) +endif() + +#find dune-common and set the module path +find_package(dune-common REQUIRED) +list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/modules" + ${dune-common_MODULE_PATH}) + +#include the dune macros +include(DuneMacros) + +# start a dune project with information from dune.module +dune_project() + +dune_enable_all_packages() + +# Set C++20 standard +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + + +add_subdirectory(doc) +add_subdirectory(cmake/modules) +add_subdirectory(test) + +# finalize the dune project, e.g. generating config.h etc. +finalize_dune_project() diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/LICENSES/GPL-3.0-or-later.txt b/benchmarks/rotating-cylinders/dumux/dumuxModule/LICENSES/GPL-3.0-or-later.txt new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/LICENSES/GPL-3.0-or-later.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/README.md b/benchmarks/rotating-cylinders/dumux/dumuxModule/README.md new file mode 100644 index 0000000..40184e3 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/README.md @@ -0,0 +1,55 @@ +This file has been created automatically. Please adapt it to your needs. + +## Content + +The content of this DUNE module was extracted from the module `dumux`. +In particular, the following subFolder of `dumux` have been extracted: +* `test/freeflow/navierstokes/rotatingcylinders` + + +Additionally, all headers in `dumux` that are required to build the +executables from the sources +* `test/freeflow/navierstokes/rotatingcylinders/main.cc` + + +have been extracted. You can configure the module just like any other DUNE +module by using `dunecontrol`. For building and running the executables, +please go to the build folders corresponding to the sources listed above. + + +## License + +This project is licensed under the terms and conditions of the GNU General Public +License (GPL) version 3 or - at your option - any later version. +The GPL can be found under [GPL-3.0-or-later.txt](LICENSES/GPL-3.0-or-later.txt) +provided in the `LICENSES` directory located at the topmost of the source code tree. + + +## Version Information + +| module name | branch name | commit sha | commit date | +|-----------------------|------------------------|--------------------------------------------|-----------------------------| +| dune-istl | origin/releases/2.10 | 21c67275b17e93918365177f93f42e4aaa9afd23 | 2025-02-03 09:13:05 +0000 | +| dune-grid | origin/releases/2.10 | 954436b88247e904628ec4d7c8bb7b2eaac08900 | 2024-10-22 15:31:40 +0000 | +| dune-localfunctions | origin/releases/2.10 | ddbf693b5f9c867b2d58d418fe130bbe92c06a99 | 2025-06-27 05:40:52 +0000 | +| dune-geometry | origin/releases/2.10 | 5673e95ac364ad3498aed9eaf65e0d224384d15a | 2024-09-04 16:39:39 +0200 | +| dune-uggrid | origin/releases/2.10 | cf2513efb6497dc95744649e0658cedef2980bff | 2024-09-05 08:40:59 +0200 | +| dune-common | origin/releases/2.10 | fa09b5bd31efa38cc051e22717b0d259de5ae8a1 | 2025-06-03 21:04:17 +0000 | + +## Installation + +The installation procedure is done as follows: +Create a root folder, e.g. `DUMUX`, enter the previously created folder, +clone this repository and use the install script `install_dumuxModule.py` +provided in this repository to install all dependent modules. + +```sh +mkdir DUMUX +cd DUMUX +git clone git@github.com:Sarbani-Roy/benchmark.git dumuxModule +./dumuxModule/install_dumuxModule.py +``` + +This will clone all modules into the directory `DUMUX`, +configure your module with `dunecontrol` and build tests. + diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/cmake.opts b/benchmarks/rotating-cylinders/dumux/dumuxModule/cmake.opts new file mode 100644 index 0000000..dc591fa --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/cmake.opts @@ -0,0 +1,68 @@ +# SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +# SPDX-License-Identifier: CC0-1.0 + +GXX_RELEASE_WARNING_OPTS=" \ + -Wall \ + -Wunused \ + -Wmissing-include-dirs \ + -Wcast-align \ + -Wno-missing-braces \ + -Wmissing-field-initializers \ + -Wno-sign-compare" + +GXX_RELEASE_OPTS=" \ + -fdiagnostics-color=always \ + -fno-strict-aliasing \ + -fstrict-overflow \ + -fno-finite-math-only \ + -DNDEBUG=1 \ + -O3 \ + -march=native \ + -funroll-loops \ + -g0" + +SPECIFIC_COMPILER="" +# if you want to specify a specific compiler, do it by setting (comment the above line) +#SPECIFIC_COMPILER=" +# -DCMAKE_C_COMPILER=/usr/bin/gcc-8 +# -DCMAKE_CXX_COMPILER=/usr/bin/g++-8 +#" + +SPECIFIC_GENERATOR="" +# if you want to specify a specific make file generator (e.g. ninja), do it by setting (comment the above line) +#SPECIFIC_GENERATOR=" +# -DCMAKE_GENERATOR='Ninja' +# -DCMAKE_MAKE_PROGRAM='/usr/bin/ninja' +#" + +OPM_FLAGS="" +# to build opm it might be necessary to set manually the following variables (comment the above line) +#OPM_FLAGS=" +#-DUSE_MPI=ON +#" + +# set this to "ON" if you want to be able to have the headercheck target +DUMUX_ENABLE_HEADERCHECK=OFF + +# DuMux Python bindings require shared libraries to be built for DuMux and upstream modules +DUMUX_ENABLE_PYTHON_BINDINGS="-DDUNE_ENABLE_PYTHONBINDINGS=ON -DBUILD_SHARED_LIBS=ON -DCMAKE_POSITION_INDEPENDENT_CODE=ON" +#DUMUX_ENABLE_PYTHON_BINDINGS="" + +# set multithreading backend, e.g. OpenMP,TBB,Cpp,Kokkos,Serial +# if left empty, it is automatically set by cmake +DUMUX_MULTITHREADING_BACKEND="" +#DUMUX_MULTITHREADING_BACKEND="-DDUMUX_MULTITHREADING_BACKEND=OpenMP" + +# for debug opts you can set DCMAKE_BUILD_TYPE to "Debug" or "RelWithDebInfo" +# you can also do this in any of the CMakeLists.txt in Dumux +# just rerun cmake again afterwards (run cmake ) + +CMAKE_FLAGS="$SPECIFIC_COMPILER $SPECIFIC_GENERATOR $OPM_FLAGS +-DCMAKE_CXX_FLAGS_RELEASE='$GXX_RELEASE_OPTS $GXX_RELEASE_WARNING_OPTS' +-DCMAKE_CXX_FLAGS_DEBUG='-O0 -g -ggdb -Wall -Wextra -Wno-unused-parameter -Wno-sign-compare -DDUNE_CHECK_BOUNDS=ON' +-DCMAKE_CXX_FLAGS_RELWITHDEBINFO='$GXX_RELEASE_OPTS $GXX_RELEASE_WARNING_OPTS -g -ggdb -Wall' +-DCMAKE_BUILD_TYPE=Release +-DENABLE_HEADERCHECK=$DUMUX_ENABLE_HEADERCHECK +$DUMUX_ENABLE_PYTHON_BINDINGS +$DUMUX_MULTITHREADING_BACKEND +" \ No newline at end of file diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/AddInputFileLinks.cmake b/benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/AddInputFileLinks.cmake new file mode 100644 index 0000000..c272782 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/AddInputFileLinks.cmake @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +# SPDX-License-Identifier: GPL-3.0-or-later + +# Creates symbolic links to all input files in the source directory +include_guard(GLOBAL) + +macro(add_input_file_links) + FILE(GLOB input_files *.input) + foreach(VAR ${input_files}) + get_filename_component(file_name ${VAR} NAME) + dune_symlink_to_source_files(FILES ${file_name}) + endforeach() +endmacro() diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/CMakeLists.txt b/benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/CMakeLists.txt new file mode 100644 index 0000000..87d7291 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/CMakeLists.txt @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +# SPDX-License-Identifier: GPL-3.0-or-later + +file(GLOB modules *.cmake) +include(GNUInstallDirs) +install(FILES ${modules} DESTINATION ${DUNE_INSTALL_MODULEDIR}) diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/DumuxAddLibrary.cmake b/benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/DumuxAddLibrary.cmake new file mode 100644 index 0000000..decfa2a --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/DumuxAddLibrary.cmake @@ -0,0 +1,27 @@ +# SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +# SPDX-License-Identifier: GPL-3.0-or-later + +# Dumux function to add a library +# +# .. cmake_function:: dumux_add_library +# +# .. cmake_brief:: +# +# Add a library to the build system. For details see :ref:`dune_add_library`. +# +include(DuneAddLibrary) + +function(dumux_add_library) + set(DUNE_ADD_LIB_ARGS "${ARGN}") + + # EXPORT_NAME and NAMESPACE did not exist prior 2.10, so we remove it from arguments + if(DUNE_COMMON_VERSION VERSION_LESS 2.10) + set(SINGLEARGS EXPORT_NAME NAMESPACE) + cmake_parse_arguments(ARG "" "${SINGLEARGS}" "" ${ARGN}) + string(REPLACE "EXPORT_NAME;${ARG_EXPORT_NAME}" "" DUNE_ADD_LIB_ARGS "${DUNE_ADD_LIB_ARGS}") + string(REPLACE "NAMESPACE;${ARG_NAMESPACE}" "" DUNE_ADD_LIB_ARGS "${DUNE_ADD_LIB_ARGS}") + endif() + + # forward to dune library creation + dune_add_library(${DUNE_ADD_LIB_ARGS}) +endfunction() diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/DumuxDoxygen.cmake b/benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/DumuxDoxygen.cmake new file mode 100644 index 0000000..c5111d1 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/DumuxDoxygen.cmake @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +# SPDX-License-Identifier: GPL-3.0-or-later + +# add_dumux_doxgen_target +# +# make sure, that the doxygen links to todo list, bibliography, etc. are correct +include_guard(GLOBAL) + +include(DuneDoxygen) + +macro (add_dumux_doxygen_target) + if(DOXYGEN_FOUND) + add_doxygen_target() + add_custom_target(doxygen_${ProjectName}_prebuild + COMMAND rm -rf ${CMAKE_BINARY_DIR}/doc/doxygen/html) + add_dependencies(doxygen_${ProjectName} doxygen_${ProjectName}_prebuild) + endif() +endmacro () diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/DumuxModuleMacros.cmake b/benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/DumuxModuleMacros.cmake new file mode 100644 index 0000000..58263ff --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/DumuxModuleMacros.cmake @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +# SPDX-License-Identifier: GPL-3.0-or-later + +# additional macros +include(AddInputFileLinks) +include(DumuxDoxygen) +include(DumuxTestMacros) + +find_package(Gnuplot QUIET) +set(DUMUX_HAVE_GNUPLOT ${GNUPLOT_FOUND}) +find_package(Gstat QUIET) +find_package(Gmsh QUIET) +find_package(PTScotch QUIET) +include(AddPTScotchFlags) diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/DumuxMultithreadingBackend.cmake b/benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/DumuxMultithreadingBackend.cmake new file mode 100644 index 0000000..4f46f7f --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/DumuxMultithreadingBackend.cmake @@ -0,0 +1,44 @@ +# SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +# SPDX-License-Identifier: GPL-3.0-or-later + +# test if we can use parallel algorithms +include(CheckCXXSymbolExists) +check_cxx_symbol_exists( + "std::execution::par_unseq" + "execution" + DUMUX_HAVE_CXX_EXECUTION_POLICY +) + +if(DUMUX_HAVE_CXX_EXECUTION_POLICY) + set(DUMUX_HAVE_CPP_PARALLEL_ALGORITHMS TRUE) +endif() + +# setup multithreading backend +if(NOT DUMUX_MULTITHREADING_BACKEND) + if(TBB_FOUND) + set(DUMUX_MULTITHREADING_BACKEND "TBB" CACHE STRING "The multithreading backend") + elseif(OpenMP_FOUND) + set(DUMUX_MULTITHREADING_BACKEND "OpenMP" CACHE STRING "The multithreading backend") + elseif(Kokkos_FOUND) + set(DUMUX_MULTITHREADING_BACKEND "Kokkos" CACHE STRING "The multithreading backend") + elseif(DUMUX_HAVE_CXX_EXECUTION_POLICY) + set(DUMUX_MULTITHREADING_BACKEND "Cpp" CACHE STRING "The multithreading backend") + else() + set(DUMUX_MULTITHREADING_BACKEND "Serial" CACHE STRING "The multithreading backend") + endif() + +# abort if a multithreading backend has been manually selected +# but it is not available +else() + if(DUMUX_MULTITHREADING_BACKEND STREQUAL "TBB" AND NOT TBB_FOUND) + message(FATAL_ERROR "Selected TBB as Dumux multithreading backed but TBB has not been found") + elseif(DUMUX_MULTITHREADING_BACKEND STREQUAL "OpenMP" AND NOT OpenMP_FOUND) + message(FATAL_ERROR "Selected OpenMP as Dumux multithreading backed but OpenMP has not been found") + elseif(DUMUX_MULTITHREADING_BACKEND STREQUAL "Kokkos" AND NOT Kokkos_FOUND) + message(FATAL_ERROR "Selected Kokkos as Dumux multithreading backed but Kokkos has not been found") + elseif(DUMUX_MULTITHREADING_BACKEND STREQUAL "Cpp" AND NOT DUMUX_HAVE_CXX_EXECUTION_POLICY) + message(FATAL_ERROR "Selected Cpp as Dumux multithreading backed but your compiler does not implement parallel STL") + endif() +endif() + +message(STATUS "Dumux multithreading backend: ${DUMUX_MULTITHREADING_BACKEND}") diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/DumuxTestMacros.cmake b/benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/DumuxTestMacros.cmake new file mode 100644 index 0000000..b28614b --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/DumuxTestMacros.cmake @@ -0,0 +1,308 @@ +# SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +# SPDX-License-Identifier: GPL-3.0-or-later + +# Dumux wrapper for the module that provides tools for testing the Dune way. +# We have a wrapper to have to possibly of supporting multiple Dune versions. +# +# .. cmake_function:: dumux_add_test +# +# .. cmake_brief:: +# +# Adds a test to the Dumux testing suite! +# +# .. cmake_param:: NAME +# :single: +# +# The name of the test that should be added. If an executable +# is also added (by specifying SOURCES), the executable is also +# named accordingly. If omitted, the name will be deduced from +# the (single) sources parameter or from the given target. Note +# that this requires you to take care, that you only use a target +# or source file for but one such test. +# +# .. cmake_param:: SOURCES +# :multi: +# +# The source files that this test depends on. These are the +# sources that will be passed to :ref:`add_executable`. +# +# You *must* specify either :code:`SOURCES` or :code:`TARGET`. +# +# .. cmake_param:: TARGET +# :single: +# +# An executable target which should be used for the test. Use +# this option over the :code:`SOURCES` parameter if you want to +# reuse already added targets. +# +# You *must* specify either :code:`SOURCES` or :code:`TARGET`. +# +# .. cmake_param:: COMPILE_DEFINITIONS +# :multi: +# :argname: def +# +# A set of compile definitions to add to the target. +# Only definitions beyond the application of :ref:`add_dune_all_flags` +# have to be stated. +# This is only used, if :code:`dumux_add_test` adds the executable itself. +# +# .. cmake_param:: COMPILE_FLAGS +# :multi: +# :argname: flag +# +# A set of non-definition compile flags to add to the target. +# Only flags beyond the application of :ref:`add_dune_all_flags` +# have to be stated. +# This is only used, if :code:`dumux_add_test` adds the executable itself. +# +# .. cmake_param:: LINK_LIBRARIES +# :multi: +# :argname: lib +# +# A list of libraries to link the target to. +# Only libraries beyond the application of :ref:`add_dune_all_flags` +# have to be stated. +# This is only used, if :code:`dumux_add_test` adds the executable itself. +# +# .. cmake_param:: EXPECT_COMPILE_FAIL +# :option: +# +# If given, the test is expected to not compile successfully! +# +# .. cmake_param:: EXPECT_FAIL +# :option: +# +# If given, this test is expected to compile, but fail to run. +# +# .. cmake_param:: CMD_ARGS +# :multi: +# :argname: arg +# +# Command line arguments that should be passed to this test. +# +# .. cmake_param:: MPI_RANKS +# :multi: +# :argname: ranks +# +# The numbers of cores that this test should be executed with. +# Note that one test (in the ctest sense) is created for each number +# given here. Any number exceeding the user-specified processor maximum +# :ref:`DUNE_MAX_TEST_CORES` will be ignored. Tests with a +# processor number :code:`n` higher than one will have the suffix +# :code:`-mpi-n` appended to their name. You need to specify the +# TIMEOUT option when specifying the MPI_RANKS option. +# +# .. cmake_param:: CMAKE_GUARD +# :multi: +# :argname: condition +# +# A number of conditions that CMake should evaluate before adding this +# test. If one of the conditions fails, the test should be shown +# as skipped in the test summary. Use this feature instead of guarding +# the call to :code:`dumux_add_test` with an :code:`if` clause. +# +# The passed condition can be a complex expression like +# `( A OR B ) AND ( C OR D )`. Mind the spaces around the parentheses. +# +# Example: Write CMAKE_GUARD dune-foo_FOUND if you want your test to only +# build and run when the dune-foo module is present. +# +# .. cmake_param:: COMMAND +# :multi: +# :argname: cmd +# +# You may specify the COMMAND option to give the exact command line to be +# executed when running the test. This defaults to the name of the executable +# added by dumux_add_test for this test. Note that if you specify both CMD_ARGS +# and COMMAND, the given CMD_ARGS will be put behind your COMMAND. If you use +# this in combination with the MPI_RANKS parameter, the call to mpi will still be +# wrapped around the given commands. +# +# .. cmake_param:: COMPILE_ONLY +# :option: +# +# Set if the given test should only be compiled during :code:`make build_tests`, +# but not run during :code:`make test`. This is useful if you compile the same +# executable twice, but with different compile flags, where you want to assure that +# it compiles with both sets of flags, but you already know they will produce the +# same result. +# +# .. cmake_param:: TIMEOUT +# :single: +# +# If set, the test will time out after the given number of seconds. This supersedes +# any timeout setting in ctest (see `cmake --help-property TIMEOUT`). If you +# specify the MPI_RANKS option, you need to specify a TIMEOUT. +# +# .. cmake_param:: LABELS +# :multi: +# +# A list of labels to add to the test. This has two effects: it sets +# the LABELS property on the test so :code:`ctest -L ${label_regex}` can +# be used to run all tests with certain labels. It also adds any +# targets created as dependencies to a custom target, so you can build +# all tests with a particular label by doing :code:`make +# build_${label}_tests` without having to build all the other tests as +# well. +# +# The :code:`build_${label}_tests` targets are created on-demand the +# first time a test with that label is added. In some situations it can +# depend on the values of cmake cache variables whether a test is added, +# and then it can happen that the :code:`build_${target}_tests` target +# exists only sometimes. If your workflow relies on the existence of +# these targets, even if building them just returns successfully without +# doing anything, you can ensure they exist by calling +# :ref:`dune_declare_test_label` unconditionally. The label +# :code:`quick` is always predeclared in this way. +# +# The label names must be non-empty, and must only contain alphanumeric +# characters other than :code:`-` or :code:`_`. This restriction is in +# place to make it easy to construct regular expressions from the label +# names for :code:`ctest -L ${label_regex}`. +# +# This function defines the Dune way of adding a test to the testing suite. +# You may either add the executable yourself through :ref:`add_executable` +# and pass it to the :code:`TARGET` option, or you may rely on :ref:`dumux_add_test` +# to do so. +# +# .. cmake_variable:: DUNE_REENABLE_ADD_TEST +# +# You may set this variable to True either through your opts file or in your module +# (before the call to :code:`include(DuneMacros)`) to suppress the error that is thrown if +# :code:`add_test` is used. You should only do that if you have proper reason to do so. +# +# .. cmake_variable:: DUNE_MAX_TEST_CORES +# +# You may set this variable to give an upperbound to the number of processors, that +# a single test may use. Defaults to 2, when MPI is found and to 1 otherwise. +# +# .. cmake_variable:: DUNE_BUILD_TESTS_ON_MAKE_ALL +# +# You may set this variable through your opts file or on a per module level (in the toplevel +# :code:`CMakeLists.txt` before :code:`include(DuneMacros)`) to have the Dune build system +# build all tests during `make all`. Note, that this may take quite some time for some modules. +# If not in use, you have to build tests through the target :code:`build_tests`. +# +# .. cmake_function:: dumux_evaluate_cmake_guard +# +# .. cmake_brief:: +# +# Fills the passed variable with TRUE if all guards evaluate to TRUE and FALSE otherwise +# +# .. cmake_param:: CMAKE_GUARD +# :multi: +# :argname: condition +# +# A number of conditions that CMake should evaluate. +# Uses the same mechanics that `dumux_add_test` uses to evaluate its CMAKE_GUARD argument. +# +# The passed condition can be a complex expression like +# `( A OR B ) AND ( C OR D )`. Mind the spaces around the parentheses. +# +# Example: Write CMAKE_GUARD dune-foo_FOUND if you want to set a variable +# that is only true if the module dune-foo has been found. +# +include_guard(GLOBAL) + +# Note: This forwards to dune_add_test but enables another layer in case we need to support +# future Dune features with older Dune versions supported by Dumux +function(dumux_add_test) + dune_add_test(${ARGV}) + + include(CMakeParseArguments) + set(OPTIONS EXPECT_COMPILE_FAIL EXPECT_FAIL SKIP_ON_77 COMPILE_ONLY) + set(SINGLEARGS NAME TARGET TIMEOUT) + set(MULTIARGS SOURCES COMPILE_DEFINITIONS COMPILE_FLAGS LINK_LIBRARIES CMD_ARGS MPI_RANKS COMMAND CMAKE_GUARD LABELS) + cmake_parse_arguments(ADDTEST "${OPTIONS}" "${SINGLEARGS}" "${MULTIARGS}" ${ARGN}) + + if(NOT ADDTEST_NAME) + # try deducing the test name from the executable name + if(ADDTEST_TARGET) + set(ADDTEST_NAME ${ADDTEST_TARGET}) + endif() + # try deducing the test name form the source name + if(ADDTEST_SOURCES) + # deducing a name is only possible with a single source argument + list(LENGTH ADDTEST_SOURCES len) + if(NOT len STREQUAL "1") + message(FATAL_ERROR "Cannot deduce test name from multiple sources!") + endif() + # strip file extension + get_filename_component(ADDTEST_NAME ${ADDTEST_SOURCES} NAME_WE) + endif() + endif() + if(NOT ADDTEST_COMMAND) + set(ADDTEST_COMMAND ${ADDTEST_NAME}) + endif() + + # Find out whether this test should be a dummy + set(SHOULD_SKIP_TEST FALSE) + set(FAILED_CONDITION_PRINTING "") + foreach(condition ${ADDTEST_CMAKE_GUARD}) + separate_arguments(condition) + if(NOT (${condition})) + set(SHOULD_SKIP_TEST TRUE) + set(FAILED_CONDITION_PRINTING "${FAILED_CONDITION_PRINTING}std::cout << \" ${condition}\" << std::endl;\n") + endif() + endforeach() + + # If we do nothing, switch the sources for a dummy source + if(SHOULD_SKIP_TEST) + dune_module_path(MODULE dune-common RESULT scriptdir SCRIPT_DIR) + set(ADDTEST_TARGET) + set(dummymain ${CMAKE_CURRENT_BINARY_DIR}/main77_${ADDTEST_NAME}.cc) + configure_file(${scriptdir}/main77.cc.in ${dummymain}) + set(ADDTEST_SOURCES ${dummymain}) + endif() + + # Add the executable if it is not already present + if(ADDTEST_SOURCES) + set(ADDTEST_TARGET ${ADDTEST_NAME}) + endif() + + # link the dumux library to the test target + target_link_libraries(${ADDTEST_TARGET} PRIVATE Dumux::Dumux) + + # enable all dune registered packages on the test target + dune_target_enable_all_packages(${ADDTEST_TARGET}) + + if(NOT ADDTEST_MPI_RANKS) + set(ADDTEST_MPI_RANKS 1) + endif() + + foreach(procnum ${ADDTEST_MPI_RANKS}) + if((NOT "${procnum}" GREATER "${DUNE_MAX_TEST_CORES}") AND (NOT ADDTEST_COMPILE_ONLY)) + set(ACTUAL_NAME ${ADDTEST_NAME}) + # add suffix + if(NOT ${procnum} STREQUAL "1") + set(ACTUAL_NAME "${ACTUAL_NAME}-mpi-${procnum}") + endif() + + file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/TestMetaData") + file(WRITE "${CMAKE_BINARY_DIR}/TestMetaData/${ACTUAL_NAME}.json" + "{\n \"name\": \"${ACTUAL_NAME}\",\n \"target\": \"${ADDTEST_TARGET}\",\n \"source_dir\": \"${CMAKE_CURRENT_SOURCE_DIR}\"\n}\n") + endif() + endforeach() +endfunction() + +# Evaluate test guards like dune_add_test internally does +function(dumux_evaluate_cmake_guard GUARD_LETS_YOU_PASS) + include(CMakeParseArguments) + set(MULTIARGS CMAKE_GUARD) + cmake_parse_arguments(EVALGUARD "${OPTIONS}" "${SINGLEARGS}" "${MULTIARGS}" ${ARGN}) + + # Check whether the parser produced any errors + if(EVALGUARD_UNPARSED_ARGUMENTS) + message(WARNING "Unrecognized arguments ('${EVALGUARD_UNPARSED_ARGUMENTS}') for dumux_evaluate_cmake_guard!") + endif() + + # determine if all condition of the guard are met + set(${GUARD_LETS_YOU_PASS} TRUE PARENT_SCOPE) + set(FAILED_CONDITION_PRINTING "") + foreach(condition ${EVALGUARD_CMAKE_GUARD}) + separate_arguments(condition) + if(NOT (${condition})) + set(${GUARD_LETS_YOU_PASS} FALSE PARENT_SCOPE) + endif() + endforeach() +endfunction() diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/FindGmsh.cmake b/benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/FindGmsh.cmake new file mode 100644 index 0000000..f0f0220 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/FindGmsh.cmake @@ -0,0 +1,51 @@ +# SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +# SPDX-License-Identifier: GPL-3.0-or-later + +# .. cmake_module:: +# +# Find the Gmsh meshing tool +# +# You may set the following variables to modify the +# behaviour of this module: +# +# :ref:`GMSH_ROOT` +# Path list to search for gmsh. +# +# Sets the following variables: +# +# :code:`gmsh_FOUND` +# True if the gmsh library was found. +# +# :code:`GMSH_EXECUTABLE` +# Path to gmsh executable +# +# .. cmake_variable:: GMSH_ROOT +# +# You may set this variable to have :ref:`FindGmsh` look +# for the gmsh library in the given path before inspecting +# system paths. +# +include_guard(GLOBAL) + +# look for header files, only at positions given by the user +find_program(GMSH_EXECUTABLE + NAMES gmsh + PATHS "${GMSH_ROOT}" + "${CMAKE_SOURCE_DIR}/../" + "/usr/bin/" + PATH_SUFFIXES "src" "external/gmsh/src" "gmsh/src" "gmsh" + NO_DEFAULT_PATH +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + "Gmsh" + DEFAULT_MSG + GMSH_EXECUTABLE +) + +# text for feature summary +include(FeatureSummary) +set_package_properties("Gmsh" PROPERTIES + DESCRIPTION "Meshing tool" + PURPOSE "Generate structured and unstructured grids") diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/FindGstat.cmake b/benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/FindGstat.cmake new file mode 100644 index 0000000..85180e6 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/cmake/modules/FindGstat.cmake @@ -0,0 +1,55 @@ +# SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +# SPDX-License-Identifier: GPL-3.0-or-later + +# .. cmake_module:: +# +# Find the gstat geostatistic library +# +# You may set the following variables to modify the +# behaviour of this module: +# +# :ref:`GSTAT_ROOT` +# Path list to search for gstat. +# +# Sets the following variables: +# +# :code:`GSTAT_FOUND` +# True if the gstat library was found. +# +# :code:`GSTAT_EXECUTABLE` +# Path to gstat executable +# +# .. cmake_variable:: GSTAT_ROOT +# +# You may set this variable to have :ref:`FindGstat` look +# for the gstat library in the given path before inspecting +# system paths. +# +include_guard(GLOBAL) + +# look for header files, only at positions given by the user +find_program(GSTAT_EXECUTABLE + NAMES gstat + PATHS "${GSTAT_ROOT}" + "${CMAKE_SOURCE_DIR}/../" + "/usr/bin/" + PATH_SUFFIXES "src" "external/gstat/src" "gstat/src" "gstat" + NO_DEFAULT_PATH +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + "Gstat" + DEFAULT_MSG + GSTAT_EXECUTABLE +) + +# set macros for config.h +set(DUMUX_HAVE_GSTAT ${GSTAT_FOUND}) +set(GSTAT_EXECUTABLE ${GSTAT_EXECUTABLE}) + +# text for feature summary +include(FeatureSummary) +set_package_properties("Gstat" PROPERTIES + DESCRIPTION "Geostatistic library" + PURPOSE "Generate random permeability and porosity fields") diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/config.h.cmake b/benchmarks/rotating-cylinders/dumux/dumuxModule/config.h.cmake new file mode 100644 index 0000000..030ce0c --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/config.h.cmake @@ -0,0 +1,45 @@ +/* begin dumuxModule + put the definitions for config.h specific to + your project here. Everything above will be + overwritten +*/ + +/* begin private */ +/* Name of package */ +#define PACKAGE "@DUNE_MOD_NAME@" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "@DUNE_MAINTAINER@" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "@DUNE_MOD_NAME@" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "@DUNE_MOD_NAME@ @DUNE_MOD_VERSION@" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "@DUNE_MOD_NAME@" + +/* Define to the home page for this package. */ +#define PACKAGE_URL "@DUNE_MOD_URL@" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "@DUNE_MOD_VERSION@" + +/* end private */ + +/* Define to the version of dumuxModule */ +#define UdumuxModule_VERSION "@UdumuxModule_VERSION@" + +/* Define to the major version of dumuxModule */ +#define UdumuxModule_VERSION_MAJOR @UdumuxModule_VERSION_MAJOR@ + +/* Define to the minor version of dumuxModule */ +#define UdumuxModule_VERSION_MINOR @UdumuxModule_VERSION_MINOR@ + +/* Define to the revision of dumuxModule */ +#define UdumuxModule_VERSION_REVISION @UdumuxModule_VERSION_REVISION@ + +/* end dumuxModule + Everything below here will be overwritten +*/ diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/doc/CMakeLists.txt b/benchmarks/rotating-cylinders/dumux/dumuxModule/doc/CMakeLists.txt new file mode 100644 index 0000000..be52cfc --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/doc/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory("doxygen") diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/doc/doxygen/CMakeLists.txt b/benchmarks/rotating-cylinders/dumux/dumuxModule/doc/doxygen/CMakeLists.txt new file mode 100644 index 0000000..f7b8ea5 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/doc/doxygen/CMakeLists.txt @@ -0,0 +1,2 @@ +# shortcut for creating the Doxyfile.in and Doxyfile +add_doxygen_target() diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/doc/doxygen/Doxylocal b/benchmarks/rotating-cylinders/dumux/dumuxModule/doc/doxygen/Doxylocal new file mode 100644 index 0000000..b73858d --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/doc/doxygen/Doxylocal @@ -0,0 +1,30 @@ +# This file contains local changes to the doxygen configuration +# please use '+=' to add files/directories to the lists + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT += @top_srcdir@/dune/ +# see e.g. dune-grid for the examples of mainpage and modules +# INPUT += @srcdir@/mainpage \ +# @srcdir@/modules + +# The EXCLUDE tag can be used to specify files and/or directories that should +# be excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +# EXCLUDE += @top_srcdir@/dune/dumuxModule/test + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +# EXAMPLE_PATH += @top_srcdir@/src + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +# IMAGE_PATH += @top_srcdir@/dune/dumuxModule/pics diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/cclocalresidual.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/cclocalresidual.hh new file mode 100644 index 0000000..885fa8e --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/cclocalresidual.hh @@ -0,0 +1,110 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Assembly + * \ingroup CCDiscretization + * \brief Calculates the element-wise residual for cell-centered discretization schemes + */ +#ifndef DUMUX_CC_LOCAL_RESIDUAL_HH +#define DUMUX_CC_LOCAL_RESIDUAL_HH + +#include +#include +#include +#include +#include + +namespace Dumux { + +/*! + * \ingroup Assembly + * \ingroup CCDiscretization + * \brief Calculates the element-wise residual for the cell-centered discretization schemes + */ +template +class CCLocalResidual : public FVLocalResidual +{ + using ParentType = FVLocalResidual; + using Problem = GetPropType; + using Element = typename GetPropType::GridView::template Codim<0>::Entity; + using NumEqVector = Dumux::NumEqVector>; + using ElementBoundaryTypes = GetPropType; + using ElementVolumeVariables = typename GetPropType::LocalView; + using ElementFluxVariablesCache = typename GetPropType::LocalView; + using GridGeometry = GetPropType; + using FVElementGeometry = typename GridGeometry::LocalView; + using Extrusion = Extrusion_t; + using SubControlVolumeFace = typename FVElementGeometry::SubControlVolumeFace; + +public: + using ElementResidualVector = typename ParentType::ElementResidualVector; + using ParentType::ParentType; + + //! evaluate the flux residual for a sub control volume face and add to residual + void evalFlux(ElementResidualVector& residual, + const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const ElementBoundaryTypes& elemBcTypes, + const ElementFluxVariablesCache& elemFluxVarsCache, + const SubControlVolumeFace& scvf) const + { + const auto& scv = fvGeometry.scv(scvf.insideScvIdx()); + const auto localScvIdx = scv.localDofIndex(); + residual[localScvIdx] += this->asImp().evalFlux(problem, element, fvGeometry, elemVolVars, elemFluxVarsCache, scvf); + } + + //! evaluate the flux residual for a sub control volume face + NumEqVector evalFlux(const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const ElementFluxVariablesCache& elemFluxVarsCache, + const SubControlVolumeFace& scvf) const + { + NumEqVector flux(0.0); + + // inner faces + if (!scvf.boundary()) + { + flux += this->asImp().computeFlux(problem, element, fvGeometry, elemVolVars, scvf, elemFluxVarsCache); + } + + // boundary faces + else + { + const auto& bcTypes = problem.boundaryTypes(element, scvf); + + // Dirichlet boundaries + if (bcTypes.hasDirichlet() && !bcTypes.hasNeumann()) + flux += this->asImp().computeFlux(problem, element, fvGeometry, elemVolVars, scvf, elemFluxVarsCache); + + // Neumann and Robin ("solution dependent Neumann") boundary conditions + else if (bcTypes.hasNeumann() && !bcTypes.hasDirichlet()) + { + auto neumannFluxes = problem.neumann(element, fvGeometry, elemVolVars, elemFluxVarsCache, scvf); + + // multiply neumann fluxes with the area and the extrusion factor + const auto& scv = fvGeometry.scv(scvf.insideScvIdx()); + neumannFluxes *= Extrusion::area(fvGeometry, scvf)*elemVolVars[scv].extrusionFactor(); + + flux += neumannFluxes; + } + + else + DUNE_THROW(Dune::NotImplemented, "Mixed boundary conditions. Use pure boundary conditions by converting Dirichlet BCs to Robin BCs"); + } + + return flux; + } +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/coloring.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/coloring.hh new file mode 100644 index 0000000..da682dc --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/coloring.hh @@ -0,0 +1,303 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Assembly + * \brief Coloring schemes for shared-memory-parallel assembly + */ +#ifndef DUMUX_ASSEMBLY_COLORING_HH +#define DUMUX_ASSEMBLY_COLORING_HH + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#ifndef DOXYGEN // hide from doxygen +namespace Dumux::Detail { + +//! Compute a map from dof indices to element indices (helper data for coloring algorithm) +template +std::vector> +computeConnectedElements(const GridGeometry& gg) +{ + std::vector> connectedElements; + + if constexpr (GridGeometry::discMethod == DiscretizationMethods::cctpfa) + { + connectedElements.resize(gg.gridView().size(0)); + const auto& eMapper = gg.elementMapper(); + for (const auto& element : elements(gg.gridView())) + { + const auto eIdx = eMapper.index(element); + for (const auto& intersection : intersections(gg.gridView(), element)) + if (intersection.neighbor()) + connectedElements[eMapper.index(intersection.outside())].push_back(eIdx); + } + } + + else if constexpr (GridGeometry::discMethod == DiscretizationMethods::box + || GridGeometry::discMethod == DiscretizationMethods::pq1bubble) + { + static constexpr int dim = GridGeometry::GridView::dimension; + connectedElements.resize(gg.gridView().size(dim)); + const auto& vMapper = gg.vertexMapper(); + for (const auto& element : elements(gg.gridView())) + { + const auto eIdx = gg.elementMapper().index(element); + for (int i = 0; i < element.subEntities(dim); i++) + connectedElements[vMapper.subIndex(element, i, dim)].push_back(eIdx); + } + } + + else if constexpr ( + GridGeometry::discMethod == DiscretizationMethods::fcstaggered + || GridGeometry::discMethod == DiscretizationMethods::ccmpfa + ) + { + // for MPFA-O schemes the assembly of each element residual touches all vertex neighbors + // for face-centered staggered it is all codim-2 neighbors (vertex neighbors in 2D, edge neighbors in 3D) + // but we use vertex neighbors also in 3D for simplicity + std::vector> vToElements; + static constexpr int dim = GridGeometry::GridView::dimension; + vToElements.resize(gg.gridView().size(dim)); + const auto& vMapper = gg.vertexMapper(); + for (const auto& element : elements(gg.gridView())) + { + const auto eIdx = gg.elementMapper().index(element); + for (int i = 0; i < element.subEntities(dim); i++) + vToElements[vMapper.subIndex(element, i, dim)].push_back(eIdx); + } + + connectedElements.resize(gg.gridView().size(0)); + for (const auto& element : elements(gg.gridView())) + { + const auto eIdx = gg.elementMapper().index(element); + for (int i = 0; i < element.subEntities(dim); i++) + { + const auto& e = vToElements[vMapper.subIndex(element, i, dim)]; + connectedElements[eIdx].insert(connectedElements[eIdx].end(), e.begin(), e.end()); + } + + // make unique + std::sort(connectedElements[eIdx].begin(), connectedElements[eIdx].end()); + connectedElements[eIdx].erase( + std::unique(connectedElements[eIdx].begin(), connectedElements[eIdx].end()), + connectedElements[eIdx].end() + ); + } + } + + // nothing has to be precomputed here as only immediate face neighbors are connected + else if constexpr (GridGeometry::discMethod == DiscretizationMethods::fcdiamond) + return connectedElements; + + else + DUNE_THROW(Dune::NotImplemented, + "Missing coloring scheme implementation for this discretization method" + ); + + return connectedElements; +} + +/*! + * \brief Compute the colors of neighboring nodes in the dependency graph + * + * Neighboring nodes are those elements that manipulate the + * same data structures (e.g. system matrix, volvars, flux cache) in the same places + * + * \param gridGeometry the grid geometry + * \param element the element we want to color + * \param colors a vector of current colors for each element (not assigned: -1) + * \param connectedElements a map from implementation-defined indices to element indices + * \param neighborColors a vector to add the colors of neighbor nodes to + */ +template +void addNeighborColors(const GridGeometry& gg, + const typename GridGeometry::LocalView::Element& element, + const std::vector& colors, + const ConnectedElements& connectedElements, + std::vector& neighborColors) +{ + if constexpr ( + GridGeometry::discMethod == DiscretizationMethods::cctpfa + || GridGeometry::discMethod == DiscretizationMethods::ccmpfa + || GridGeometry::discMethod == DiscretizationMethods::fcstaggered + ) + { + // we modify neighbor elements during the assembly + // check who else modifies these neighbor elements + const auto& eMapper = gg.elementMapper(); + for (const auto& intersection : intersections(gg.gridView(), element)) + { + if (intersection.neighbor()) + { + // direct face neighbors + const auto nIdx = eMapper.index(intersection.outside()); + neighborColors.push_back(colors[nIdx]); + + // neighbor-neighbors + for (const auto nnIdx : connectedElements[eMapper.index(intersection.outside())]) + neighborColors.push_back(colors[nnIdx]); + } + } + } + + else if constexpr (GridGeometry::discMethod == DiscretizationMethods::box + || GridGeometry::discMethod == DiscretizationMethods::pq1bubble) + { + // we modify the vertex dofs of our element during the assembly + // check who else modifies these vertex dofs + const auto& vMapper = gg.vertexMapper(); + static constexpr int dim = GridGeometry::GridView::dimension; + // direct vertex neighbors + for (int i = 0; i < element.subEntities(dim); i++) + for (auto eIdx : connectedElements[vMapper.subIndex(element, i, dim)]) + neighborColors.push_back(colors[eIdx]); + } + + else if constexpr (GridGeometry::discMethod == DiscretizationMethods::fcdiamond) + { + // we modify neighbor faces during the assembly + // check who else modifies these neighbor elements + const auto& eMapper = gg.elementMapper(); + for (const auto& intersection : intersections(gg.gridView(), element)) + if (intersection.neighbor()) + neighborColors.push_back(colors[eMapper.index(intersection.outside())]); + } + + else + DUNE_THROW(Dune::NotImplemented, + "Missing coloring scheme implementation for this discretization method" + ); +} + +/*! + * \brief Find the smallest color (integer >= 0) _not_ present in the given list of colors + * \param colors list of colors which are already taken + * \param notAssigned container to store which colors are not yet taken (is resized as required) + */ +int smallestAvailableColor(const std::vector& colors, + std::vector& colorUsed) +{ + const int numColors = colors.size(); + colorUsed.assign(numColors, false); + + // The worst case for e.g. numColors=3 is colors={0, 1, 2} + // in which case we return 3 as smallest available color + // That means, we only track candidates in the (half-open) interval [0, numColors) + // Mark candidate colors which are present in colors + for (int i = 0; i < numColors; i++) + if (colors[i] >= 0 && colors[i] < numColors) + colorUsed[colors[i]] = true; + + // return smallest color not in colors + for (int i = 0; i < numColors; i++) + if (!colorUsed[i]) + return i; + + return numColors; +} + +} // end namespace Dumux::Detail +#endif // DOXYGEN + +namespace Dumux { + +/*! + * \brief Compute iterable lists of element seeds partitioned by color + * + * Splits up the elements of a grid view into partitions such that + * all elements in one partition do not modify global data structures + * at the same place during assembly. This is used to allow for + * lock-free thread-parallel (shared memory) assembly routines. + * + * Implements a simply greedy graph coloring algorithm: + * For each node (element), assign the smallest available color + * not used by any of the neighboring nodes (element with conflicting memory access) + * The greedy algorithm doesn't necessarily return the smallest + * possible number of colors (that's a hard problem) but is fast + * + * Returns a struct with access to the colors of each element (member colors) + * and vector of element seed sets of the same color (member sets) + * + * \param gg the grid geometry + * \param verbosity the verbosity level + */ +template +auto computeColoring(const GridGeometry& gg, int verbosity = 1) +{ + Dune::Timer timer; + + using ElementSeed = typename GridGeometry::GridView::Grid::template Codim<0>::EntitySeed; + struct Coloring + { + using Sets = std::deque>; + using Colors = std::vector; + + Coloring(std::size_t size) : sets{}, colors(size, -1) {} + + Sets sets; + Colors colors; + }; + + Coloring coloring(gg.gridView().size(0)); + + // pre-reserve some memory for helper arrays to avoid reallocation + std::vector neighborColors; neighborColors.reserve(30); + std::vector colorUsed; colorUsed.reserve(30); + + // dof to element map to speed up neighbor search + const auto connectedElements = Detail::computeConnectedElements(gg); + + for (const auto& element : elements(gg.gridView())) + { + // compute neighbor colors based on discretization-dependent stencil + neighborColors.clear(); + Detail::addNeighborColors(gg, element, coloring.colors, connectedElements, neighborColors); + + // find smallest color (positive integer) not in neighborColors + const auto color = Detail::smallestAvailableColor(neighborColors, colorUsed); + + // assign color to element + coloring.colors[gg.elementMapper().index(element)] = color; + + // add element to the set of elements with the same color + if (color < coloring.sets.size()) + coloring.sets[color].push_back(element.seed()); + else + coloring.sets.push_back(std::vector{ element.seed() }); + } + + if (verbosity > 0) + std::cout << Fmt::format("Colored {} elements with {} colors in {} seconds.\n", + gg.gridView().size(0), coloring.sets.size(), timer.elapsed()); + + return coloring; +} + +//! Traits specifying if a given discretization tag supports coloring +template +struct SupportsColoring : public std::false_type {}; + +template<> struct SupportsColoring : public std::true_type {}; +template<> struct SupportsColoring : public std::true_type {}; +template<> struct SupportsColoring : public std::true_type {}; +template<> struct SupportsColoring : public std::true_type {}; +template<> struct SupportsColoring : public std::true_type {}; +template<> struct SupportsColoring : public std::true_type {}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/cvfelocalassembler.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/cvfelocalassembler.hh new file mode 100644 index 0000000..a224147 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/cvfelocalassembler.hh @@ -0,0 +1,768 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Assembly + * \ingroup CVFEDiscretization + * \brief An assembler for Jacobian and residual contribution per element (CVFE methods) + */ +#ifndef DUMUX_CVFE_LOCAL_ASSEMBLER_HH +#define DUMUX_CVFE_LOCAL_ASSEMBLER_HH + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "volvardeflectionhelper_.hh" + +namespace Dumux { + +#ifndef DOXYGEN +namespace Detail::CVFE { + +struct NoOperator +{ + template + constexpr void operator()(Args&&...) const {} +}; + +template +using Impl = std::conditional_t, X, Y>; + +} // end namespace Detail +#endif // DOXYGEN + +/*! + * \ingroup Assembly + * \ingroup CVFEDiscretization + * \brief A base class for all local CVFE assemblers + * \tparam TypeTag The TypeTag + * \tparam Assembler The assembler type + * \tparam Implementation The actual implementation + * \tparam implicit Specifies whether the time discretization is implicit or not not (i.e. explicit) + */ +template +class CVFELocalAssemblerBase : public FVLocalAssemblerBase +{ + using ParentType = FVLocalAssemblerBase; + using JacobianMatrix = GetPropType; + using GridVariables = GetPropType; + using ElementVolumeVariables = typename GridVariables::GridVolumeVariables::LocalView; + using SolutionVector = typename Assembler::SolutionVector; + + static constexpr int numEq = GetPropType::numEq(); + static constexpr int dim = GetPropType::GridView::dimension; + +public: + + using ParentType::ParentType; + + void bindLocalViews() + { + ParentType::bindLocalViews(); + this->elemBcTypes().update(this->asImp_().problem(), this->element(), this->fvGeometry()); + } + + + /*! + * \brief Computes the derivatives with respect to the given element and adds them + * to the global matrix. The element residual is written into the right hand side. + */ + template + void assembleJacobianAndResidual(JacobianMatrix& jac, ResidualVector& res, GridVariables& gridVariables, + const PartialReassembler* partialReassembler = nullptr, + const CouplingFunction& maybeAssembleCouplingBlocks = {}) + { + this->asImp_().bindLocalViews(); + const auto eIdxGlobal = this->asImp_().problem().gridGeometry().elementMapper().index(this->element()); + if (partialReassembler + && partialReassembler->elementColor(eIdxGlobal) == EntityColor::green) + { + const auto residual = this->asImp_().evalLocalResidual(); // forward to the internal implementation + for (const auto& scv : scvs(this->fvGeometry())) + res[scv.dofIndex()] += residual[scv.localDofIndex()]; + + // assemble the coupling blocks for coupled models (does nothing if not coupled) + maybeAssembleCouplingBlocks(residual); + } + else if (!this->elementIsGhost()) + { + const auto residual = this->asImp_().assembleJacobianAndResidualImpl(jac, gridVariables, partialReassembler); // forward to the internal implementation + for (const auto& scv : scvs(this->fvGeometry())) + res[scv.dofIndex()] += residual[scv.localDofIndex()]; + + // assemble the coupling blocks for coupled models (does nothing if not coupled) + maybeAssembleCouplingBlocks(residual); + } + else + { + // Treatment of ghost elements + assert(this->elementIsGhost()); + + // handle dofs per codimension + const auto& gridGeometry = this->asImp_().problem().gridGeometry(); + Dune::Hybrid::forEach(std::make_integer_sequence{}, [&](auto d) + { + constexpr int codim = dim - d; + const auto& localCoeffs = gridGeometry.feCache().get(this->element().type()).localCoefficients(); + for (int idx = 0; idx < localCoeffs.size(); ++idx) + { + const auto& localKey = localCoeffs.localKey(idx); + + // skip if we are not handling this codim right now + if (localKey.codim() != codim) + continue; + + // do not change the non-ghost entities + auto entity = this->element().template subEntity(localKey.subEntity()); + if (entity.partitionType() == Dune::InteriorEntity || entity.partitionType() == Dune::BorderEntity) + continue; + + // WARNING: this only works if the mapping from codim+subEntity to + // global dofIndex is unique (on dof per entity of this codim). + // For more general mappings, we should use a proper local-global mapping here. + // For example through dune-functions. + const auto dofIndex = gridGeometry.dofMapper().index(entity); + + // this might be a vector-valued dof + using BlockType = typename JacobianMatrix::block_type; + BlockType &J = jac[dofIndex][dofIndex]; + for (int j = 0; j < BlockType::rows; ++j) + J[j][j] = 1.0; + + // set residual for the ghost dof + res[dofIndex] = 0; + } + }); + } + + auto applyDirichlet = [&] (const auto& scvI, + const auto& dirichletValues, + const auto eqIdx, + const auto pvIdx) + { + res[scvI.dofIndex()][eqIdx] = this->curElemVolVars()[scvI].priVars()[pvIdx] - dirichletValues[pvIdx]; + + auto& row = jac[scvI.dofIndex()]; + for (auto col = row.begin(); col != row.end(); ++col) + row[col.index()][eqIdx] = 0.0; + + jac[scvI.dofIndex()][scvI.dofIndex()][eqIdx][pvIdx] = 1.0; + + // if a periodic dof has Dirichlet values also apply the same Dirichlet values to the other dof + if (this->asImp_().problem().gridGeometry().dofOnPeriodicBoundary(scvI.dofIndex())) + { + const auto periodicDof = this->asImp_().problem().gridGeometry().periodicallyMappedDof(scvI.dofIndex()); + res[periodicDof][eqIdx] = this->asImp_().curSol()[periodicDof][pvIdx] - dirichletValues[pvIdx]; + + auto& rowP = jac[periodicDof]; + for (auto col = rowP.begin(); col != rowP.end(); ++col) + rowP[col.index()][eqIdx] = 0.0; + + rowP[periodicDof][eqIdx][pvIdx] = 1.0; + } + }; + + this->asImp_().enforceDirichletConstraints(applyDirichlet); + } + + /*! + * \brief Computes the derivatives with respect to the given element and adds them + * to the global matrix. + */ + void assembleJacobian(JacobianMatrix& jac, GridVariables& gridVariables) + { + this->asImp_().bindLocalViews(); + this->asImp_().assembleJacobianAndResidualImpl(jac, gridVariables); // forward to the internal implementation + + auto applyDirichlet = [&] (const auto& scvI, + const auto& dirichletValues, + const auto eqIdx, + const auto pvIdx) + { + auto& row = jac[scvI.dofIndex()]; + for (auto col = row.begin(); col != row.end(); ++col) + row[col.index()][eqIdx] = 0.0; + + jac[scvI.dofIndex()][scvI.dofIndex()][eqIdx][pvIdx] = 1.0; + }; + + this->asImp_().enforceDirichletConstraints(applyDirichlet); + } + + /*! + * \brief Assemble the residual only + */ + template + void assembleResidual(ResidualVector& res) + { + this->asImp_().bindLocalViews(); + const auto residual = this->evalLocalResidual(); + + for (const auto& scv : scvs(this->fvGeometry())) + res[scv.dofIndex()] += residual[scv.localDofIndex()]; + + auto applyDirichlet = [&] (const auto& scvI, + const auto& dirichletValues, + const auto eqIdx, + const auto pvIdx) + { + res[scvI.dofIndex()][eqIdx] = this->curElemVolVars()[scvI].priVars()[pvIdx] - dirichletValues[pvIdx]; + }; + + this->asImp_().enforceDirichletConstraints(applyDirichlet); + } + + //! Enforce Dirichlet constraints + template + void enforceDirichletConstraints(const ApplyFunction& applyDirichlet) + { + // enforce Dirichlet boundary conditions + this->asImp_().evalDirichletBoundaries(applyDirichlet); + // take care of internal Dirichlet constraints (if enabled) + this->asImp_().enforceInternalDirichletConstraints(applyDirichlet); + } + + /*! + * \brief Evaluates Dirichlet boundaries + */ + template< typename ApplyDirichletFunctionType > + void evalDirichletBoundaries(ApplyDirichletFunctionType applyDirichlet) + { + // enforce Dirichlet boundaries by overwriting partial derivatives with 1 or 0 + // and set the residual to (privar - dirichletvalue) + if (this->elemBcTypes().hasDirichlet()) + { + for (const auto& scvI : scvs(this->fvGeometry())) + { + const auto bcTypes = this->elemBcTypes().get(this->fvGeometry(), scvI); + if (bcTypes.hasDirichlet()) + { + const auto dirichletValues = this->asImp_().problem().dirichlet(this->element(), scvI); + + // set the Dirichlet conditions in residual and jacobian + for (int eqIdx = 0; eqIdx < numEq; ++eqIdx) + { + if (bcTypes.isDirichlet(eqIdx)) + { + const auto pvIdx = bcTypes.eqToDirichletIndex(eqIdx); + assert(0 <= pvIdx && pvIdx < numEq); + applyDirichlet(scvI, dirichletValues, eqIdx, pvIdx); + } + } + } + } + } + } + + /*! + * \brief Update the coupling context for coupled models. + * \note This does nothing per default (not a coupled model). + */ + template + void maybeUpdateCouplingContext(Args&&...) {} + + /*! + * \brief Update the additional domain derivatives for coupled models. + * \note This does nothing per default (not a coupled model). + */ + template + void maybeEvalAdditionalDomainDerivatives(Args&&...) {} +}; + +/*! + * \ingroup Assembly + * \ingroup CVFEDiscretization + * \brief An assembler for Jacobian and residual contribution per element (CVFE methods) + * \tparam TypeTag The TypeTag + * \tparam diffMethod The differentiation method to residual compute derivatives + * \tparam implicit Specifies whether the time discretization is implicit or not not (i.e. explicit) + * \tparam Implementation via CRTP + */ +template +class CVFELocalAssembler; + +/*! + * \ingroup Assembly + * \ingroup CVFEDiscretization + * \brief Control volume finite element local assembler using numeric differentiation and implicit time discretization + */ +template +class CVFELocalAssembler +: public CVFELocalAssemblerBase>, + true> +{ + using ThisType = CVFELocalAssembler; + using ParentType = CVFELocalAssemblerBase, true>; + using Scalar = GetPropType; + using GridVariables = GetPropType; + using VolumeVariables = GetPropType; + using JacobianMatrix = GetPropType; + + static constexpr int numEq = GetPropType::numEq(); + static constexpr int dim = GetPropType::GridView::dimension; + + static constexpr bool enableGridFluxVarsCache + = GridVariables::GridFluxVariablesCache::cachingEnabled; + static constexpr bool solutionDependentFluxVarsCache + = GridVariables::GridFluxVariablesCache::FluxVariablesCache::isSolDependent; + +public: + + using LocalResidual = typename ParentType::LocalResidual; + using ElementResidualVector = typename LocalResidual::ElementResidualVector; + using ParentType::ParentType; + + /*! + * \brief Computes the derivatives with respect to the given element and adds them + * to the global matrix. + * + * \return The element residual at the current solution. + */ + template + ElementResidualVector assembleJacobianAndResidualImpl(JacobianMatrix& A, GridVariables& gridVariables, + const PartialReassembler* partialReassembler = nullptr) + { + // get some aliases for convenience + const auto& element = this->element(); + const auto& fvGeometry = this->fvGeometry(); + const auto& curSol = this->asImp_().curSol(); + + auto&& curElemVolVars = this->curElemVolVars(); + auto&& elemFluxVarsCache = this->elemFluxVarsCache(); + + // get the vector of the actual element residuals + const auto origResiduals = this->evalLocalResidual(); + + ////////////////////////////////////////////////////////////////////////////////////////////////// + // // + // Calculate derivatives of all dofs in stencil with respect to the dofs in the element. In the // + // neighboring elements we do so by computing the derivatives of the fluxes which depend on the // + // actual element. In the actual element we evaluate the derivative of the entire residual. // + // // + ////////////////////////////////////////////////////////////////////////////////////////////////// + + // if all volvars in the stencil have to be updated or if it's enough to only update the + // volVars for the scv whose associated dof has been deflected + static const bool updateAllVolVars = getParamFromGroup( + this->asImp_().problem().paramGroup(), "Assembly.BoxVolVarsDependOnAllElementDofs", false + ); + + // create the element solution + auto elemSol = elementSolution(element, curSol, fvGeometry.gridGeometry()); + + // create the vector storing the partial derivatives + ElementResidualVector partialDerivs(fvGeometry.numScv()); + + Detail::VolVarsDeflectionHelper deflectionHelper( + [&] (const auto& scv) -> VolumeVariables& { + return this->getVolVarAccess(gridVariables.curGridVolVars(), curElemVolVars, scv); + }, + fvGeometry, + updateAllVolVars + ); + + // calculation of the derivatives + for (const auto& scv : scvs(fvGeometry)) + { + // dof index and corresponding actual pri vars + const auto dofIdx = scv.dofIndex(); + deflectionHelper.setCurrent(scv); + + // calculate derivatives w.r.t to the privars at the dof at hand + for (int pvIdx = 0; pvIdx < numEq; pvIdx++) + { + partialDerivs = 0.0; + + auto evalResiduals = [&](Scalar priVar) + { + // update the volume variables and compute element residual + elemSol[scv.localDofIndex()][pvIdx] = priVar; + deflectionHelper.deflect(elemSol, scv, this->asImp_().problem()); + if constexpr (solutionDependentFluxVarsCache) + { + elemFluxVarsCache.update(element, fvGeometry, curElemVolVars); + if constexpr (enableGridFluxVarsCache) + gridVariables.gridFluxVarsCache().updateElement(element, fvGeometry, curElemVolVars); + } + this->asImp_().maybeUpdateCouplingContext(scv, elemSol, pvIdx); + return this->evalLocalResidual(); + }; + + // derive the residuals numerically + static const NumericEpsilon eps_{this->asImp_().problem().paramGroup()}; + static const int numDiffMethod = getParamFromGroup(this->asImp_().problem().paramGroup(), "Assembly.NumericDifferenceMethod"); + NumericDifferentiation::partialDerivative(evalResiduals, elemSol[scv.localDofIndex()][pvIdx], partialDerivs, origResiduals, + eps_(elemSol[scv.localDofIndex()][pvIdx], pvIdx), numDiffMethod); + + // update the global stiffness matrix with the current partial derivatives + for (const auto& scvJ : scvs(fvGeometry)) + { + // don't add derivatives for green dofs + if (!partialReassembler + || partialReassembler->dofColor(scvJ.dofIndex()) != EntityColor::green) + { + for (int eqIdx = 0; eqIdx < numEq; eqIdx++) + { + // A[i][col][eqIdx][pvIdx] is the rate of change of + // the residual of equation 'eqIdx' at dof 'i' + // depending on the primary variable 'pvIdx' at dof + // 'col'. + A[scvJ.dofIndex()][dofIdx][eqIdx][pvIdx] += partialDerivs[scvJ.localDofIndex()][eqIdx]; + } + } + } + + // restore the original state of the scv's volume variables + deflectionHelper.restore(scv); + + // restore the original element solution + elemSol[scv.localDofIndex()][pvIdx] = curSol[scv.dofIndex()][pvIdx]; + this->asImp_().maybeUpdateCouplingContext(scv, elemSol, pvIdx); + } + } + + // restore original state of the flux vars cache in case of global caching. + // In the case of local caching this is obsolete because the elemFluxVarsCache used here goes out of scope after this. + if constexpr (enableGridFluxVarsCache) + gridVariables.gridFluxVarsCache().updateElement(element, fvGeometry, curElemVolVars); + + // evaluate additional derivatives that might arise from the coupling (no-op if not coupled) + this->asImp_().maybeEvalAdditionalDomainDerivatives(origResiduals, A, gridVariables); + + return origResiduals; + } + +}; // implicit CVFEAssembler with numeric Jacobian + +/*! + * \ingroup Assembly + * \ingroup CVFEDiscretization + * \brief Control volume finite element local assembler using numeric differentiation and explicit time discretization + */ +template +class CVFELocalAssembler +: public CVFELocalAssemblerBase>, + false> +{ + using ThisType = CVFELocalAssembler; + using ParentType = CVFELocalAssemblerBase, false>; + using Scalar = GetPropType; + using GridVariables = GetPropType; + using VolumeVariables = GetPropType; + using JacobianMatrix = GetPropType; + + static constexpr int numEq = GetPropType::numEq(); + static constexpr int dim = GetPropType::GridView::dimension; + +public: + + using LocalResidual = typename ParentType::LocalResidual; + using ElementResidualVector = typename LocalResidual::ElementResidualVector; + using ParentType::ParentType; + + /*! + * \brief Computes the derivatives with respect to the given element and adds them + * to the global matrix. + * + * \return The element residual at the current solution. + */ + template + ElementResidualVector assembleJacobianAndResidualImpl(JacobianMatrix& A, GridVariables& gridVariables, + const PartialReassembler* partialReassembler = nullptr) + { + if (partialReassembler) + DUNE_THROW(Dune::NotImplemented, "partial reassembly for explicit time discretization"); + + // get some aliases for convenience + const auto& element = this->element(); + const auto& fvGeometry = this->fvGeometry(); + const auto& curSol = this->asImp_().curSol(); + auto&& curElemVolVars = this->curElemVolVars(); + + // get the vecor of the actual element residuals + const auto origResiduals = this->evalLocalResidual(); + const auto origStorageResiduals = this->evalLocalStorageResidual(); + + ////////////////////////////////////////////////////////////////////////////////////////////////// + // // + // Calculate derivatives of all dofs in stencil with respect to the dofs in the element. In the // + // neighboring elements we do so by computing the derivatives of the fluxes which depend on the // + // actual element. In the actual element we evaluate the derivative of the entire residual. // + // // + ////////////////////////////////////////////////////////////////////////////////////////////////// + + // create the element solution + auto elemSol = elementSolution(element, curSol, fvGeometry.gridGeometry()); + + // create the vector storing the partial derivatives + ElementResidualVector partialDerivs(fvGeometry.numScv()); + + // calculation of the derivatives + for (const auto& scv : scvs(fvGeometry)) + { + // dof index and corresponding actual pri vars + const auto dofIdx = scv.dofIndex(); + auto& curVolVars = this->getVolVarAccess(gridVariables.curGridVolVars(), curElemVolVars, scv); + const VolumeVariables origVolVars(curVolVars); + + // calculate derivatives w.r.t to the privars at the dof at hand + for (int pvIdx = 0; pvIdx < numEq; pvIdx++) + { + partialDerivs = 0.0; + + auto evalStorage = [&](Scalar priVar) + { + // auto partialDerivsTmp = partialDerivs; + elemSol[scv.localDofIndex()][pvIdx] = priVar; + curVolVars.update(elemSol, this->asImp_().problem(), element, scv); + return this->evalLocalStorageResidual(); + }; + + // derive the residuals numerically + static const NumericEpsilon eps_{this->asImp_().problem().paramGroup()}; + static const int numDiffMethod = getParamFromGroup(this->asImp_().problem().paramGroup(), "Assembly.NumericDifferenceMethod"); + NumericDifferentiation::partialDerivative(evalStorage, elemSol[scv.localDofIndex()][pvIdx], partialDerivs, origStorageResiduals, + eps_(elemSol[scv.localDofIndex()][pvIdx], pvIdx), numDiffMethod); + + // update the global stiffness matrix with the current partial derivatives + for (int eqIdx = 0; eqIdx < numEq; eqIdx++) + { + // A[i][col][eqIdx][pvIdx] is the rate of change of + // the residual of equation 'eqIdx' at dof 'i' + // depending on the primary variable 'pvIdx' at dof + // 'col'. + A[dofIdx][dofIdx][eqIdx][pvIdx] += partialDerivs[scv.localDofIndex()][eqIdx]; + } + + // restore the original state of the scv's volume variables + curVolVars = origVolVars; + + // restore the original element solution + elemSol[scv.localDofIndex()][pvIdx] = curSol[scv.dofIndex()][pvIdx]; + // TODO additional dof dependencies + } + } + + return origResiduals; + } +}; // explicit CVFEAssembler with numeric Jacobian + +/*! + * \ingroup Assembly + * \ingroup CVFEDiscretization + * \brief Control volume finite element local assembler using analytic differentiation and implicit time discretization + */ +template +class CVFELocalAssembler +: public CVFELocalAssemblerBase>, + true> +{ + using ThisType = CVFELocalAssembler; + using ParentType = CVFELocalAssemblerBase, true>; + using GridVariables = GetPropType; + using JacobianMatrix = GetPropType; + +public: + + using LocalResidual = typename ParentType::LocalResidual; + using ElementResidualVector = typename LocalResidual::ElementResidualVector; + using ParentType::ParentType; + + /*! + * \brief Computes the derivatives with respect to the given element and adds them + * to the global matrix. + * + * \return The element residual at the current solution. + */ + template + ElementResidualVector assembleJacobianAndResidualImpl(JacobianMatrix& A, GridVariables& gridVariables, + const PartialReassembler* partialReassembler = nullptr) + { + if (partialReassembler) + DUNE_THROW(Dune::NotImplemented, "partial reassembly for analytic differentiation"); + + // get some aliases for convenience + const auto& element = this->element(); + const auto& fvGeometry = this->fvGeometry(); + const auto& problem = this->asImp_().problem(); + const auto& curElemVolVars = this->curElemVolVars(); + const auto& elemFluxVarsCache = this->elemFluxVarsCache(); + + // get the vecor of the actual element residuals + const auto origResiduals = this->evalLocalResidual(); + + ////////////////////////////////////////////////////////////////////////////////////////////////// + // // + // Calculate derivatives of all dofs in stencil with respect to the dofs in the element. In the // + // neighboring elements we do so by computing the derivatives of the fluxes which depend on the // + // actual element. In the actual element we evaluate the derivative of the entire residual. // + // // + ////////////////////////////////////////////////////////////////////////////////////////////////// + + // calculation of the source and storage derivatives + for (const auto& scv : scvs(fvGeometry)) + { + // dof index and corresponding actual pri vars + const auto dofIdx = scv.dofIndex(); + const auto& volVars = curElemVolVars[scv]; + + // derivative of this scv residual w.r.t the d.o.f. of the same scv (because of mass lumping) + // only if the problem is instationary we add derivative of storage term + // TODO if e.g. porosity depends on all dofs in the element, we would have off-diagonal matrix entries!? + if (!this->assembler().isStationaryProblem()) + this->localResidual().addStorageDerivatives(A[dofIdx][dofIdx], + problem, + element, + fvGeometry, + volVars, + scv); + + // derivative of this scv residual w.r.t the d.o.f. of the same scv (because of mass lumping) + // add source term derivatives + this->localResidual().addSourceDerivatives(A[dofIdx][dofIdx], + problem, + element, + fvGeometry, + volVars, + scv); + } + + // localJacobian[scvIdx][otherScvIdx][eqIdx][priVarIdx] of the fluxes + for (const auto& scvf : scvfs(fvGeometry)) + { + if (!scvf.boundary()) + { + // add flux term derivatives + this->localResidual().addFluxDerivatives(A, + problem, + element, + fvGeometry, + curElemVolVars, + elemFluxVarsCache, + scvf); + } + + // the boundary gets special treatment to simplify + // for the user + else + { + const auto& insideScv = fvGeometry.scv(scvf.insideScvIdx()); + if (this->elemBcTypes().get(fvGeometry, insideScv).hasNeumann()) + { + // add flux term derivatives + this->localResidual().addRobinFluxDerivatives(A[insideScv.dofIndex()], + problem, + element, + fvGeometry, + curElemVolVars, + elemFluxVarsCache, + scvf); + } + } + } + + return origResiduals; + } + +}; // implicit CVFEAssembler with analytic Jacobian + +/*! + * \ingroup Assembly + * \ingroup CVFEDiscretization + * \brief Control volume finite element local assembler using analytic differentiation and explicit time discretization + */ +template +class CVFELocalAssembler +: public CVFELocalAssemblerBase>, + false> +{ + using ThisType = CVFELocalAssembler; + using ParentType = CVFELocalAssemblerBase, false>; + using GridVariables = GetPropType; + using JacobianMatrix = GetPropType; + +public: + + using LocalResidual = typename ParentType::LocalResidual; + using ElementResidualVector = typename LocalResidual::ElementResidualVector; + using ParentType::ParentType; + + /*! + * \brief Computes the derivatives with respect to the given element and adds them + * to the global matrix. + * + * \return The element residual at the current solution. + */ + template + ElementResidualVector assembleJacobianAndResidualImpl(JacobianMatrix& A, GridVariables& gridVariables, + const PartialReassembler* partialReassembler = nullptr) + { + if (partialReassembler) + DUNE_THROW(Dune::NotImplemented, "partial reassembly for explicit time discretization"); + + // get some aliases for convenience + const auto& element = this->element(); + const auto& fvGeometry = this->fvGeometry(); + const auto& problem = this->asImp_().problem(); + const auto& curElemVolVars = this->curElemVolVars(); + + // get the vecor of the actual element residuals + const auto origResiduals = this->evalLocalResidual(); + + ////////////////////////////////////////////////////////////////////////////////////////////////// + // // + // Calculate derivatives of all dofs in stencil with respect to the dofs in the element. In the // + // neighboring elements we do so by computing the derivatives of the fluxes which depend on the // + // actual element. In the actual element we evaluate the derivative of the entire residual. // + // // + ////////////////////////////////////////////////////////////////////////////////////////////////// + + // calculation of the source and storage derivatives + for (const auto& scv : scvs(fvGeometry)) + { + // dof index and corresponding actual pri vars + const auto dofIdx = scv.dofIndex(); + const auto& volVars = curElemVolVars[scv]; + + // derivative of this scv residual w.r.t the d.o.f. of the same scv (because of mass lumping) + // only if the problem is instationary we add derivative of storage term + this->localResidual().addStorageDerivatives(A[dofIdx][dofIdx], + problem, + element, + fvGeometry, + volVars, + scv); + } + + return origResiduals; + } + +}; // explicit CVFEAssembler with analytic Jacobian + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/cvfelocalresidual.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/cvfelocalresidual.hh new file mode 100644 index 0000000..5e50d70 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/cvfelocalresidual.hh @@ -0,0 +1,217 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Assembly + * \ingroup CVFEDiscretization + * \brief Calculates the element-wise residual for control-volume finite element schemes + */ +#ifndef DUMUX_CVFE_LOCAL_RESIDUAL_HH +#define DUMUX_CVFE_LOCAL_RESIDUAL_HH + +#include +#include +#include + +#include +#include +#include +#include + +namespace Dumux::Detail { + +template +using TimeInfoInterfaceCVFEDetector = decltype( + std::declval().computeStorage( + std::declval

(), std::declval(), std::declval(), std::declval(), true + ) +); + +template +constexpr inline bool hasTimeInfoInterfaceCVFE() +{ return Dune::Std::is_detected::value; } + +template +using SCVFIsOverlappingDetector = decltype( + std::declval().isOverlapping() +); + +template +constexpr inline bool hasScvfIsOverlapping() +{ return Dune::Std::is_detected::value; } + +} // end namespace Dumux::Detail + + +namespace Dumux { + +/*! + * \ingroup Assembly + * \ingroup CVFEDiscretization + * \brief The element-wise residual for control-volume finite element schemes + * \tparam TypeTag the TypeTag + */ +template +class CVFELocalResidual : public FVLocalResidual +{ + using ParentType = FVLocalResidual; + using Implementation = GetPropType; + using Scalar = GetPropType; + using Problem = GetPropType; + using GridGeometry = GetPropType; + using GridView = typename GridGeometry::GridView; + using Element = typename GridView::template Codim<0>::Entity; + using ElementBoundaryTypes = GetPropType; + using FVElementGeometry = typename GridGeometry::LocalView; + using GridVolumeVariables = GetPropType; + using ElementVolumeVariables = typename GridVolumeVariables::LocalView; + using VolumeVariables = typename GridVolumeVariables::VolumeVariables; + using SubControlVolume = typename FVElementGeometry::SubControlVolume; + using SubControlVolumeFace = typename FVElementGeometry::SubControlVolumeFace; + using ElementFluxVariablesCache = typename GetPropType::LocalView; + using PrimaryVariables = GetPropType; + using NumEqVector = Dumux::NumEqVector; + using Extrusion = Extrusion_t; + +public: + using ElementResidualVector = typename ParentType::ElementResidualVector; + using ParentType::ParentType; + + //! evaluate flux residuals for one sub control volume face and add to residual + void evalFlux(ElementResidualVector& residual, + const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const ElementBoundaryTypes& elemBcTypes, + const ElementFluxVariablesCache& elemFluxVarsCache, + const SubControlVolumeFace& scvf) const + { + const auto flux = evalFlux(problem, element, fvGeometry, elemVolVars, elemBcTypes, elemFluxVarsCache, scvf); + if (!scvf.boundary()) + { + const auto& insideScv = fvGeometry.scv(scvf.insideScvIdx()); + const auto& outsideScv = fvGeometry.scv(scvf.outsideScvIdx()); + residual[insideScv.localDofIndex()] += flux; + + // for control-volume finite element schemes with overlapping control volumes + if constexpr (Detail::hasScvfIsOverlapping()) + { + if (!scvf.isOverlapping()) + residual[outsideScv.localDofIndex()] -= flux; + } + else + residual[outsideScv.localDofIndex()] -= flux; + } + else + { + const auto& insideScv = fvGeometry.scv(scvf.insideScvIdx()); + residual[insideScv.localDofIndex()] += flux; + } + } + + //! evaluate flux residuals for one sub control volume face + NumEqVector evalFlux(const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const ElementBoundaryTypes& elemBcTypes, + const ElementFluxVariablesCache& elemFluxVarsCache, + const SubControlVolumeFace& scvf) const + { + NumEqVector flux(0.0); + + // inner faces + if (!scvf.boundary()) + flux += this->asImp().computeFlux(problem, element, fvGeometry, elemVolVars, scvf, elemFluxVarsCache); + + // boundary faces + else + { + const auto& scv = fvGeometry.scv(scvf.insideScvIdx()); + const auto& bcTypes = elemBcTypes.get(fvGeometry, scv); + + // Treat Neumann and Robin ("solution dependent Neumann") boundary conditions. + // For Dirichlet there is no addition to the residual here but they + // are enforced strongly by replacing the residual entry afterwards. + if (bcTypes.hasNeumann()) + { + auto neumannFluxes = problem.neumann(element, fvGeometry, elemVolVars, elemFluxVarsCache, scvf); + + // multiply neumann fluxes with the area and the extrusion factor + neumannFluxes *= Extrusion::area(fvGeometry, scvf)*elemVolVars[scv].extrusionFactor(); + + // only add fluxes to equations for which Neumann is set + for (int eqIdx = 0; eqIdx < NumEqVector::dimension; ++eqIdx) + if (bcTypes.isNeumann(eqIdx)) + flux[eqIdx] += neumannFluxes[eqIdx]; + } + } + + return flux; + } + + using ParentType::evalStorage; + /*! + * \brief Compute the storage local residual, i.e. the deviation of the + * storage term from zero for instationary problems. + * + * \param residual The residual vector to fill + * \param problem The problem to solve + * \param element The DUNE Codim<0> entity for which the residual + * ought to be calculated + * \param fvGeometry The finite-volume geometry of the element + * \param prevElemVolVars The volume averaged variables for all + * sub-control volumes of the element at the previous time level + * \param curElemVolVars The volume averaged variables for all + * sub-control volumes of the element at the current time level + * \param scv The sub control volume the storage term is integrated over + */ + void evalStorage(ElementResidualVector& residual, + const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& prevElemVolVars, + const ElementVolumeVariables& curElemVolVars, + const SubControlVolume& scv) const + { + const auto& curVolVars = curElemVolVars[scv]; + const auto& prevVolVars = prevElemVolVars[scv]; + + // Compute storage with the model specific storage residual + // This addresses issues #792/#940 in ad-hoc way by additionally providing crude time level information (previous or current) + // to the low-level interfaces if this is supported by the LocalResidual implementation + NumEqVector prevStorage = computeStorageImpl_(problem, fvGeometry, scv, prevVolVars, /*previous time level?*/true); + NumEqVector storage = computeStorageImpl_(problem, fvGeometry, scv, curVolVars, /*previous time level?*/false); + + prevStorage *= prevVolVars.extrusionFactor(); + storage *= curVolVars.extrusionFactor(); + + storage -= prevStorage; + storage *= Extrusion::volume(fvGeometry, scv); + storage /= this->timeLoop().timeStepSize(); + + residual[scv.localDofIndex()] += storage; + } + +private: + NumEqVector computeStorageImpl_(const Problem& problem, + const FVElementGeometry& fvGeometry, + const SubControlVolume& scv, + const VolumeVariables& volVars, + [[maybe_unused]] bool isPreviousTimeStep) const + { + if constexpr (Detail::hasTimeInfoInterfaceCVFE()) + return this->asImp().computeStorage(problem, fvGeometry, scv, volVars, isPreviousTimeStep); + else + return this->asImp().computeStorage(problem, scv, volVars); + } +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/diffmethod.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/diffmethod.hh new file mode 100644 index 0000000..86295c6 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/diffmethod.hh @@ -0,0 +1,31 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Assembly + * \brief An enum class to define various differentiation methods available in order to compute + the derivatives of the residual i.e. the entries in the jacobian matrix. + */ +#ifndef DUMUX_JACOBIAN_DIFFERENTIATION_METHODS_HH +#define DUMUX_JACOBIAN_DIFFERENTIATION_METHODS_HH + +namespace Dumux { + +/*! + * \ingroup Assembly + * \brief Differentiation methods in order to compute the derivatives + * of the residual i.e. the entries in the jacobian matrix. + * \todo automatic differentation is not yet implemented + */ +enum class DiffMethod +{ + numeric, analytic, automatic +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/entitycolor.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/entitycolor.hh new file mode 100644 index 0000000..33964c7 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/entitycolor.hh @@ -0,0 +1,45 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Assembly + * \brief An enum class to define the colors of elements and vertices required + * for partial Jacobian reassembly. + */ +#ifndef DUMUX_ENTITY_COLOR_HH +#define DUMUX_ENTITY_COLOR_HH + +namespace Dumux { + +/*! + * \ingroup Assembly + * \brief The colors of elements and vertices required for partial + * Jacobian reassembly. + */ +enum class EntityColor { + //! distance from last linearization is above the tolerance + red, + + //! neighboring entity is red + yellow, + + /*! + * A yellow entity that has only non-green neighbor elements. + * + * This means that its relative error is below the tolerance, + * but its defect can be linearized without any additional + * cost. + */ + orange, + + //! does not need to be reassembled + green +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/fclocalassembler.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/fclocalassembler.hh new file mode 100644 index 0000000..148dd47 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/fclocalassembler.hh @@ -0,0 +1,781 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Assembly + * \ingroup FaceCenteredStaggeredDiscretization + * \brief An assembler for Jacobian and residual contribution per element (face-centered staggered methods) + */ +#ifndef DUMUX_FC_LOCAL_ASSEMBLER_HH +#define DUMUX_FC_LOCAL_ASSEMBLER_HH + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Dumux { + +namespace Detail { + +struct NoOpFunctor +{ + template + constexpr void operator()(Args&&...) const {} +}; + +template +using NonVoidOrDefault_t = std::conditional_t, T, Default>; + +} // end namespace Detail + +/*! + * \ingroup Assembly + * \ingroup FaceCenteredStaggeredDiscretization + * \brief A base class for all local cell-centered assemblers + * \tparam TypeTag The TypeTag + * \tparam Assembler The assembler type + * \tparam Implementation The actual implementation + * \tparam implicit Specifies whether the time discretization is implicit or not (i.e. explicit) + */ +template +class FaceCenteredLocalAssemblerBase : public FVLocalAssemblerBase +{ + using ParentType = FVLocalAssemblerBase; + using GridView = typename GetPropType::GridView; + using JacobianMatrix = GetPropType; + using GridVariables = GetPropType; + using PrimaryVariables = GetPropType; + using Scalar = GetPropType; + + static constexpr auto numEq = GetPropType::numEq(); + +public: + + using ParentType::ParentType; + + void bindLocalViews() + { + ParentType::bindLocalViews(); + this->elemBcTypes().update(this->asImp_().problem(), this->element(), this->fvGeometry()); + } + + /*! + * \brief Computes the derivatives with respect to the given element and adds them + * to the global matrix. The element residual is written into the right hand side. + */ + template + void assembleJacobianAndResidual(JacobianMatrix& jac, ResidualVector& res, GridVariables& gridVariables, + const PartialReassembler* partialReassembler, + const CouplingFunction& maybeAssembleCouplingBlocks = CouplingFunction{}) + { + static_assert(!std::decay_tasImp_().problem())>::enableInternalDirichletConstraints(), + "Internal Dirichlet constraints are currently not implemented for face-centered staggered models!"); + + this->asImp_().bindLocalViews(); + const auto& gridGeometry = this->asImp_().problem().gridGeometry(); + const auto eIdxGlobal = gridGeometry.elementMapper().index(this->element()); + if (partialReassembler + && partialReassembler->elementColor(eIdxGlobal) == EntityColor::green) + { + const auto residual = this->asImp_().evalLocalResidual(); // forward to the internal implementation + for (const auto& scv : scvs(this->fvGeometry())) + res[scv.dofIndex()] += residual[scv.localDofIndex()]; + + // assemble the coupling blocks for coupled models (does nothing if not coupled) + maybeAssembleCouplingBlocks(residual); + } + else if (!this->elementIsGhost()) + { + const auto residual = this->asImp_().assembleJacobianAndResidualImpl(jac, gridVariables, partialReassembler); // forward to the internal implementation + + if (this->element().partitionType() == Dune::InteriorEntity) + { + for (const auto& scv : scvs(this->fvGeometry())) + res[scv.dofIndex()] += residual[scv.localDofIndex()]; + } + else + { + // handle residual and matrix entries for parallel runs + for (const auto& scv : scvs(this->fvGeometry())) + { + const auto& facet = this->element().template subEntity <1> (scv.indexInElement()); + // make sure that the residual at border entities is consistent by adding the + // the contribution from the neighboring overlap element's scv + if (facet.partitionType() == Dune::BorderEntity) + res[scv.dofIndex()] += residual[scv.localDofIndex()]; + + // set the matrix entries of all DOFs within the overlap region (except the border DOF) + // to 1.0 and the residual entries to 0.0 + else + { + const auto idx = scv.dofIndex(); + jac[idx][idx] = 0.0; + for (int i = 0; i < jac[idx][idx].size(); ++i) + jac[idx][idx][i][i] = 1.0; + res[idx] = 0; + } + } + } + + // assemble the coupling blocks for coupled models (does nothing if not coupled) + maybeAssembleCouplingBlocks(residual); + } + else + DUNE_THROW(Dune::NotImplemented, "Ghost elements not supported"); + + + auto applyDirichlet = [&] (const auto& scvI, + const auto& dirichletValues, + const auto eqIdx, + const auto pvIdx) + { + res[scvI.dofIndex()][eqIdx] = this->curElemVolVars()[scvI].priVars()[pvIdx] - dirichletValues[pvIdx]; + + auto& row = jac[scvI.dofIndex()]; + for (auto col = row.begin(); col != row.end(); ++col) + row[col.index()][eqIdx] = 0.0; + + jac[scvI.dofIndex()][scvI.dofIndex()][eqIdx][pvIdx] = 1.0; + + // if a periodic dof has Dirichlet values also apply the same Dirichlet values to the other dof + if (this->asImp_().problem().gridGeometry().dofOnPeriodicBoundary(scvI.dofIndex())) + { + const auto periodicDof = this->asImp_().problem().gridGeometry().periodicallyMappedDof(scvI.dofIndex()); + res[periodicDof][eqIdx] = this->asImp_().curSol()[periodicDof][pvIdx] - dirichletValues[pvIdx]; + + auto& rowP = jac[periodicDof]; + for (auto col = rowP.begin(); col != rowP.end(); ++col) + row[col.index()][eqIdx] = 0.0; + + rowP[periodicDof][eqIdx][pvIdx] = 1.0; + } + }; + + this->asImp_().enforceDirichletConstraints(applyDirichlet); + } + + /*! + * \brief Computes the derivatives with respect to the given element and adds them + * to the global matrix. + */ + void assembleJacobian(JacobianMatrix& jac, GridVariables& gridVariables) + { + this->asImp_().bindLocalViews(); + this->asImp_().assembleJacobianAndResidualImpl(jac, gridVariables); // forward to the internal implementation + + auto applyDirichlet = [&] (const auto& scvI, + const auto& dirichletValues, + const auto eqIdx, + const auto pvIdx) + { + auto& row = jac[scvI.dofIndex()]; + for (auto col = row.begin(); col != row.end(); ++col) + row[col.index()][eqIdx] = 0.0; + + jac[scvI.dofIndex()][scvI.dofIndex()][eqIdx][pvIdx] = 1.0; + }; + + this->asImp_().enforceDirichletConstraints(applyDirichlet); + } + + /*! + * \brief Assemble the residual only + */ + template + void assembleResidual(ResidualVector& res) + { + this->asImp_().bindLocalViews(); + const auto residual = this->evalLocalResidual(); + + for (const auto& scv : scvs(this->fvGeometry())) + res[scv.dofIndex()] += residual[scv.localDofIndex()]; + + auto applyDirichlet = [&] (const auto& scvI, + const auto& dirichletValues, + const auto eqIdx, + const auto pvIdx) + { + res[scvI.dofIndex()][eqIdx] = this->curElemVolVars()[scvI].priVars()[pvIdx] - dirichletValues[pvIdx]; + }; + + this->asImp_().enforceDirichletConstraints(applyDirichlet); + } + + /*! + * \brief Enforce Dirichlet constraints + */ + template + void enforceDirichletConstraints(const ApplyFunction& applyDirichlet) + { + // enforce Dirichlet boundary conditions + this->asImp_().evalDirichletBoundaries(applyDirichlet); + // take care of internal Dirichlet constraints (if enabled) + this->asImp_().enforceInternalDirichletConstraints(applyDirichlet); + } + + /*! + * \brief Evaluates Dirichlet boundaries + */ + template< typename ApplyDirichletFunctionType > + void evalDirichletBoundaries(ApplyDirichletFunctionType applyDirichlet) + { + // enforce Dirichlet boundaries by overwriting partial derivatives with 1 or 0 + // and set the residual to (privar - dirichletvalue) + if (this->elemBcTypes().hasDirichlet()) + { + for (const auto& scvf : scvfs(this->fvGeometry())) + { + if (scvf.isFrontal() && scvf.boundary()) + { + const auto bcTypes = this->elemBcTypes()[scvf.localIndex()]; + if (bcTypes.hasDirichlet()) + { + const auto& scv = this->fvGeometry().scv(scvf.insideScvIdx()); + const auto dirichletValues = this->asImp_().problem().dirichlet(this->element(), scvf); + + // set the Dirichlet conditions in residual and jacobian + for (int eqIdx = 0; eqIdx < numEq; ++eqIdx) + { + static_assert(numEq == 1, "Not yet implemented for more than one vector-valued primary variable"); + const int pvIdx = eqIdx; + const int componentIdx = scv.dofAxis(); + if (bcTypes.isDirichlet(componentIdx)) + applyDirichlet(scv, std::array{{dirichletValues[componentIdx]}}, eqIdx, pvIdx); + } + } + } + } + } + } + + /*! + * \brief Update the coupling context for coupled models. + * \note This does nothing per default (not a coupled model). + */ + template + void maybeUpdateCouplingContext(Args&&...) {} + + /*! + * \brief Update the additional domain derivatives for coupled models. + * \note This does nothing per default (not a coupled model). + */ + template + void maybeEvalAdditionalDomainDerivatives(Args&&...) {} +}; + +/*! + * \ingroup Assembly + * \ingroup FaceCenteredStaggeredDiscretization + * \brief An assembler for Jacobian and residual contribution per element (Face-centered methods) + * \tparam TypeTag The TypeTag + * \tparam diffMethod The differentiation method to residual compute derivatives + * \tparam implicit Specifies whether the time discretization is implicit or not (i.e. explicit) + * \tparam Implementation The actual implementation, if void this class is the actual implementation + */ +template +class FaceCenteredLocalAssembler; + +/*! + * \ingroup Assembly + * \ingroup FaceCenteredStaggeredDiscretization + * \brief Face-centered scheme local assembler using numeric differentiation and implicit time discretization + */ +template +class FaceCenteredLocalAssembler +: public FaceCenteredLocalAssemblerBase< + TypeTag, Assembler, + Detail::NonVoidOrDefault_t>, + /*implicit=*/true +> +{ + using ThisType = FaceCenteredLocalAssembler; + using ParentType = FaceCenteredLocalAssemblerBase, true>; + using Scalar = GetPropType; + using Element = typename GetPropType::GridView::template Codim<0>::Entity; + using GridGeometry = GetPropType; + using FVElementGeometry = typename GridGeometry::LocalView; + using SubControlVolume = typename FVElementGeometry::SubControlVolume; + using SubControlVolumeFace = typename FVElementGeometry::SubControlVolumeFace; + using GridVariables = GetPropType; + using JacobianMatrix = GetPropType; + using VolumeVariables = GetPropType; + + static constexpr auto numEq = GetPropType::numEq(); + static constexpr bool enableGridFluxVarsCache = GetPropType::GridFluxVariablesCache::cachingEnabled; + +public: + + using LocalResidual = typename ParentType::LocalResidual; + using ElementResidualVector = typename LocalResidual::ElementResidualVector; + using ParentType::ParentType; + + /*! + * \brief Computes the derivatives with respect to the given element and adds them + * to the global matrix. + * + * \return The element residual at the current solution. + */ + template + ElementResidualVector assembleJacobianAndResidualImpl(JacobianMatrix& A, GridVariables& gridVariables, + const PartialReassembler* partialReassembler = nullptr) + { + // get some aliases for convenience + const auto& problem = this->asImp_().problem(); + const auto& element = this->element(); + const auto& fvGeometry = this->fvGeometry(); + const auto& curSol = this->asImp_().curSol(); + auto&& curElemVolVars = this->curElemVolVars(); + + // get the vector of the actual element residuals + const auto origResiduals = this->evalLocalResidual(); + + ////////////////////////////////////////////////////////////////////////////////////////////////// + // // + // Calculate derivatives of all dofs in stencil with respect to the dofs in the element. In the // + // neighboring elements we do so by computing the derivatives of the fluxes which depend on the // + // actual element. In the actual element we evaluate the derivative of the entire residual. // + // // + ////////////////////////////////////////////////////////////////////////////////////////////////// + + // one residual per element facet + const auto numElementResiduals = fvGeometry.numScv(); + + // create the vector storing the partial derivatives + ElementResidualVector partialDerivs(numElementResiduals); + + const auto evalSource = [&](ElementResidualVector& residual, const SubControlVolume& scv) + { + this->localResidual().evalSource(residual, problem, element, fvGeometry, curElemVolVars, scv); + }; + + const auto evalStorage = [&](ElementResidualVector& residual, const SubControlVolume& scv) + { + this->localResidual().evalStorage(residual, problem, element, fvGeometry, this->prevElemVolVars(), curElemVolVars, scv); + }; + + const auto evalFlux = [&](ElementResidualVector& residual, const SubControlVolumeFace& scvf) + { + if (!scvf.processorBoundary()) + this->localResidual().evalFlux(residual, problem, element, fvGeometry, curElemVolVars, this->elemBcTypes(), this->elemFluxVarsCache(), scvf); + }; + + const auto evalDerivative = [&] (const auto& scvI, const auto& scvJ) + { + // derivative w.r.t. own DOF + for (int pvIdx = 0; pvIdx < numEq; pvIdx++) + { + partialDerivs = 0.0; + const auto& otherElement = fvGeometry.gridGeometry().element(scvJ.elementIndex()); + auto otherElemSol = elementSolution(otherElement, curSol, fvGeometry.gridGeometry()); // TODO allow selective creation of elemsol (for one scv) + auto& curOtherVolVars = this->getVolVarAccess(gridVariables.curGridVolVars(), curElemVolVars, scvJ); + const VolumeVariables origOtherVolVars(curOtherVolVars); + + auto evalResiduals = [&](Scalar priVar) + { + // update the volume variables and compute element residual + otherElemSol[scvJ.localDofIndex()][pvIdx] = priVar; + curOtherVolVars.update(otherElemSol, problem, otherElement, scvJ); + this->asImp_().maybeUpdateCouplingContext(scvJ, otherElemSol, pvIdx); + + ElementResidualVector residual(numElementResiduals); + residual = 0; + + evalSource(residual, scvI); + + if (!this->assembler().isStationaryProblem()) + evalStorage(residual, scvI); + + for (const auto& scvf : scvfs(fvGeometry, scvI)) + evalFlux(residual, scvf); + + return residual; + }; + + // derive the residuals numerically + static const NumericEpsilon eps_{this->asImp_().problem().paramGroup()}; + static const int numDiffMethod = getParamFromGroup(this->asImp_().problem().paramGroup(), "Assembly.NumericDifferenceMethod"); + NumericDifferentiation::partialDerivative(evalResiduals, otherElemSol[scvJ.localDofIndex()][pvIdx], partialDerivs, origResiduals, + eps_(otherElemSol[scvJ.localDofIndex()][pvIdx], pvIdx), numDiffMethod); + + const auto updateJacobian = [&]() + { + for (int eqIdx = 0; eqIdx < numEq; eqIdx++) + { + // A[i][col][eqIdx][pvIdx] is the rate of change of + // the residual of equation 'eqIdx' at dof 'i' + // depending on the primary variable 'pvIdx' at dof + // 'col'. + A[scvI.dofIndex()][scvJ.dofIndex()][eqIdx][pvIdx] += partialDerivs[scvI.localDofIndex()][eqIdx]; + } + }; + + if (element.partitionType() == Dune::InteriorEntity) + updateJacobian(); + else + { + const auto localIdxI = scvI.indexInElement(); + const auto& facetI = element.template subEntity <1> (localIdxI); + + if (facetI.partitionType() == Dune::BorderEntity) + updateJacobian(); + } + + // restore the original state of the scv's volume variables + curOtherVolVars = origOtherVolVars; + + // restore the original element solution + otherElemSol[scvJ.localDofIndex()][pvIdx] = curSol[scvJ.dofIndex()][pvIdx]; + this->asImp_().maybeUpdateCouplingContext(scvJ, otherElemSol, pvIdx); + // TODO additional dof dependencies + } + }; + + // calculation of the derivatives + for (const auto& scvI : scvs(fvGeometry)) + { + // derivative w.r.t. own DOFs + evalDerivative(scvI, scvI); + + // derivative w.r.t. other DOFs + const auto& otherScvIndices = fvGeometry.gridGeometry().connectivityMap()[scvI.index()]; + for (const auto globalJ : otherScvIndices) + evalDerivative(scvI, fvGeometry.scv(globalJ)); + } + + // evaluate additional derivatives that might arise from the coupling + this->asImp_().maybeEvalAdditionalDomainDerivatives(origResiduals, A, gridVariables); + + return origResiduals; + } +}; + + +/*! + * \ingroup Assembly + * \ingroup FaceCenteredStaggeredDiscretization + * \brief TODO docme + */ +template +class FaceCenteredLocalAssembler +: public FaceCenteredLocalAssemblerBase< + TypeTag, Assembler, + Detail::NonVoidOrDefault_t>, + /*implicit=*/false +> +{ + using ThisType = FaceCenteredLocalAssembler; + using ParentType = FaceCenteredLocalAssemblerBase, false>; + using Scalar = GetPropType; + using Element = typename GetPropType::GridView::template Codim<0>::Entity; + using GridVariables = GetPropType; + using JacobianMatrix = GetPropType; + using VolumeVariables = GetPropType; + + enum { numEq = GetPropType::numEq() }; + +public: + using LocalResidual = typename ParentType::LocalResidual; + using ElementResidualVector = typename LocalResidual::ElementResidualVector; + using ParentType::ParentType; + + /*! + * \brief Computes the derivatives with respect to the given element and adds them + * to the global matrix. + * + * \return The element residual at the current solution. + */ + template + ElementResidualVector assembleJacobianAndResidualImpl(JacobianMatrix& A, GridVariables& gridVariables, + const PartialReassembler* partialReassembler = nullptr) + { + if (partialReassembler) + DUNE_THROW(Dune::NotImplemented, "partial reassembly for explicit time discretization"); + + // get some aliases for convenience + const auto& problem = this->asImp_().problem(); + const auto& element = this->element(); + const auto& fvGeometry = this->fvGeometry(); + const auto& curSol = this->asImp_().curSol(); + auto&& curElemVolVars = this->curElemVolVars(); + + // get the vector of the actual element residuals + const auto origResiduals = this->evalLocalResidual(); + const auto origStorageResiduals = this->evalLocalStorageResidual(); + + ////////////////////////////////////////////////////////////////////////////////////////////////// + // // + // Calculate derivatives of all dofs in stencil with respect to the dofs in the element. In the // + // neighboring elements we do so by computing the derivatives of the fluxes which depend on the // + // actual element. In the actual element we evaluate the derivative of the entire residual. // + // // + ////////////////////////////////////////////////////////////////////////////////////////////////// + + // create the element solution + auto elemSol = elementSolution(element, curSol, fvGeometry.gridGeometry()); + + // create the vector storing the partial derivatives + ElementResidualVector partialDerivs(fvGeometry.numScv()); + + // calculation of the derivatives + for (const auto& scv : scvs(fvGeometry)) + { + // dof index and corresponding actual pri vars + const auto dofIdx = scv.dofIndex(); + auto& curVolVars = this->getVolVarAccess(gridVariables.curGridVolVars(), curElemVolVars, scv); + const VolumeVariables origVolVars(curVolVars); + + // calculate derivatives w.r.t to the privars at the dof at hand + for (int pvIdx = 0; pvIdx < numEq; pvIdx++) + { + partialDerivs = 0.0; + + auto evalStorage = [&](Scalar priVar) + { + // auto partialDerivsTmp = partialDerivs; + elemSol[scv.localDofIndex()][pvIdx] = priVar; + curVolVars.update(elemSol, problem, element, scv); + return this->evalLocalStorageResidual(); + }; + + // derive the residuals numerically + static const NumericEpsilon eps_{problem.paramGroup()}; + static const int numDiffMethod = getParamFromGroup(problem.paramGroup(), "Assembly.NumericDifferenceMethod"); + NumericDifferentiation::partialDerivative(evalStorage, elemSol[scv.localDofIndex()][pvIdx], partialDerivs, origStorageResiduals, + eps_(elemSol[scv.localDofIndex()][pvIdx], pvIdx), numDiffMethod); + + // update the global stiffness matrix with the current partial derivatives + for (int eqIdx = 0; eqIdx < numEq; eqIdx++) + { + // A[i][col][eqIdx][pvIdx] is the rate of change of + // the residual of equation 'eqIdx' at dof 'i' + // depending on the primary variable 'pvIdx' at dof + // 'col'. + A[dofIdx][dofIdx][eqIdx][pvIdx] += partialDerivs[scv.localDofIndex()][eqIdx]; + } + + // restore the original state of the scv's volume variables + curVolVars = origVolVars; + + // restore the original element solution + elemSol[scv.localDofIndex()][pvIdx] = curSol[scv.dofIndex()][pvIdx]; + } + } + return origResiduals; + } +}; + +/*! + * \ingroup Assembly + * \ingroup FaceCenteredStaggeredDiscretization + * \brief TODO docme + */ +template +class FaceCenteredLocalAssembler +: public FaceCenteredLocalAssemblerBase< + TypeTag, Assembler, + FaceCenteredLocalAssembler, + /*implicit=*/true +> +{ + using ThisType = FaceCenteredLocalAssembler; + using ParentType = FaceCenteredLocalAssemblerBase, true>; + using JacobianMatrix = GetPropType; + using GridVariables = GetPropType; + using VolumeVariables = GetPropType; + + enum { numEq = GetPropType::numEq() }; + +public: + using LocalResidual = typename ParentType::LocalResidual; + using ElementResidualVector = typename LocalResidual::ElementResidualVector; + using ParentType::ParentType; + + /*! + * \brief Computes the derivatives with respect to the given element and adds them + * to the global matrix. + * + * \return The element residual at the current solution. + */ + template + ElementResidualVector assembleJacobianAndResidualImpl(JacobianMatrix& A, GridVariables& gridVariables, + const PartialReassembler* partialReassembler = nullptr) + { + if (partialReassembler) + DUNE_THROW(Dune::NotImplemented, "partial reassembly for analytic differentiation"); + + // get some aliases for convenience + const auto& element = this->element(); + const auto& fvGeometry = this->fvGeometry(); + const auto& problem = this->asImp_().problem(); + const auto& curElemVolVars = this->curElemVolVars(); + const auto& elemFluxVarsCache = this->elemFluxVarsCache(); + + // get the vecor of the actual element residuals + const auto origResiduals = this->evalLocalResidual(); + + ////////////////////////////////////////////////////////////////////////////////////////////////// + // // + // Calculate derivatives of all dofs in stencil with respect to the dofs in the element. In the // + // neighboring elements we do so by computing the derivatives of the fluxes which depend on the // + // actual element. In the actual element we evaluate the derivative of the entire residual. // + // // + ////////////////////////////////////////////////////////////////////////////////////////////////// + + // calculation of the source and storage derivatives + for (const auto& scv : scvs(fvGeometry)) + { + // dof index and corresponding actual pri vars + const auto dofIdx = scv.dofIndex(); + const auto& volVars = curElemVolVars[scv]; + + // derivative of this scv residual w.r.t the d.o.f. of the same scv (because of mass lumping) + // only if the problem is instationary we add derivative of storage term + // TODO if e.g. porosity depends on all dofs in the element, we would have off-diagonal matrix entries!? + if (!this->assembler().isStationaryProblem()) + this->localResidual().addStorageDerivatives(A[dofIdx][dofIdx], + problem, + element, + fvGeometry, + volVars, + scv); + + // derivative of this scv residual w.r.t the d.o.f. of the same scv (because of mass lumping) + // add source term derivatives + this->localResidual().addSourceDerivatives(A[dofIdx][dofIdx], + problem, + element, + fvGeometry, + volVars, + scv); + } + + // localJacobian[scvIdx][otherScvIdx][eqIdx][priVarIdx] of the fluxes + for (const auto& scvf : scvfs(fvGeometry)) + { + if (!scvf.boundary()) + { + // add flux term derivatives + this->localResidual().addFluxDerivatives(A, + problem, + element, + fvGeometry, + curElemVolVars, + elemFluxVarsCache, + scvf); + } + + // the boundary gets special treatment to simplify + // for the user + else + { + const auto& insideScv = fvGeometry.scv(scvf.insideScvIdx()); + if (this->elemBcTypes()[insideScv.localDofIndex()].hasNeumann()) + { + // add flux term derivatives + this->localResidual().addRobinFluxDerivatives(A[insideScv.dofIndex()], + problem, + element, + fvGeometry, + curElemVolVars, + elemFluxVarsCache, + scvf); + } + } + } + + return origResiduals; + } +}; + +/*! + * \ingroup Assembly + * \ingroup FaceCenteredStaggeredDiscretization + * \brief TODO docme + */ +template +class FaceCenteredLocalAssembler +: public FaceCenteredLocalAssemblerBase< + TypeTag, Assembler, + FaceCenteredLocalAssembler, + /*implicit=*/false +> +{ + using ThisType = FaceCenteredLocalAssembler; + using ParentType = FaceCenteredLocalAssemblerBase, false>; + using JacobianMatrix = GetPropType; + using GridVariables = GetPropType; + using VolumeVariables = GetPropType; + + enum { numEq = GetPropType::numEq() }; + +public: + using LocalResidual = typename ParentType::LocalResidual; + using ElementResidualVector = typename LocalResidual::ElementResidualVector; + using ParentType::ParentType; + + /*! + * \brief Computes the derivatives with respect to the given element and adds them + * to the global matrix. + * + * \return The element residual at the current solution. + */ + template + ElementResidualVector assembleJacobianAndResidualImpl(JacobianMatrix& A, GridVariables& gridVariables, + const PartialReassembler* partialReassembler = nullptr) + { + if (partialReassembler) + DUNE_THROW(Dune::NotImplemented, "partial reassembly for explicit time discretization"); + + // get some aliases for convenience + const auto& element = this->element(); + const auto& fvGeometry = this->fvGeometry(); + const auto& problem = this->asImp_().problem(); + const auto& curElemVolVars = this->curElemVolVars(); + + // get the vector of the actual element residuals + const auto origResiduals = this->evalLocalResidual(); + + ////////////////////////////////////////////////////////////////////////////////////////////////// + // // + // Calculate derivatives of all dofs in stencil with respect to the dofs in the element. In the // + // neighboring elements we do so by computing the derivatives of the fluxes which depend on the // + // actual element. In the actual element we evaluate the derivative of the entire residual. // + // // + ////////////////////////////////////////////////////////////////////////////////////////////////// + + // calculation of the source and storage derivatives + for (const auto& scv : scvs(fvGeometry)) + { + // dof index and corresponding actual pri vars + const auto dofIdx = scv.dofIndex(); + const auto& volVars = curElemVolVars[scv]; + + // derivative of this scv residual w.r.t the d.o.f. of the same scv (because of mass lumping) + // only if the problem is instationary we add derivative of storage term + this->localResidual().addStorageDerivatives(A[dofIdx][dofIdx], + problem, + element, + fvGeometry, + volVars, + scv); + } + + return origResiduals; + } +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/fvlocalassemblerbase.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/fvlocalassemblerbase.hh new file mode 100644 index 0000000..0307d07 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/fvlocalassemblerbase.hh @@ -0,0 +1,330 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Assembly + * \copydoc Dumux::FVLocalAssemblerBase + */ +#ifndef DUMUX_FV_LOCAL_ASSEMBLER_BASE_HH +#define DUMUX_FV_LOCAL_ASSEMBLER_BASE_HH + +#include +#include // for GhostEntity +#include + +#include +#include +#include +#include + +namespace Dumux { + +/*! + * \ingroup Assembly + * \brief A base class for all local assemblers + * \tparam TypeTag The TypeTag + * \tparam Assembler The assembler type + * \tparam Implementation The assembler implementation + * \tparam useImplicitAssembly Specifies whether the time discretization is implicit or not not (i.e. explicit) + */ +template +class FVLocalAssemblerBase +{ + using Problem = GetPropType; + using GridView = typename GetPropType::GridView; + using Scalar = GetPropType; + using JacobianMatrix = GetPropType; + using GridVariables = GetPropType; + using SolutionVector = typename Assembler::SolutionVector; + using ElementBoundaryTypes = GetPropType; + using FVElementGeometry = typename GetPropType::LocalView; + using SubControlVolume = typename FVElementGeometry::SubControlVolume; + using SubControlVolumeFace = typename FVElementGeometry::SubControlVolumeFace; + using GridVolumeVariables = GetPropType; + using ElementVolumeVariables = typename GetPropType::LocalView; + using VolumeVariables = GetPropType; + using ElementFluxVariablesCache = typename GetPropType::LocalView; + using Element = typename GridView::template Codim<0>::Entity; + static constexpr auto numEq = GetPropType::numEq(); + +public: + using LocalResidual = std::decay_t().localResidual())>; + using ElementResidualVector = typename LocalResidual::ElementResidualVector; + + /*! + * \brief The constructor. Delegates to the general constructor. + */ + explicit FVLocalAssemblerBase(const Assembler& assembler, + const Element& element, + const SolutionVector& curSol) + : FVLocalAssemblerBase(assembler, + element, + curSol, + localView(assembler.gridGeometry()), + localView(assembler.gridVariables().curGridVolVars()), + localView(assembler.gridVariables().prevGridVolVars()), + localView(assembler.gridVariables().gridFluxVarsCache()), + assembler.localResidual(), + element.partitionType() == Dune::GhostEntity) + {} + + /*! + * \brief The constructor. General version explicitly expecting each argument. + */ + explicit FVLocalAssemblerBase(const Assembler& assembler, + const Element& element, + const SolutionVector& curSol, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& curElemVolVars, + const ElementVolumeVariables& prevElemVolVars, + const ElementFluxVariablesCache& elemFluxVarsCache, + const LocalResidual& localResidual, + const bool elementIsGhost) + : assembler_(assembler) + , element_(element) + , curSol_(curSol) + , fvGeometry_(fvGeometry) + , curElemVolVars_(curElemVolVars) + , prevElemVolVars_(prevElemVolVars) + , elemFluxVarsCache_(elemFluxVarsCache) + , localResidual_(localResidual) + , elementIsGhost_(elementIsGhost) + {} + + /*! + * \brief Returns true if the assembler considers implicit assembly. + */ + static constexpr bool isImplicit() + { return useImplicitAssembly; } + + /*! + * \brief Convenience function to evaluate the complete local residual for the current element. Automatically chooses the the appropriate + * element volume variables. + */ + ElementResidualVector evalLocalResidual() const + { + if (!isImplicit()) + if (this->assembler().isStationaryProblem()) + DUNE_THROW(Dune::InvalidStateException, "Using explicit jacobian assembler with stationary local residual"); + + if (elementIsGhost()) + return ElementResidualVector(0.0); + + return isImplicit() ? evalLocalResidual(curElemVolVars()) + : evalLocalResidual(prevElemVolVars()); + } + + /*! + * \brief Evaluates the complete local residual for the current element. + * \param elemVolVars The element volume variables + */ + ElementResidualVector evalLocalResidual(const ElementVolumeVariables& elemVolVars) const + { + if (!assembler().isStationaryProblem()) + { + ElementResidualVector residual = evalLocalFluxAndSourceResidual(elemVolVars); + residual += evalLocalStorageResidual(); + return residual; + } + else + return evalLocalFluxAndSourceResidual(elemVolVars); + } + + /*! + * \brief Convenience function to evaluate the flux and source terms (i.e, the terms without a time derivative) + * of the local residual for the current element. Automatically chooses the the appropriate + * element volume variables. + */ + ElementResidualVector evalLocalFluxAndSourceResidual() const + { + return isImplicit() ? evalLocalFluxAndSourceResidual(curElemVolVars()) + : evalLocalFluxAndSourceResidual(prevElemVolVars()); + } + + /*! + * \brief Evaluates the flux and source terms (i.e, the terms without a time derivative) + * of the local residual for the current element. + * + * \param elemVolVars The element volume variables + */ + ElementResidualVector evalLocalFluxAndSourceResidual(const ElementVolumeVariables& elemVolVars) const + { + return localResidual_.evalFluxAndSource(element_, fvGeometry_, elemVolVars, elemFluxVarsCache_, elemBcTypes_); + } + + /*! + * \brief Convenience function to evaluate storage term (i.e, the term with a time derivative) + * of the local residual for the current element. Automatically chooses the the appropriate + * element volume variables. + */ + ElementResidualVector evalLocalStorageResidual() const + { + return localResidual_.evalStorage(element_, fvGeometry_, prevElemVolVars_, curElemVolVars_); + } + + /*! + * \brief Convenience function bind and prepare all relevant variables required for the + * evaluation of the local residual. + */ + void bindLocalViews() + { + // get some references for convenience + const auto& element = this->element(); + const auto& curSol = this->curSol(); + const auto& prevSol = this->assembler().prevSol(); + auto&& fvGeometry = this->fvGeometry(); + auto&& curElemVolVars = this->curElemVolVars(); + auto&& prevElemVolVars = this->prevElemVolVars(); + auto&& elemFluxVarsCache = this->elemFluxVarsCache(); + + // bind the caches + fvGeometry.bind(element); + + if (isImplicit()) + { + curElemVolVars.bind(element, fvGeometry, curSol); + elemFluxVarsCache.bind(element, fvGeometry, curElemVolVars); + if (!this->assembler().isStationaryProblem()) + prevElemVolVars.bindElement(element, fvGeometry, this->assembler().prevSol()); + } + else + { + curElemVolVars.bindElement(element, fvGeometry, curSol); + prevElemVolVars.bind(element, fvGeometry, prevSol); + elemFluxVarsCache.bind(element, fvGeometry, prevElemVolVars); + } + } + + /*! + * \brief Enforces Dirichlet constraints if enabled in the problem + */ + template = 0> + void enforceInternalDirichletConstraints(const ApplyFunction& applyDirichlet) + { + // enforce Dirichlet constraints strongly by overwriting partial derivatives with 1 or 0 + // and set the residual to (privar - dirichletvalue) + for (const auto& scvI : scvs(this->fvGeometry())) + { + const auto internalDirichletConstraints = asImp_().problem().hasInternalDirichletConstraint(this->element(), scvI); + if (internalDirichletConstraints.any()) + { + const auto dirichletValues = asImp_().problem().internalDirichlet(this->element(), scvI); + // set the Dirichlet conditions in residual and jacobian + for (int eqIdx = 0; eqIdx < internalDirichletConstraints.size(); ++eqIdx) + if (internalDirichletConstraints[eqIdx]) + applyDirichlet(scvI, dirichletValues, eqIdx, eqIdx); + } + } + } + + template = 0> + void enforceInternalDirichletConstraints(const ApplyFunction& applyDirichlet) + {} + + //! The problem + const Problem& problem() const + { return assembler_.problem(); } + + //! The assembler + const Assembler& assembler() const + { return assembler_; } + + //! The current element + const Element& element() const + { return element_; } + + //! Returns if element is a ghost entity + bool elementIsGhost() const + { return elementIsGhost_; } + + //! The current solution + const SolutionVector& curSol() const + { return curSol_; } + + //! The global finite volume geometry + FVElementGeometry& fvGeometry() + { return fvGeometry_; } + + //! The current element volume variables + ElementVolumeVariables& curElemVolVars() + { return curElemVolVars_; } + + //! The element volume variables of the provious time step + ElementVolumeVariables& prevElemVolVars() + { return prevElemVolVars_; } + + //! The element flux variables cache + ElementFluxVariablesCache& elemFluxVarsCache() + { return elemFluxVarsCache_; } + + //! The local residual for the current element + LocalResidual& localResidual() + { return localResidual_; } + + //! The element's boundary types + ElementBoundaryTypes& elemBcTypes() + { return elemBcTypes_; } + + //! The finite volume geometry + const FVElementGeometry& fvGeometry() const + { return fvGeometry_; } + + //! The current element volume variables + const ElementVolumeVariables& curElemVolVars() const + { return curElemVolVars_; } + + //! The element volume variables of the provious time step + const ElementVolumeVariables& prevElemVolVars() const + { return prevElemVolVars_; } + + //! The element flux variables cache + const ElementFluxVariablesCache& elemFluxVarsCache() const + { return elemFluxVarsCache_; } + + //! The element's boundary types + const ElementBoundaryTypes& elemBcTypes() const + { return elemBcTypes_; } + + //! The local residual for the current element + const LocalResidual& localResidual() const + { return localResidual_; } + +protected: + Implementation &asImp_() + { return *static_cast(this); } + + const Implementation &asImp_() const + { return *static_cast(this); } + + template::GridVolumeVariables::cachingEnabled, int> = 0> + VolumeVariables& getVolVarAccess(GridVolumeVariables& gridVolVars, ElementVolumeVariables& elemVolVars, const SubControlVolume& scv) + { return elemVolVars[scv]; } + + template::GridVolumeVariables::cachingEnabled, int> = 0> + VolumeVariables& getVolVarAccess(GridVolumeVariables& gridVolVars, ElementVolumeVariables& elemVolVars, const SubControlVolume& scv) + { return gridVolVars.volVars(scv); } + +private: + + const Assembler& assembler_; //!< access pointer to assembler instance + const Element& element_; //!< the element whose residual is assembled + const SolutionVector& curSol_; //!< the current solution + + FVElementGeometry fvGeometry_; + ElementVolumeVariables curElemVolVars_; + ElementVolumeVariables prevElemVolVars_; + ElementFluxVariablesCache elemFluxVarsCache_; + ElementBoundaryTypes elemBcTypes_; + + LocalResidual localResidual_; //!< the local residual evaluating the equations per element + bool elementIsGhost_; //!< whether the element's partitionType is ghost +}; + + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/fvlocalresidual.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/fvlocalresidual.hh new file mode 100644 index 0000000..f69a1d0 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/fvlocalresidual.hh @@ -0,0 +1,501 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Assembly + * \brief The element-wise residual for finite volume schemes + */ +#ifndef DUMUX_FV_LOCAL_RESIDUAL_HH +#define DUMUX_FV_LOCAL_RESIDUAL_HH + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace Dumux { + +/*! + * \ingroup Assembly + * \brief The element-wise residual for finite volume schemes + * \note This class defines the interface used by the assembler using + * static polymorphism. Implementations are specialized for a certain discretization scheme + */ +template +class FVLocalResidual +{ + using Implementation = GetPropType; + using Problem = GetPropType; + using Scalar = GetPropType; + using GridView = typename GetPropType::GridView; + using Element = typename GridView::template Codim<0>::Entity; + using FVElementGeometry = typename GetPropType::LocalView; + using GridVariables = GetPropType; + using GridGeometry = GetPropType; + using SubControlVolume = typename GridGeometry::SubControlVolume; + using SubControlVolumeFace = typename GridGeometry::SubControlVolumeFace; + using Extrusion = Extrusion_t; + using NumEqVector = Dumux::NumEqVector>; + using ElementBoundaryTypes = GetPropType; + using ElementFluxVariablesCache = typename GetPropType::LocalView; + using VolumeVariables = GetPropType; + using ElementVolumeVariables = typename GetPropType::LocalView; + using SolutionVector = GetPropType; + using TimeLoop = TimeLoopBase; + +public: + //! the container storing all element residuals + using ElementResidualVector = ReservedBlockVector; + + //! the constructor + FVLocalResidual(const Problem* problem, + const TimeLoop* timeLoop = nullptr) + : problem_(problem) + , timeLoop_(timeLoop) + {} + + /*! + * \name User interface + * \note The following methods are usually expensive to evaluate + * They are useful for outputting / postprocessing residual information. + */ + // \{ + + /*! + * \brief Compute the storage term for the current solution. + * + * This can be used to figure out how much of each conservation + * quantity is inside the element. + * + * \param problem The problem to solve + * \param element The DUNE Codim<0> entity for which the storage + * term ought to be calculated + * \param gridGeometry The finite-volume grid geometry + * \param gridVariables The grid variables (volume and flux variables) + * \param sol The solution vector + */ + ElementResidualVector evalStorage(const Problem& problem, + const Element &element, + const GridGeometry& gridGeometry, + const GridVariables& gridVariables, + const SolutionVector& sol) const + { + // make sure FVElementGeometry and volume variables are bound to the element + const auto fvGeometry = localView(gridGeometry).bind(element); + const auto elemVolVars = localView(gridVariables.curGridVolVars()).bind(element, fvGeometry, sol); + + ElementResidualVector storage(fvGeometry.numScv()); + + // calculate the amount of conservation each quantity inside + // all sub control volumes + for (auto&& scv : scvs(fvGeometry)) + { + auto localScvIdx = scv.localDofIndex(); + const auto& volVars = elemVolVars[scv]; + storage[localScvIdx] = asImp().computeStorage(problem, scv, volVars); + storage[localScvIdx] *= Extrusion::volume(fvGeometry, scv) * volVars.extrusionFactor(); + } + + return storage; + } + + // \} + + /*! + * \name Main interface + * \note Methods used by the assembler to compute derivatives and residual + */ + // \{ + + /*! + * \brief Compute the storage local residual, i.e. the deviation of the + * storage term from zero for instationary problems. + * + * \param element The DUNE Codim<0> entity for which the residual + * ought to be calculated + * \param fvGeometry The finite-volume geometry of the element + * \param prevElemVolVars The volume averaged variables for all + * sub-control volumes of the element at the previous time level + * \param curElemVolVars The volume averaged variables for all + * sub-control volumes of the element at the current time level + */ + ElementResidualVector evalStorage(const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& prevElemVolVars, + const ElementVolumeVariables& curElemVolVars) const + { + assert(timeLoop_ && "no time loop set for storage term evaluation"); + + // initialize the residual vector for all scvs in this element + ElementResidualVector residual(fvGeometry.numScv()); + + // evaluate the volume terms (storage + source terms) + // forward to the local residual specialized for the discretization methods + for (auto&& scv : scvs(fvGeometry)) + asImp().evalStorage(residual, this->problem(), element, fvGeometry, prevElemVolVars, curElemVolVars, scv); + + return residual; + } + + ElementResidualVector evalFluxAndSource(const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const ElementFluxVariablesCache& elemFluxVarsCache, + const ElementBoundaryTypes &bcTypes) const + { + // initialize the residual vector for all scvs in this element + ElementResidualVector residual(fvGeometry.numScv()); + + // evaluate the volume terms (storage + source terms) + // forward to the local residual specialized for the discretization methods + for (auto&& scv : scvs(fvGeometry)) + asImp().evalSource(residual, this->problem(), element, fvGeometry, elemVolVars, scv); + + // forward to the local residual specialized for the discretization methods + for (auto&& scvf : scvfs(fvGeometry)) + asImp().evalFlux(residual, this->problem(), element, fvGeometry, elemVolVars, bcTypes, elemFluxVarsCache, scvf); + + return residual; + } + + // \} + + + /*! + * \name Model specific interface + * \note The following method are the model specific implementations of the local residual + */ + // \{ + + /*! + * \brief Calculate the source term of the equation + * + * \param problem The problem to solve + * \param scv The sub-control volume over which we integrate the storage term + * \param volVars The volume variables associated with the scv + * \note has to be implemented by the model specific residual class + * + */ + NumEqVector computeStorage(const Problem& problem, + const SubControlVolume& scv, + const VolumeVariables& volVars) const + { + DUNE_THROW(Dune::NotImplemented, "This model does not implement a storage method!"); + } + + /*! + * \brief Calculate the source term of the equation + * + * \param problem The problem to solve + * \param element The DUNE Codim<0> entity for which the residual + * ought to be calculated + * \param fvGeometry The finite-volume geometry of the element + * \param elemVolVars The volume variables associated with the element stencil + * \param scv The sub-control volume over which we integrate the source term + * \note This is the default implementation for all models as sources are computed + * in the user interface of the problem + * + */ + NumEqVector computeSource(const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const SubControlVolume &scv) const + { + NumEqVector source(0.0); + + // add contributions from volume flux sources + source += problem.source(element, fvGeometry, elemVolVars, scv); + + // add contribution from possible point sources + if (!problem.pointSourceMap().empty()) + source += problem.scvPointSources(element, fvGeometry, elemVolVars, scv); + + return source; + } + + /*! + * \brief Calculate the flux term of the equation + * + * \param problem The problem to solve + * \param element The DUNE Codim<0> entity for which the residual + * ought to be calculated + * \param fvGeometry The finite-volume geometry of the element + * \param elemVolVars The volume variables associated with the element stencil + * \param scvf The sub-control volume over which we integrate the flux + * \param elemFluxVarsCache the flux variable caches for the element's flux stencils + * + * \note has to be implemented by the model specific residual class + * + */ + NumEqVector computeFlux(const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const SubControlVolumeFace& scvf, + const ElementFluxVariablesCache& elemFluxVarsCache) const + { + DUNE_THROW(Dune::NotImplemented, "This model does not implement a flux method!"); + } + + // \} + + /*! + * \name Discretization specific interface + * \note The following method are the discretization specific wrapper methods + */ + // \{ + + /*! + * \brief Compute the storage local residual, i.e. the deviation of the + * storage term from zero for instationary problems. + * + * \param residual The residual vector to fill + * \param problem The problem to solve + * \param element The DUNE Codim<0> entity for which the residual + * ought to be calculated + * \param fvGeometry The finite-volume geometry of the element + * \param prevElemVolVars The volume averaged variables for all + * sub-control volumes of the element at the previous time level + * \param curElemVolVars The volume averaged variables for all + * sub-control volumes of the element at the current time level + * \param scv The sub control volume the storage term is integrated over + */ + void evalStorage(ElementResidualVector& residual, + const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& prevElemVolVars, + const ElementVolumeVariables& curElemVolVars, + const SubControlVolume& scv) const + { + const auto& curVolVars = curElemVolVars[scv]; + const auto& prevVolVars = prevElemVolVars[scv]; + + // mass balance within the element. this is the + // \f$\frac{m}{\partial t}\f$ term if using implicit or explicit + // euler as time discretization. + // + // TODO: We might need a more explicit way for + // doing the time discretization... + + //! Compute storage with the model specific storage residual + NumEqVector prevStorage = asImp().computeStorage(problem, scv, prevVolVars); + NumEqVector storage = asImp().computeStorage(problem, scv, curVolVars); + + prevStorage *= prevVolVars.extrusionFactor(); + storage *= curVolVars.extrusionFactor(); + + storage -= prevStorage; + storage *= Extrusion::volume(fvGeometry, scv); + storage /= timeLoop_->timeStepSize(); + + residual[scv.localDofIndex()] += storage; + } + + /*! + * \brief Compute the source local residual, i.e. the deviation of the + * source term from zero. + * + * \param residual The residual vector to fill + * \param problem The problem to solve + * \param element The DUNE Codim<0> entity for which the residual + * ought to be calculated + * \param fvGeometry The finite-volume geometry of the element + * \param curElemVolVars The volume averaged variables for all + * sub-control volumes of the element at the current time level + * \param scv The sub control volume the source term is integrated over + */ + void evalSource(ElementResidualVector& residual, + const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& curElemVolVars, + const SubControlVolume& scv) const + { + //! Compute source with the model specific storage residual + const auto& curVolVars = curElemVolVars[scv]; + NumEqVector source = asImp().computeSource(problem, element, fvGeometry, curElemVolVars, scv); + source *= Extrusion::volume(fvGeometry, scv)*curVolVars.extrusionFactor(); + + //! subtract source from local rate (sign convention in user interface) + residual[scv.localDofIndex()] -= source; + } + + /*! + * \brief Compute the flux local residual, i.e. the deviation of the + * flux term from zero. + * \param residual The residual vector to fill + * \param problem The problem to solve + * \param element The DUNE Codim<0> entity for which the residual + * ought to be calculated + * \param fvGeometry The finite-volume geometry of the element + * \param elemVolVars The volume averaged variables for all + * sub-control volumes of the element at the current time level + * \param elemBcTypes the boundary types for the boundary entities of an elements + * \param elemFluxVarsCache The flux variable caches for the element stencil + * \param scvf The sub control volume face the flux term is integrated over + */ + void evalFlux(ElementResidualVector& residual, + const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const ElementBoundaryTypes& elemBcTypes, + const ElementFluxVariablesCache& elemFluxVarsCache, + const SubControlVolumeFace& scvf) const {} + + /*! + * \brief Compute the flux local residual, i.e. the deviation of the + * flux term from zero. + * + * \param problem The problem to solve + * \param element The DUNE Codim<0> entity for which the residual + * ought to be calculated + * \param fvGeometry The finite-volume geometry of the element + * \param elemVolVars The volume averaged variables for all + * sub-control volumes of the element at the current time level + * \param elemFluxVarsCache The flux variable caches for the element stencil + * \param scvf The sub control volume face the flux term is integrated over + */ + NumEqVector evalFlux(const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const ElementFluxVariablesCache& elemFluxVarsCache, + const SubControlVolumeFace& scvf) const + { + return asImp().evalFlux(problem, element, fvGeometry, elemVolVars, elemFluxVarsCache, scvf); + } + + //\} + + /*! + * \name Interfaces for analytic Jacobian computation + */ + // \{ + + //! Compute the derivative of the storage residual + template + void addStorageDerivatives(PartialDerivativeMatrix& partialDerivatives, + const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const VolumeVariables& curVolVars, + const SubControlVolume& scv) const + { + DUNE_THROW(Dune::NotImplemented, "analytic storage derivative"); + } + + //! Compute the derivative of the source residual + template + void addSourceDerivatives(PartialDerivativeMatrix& partialDerivatives, + const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const VolumeVariables& curVolVars, + const SubControlVolume& scv) const + { + DUNE_THROW(Dune::NotImplemented, "analytic source derivative"); + } + + //! Compute the derivative of the flux residual + template + std::enable_if_t::discMethod != DiscretizationMethods::box, void> + addFluxDerivatives(PartialDerivativeMatrices& derivativeMatrices, + const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& curElemVolVars, + const ElementFluxVariablesCache& elemFluxVarsCache, + const SubControlVolumeFace& scvf) const + { + DUNE_THROW(Dune::NotImplemented, "analytic flux derivative for cell-centered models"); + } + + //! Compute the derivative of the flux residual for the box method + template + std::enable_if_t::discMethod == DiscretizationMethods::box, void> + addFluxDerivatives(JacobianMatrix& A, + const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& curElemVolVars, + const ElementFluxVariablesCache& elemFluxVarsCache, + const SubControlVolumeFace& scvf) const + { + DUNE_THROW(Dune::NotImplemented, "analytic flux derivative for box models"); + } + + //! Compute the derivative of the Dirichlet flux residual for cell-centered schemes + template + void addCCDirichletFluxDerivatives(PartialDerivativeMatrices& derivativeMatrices, + const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& curElemVolVars, + const ElementFluxVariablesCache& elemFluxVarsCache, + const SubControlVolumeFace& scvf) const + { + DUNE_THROW(Dune::NotImplemented, "analytic Dirichlet flux derivative"); + } + + //! Compute the derivative of Robin type boundary conditions ("solution dependent Neumann") + template + void addRobinFluxDerivatives(PartialDerivativeMatrices& derivativeMatrices, + const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& curElemVolVars, + const ElementFluxVariablesCache& elemFluxVarsCache, + const SubControlVolumeFace& scvf) const + { + DUNE_THROW(Dune::NotImplemented, "analytic Robin flux derivative"); + } + + //\} + + /*! + * \name Interfaces accessed by local residual implementations + */ + // \{ + + //! the problem + const Problem& problem() const + { return *problem_; } + + //! the timeloop for instationary problems + //! calling this for stationary leads to undefined behaviour + const TimeLoop& timeLoop() const + { return *timeLoop_; } + + //! returns true if the residual is stationary + bool isStationary() const + { return !timeLoop_; } + + // \} +protected: + + Implementation &asImp() + { return *static_cast(this); } + + const Implementation &asImp() const + { return *static_cast(this); } + +private: + const Problem* problem_; //!< the problem we are assembling this residual for + const TimeLoop* timeLoop_; //!< the timeloop for instationary problems +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/initialsolution.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/initialsolution.hh new file mode 100644 index 0000000..a84b021 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/initialsolution.hh @@ -0,0 +1,97 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Assembly + * \brief Function to create initial solution vectors + */ +#ifndef DUMUX_ASSEMBLY_INITIAL_SOLUTION_HH +#define DUMUX_ASSEMBLY_INITIAL_SOLUTION_HH + +#include +#include + +#include + +namespace Dumux { + +/*! + * \ingroup Assembly + * \brief Set a solution vector to the initial solution provided by the problem + */ +template +void assembleInitialSolution(SolutionVector& sol, const Problem& problem) +{ + const auto& gg = problem.gridGeometry(); + using GridGeometry = std::decay_t; + + // box method + if constexpr (GridGeometry::discMethod == DiscretizationMethods::box) + { + constexpr int dim = GridGeometry::GridView::dimension; + const auto numDofs = gg.vertexMapper().size(); + const auto numVert = gg.gridView().size(dim); + sol.resize(numDofs); + + // if there are more dofs than vertices (enriched nodal dofs), we have to + // call initial for all dofs at the nodes, coming from all neighboring elements. + if (numDofs != numVert) + { + std::vector dofVisited(numDofs, false); + for (const auto& element : elements(gg.gridView())) + { + for (int i = 0; i < element.subEntities(dim); ++i) + { + const auto dofIdxGlobal = gg.vertexMapper().subIndex(element, i, dim); + // forward to implementation if value at dof is not set yet + if (!dofVisited[dofIdxGlobal]) + { + sol[dofIdxGlobal] = problem.initial(element.template subEntity(i)); + dofVisited[dofIdxGlobal] = true; + } + } + } + } + + // otherwise we directly loop over the vertices + else + { + for (const auto& vertex : vertices(gg.gridView())) + sol[gg.vertexMapper().index(vertex)] = problem.initial(vertex); + } + } + + // staggered methods + else if constexpr (GridGeometry::discMethod == DiscretizationMethods::staggered) + { + problem.applyInitialSolution(sol); + } + + // default: cell-centered methods + else + { + sol.resize(gg.numDofs()); + for (const auto& element : elements(gg.gridView())) + sol[gg.elementMapper().index(element)] = problem.initial(element); + } +} + +/*! + * \ingroup Assembly + * \brief Create a solution vector filled with the initial solution provided by the problem + */ +template +SolutionVector makeInitialSolution(const Problem& problem) +{ + SolutionVector sol; + assembleInitialSolution(sol, problem); + return sol; +} + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/jacobianpattern.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/jacobianpattern.hh new file mode 100644 index 0000000..1dbebbf --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/jacobianpattern.hh @@ -0,0 +1,251 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Assembly + * \brief Helper function to generate Jacobian pattern for different discretization methods + */ +#ifndef DUMUX_JACOBIAN_PATTERN_HH +#define DUMUX_JACOBIAN_PATTERN_HH + +#include +#include +#include + +namespace Dumux { + +/*! + * \ingroup Assembly + * \brief Helper function to generate Jacobian pattern for cell-centered methods + */ +template = 0> +Dune::MatrixIndexSet getJacobianPattern(const GridGeometry& gridGeometry) +{ + const auto numDofs = gridGeometry.numDofs(); + Dune::MatrixIndexSet pattern; + pattern.resize(numDofs, numDofs); + + // matrix pattern for implicit Jacobians + if (isImplicit) + { + for (unsigned int globalI = 0; globalI < numDofs; ++globalI) + { + pattern.add(globalI, globalI); + for (const auto& dataJ : gridGeometry.connectivityMap()[globalI]) + pattern.add(dataJ.globalJ, globalI); + } + } + + // matrix pattern for explicit Jacobians -> diagonal matrix + else + { + for (unsigned int globalI = 0; globalI < numDofs; ++globalI) + pattern.add(globalI, globalI); + } + + return pattern; +} + +/*! + * \ingroup Assembly + * \brief Helper function to generate Jacobian pattern for the staggered method + */ +template = 0> +auto getJacobianPattern(const GridGeometry& gridGeometry) +{ + // resize the jacobian and the residual + const auto numDofs = gridGeometry.numDofs(); + Dune::MatrixIndexSet pattern(numDofs, numDofs); + + const auto& connectivityMap = gridGeometry.connectivityMap(); + + auto fvGeometry = localView(gridGeometry); + // evaluate the actual pattern + for (const auto& element : elements(gridGeometry.gridView())) + { + if(gridGeometry.isCellCenter()) + { + // the global index of the element at hand + static constexpr auto cellCenterIdx = GridGeometry::cellCenterIdx(); + const auto ccGlobalI = gridGeometry.elementMapper().index(element); + pattern.add(ccGlobalI, ccGlobalI); + for (auto&& ccGlobalJ : connectivityMap(cellCenterIdx, cellCenterIdx, ccGlobalI)) + pattern.add(ccGlobalI, ccGlobalJ); + } + else + { + static constexpr auto faceIdx = GridGeometry::faceIdx(); + fvGeometry.bindElement(element); + + // loop over sub control faces + for (auto&& scvf : scvfs(fvGeometry)) + { + const auto faceGlobalI = scvf.dofIndex(); + pattern.add(faceGlobalI, faceGlobalI); + for (auto&& faceGlobalJ : connectivityMap(faceIdx, faceIdx, scvf.index())) + pattern.add(faceGlobalI, faceGlobalJ); + } + } + } + + return pattern; +} + +/*! + * \ingroup Assembly + * \brief Helper function to generate Jacobian pattern for finite element scheme + */ +template +Dune::MatrixIndexSet getFEJacobianPattern(const FEBasis& feBasis) +{ + const auto numDofs = feBasis.size(); + + Dune::MatrixIndexSet pattern; + pattern.resize(numDofs, numDofs); + + auto localView = feBasis.localView(); + // matrix pattern for implicit Jacobians + for (const auto& element : elements(feBasis.gridView())) + { + localView.bind(element); + + const auto& finiteElement = localView.tree().finiteElement(); + const auto numLocalDofs = finiteElement.localBasis().size(); + for (std::size_t i = 0; i < numLocalDofs; i++) + { + const auto dofIdxI = localView.index(i); + for (std::size_t j = 0; j < numLocalDofs; j++) + { + const auto dofIdxJ = localView.index(j); + pattern.add(dofIdxI, dofIdxJ); + } + } + } + + return pattern; +} + +/*! + * \ingroup Assembly + * \brief Helper function to generate Jacobian pattern for finite element scheme + * \note This interface is for compatibility with the other schemes. The pattern + * in fem is the same independent of the time discretization scheme. + */ +template = 0> +Dune::MatrixIndexSet getJacobianPattern(const GridGeometry& gridGeometry) +{ return getFEJacobianPattern(gridGeometry.feBasis()); } + +/*! + * \ingroup Assembly + * \brief Helper function to generate Jacobian pattern for the face-centered methods + */ +template = 0> +Dune::MatrixIndexSet getJacobianPattern(const GridGeometry& gridGeometry) +{ + // resize the jacobian and the residual + const auto numDofs = gridGeometry.numDofs(); + Dune::MatrixIndexSet pattern(numDofs, numDofs); + + const auto& connectivityMap = gridGeometry.connectivityMap(); + auto fvGeometry = localView(gridGeometry); + + // set the pattern + for (const auto& element : elements(gridGeometry.gridView())) + { + fvGeometry.bind(element); + for (const auto& scv : scvs(fvGeometry)) + { + const auto globalI = scv.dofIndex(); + pattern.add(globalI, globalI); + + for (const auto& scvIdxJ : connectivityMap[scv.index()]) + { + const auto globalJ = fvGeometry.scv(scvIdxJ).dofIndex(); + pattern.add(globalI, globalJ); + + if (gridGeometry.isPeriodic()) + { + if (gridGeometry.dofOnPeriodicBoundary(globalI) && globalI != globalJ) + { + const auto globalIP = gridGeometry.periodicallyMappedDof(globalI); + pattern.add(globalIP, globalI); + pattern.add(globalI, globalIP); + + if (globalI > globalIP) + pattern.add(globalIP, globalJ); + } + } + } + } + } + + return pattern; +} + +/*! + * \ingroup Assembly + * \brief Compute the Jacobian matrix sparsity pattern for control volume finite element schemes + */ +template, int> = 0> +Dune::MatrixIndexSet getJacobianPattern(const GridGeometry& gridGeometry) +{ + // resize the jacobian and the residual + const auto numDofs = gridGeometry.numDofs(); + Dune::MatrixIndexSet pattern(numDofs, numDofs); + + // matrix pattern for implicit Jacobians + if constexpr (isImplicit) + { + auto fvGeometry = localView(gridGeometry); + for (const auto& element : elements(gridGeometry.gridView())) + { + fvGeometry.bindElement(element); + for (const auto& scv : scvs(fvGeometry)) + { + const auto globalI = scv.dofIndex(); + for (const auto& scvJ : scvs(fvGeometry)) + { + const auto globalJ = scvJ.dofIndex(); + pattern.add(globalI, globalJ); + + if (gridGeometry.isPeriodic()) + { + if (gridGeometry.dofOnPeriodicBoundary(globalI) && globalI != globalJ) + { + const auto globalIP = gridGeometry.periodicallyMappedDof(globalI); + pattern.add(globalIP, globalI); + pattern.add(globalI, globalIP); + + if (globalI > globalIP) + pattern.add(globalIP, globalJ); + } + } + } + } + } + } + + // matrix pattern for explicit Jacobians -> diagonal matrix + else + { + for (unsigned int globalI = 0; globalI < numDofs; ++globalI) + pattern.add(globalI, globalI); + } + + return pattern; +} + + +} // namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/numericepsilon.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/numericepsilon.hh new file mode 100644 index 0000000..4b76237 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/numericepsilon.hh @@ -0,0 +1,58 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Assembly + * \brief An adapter class for local assemblers using numeric differentiation + */ +#ifndef DUMUX_ASSEMBLY_NUMERIC_EPSILON_HH +#define DUMUX_ASSEMBLY_NUMERIC_EPSILON_HH + +#include +#include +#include + +namespace Dumux { + +/*! + * \ingroup Assembly + * \brief A helper class for local assemblers using numeric differentiation to determine the epsilon + * \tparam Scalar the scalar type + * \tparam numEq the number of primary variables / equations + */ +template +class NumericEpsilon +{ + using NumEqVector = Dune::FieldVector; + +public: + explicit NumericEpsilon(const std::string& paramGroup = "") + { + // get epsilons from input file with invalid default + baseEps_ = getParamFromGroup(paramGroup, "Assembly.NumericDifference.BaseEpsilon", 1e-10); + magnitude_ = getParamFromGroup(paramGroup, "Assembly.NumericDifference.PriVarMagnitude", NumEqVector(-1)); + } + + /*! + * \brief get the epsilon + * \note If no user input was specified -> try to estimate magnitude from primary variable value + * else -> use given magnitude for the primary variable times the base epsilon (default 1e-10) + */ + Scalar operator() (Scalar priVar, int priVarIdx) const noexcept + { + return magnitude_[priVarIdx] > 0.0 ? baseEps_*magnitude_[priVarIdx] + : NumericDifferentiation::epsilon(priVar, baseEps_); + } + +private: + Scalar baseEps_; + NumEqVector magnitude_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/partialreassembler.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/partialreassembler.hh new file mode 100644 index 0000000..f2c1426 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/partialreassembler.hh @@ -0,0 +1,510 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Assembly + * \brief Detects which entries in the Jacobian have to be recomputed + */ +#ifndef DUMUX_PARTIAL_REASSEMBLER_HH +#define DUMUX_PARTIAL_REASSEMBLER_HH + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "entitycolor.hh" + +namespace Dumux { + +class DefaultPartialReassembler +{ +public: + template + DefaultPartialReassembler(Args&&... args) + { DUNE_THROW(Dune::InvalidStateException, "DefaultPartialReassembler should be never constructed!"); } + + template + void report(Args&&... args) {} + + template + void resetJacobian(Args&&... args) const {} + + template + void computeColors(Args&&... args) {} + + template + void resetColors(Args&&... args) {} + + EntityColor dofColor(size_t idx) const + { return EntityColor::red; } + + EntityColor elementColor(size_t idx) const + { return EntityColor::red; } + + EntityColor vertexColor(size_t idx) const + { return EntityColor::red; } +}; + +//! the partial reassembler engine specialized for discretization methods +template +class PartialReassemblerEngine +{ +public: + PartialReassemblerEngine(const Assembler&) + { DUNE_THROW(Dune::NotImplemented, "PartialReassembler for this discretization method!"); } + + EntityColor elementColor(size_t idx) const + { return EntityColor::red; } + + EntityColor dofColor(size_t idx) const + { return EntityColor::red; } + + template + std::size_t computeColors(Args&&... args) { return 0; } + + template + void resetJacobian(Args&&... args) const {} + + template + void resetColors(Args&&... args) {} +}; + +/*! + * \ingroup Assembly + * \brief The partial reassembler engine specialized for the box method + */ +template +class PartialReassemblerEngine +{ + using Scalar = typename Assembler::Scalar; + using GridGeometry = typename Assembler::GridGeometry; + using JacobianMatrix = typename Assembler::JacobianMatrix; + using VertexMapper = typename GridGeometry::VertexMapper; + static constexpr int dim = GridGeometry::GridView::dimension; + +public: + PartialReassemblerEngine(const Assembler& assembler) + : elementColor_(assembler.gridGeometry().elementMapper().size(), EntityColor::red) + , vertexColor_(assembler.gridGeometry().vertexMapper().size(), EntityColor::red) + {} + + // returns number of green elements + std::size_t computeColors(const Assembler& assembler, + const std::vector& distanceFromLastLinearization, + Scalar threshold) + { + const auto& gridGeometry = assembler.gridGeometry(); + const auto& gridView = gridGeometry.gridView(); + const auto& elementMapper = gridGeometry.elementMapper(); + const auto& vertexMapper = gridGeometry.vertexMapper(); + + // set all vertices to green + vertexColor_.assign(vertexColor_.size(), EntityColor::green); + + // mark the red vertices + for (unsigned int i = 0; i < vertexColor_.size(); ++i) + { + using std::max; + if (distanceFromLastLinearization[i] > threshold) + // mark vertex as red if discrepancy is larger than + // the relative tolerance + vertexColor_[i] = EntityColor::red; + } + + // Mark all red elements + for (const auto& element : elements(gridView)) + { + // find out whether the current element features a red vertex + bool isRed = false; + + int numVertices = element.subEntities(dim); + + for (int i = 0; i < numVertices; ++i) { + int globalI = vertexMapper.subIndex(element, i, dim); + + if (vertexColor_[globalI] == EntityColor::red) { + isRed = true; + break; + } + } + + int eIdx = elementMapper.index(element); + // if a vertex is red, the element color is also red, otherwise green + if (isRed) + elementColor_[eIdx] = EntityColor::red; + else + elementColor_[eIdx] = EntityColor::green; + } + + // mark orange vertices + for (const auto& element : elements(gridView)) + { + int eIdx = elementMapper.index(element); + + // only red elements tint vertices yellow + if (elementColor_[eIdx] == EntityColor::red) + { + int numVertices = element.subEntities(dim); + + for (int i = 0; i < numVertices; ++i) { + int globalI = vertexMapper.subIndex(element, i, dim); + + // red vertices don't become orange + if (vertexColor_[globalI] != EntityColor::red) + vertexColor_[globalI] = EntityColor::orange; + } + } + } + + // at this point we communicate the yellow vertices to the + // neighboring processes because a neighbor process may not see + // the red vertex for yellow border vertices + VectorCommDataHandleMin, dim> + minHandle(vertexMapper, vertexColor_); + if constexpr (Detail::canCommunicate) + gridView.communicate(minHandle, + Dune::InteriorBorder_InteriorBorder_Interface, + Dune::ForwardCommunication); + else + DUNE_THROW(Dune::InvalidStateException, "Cannot call computeColors on multiple processes for a grid that cannot communicate codim-" << dim << "-entities."); + + // mark yellow elements + for (const auto& element : elements(gridView)) + { + int eIdx = elementMapper.index(element); + + // only treat non-red elements + if (elementColor_[eIdx] != EntityColor::red) + { + // check whether the element features a orange vertex + bool isOrange = false; + int numVertices = element.subEntities(dim); + + for (int i = 0; i < numVertices; ++i) { + int globalI = vertexMapper.subIndex(element, i, dim); + + if (vertexColor_[globalI] == EntityColor::orange) { + isOrange = true; + break; + } + } + + if (isOrange) + elementColor_[eIdx] = EntityColor::yellow; + } + } + + // change orange vertices to yellow ones if it has at least + // one green element as a neighbor + for (const auto& element : elements(gridView)) + { + int eIdx = elementMapper.index(element); + + // only green elements are considered + if (elementColor_[eIdx] == EntityColor::green) + { + int numVertices = element.subEntities(dim); + + for (int i = 0; i < numVertices; ++i) { + int globalI = vertexMapper.subIndex(element, i, dim); + + // if a vertex is orange, recolor it to yellow + if (vertexColor_[globalI] == EntityColor::orange) + vertexColor_[globalI] = EntityColor::yellow; + } + } + } + + // demote the border orange vertices + VectorCommDataHandleMax, dim> + maxHandle(vertexMapper, vertexColor_); + if constexpr (Detail::canCommunicate) + gridView.communicate(maxHandle, + Dune::InteriorBorder_InteriorBorder_Interface, + Dune::ForwardCommunication); + else + DUNE_THROW(Dune::InvalidStateException, "Cannot call computeColors on multiple processes for a grid that cannot communicate codim-" << dim << "-entities."); + + // promote the remaining orange vertices to red + for (unsigned int i=0; i < vertexColor_.size(); ++i) { + // if a vertex is green or yellow don't do anything + if (vertexColor_[i] == EntityColor::green || vertexColor_[i] == EntityColor::yellow) + continue; + + // set the vertex to red + vertexColor_[i] = EntityColor::red; + } + + // count green elements + return std::count_if(elementColor_.begin(), elementColor_.end(), + [](EntityColor c){ return c == EntityColor::green; }); + } + + void resetJacobian(Assembler& assembler) const + { + auto& jacobian = assembler.jacobian(); + + // loop over all dofs + for (unsigned int rowIdx = 0; rowIdx < jacobian.N(); ++rowIdx) + { + // reset all entries corresponding to a non-green vertex + if (vertexColor_[rowIdx] != EntityColor::green) + { + // set all matrix entries in the row to 0 + auto colIt = jacobian[rowIdx].begin(); + const auto& colEndIt = jacobian[rowIdx].end(); + for (; colIt != colEndIt; ++colIt) { + *colIt = 0.0; + } + } + } + } + + void resetColors() + { + elementColor_.assign(elementColor_.size(), EntityColor::red); + vertexColor_.assign(vertexColor_.size(), EntityColor::red); + } + + EntityColor elementColor(size_t idx) const + { return elementColor_[idx]; } + + EntityColor vertexColor(size_t idx) const + { return vertexColor_[idx]; } + + EntityColor dofColor(size_t idx) const + { return vertexColor_[idx]; } + +private: + //! entity colors for partial reassembly + std::vector elementColor_; + std::vector vertexColor_; +}; + +/*! + * \ingroup Assembly + * \brief The partial reassembler engine specialized for the cellcentered TPFA method + */ +template +class PartialReassemblerEngine +{ + using Scalar = typename Assembler::Scalar; + using GridGeometry = typename Assembler::GridGeometry; + using JacobianMatrix = typename Assembler::JacobianMatrix; + +public: + PartialReassemblerEngine(const Assembler& assembler) + : elementColor_(assembler.gridGeometry().elementMapper().size(), EntityColor::red) + {} + + // returns number of green elements + std::size_t computeColors(const Assembler& assembler, + const std::vector& distanceFromLastLinearization, + Scalar threshold) + { + const auto& gridGeometry = assembler.gridGeometry(); + const auto& gridView = gridGeometry.gridView(); + const auto& elementMapper = gridGeometry.elementMapper(); + + // mark the red elements + for (const auto& element : elements(gridView)) + { + int eIdx = elementMapper.index(element); + + if (distanceFromLastLinearization[eIdx] > threshold) + { + // mark element as red if discrepancy is larger than + // the relative tolerance + elementColor_[eIdx] = EntityColor::red; + } + else + { + elementColor_[eIdx] = EntityColor::green; + } + } + + // mark the neighbors also red + const auto& connectivityMap = gridGeometry.connectivityMap(); + for (unsigned eIdx = 0; eIdx < elementColor_.size(); ++eIdx) + { + if (elementColor_[eIdx] == EntityColor::red) + continue; // element is red already! + + if (distanceFromLastLinearization[eIdx] > threshold) + { + for (const auto& connectedDof : connectivityMap[eIdx]) + elementColor_[connectedDof.globalJ] = EntityColor::red; + } + } + + // count green elements + return std::count_if(elementColor_.begin(), elementColor_.end(), + [](EntityColor c){return c == EntityColor::green;}); + + } + + void resetJacobian(Assembler& assembler) const + { + auto& jacobian = assembler.jacobian(); + const auto& connectivityMap = assembler.gridGeometry().connectivityMap(); + + // loop over all dofs + for (unsigned int colIdx = 0; colIdx < jacobian.M(); ++colIdx) + { + // reset all entries corresponding to a non-green element + if (elementColor_[colIdx] != EntityColor::green) + { + // set all matrix entries in the column to 0 + jacobian[colIdx][colIdx] = 0; + for (const auto& dataJ : connectivityMap[colIdx]) + jacobian[dataJ.globalJ][colIdx] = 0; + } + } + } + + void resetColors() + { + elementColor_.assign(elementColor_.size(), EntityColor::red); + } + + EntityColor elementColor(size_t idx) const + { return elementColor_[idx]; } + + EntityColor dofColor(size_t idx) const + { return elementColor_[idx]; } + +private: + //! entity colors for partial reassembly + std::vector elementColor_; +}; + +/*! + * \ingroup Assembly + * \brief The partial reassembler engine specialized for the cellcentered MPFA method + */ +template +class PartialReassemblerEngine +: public PartialReassemblerEngine +{ + using ParentType = PartialReassemblerEngine; +public: + using ParentType::ParentType; +}; + +//! helper struct to determine whether the an engine class has vertex colors +struct hasVertexColor +{ + template + auto operator()(Engine&& e) -> decltype(e.vertexColor(0)) {} +}; + +/*! + * \ingroup Assembly + * \brief detects which entries in the Jacobian have to be recomputed + * \tparam TypeTag The TypeTag + */ +template +class PartialReassembler +{ + using Scalar = typename Assembler::Scalar; + using GridGeometry = typename Assembler::GridGeometry; + using JacobianMatrix = typename Assembler::JacobianMatrix; + using VertexMapper = typename GridGeometry::VertexMapper; + + using DiscretizationMethod = typename GridGeometry::DiscretizationMethod; + using Engine = PartialReassemblerEngine; + +public: + + /*! + * \brief constructor + * \param assembler the assembler + */ + PartialReassembler(const Assembler& assembler) + : engine_(assembler) + , greenElems_(0) + { + const auto& gridGeometry = assembler.gridGeometry(); + totalElems_ = gridGeometry.elementMapper().size(); + totalElems_ = gridGeometry.gridView().comm().sum(totalElems_); + } + + /*! + * \brief Determine the colors of entities for partial reassembly. + * + * The following approach is used: + * + * - Set all elements to 'green' + * - Mark all elements as 'red' which exhibit an relative error above + * the tolerance + * - Mark all neighbors of 'red' elements also 'red' + * + * \param assembler the assembler + * \param distanceFromLastLinearization The distance from the last linearization + * \param threshold Reassemble only if the distance from the last + * linearization is above this value. + */ + void computeColors(const Assembler& assembler, + const std::vector& distanceFromLastLinearization, + Scalar threshold) + { + greenElems_ = engine_.computeColors(assembler, distanceFromLastLinearization, threshold); + } + + void resetColors() + { + engine_.resetColors(); + } + + void resetJacobian(Assembler& assembler) const + { + engine_.resetJacobian(assembler); + } + + /*! + * \brief called by the assembler after successful assembly + */ + template + void report(const Communication& comm, std::ostream& outStream) + { + if (comm.size() > 1) + greenElems_ = comm.sum(greenElems_); + + const auto reassembledElems = totalElems_ - greenElems_; + const auto width = Fmt::formatted_size("{}", totalElems_); + outStream << Fmt::format(", reassembled {:{}} ({:3}%) elements", + reassembledElems, width, 100*reassembledElems/totalElems_); + } + + EntityColor elementColor(size_t idx) const + { return engine_.elementColor(idx); } + + EntityColor dofColor(size_t idx) const + { return engine_.dofColor(idx); } + + template())::value, + typename std::enable_if_t = 0> + EntityColor vertexColor(size_t idx) const + { return engine_.vertexColor(idx); } + +private: + Engine engine_; + size_t totalElems_; + size_t greenElems_; +}; + +} // namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/volvardeflectionhelper_.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/volvardeflectionhelper_.hh new file mode 100644 index 0000000..c1fdafd --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/assembly/volvardeflectionhelper_.hh @@ -0,0 +1,91 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Assembly + * \brief Volume variables deflection helper. + */ + +#ifndef DUMUX_ASSEMBLY_VOLVARS_DEFLECTION_HELPER_HH +#define DUMUX_ASSEMBLY_VOLVARS_DEFLECTION_HELPER_HH + +#include +#include + +#ifndef DOXYGEN + +namespace Dumux::Detail { + +template +class VolVarsDeflectionHelper +{ + static constexpr int maxNumscvs = FVElementGeometry::maxNumElementScvs; + + using SubControlVolume = typename FVElementGeometry::GridGeometry::SubControlVolume; + using VolumeVariablesRef = std::invoke_result_t; + using VolumeVariables = std::decay_t; + static_assert(std::is_lvalue_reference_v + && !std::is_const_v>); + +public: + VolVarsDeflectionHelper(VolVarAccessor&& accessor, + const FVElementGeometry& fvGeometry, + bool deflectAllVolVars) + : deflectAll_(deflectAllVolVars) + , accessor_(std::move(accessor)) + , fvGeometry_(fvGeometry) + { + if (deflectAll_) + for (const auto& curScv : scvs(fvGeometry)) + origVolVars_.push_back(accessor_(curScv)); + } + + void setCurrent(const SubControlVolume& scv) + { + if (!deflectAll_) + { + origVolVars_.clear(); + origVolVars_.push_back(accessor_(scv)); + } + } + + template + void deflect(const ElementSolution& elemSol, + const SubControlVolume& scv, + const Problem& problem) + { + if (deflectAll_) + for (const auto& curScv : scvs(fvGeometry_)) + accessor_(curScv).update(elemSol, problem, fvGeometry_.element(), curScv); + else + accessor_(scv).update(elemSol, problem, fvGeometry_.element(), scv); + } + + void restore(const SubControlVolume& scv) + { + if (!deflectAll_) + accessor_(scv) = origVolVars_[0]; + else + for (const auto& curScv : scvs(fvGeometry_)) + accessor_(curScv) = origVolVars_[curScv.localDofIndex()]; + } + +private: + const bool deflectAll_; + VolVarAccessor accessor_; + const FVElementGeometry& fvGeometry_; + Dune::ReservedVector origVolVars_; +}; + +template +VolVarsDeflectionHelper(Accessor&&, FVElementGeometry&&) -> VolVarsDeflectionHelper>; + +} // end namespace Dumux::Detail + +#endif // DOXYGEN + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/balanceequationopts.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/balanceequationopts.hh new file mode 100644 index 0000000..4ae6ea5 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/balanceequationopts.hh @@ -0,0 +1,40 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Core + * \brief Traits class to set options used by the local residual when + * when evaluating the balance equations. + */ +#ifndef BALANCE_EQUATION_OPTIONS_HH +#define BALANCE_EQUATION_OPTIONS_HH + +namespace Dumux { + +/*! + * \ingroup Core + * \brief Traits class to set options used by the local residual when + * when evaluating the balance equations. + * \todo include useMoles here + * \todo include replaceCompIdx here + */ +template +class BalanceEquationOptions +{ +public: + + //! If a certain component is balanced in this model + // per default all phases are balanced. See e.g. Richards for an example where + // the air component exists but is not balanced. Or the tracer model where the + // carrier phase main component exists but is not balanced. + static constexpr bool mainComponentIsBalanced(int phaseIdx) + { return true; } +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/boundaryflag.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/boundaryflag.hh new file mode 100644 index 0000000..3165fd0 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/boundaryflag.hh @@ -0,0 +1,59 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Core + * \brief Boundary flag to store e.g. in sub control volume faces + */ +#ifndef DUMUX_BOUNDARY_FLAG_HH +#define DUMUX_BOUNDARY_FLAG_HH + +#include +#include + +namespace Dumux { + +/*! + * \ingroup Core + * \brief Class for accessing boundary flags + * \note this works for all grid managers with gmsh meshes. + */ +class BoundarySegmentIndexFlag +{ +public: + BoundarySegmentIndexFlag() + : flag_(std::numeric_limits::max()) {} + + template + BoundarySegmentIndexFlag(const Intersection& i) + : flag_(std::numeric_limits::max()) + { + if (i.boundary()) + flag_ = i.boundarySegmentIndex(); + } + + using value_type = std::size_t; + + value_type get() const { return flag_; } + +private: + value_type flag_; +}; + +/*! + * \ingroup Core + * \brief Boundary flag to store e.g. in sub control volume faces + * \note Can be specialized for each grid manager (in the gridmanager headers) + * \tparam Grid the type of the grid + */ +template +class BoundaryFlag : public BoundarySegmentIndexFlag +{ using BoundarySegmentIndexFlag::BoundarySegmentIndexFlag; }; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/boundarytypes.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/boundarytypes.hh new file mode 100644 index 0000000..c472207 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/boundarytypes.hh @@ -0,0 +1,374 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Core + * \brief Class to specify the type of a boundary. + */ +#ifndef DUMUX_BOUNDARY_TYPES_HH +#define DUMUX_BOUNDARY_TYPES_HH + +#include +#include + +namespace Dumux { + +/*! + * \ingroup Core + * \brief Class to specify the type of a boundary. + */ +template +class BoundaryTypes +{ +public: + BoundaryTypes() + { reset(); } + + //! we have a boundary condition for each equation + static constexpr int size() + { return numEq; } + + /*! + * \brief Reset the boundary types for all equations. + * + * After this method no equations will be disabled and neither + * Neumann nor Dirichlet conditions will be evaluated. This + * corresponds to a Neumann zero boundary. + */ + void reset() + { + for (int eqIdx=0; eqIdx < numEq; ++eqIdx) + resetEq(eqIdx); + } + + /*! + * \brief Reset the boundary types for one equation. + */ + void resetEq(int eqIdx) + { + boundaryInfo_[eqIdx].visited = false; + + boundaryInfo_[eqIdx].isDirichlet = false; + boundaryInfo_[eqIdx].isNeumann = false; + boundaryInfo_[eqIdx].isOutflow = false; + boundaryInfo_[eqIdx].isCouplingDirichlet = false; + boundaryInfo_[eqIdx].isCouplingNeumann = false; + + eq2pvIdx_[eqIdx] = eqIdx; + pv2eqIdx_[eqIdx] = eqIdx; + } + + /*! + * \brief Returns true if the boundary types for a given equation + * has been specified. + * + * \param eqIdx The index of the equation + */ + bool isSet(int eqIdx) const + { return boundaryInfo_[eqIdx].visited; } + + /*! + * \brief Make sure the boundary conditions are well-posed. + * + * If they are not, an assertion fails and the program aborts! + * (if the NDEBUG macro is not defined) + */ + void checkWellPosed() const + { + // if this fails, at least one condition is missing. + for (int i=0; i < numEq; ++i) + assert(boundaryInfo_[i].visited); + } + + /*! + * \brief Set all boundary conditions to Neumann. + */ + void setAllNeumann() + { + for (int eqIdx = 0; eqIdx < numEq; ++eqIdx) + setNeumann(eqIdx); + } + + /*! + * \brief Set all boundary conditions to Dirichlet. + */ + void setAllDirichlet() + { + for (int eqIdx = 0; eqIdx < numEq; ++ eqIdx) + setDirichlet(eqIdx); + } + + /*! + * \brief Set all boundary conditions to Dirichlet-like coupling + */ + void setAllCouplingDirichlet() + { + for (int eqIdx = 0; eqIdx < numEq; ++eqIdx) + setCouplingDirichlet(eqIdx); + } + + /*! + * \brief Set all boundary conditions to Neumann-like coupling. + */ + void setAllCouplingNeumann() + { + for (int eqIdx = 0; eqIdx < numEq; ++eqIdx) + setCouplingNeumann(eqIdx); + } + + /*! + * \brief Set a Neumann boundary condition for a single equation. + * + * \param eqIdx The index of the equation + */ + void setNeumann(int eqIdx) + { + resetEq(eqIdx); + boundaryInfo_[eqIdx].visited = true; + boundaryInfo_[eqIdx].isNeumann = true; + } + + /*! + * \brief Set a Dirichlet boundary condition for a single primary + * variable + * + * \param pvIdx The index of the primary variable for which the + * Dirichlet condition should apply. + * \param eqIdx The index of the equation which should used to set + * the Dirichlet condition + */ + void setDirichlet(int pvIdx, int eqIdx) + { + resetEq(eqIdx); + boundaryInfo_[eqIdx].visited = true; + boundaryInfo_[eqIdx].isDirichlet = true; + + // update the equation <-> primary variable mapping + eq2pvIdx_[eqIdx] = pvIdx; + pv2eqIdx_[pvIdx] = eqIdx; + } + + /*! + * \brief Set a boundary condition for a single equation to + * a Dirichlet-like coupling condition. + */ + void setCouplingDirichlet(int eqIdx) + { + resetEq(eqIdx); + boundaryInfo_[eqIdx].visited = true; + boundaryInfo_[eqIdx].isCouplingDirichlet = true; + boundaryInfo_[eqIdx].isDirichlet = true; + } + + /*! + * \brief Set a boundary condition for a single equation to + * a Neumann-like coupling condition. + */ + void setCouplingNeumann(int eqIdx) + { + resetEq(eqIdx); + boundaryInfo_[eqIdx].visited = true; + boundaryInfo_[eqIdx].isCouplingNeumann = true; + boundaryInfo_[eqIdx].isNeumann = true; + } + + /*! + * \brief Set a Dirichlet boundary condition for a single primary + * variable. + * + * Depending on the discretization, setting the Dirichlet condition + * will replace the balance equation with index equal to pvIdx. + * + * \param pvIdx The index of the primary variable inside a + * PrimaryVariables object. + */ + void setDirichlet(int pvIdx) + { + setDirichlet(pvIdx, pvIdx); + } + + /*! + * \brief Returns true if an equation is used to specify a + * Dirichlet condition. + * + * \param eqIdx The index of the equation + */ + bool isDirichlet(unsigned eqIdx) const + { return boundaryInfo_[eqIdx].isDirichlet || + boundaryInfo_[eqIdx].isCouplingDirichlet; + } + + /*! + * \brief Returns true if all equations are used to specify a + * Dirichlet condition. + */ + bool hasOnlyDirichlet() const + { + return std::all_of(boundaryInfo_.begin(), + boundaryInfo_.end(), + [](const BoundaryInfo& b){ return b.isDirichlet || + b.isCouplingDirichlet; } + ); + } + + /*! + * \brief Returns true if some equation is used to specify a + * Dirichlet condition. + */ + bool hasDirichlet() const + { + return std::any_of(boundaryInfo_.begin(), + boundaryInfo_.end(), + [](const BoundaryInfo& b){ return b.isDirichlet || + b.isCouplingDirichlet; } + ); + } + + /*! + * \brief Returns true if an equation is used to specify a + * Neumann condition. + * + * \param eqIdx The index of the equation + */ + bool isNeumann(unsigned eqIdx) const + { + return boundaryInfo_[eqIdx].isNeumann || + boundaryInfo_[eqIdx].isCouplingNeumann; + } + + /*! + * \brief Returns true if all equations are used to specify a + * Neumann condition. + */ + bool hasOnlyNeumann() const + { + return std::all_of(boundaryInfo_.begin(), + boundaryInfo_.end(), + [](const BoundaryInfo& b){ return b.isNeumann || + b.isCouplingNeumann; } + ); + } + + /*! + * \brief Returns true if some equation is used to specify a + * Neumann condition. + */ + bool hasNeumann() const + { + return std::any_of(boundaryInfo_.begin(), + boundaryInfo_.end(), + [](const BoundaryInfo& b){ return b.isNeumann || + b.isCouplingNeumann; } + ); + } + + /*! + * \brief Returns true if an equation is used to specify an + * Dirichlet coupling condition. + * + * \param eqIdx The index of the equation + */ + bool isCouplingDirichlet(unsigned eqIdx) const + { return boundaryInfo_[eqIdx].isCouplingDirichlet; } + + /*! + * \brief Returns true if some equation is used to specify an + * Dirichlet coupling condition. + */ + bool hasCouplingDirichlet() const + { + return std::any_of(boundaryInfo_.begin(), + boundaryInfo_.end(), + [](const BoundaryInfo& b){ return b.isCouplingDirichlet; } + ); + } + + /*! + * \brief Returns true if an equation is used to specify an + * Neumann coupling condition. + * + * \param eqIdx The index of the equation + */ + bool isCouplingNeumann(unsigned eqIdx) const + { return boundaryInfo_[eqIdx].isCouplingNeumann; } + + /*! + * \brief Returns true if some equation is used to specify an + * Neumann coupling condition. + */ + bool hasCouplingNeumann() const + { + return std::any_of(boundaryInfo_.begin(), + boundaryInfo_.end(), + [](const BoundaryInfo& b){ return b.isCouplingNeumann; } + ); + } + + /*! + * \brief Returns true if an equation is used to specify a + * coupling condition. + * + * \param eqIdx The index of the equation + */ + bool isCoupling(unsigned eqIdx) const + { + return boundaryInfo_[eqIdx].isCouplingDirichlet + || boundaryInfo_[eqIdx].isCouplingNeumann; + } + + /*! + * \brief Returns true if some equation is used to specify a + * coupling condition. + */ + bool hasCoupling() const + { + for (int i = 0; i < numEq; ++i) + if (isCoupling(i)) + return true; + return false; + } + + /*! + * \brief Returns the index of the equation which should be used + * for the Dirichlet condition of the pvIdx's primary + * variable. + * + * \param pvIdx The index of the primary variable which is be set + * by the Dirichlet condition. + */ + unsigned dirichletToEqIndex(unsigned pvIdx) const + { return pv2eqIdx_[pvIdx]; } + + /*! + * \brief Returns the index of the primary variable which should + * be used for the Dirichlet condition given an equation + * index. + * + * \param eqIdx The index of the equation which is used to set + * the Dirichlet condition. + */ + unsigned eqToDirichletIndex(unsigned eqIdx) const + { return eq2pvIdx_[eqIdx]; } + +protected: + //! use bitfields to minimize the size + struct BoundaryInfo { + bool visited : 1; + bool isDirichlet : 1; + bool isNeumann : 1; + bool isOutflow : 1; + bool isCouplingDirichlet : 1; + bool isCouplingNeumann : 1; + }; + + std::array boundaryInfo_; + std::array eq2pvIdx_, pv2eqIdx_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/defaultmappertraits.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/defaultmappertraits.hh new file mode 100644 index 0000000..3df9160 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/defaultmappertraits.hh @@ -0,0 +1,30 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Core + * \brief Defines the default element and vertex mapper types + */ +#ifndef DUMUX_DEFAULT_MAPPER_TRAITS_HH +#define DUMUX_DEFAULT_MAPPER_TRAITS_HH + +#include + +namespace Dumux { + +template , + class VM = Dune::MultipleCodimMultipleGeomTypeMapper> +struct DefaultMapperTraits +{ + using ElementMapper = EM; + using VertexMapper = VM; +}; + +} // namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/defaultusagemessage.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/defaultusagemessage.hh new file mode 100644 index 0000000..1bbb713 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/defaultusagemessage.hh @@ -0,0 +1,57 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Core + * \brief Function printing a default usage message + */ +#ifndef DUMUX_DEFAULT_USAGE_MESSAGE_HH +#define DUMUX_DEFAULT_USAGE_MESSAGE_HH + +#include + +namespace Dumux { + +/*! + * \ingroup Core + * \brief Provides a general text block, that is part of error/ help messages. + * + * \return The string that is the help / error message. + */ +inline std::string defaultUsageMessage(const std::string& programName) +{ + return "Usage: " + programName + " [options] \n" + "Options usually are parameters given to the simulation, \n" + "and have to be specified with this syntax: \n" + "\t-GroupName.ParameterName VALUE, for example -TimeLoop.TEnd 100\n" + "\n" + "Parameters can also be defined in a parameter file that consists of\n" + "lines of the form \n" + "GroupName.ParameterName = VALUE # comment \n" + "have to be used. More conveniently, group names can be specified in square brackets, \n" + "such that each following parameter name belongs to that group, \n" + "[GroupName] \n" + "ParameterName = VALUE \n" + "See files named `params.input` in the `test` folder for examples \n" + "and the Dune documentation of ParameterTreeParser for the format specification. \n" + "\n" + "Parameters specified on the command line have priority over those in the parameter file.\n" + "If no parameter file name is given, './.input' is chosen as first\n" + "and './params.input' as second default.\n" + "\n" + "Important options include:\n" + "\t-h, --help Print this usage message and exit\n" + "\t-PrintParameters [true|false] Print the run-time modifiable parameters _after_ \n" + "\t the simulation [default: true]\n" + "\t-ParameterFile FILENAME File with parameter definitions\n" + "\t-TimeLoop.Restart RESTARTTIME Restart simulation from a restart file\n" + "\n\n"; +} + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/dumuxmessage.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/dumuxmessage.hh new file mode 100644 index 0000000..ea1ab46 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/dumuxmessage.hh @@ -0,0 +1,423 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Core + * \brief Provides the class creating the famous DuMux start and end messages + */ +#ifndef DUMUX_MESSAGE_HH +#define DUMUX_MESSAGE_HH + +#include +#include +#include + +#include + +namespace Dumux { + +/*! + * \ingroup Core + * \brief DuMux start and end message. + */ +class DumuxMessage +{ + //! The current number of messages. Please adjust if you add one. + static const int nMessages_ = 34; + +public: + + /*! + * \brief Selects random messages to write out at the start and end of a simulation run. + * \param firstCall Indicates if it's the first call and we have to dice (simulation is starting). + */ + static void print(bool firstCall = false) + { + // initialize in case someone forgets to set first call + static int dice = 8; + + if(firstCall) + { + // roll the dice to decide which start message will be displayed + std::srand(std::time(0)); + dice = std::rand() % (nMessages_ + 1); + } + + std::cout << std::endl; + + switch (dice) + { + case 0: + if(firstCall) + std::cout << "Welcome aboard DuMuX airlines. Please fasten your seatbelts! " + << "Emergency exits are near the time integration." << std::endl; + else + std::cout << "We hope that you enjoyed simulating with us " << std::endl + << "and that you will choose us next time, too." << std::endl; + break; + case 1: + if(firstCall) + std::cout << "Let's get the cow off the ice." << std::endl; + else + std::cout << "DuMuX got the cow off the ice." << std::endl; + break; + case 2: + if(firstCall) + std::cout << "Science, my lad, is made up of mistakes, but they are " + << "mistakes which it is useful to make, because they lead little " + << "by little to the truth." << std::endl + << " - Jules Verne, A journey to the center of the earth" << std::endl; + else + std::cout << "[We see that] science is eminently perfectible, and that each theory has " + << "constantly to give way to a fresh one." << std::endl + << " - Jules Verne, Journey to the Center of the Earth" << std::endl; + + break; + case 3: + if(firstCall) + std::cout << "Wherever he saw a hole he always wanted to know the depth of it. " + << "To him this was important." << std::endl + << " - Jules Verne, A journey to the center of the earth" << std::endl; + else + std::cout << "We may brave human laws, but we cannot resist natural ones." << std::endl + << " - Jules Verne, 20,000 Leagues Under the Sea" << std::endl; + break; + case 4: + if(firstCall) + std::cout << "Silence - to delight Bernd." << std::endl; + else + std::cout << std::endl << std::endl; + break; + case 5: + std::cout << "Don't panic... !" << std::endl; + break; + case 6: + if(firstCall) + std::cout << "You idiot! You signed the order to destroy Earth!" << std::endl + << " - Douglas Adams, HGttG" << std::endl; + else + std::cout << "Marvin: I've been talking to the main computer." << std::endl + << "Arthur: And?" << std::endl + << "Marvin: It hates me." << std::endl + << " - Douglas Adams, HGttG" << std::endl; + break; + case 7: + if(firstCall) + std::cout << "In the beginning the Universe was created. This has made a lot of " + << "people very angry and has been widely regarded as a bad move.!" << std::endl + << " - Douglas Adams, HGttG " << std::endl; + else + std::cout << "Forty-two. I checked it very thoroughly, and that quite definitely is the answer. I think " + << "the problem, to be quite honest with you, is that you\'ve never actually known what the question is." << std::endl + << " - Douglas Adams, HGttG " << std::endl; + break; + case 8: + std::cout << " ## @@@@ @ @ @ @" << std::endl; + std::cout << " ### # @ @ @@ @@ @ " << std::endl; + std::cout << " ## # @ @ @ @ @ @ @ @ @ @ @" << std::endl; + std::cout << " ## # @ @ @ @ @ @ @ @ " << std::endl; + std::cout << " # # @@@@ @@@ @ @ @@@ " << std::endl; + std::cout << " # # " << std::endl; + std::cout << " # # " << std::endl; + std::cout << " # ## %%% " << std::setw(8) << std::right << DUMUX_VERSION << std::endl; + std::cout << " # ### % % %% %% " << std::endl; + std::cout << "#### #%%% %% %%%%% %%%%%%%%%%%%%%%%%" << std::endl; + break; + case 9: + std::cout << "### # # # # " << std::endl; + std::cout << "# # # # ## ## # # # " << std::endl; + std::cout << "# # # # # # # # # # # " << std::endl; + std::cout << "### ## # # ## " << std::endl; + std::cout << " " << std::endl; + std::cout << "Dune for Multi-{ Phase, " << std::endl; + std::cout << " Component, " << std::endl; + std::cout << " Scale, " << std::endl; + std::cout << " Physics, " << std::endl; + std::cout << " ...} flow and transport in porous media" << std::endl; + break; + case 10: + if(firstCall) + std::cout << "Elliot Carver: Mr. Jones, are we ready to release our new software?" << std::endl + << "Jones: Yes, sir. As requested, it's full of bugs, which means people will be forced to upgrade for years." << std::endl + << " - James Bond, Tomorrow Never Dies" << std::endl; + else + { + std::cout << "Elliot Carver: Outstanding." << std::endl + << " - James Bond, Tomorrow Never Dies" << std::endl; + } + break; + case 11: + if(firstCall) + std::cout << "Chuck Norris has successfully compiled DuMuX." << std::endl; + else + std::cout << "Chuck Norris has compiled DuMuX even two times in a row!" << std::endl; + break; + case 12: + if (firstCall) + { + std::cout << " ┌──────────────────┐" << std::endl; + std::cout << Fmt::format(" │{:^20}│", Fmt::format("DuMuX {} \u2661", DUMUX_VERSION)) << std::endl; + std::cout << " └──────────────────┘" << std::endl; + } + else + std::cout << "\n" << std::endl; + break; + case 13: + if(firstCall) + { + std::cout << "Everything starts somewhere, though many physicists disagree." << std::endl + << " - Terry Pratchett " << std::endl; + } + else + { + std::cout << "Opera happens because a large number of things amazingly fail to go wrong." << std::endl + << " - Terry Pratchett " << std::endl; + } + break; + case 14: + std::cout << "To infinity and beyond." << std::endl + << " - Buzz Lightyear, Toy Story" << std::endl; + break; + case 15: + if(firstCall) + { + std::cout << "C makes it easy to shoot yourself in the foot; C++ makes it harder, but when you do it blows your whole leg off." << std::endl + << " - Bjarne Stroustrup " << std::endl; + } + else + { + std::cout << "There's an old story about the person who wished his computer were as easy to use as his telephone." << std::endl + << "That wish has come true, since I no longer know how to use my telephone." << std::endl + << " - Bjarne Stroustrup " << std::endl; + } + break; + case 16: + if(firstCall) + { + std::cout << "Now, all we need is a little Energon and a lot of luck!" << std::endl + << " - Optimus Prime, The Transformers: The Movie " << std::endl; + } + else + { + std::cout << "Sometimes even the wisest of men and machines can be in error." << std::endl + << " - Optimus Prime, The Transformers: The Movie " << std::endl; + } + break; + case 17: + if(firstCall) + { + std::cout << "Let's go. In and out, 20 minutes adventure." << std::endl + << " - Rick Sanchez, Rick & Morty " << std::endl; + } + else + { + std::cout << "Losers look stuff up while the rest of us are carpin' all them diems." << std::endl + << " - Summer Smith, Rick & Morty" << std::endl; + } + break; + case 18: + if(firstCall) + { + std::cout << "It's the job that's never started as takes longest to finish." << std::endl + << " - Sam Gamgee, LotR " << std::endl; + } + else + { + std::cout << "He that breaks a thing to find out what it is, has left the path of wisdom." << std::endl + << " - Gandalf, LotR " << std::endl; + } + break; + case 19: + if(firstCall) + { + std::cout << "The Ring has awoken, it's heard its master's call." << std::endl + << " - Gandalf, LotR " << std::endl; + } + else + { + std::cout << "It's a dangerous business, Frodo, going out your door. " << std::endl + << "You step onto the road, and if you don't keep your feet, there's no knowing where you might be swept off to." << std::endl + << " - Frodo Baggins, LotR " << std::endl; + } + break; + case 20: + if(firstCall) + { + std::cout << "Who knows? Have patience. Go where you must go, and hope!" << std::endl + << " - Gandalf, LotR " << std::endl; + } + else + { + std::cout << "Don't adventures ever have an end? I suppose not. Someone else always has to carry on the story." << std::endl + << " - Bilbo Baggins, LotR " << std::endl; + } + break; + case 21: + if(firstCall) + { + std::cout << "As long as I'm better than everyone else I suppose it doesn't matter." << std::endl + << " - Jamie Lannister, GoT" << std::endl; + } + else + { + std::cout << "My watch has ended." << std::endl + << " - Jon Snow, GoT" << std::endl; + } + break; + case 22: + if(firstCall) + { + std::cout << "You'll find I'm full of surprises." << std::endl + << " - Luke Skywalker, Star Wars: The Empire Strikes Back " << std::endl; + } + else + { + std::cout << "I find your lack of faith disturbing." << std::endl + << " - Darth Vader, Star Wars: A New Hope " << std::endl; + } + break; + case 23: + if(firstCall) + { + std::cout << "Here goes nothing." << std::endl + << " - Lando Calrissian, Star Wars: Return of the Jedi" << std::endl; + } + else + { + std::cout << "Chewie, we're home." << std::endl + << " - Han Solo, Star Wars: The Force Awakens" << std::endl; + } + break; + case 24: + if(firstCall) + { + std::cout << "The Force is strong with this one." << std::endl + << " - Darth Vader, Star Wars: A New Hope " << std::endl; + } + else + { + std::cout << "In my experience, there's no such thing as luck." << std::endl + << " - Obi-Wan Kenobi, Star Wars: A New Hope " << std::endl; + } + break; + case 25: + if(firstCall) + { + std::cout << "The city's central computer told you? R2D2, you know better than to trust a strange computer!" << std::endl + << " - C3PO, Star Wars: The Empire Strikes Back " << std::endl; + } + else + { + std::cout << "He's quite clever, you know...for a human being." << std::endl + << " - C3PO, Star Wars: The Empire Strikes Back " << std::endl; + } + break; + case 26: + if(firstCall) + { + std::cout << "I know some things. I can, you know, do math and stuff." << std::endl + << " - Harry Potter " << std::endl; + } + else + { + std::cout << "Harry then did something that was both very brave and very stupid." << std::endl + << " - Harry Potter and the Sorcerer's Stone " << std::endl; + } + break; + case 27: + if(firstCall) + { + std::cout << "I'll be in my bedroom, making no noise and pretending I'm not there." << std::endl + << " - Harry Potter " << std::endl; + } + else + { + std::cout << "Honestly, if you were any slower, you'd be going backward." << std::endl + << " - Draco Malfoy " << std::endl; + } + break; + case 28: + std::cout << "I can do this all day." << std::endl + << " - Captain America " << std::endl; + break; + case 29: + if(firstCall) + { + std::cout << "Your scientists were so preoccupied with whether or not they could, they didn't stop to think if they should." << std::endl + << " - Ian Malcolm, Jurassic Park " << std::endl; + } + else + { + std::cout << "Boy, do I hate being right all the time." << std::endl + << " - Ian Malcolm, Jurassic Park " << std::endl; + } + break; + case 30: + if(firstCall) + { + std::cout << "It's a UNIX System! I know this! " + << " - Lex Murphy, Jurassic Park " << std::endl; + } + else + { + std::cout << "When you gotta go, you gotta go." << std::endl + << " - Ian Malcolm, Jurassic Park " << std::endl; + } + break; + case 31: + if(firstCall) + { + std::cout << "Whatever happens, that's the plan. " + << " - Kayla Watts, Jurassic World Dominion " << std::endl; + } + else + { + std::cout << "Can we start over?" << std::endl + << " - Claire Dearing, Jurassic World Dominion " << std::endl; + } + break; + case 32: + if(firstCall) + { + std::cout << "The code is more what you'd call 'guidelines' than actual rules. " + << " - Hector Barbossa, Pirates of the Caribbean " << std::endl; + } + else + { + std::cout << "Did everyone see that? Because I will not be doing it again." << std::endl + << " - Jack Sparrow, Pirates of the Caribbean " << std::endl; + } + break; + case 33: + if(firstCall) + { + std::cout << "If you were waiting for the opportune moment, that was it. " + << " - Jack Sparrow, Pirates of the Caribbean " << std::endl; + } + else + { + std::cout << "I love those moments. I like to wave at them as they pass by." << std::endl + << " - Jack Sparrow, Pirates of the Caribbean " << std::endl; + } + break; + case 34: + std::cout << "And that was without even a single drop of rum." << std::endl + << " - Jack Sparrow, Pirates of the Caribbean " << std::endl; + break; + + // Note: If you add a case, you have to increase the number of messages (nMessages_ variable). + + default: // silence to delight Bernd + return; + } + std::cout << std::endl; + } +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/entitymap.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/entitymap.hh new file mode 100644 index 0000000..9bef0e9 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/entitymap.hh @@ -0,0 +1,79 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Core + * \brief A map from indices to entities using grid entity seeds + */ +#ifndef DUMUX_ENTITY_INDEX_MAP_HH +#define DUMUX_ENTITY_INDEX_MAP_HH + +#include +#include +#include + +namespace Dumux { + +/*! + * \ingroup Core + * \brief A map from indices to entities using grid entity seeds + */ +template +class EntityMap +{ +public: + using Grid = typename GridView::Traits::Grid; + using Entity = typename Grid::template Codim::Entity; + using EntitySeed = typename Grid::template Codim::EntitySeed; + + //! constructor moving a ready seed list in here + EntityMap(const Grid& grid, std::vector&& seeds) + : grid_(grid) + , seeds_(std::move(seeds)) + {} + + //! constructor with all entities of codim + template + EntityMap(const Grid& grid, const Mapper& mapper) + : grid_(grid) + { + update(mapper); + } + + //! update the map after the grid changed + void update(std::vector&& seeds) + { seeds_.swap(std::move(seeds)); } + + //! update the map after the grid changed + template + void update(const Mapper& mapper) + { + const auto& gv = grid_.leafGridView(); + seeds_.resize(gv.size(codim)); + for (const auto& entity : entities(gv, Dune::Codim())) + seeds_[mapper.index(entity)] = entity.seed(); + } + + //! get an element from an index i + Entity operator[](std::size_t i) const + { return grid_.entity(seeds_[i]); } + + //! get the size of the map + std::size_t size() const + { return seeds_.size(); } + +private: + const Grid& grid_; + std::vector seeds_; +}; + +template +using ElementMap = EntityMap; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/exceptions.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/exceptions.hh new file mode 100644 index 0000000..2c6bda3 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/exceptions.hh @@ -0,0 +1,66 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Core + * \brief Some exceptions thrown in DuMux + */ +#ifndef DUMUX_EXCEPTIONS_HH +#define DUMUX_EXCEPTIONS_HH + +#include + +#include + +namespace Dumux { +/*! + * \ingroup Core + * \brief Exception thrown if a fixable numerical problem occurs. + * + * (e.g. time step too big, etc.) + */ +class NumericalProblem : public Dune::Exception +{ +public: + // copy constructor + NumericalProblem(const NumericalProblem &v) + : Dune::Exception(v) + {} + + // default constructor + NumericalProblem() + {} + + // constructor with error message + explicit NumericalProblem(const std::string &s) + { this->message(s); } +}; + +/*! + * \ingroup Core + * \brief Exception thrown if a run-time parameter is not specified correctly. + */ +class ParameterException : public Dune::Exception +{ +public: + // copy constructor + ParameterException(const ParameterException &v) + : Dune::Exception(v) + {} + + // default constructor + ParameterException() + {} + + // constructor with error message + explicit ParameterException(const std::string &s) + { this->message(s); } +}; + +} // namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/fvproblem.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/fvproblem.hh new file mode 100644 index 0000000..0ec64b6 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/fvproblem.hh @@ -0,0 +1,552 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Core + * \brief Base class for all finite volume problems + */ +#ifndef DUMUX_COMMON_FV_PROBLEM_HH +#define DUMUX_COMMON_FV_PROBLEM_HH + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +namespace Dumux { + +/*! + * \ingroup Core + * \brief Base class for all finite-volume problems + * + * \note All quantities (regarding the units) are specified assuming a + * three-dimensional world. Problems discretized using 2D grids + * are assumed to be extruded by \f$1 m\f$ and 1D grids are assumed + * to have a cross section of \f$1m \times 1m\f$. + */ +template +class FVProblem +{ + using Implementation = GetPropType; + + using GridGeometry = GetPropType; + using FVElementGeometry = typename GridGeometry::LocalView; + using GridView = typename GridGeometry::GridView; + using SubControlVolume = typename FVElementGeometry::SubControlVolume; + using SubControlVolumeFace = typename FVElementGeometry::SubControlVolumeFace; + using Extrusion = Extrusion_t; + using Element = typename GridView::template Codim<0>::Entity; + using GlobalPosition = typename Element::Geometry::GlobalCoordinate; + + enum { dim = GridView::dimension }; + + using PointSource = GetPropType; + using PointSourceHelper = GetPropType; + using PointSourceMap = std::map< std::pair, + std::vector >; + + static constexpr bool isCVFE = DiscretizationMethods::isCVFE; + static constexpr bool isStaggered = GridGeometry::discMethod == DiscretizationMethods::staggered; + + using Scalar = GetPropType; + using PrimaryVariables = GetPropType; + using NumEqVector = Dumux::NumEqVector>; + using BoundaryTypes = Dumux::BoundaryTypes; + +public: + //! export traits of this problem + struct Traits + { + using Scalar = FVProblem::Scalar; + using PrimaryVariables = FVProblem::PrimaryVariables; + using NumEqVector = FVProblem::NumEqVector; + }; + + /*! + * \brief Constructor + * \param gridGeometry The finite volume grid geometry + * \param paramGroup The parameter group in which to look for runtime parameters first (default is "") + */ + FVProblem(std::shared_ptr gridGeometry, const std::string& paramGroup = "") + : gridGeometry_(gridGeometry) + , paramGroup_(paramGroup) + { + // set a default name for the problem + problemName_ = getParamFromGroup(paramGroup, "Problem.Name", "sim"); + } + + /*! + * \brief The problem name. + * + * This is used as a prefix for files generated by the simulation. + * It could be either overwritten by the problem files, or simply + * declared over the setName() function in the application file. + */ + const std::string& name() const + { + return problemName_; + } + + /*! + * \brief Set the problem name. + * + * \param newName The problem's name + */ + void setName(const std::string& newName) + { + problemName_ = newName; + } + + /*! + * \name Boundary conditions and sources defining the problem + */ + // \{ + + /*! + * \brief Specifies which kind of boundary condition should be + * used for which equation on a given boundary segment. + * + * \param element The finite element + * \param scv The sub control volume + */ + auto boundaryTypes(const Element &element, + const SubControlVolume &scv) const + { + if (!isCVFE) + DUNE_THROW(Dune::InvalidStateException, + "boundaryTypes(..., scv) called for non-CVFE method."); + + // forward it to the method which only takes the global coordinate + return asImp_().boundaryTypesAtPos(scv.dofPosition()); + } + + /*! + * \brief Specifies which kind of boundary condition should be + * used for which equation on a given boundary segment. + * + * \param element The finite element + * \param scvf The sub control volume face + */ + auto boundaryTypes(const Element &element, + const SubControlVolumeFace &scvf) const + { + if (isCVFE) + DUNE_THROW(Dune::InvalidStateException, + "boundaryTypes(..., scvf) called for CVFE method."); + + // forward it to the method which only takes the global coordinate + return asImp_().boundaryTypesAtPos(scvf.ipGlobal()); + } + + /*! + * \brief Specifies which kind of boundary condition should be + * used for which equation on a given boundary segment. + * + * \param globalPos The position of the finite volume in global coordinates + */ + BoundaryTypes boundaryTypesAtPos(const GlobalPosition &globalPos) const + { + //! As a default, i.e. if the user's problem does not overload any boundaryTypes method + //! set Dirichlet boundary conditions everywhere for all primary variables + BoundaryTypes bcTypes; + bcTypes.setAllDirichlet(); + return bcTypes; + } + + /*! + * \brief Evaluate the boundary conditions for a dirichlet + * control volume face. + * + * \param element The finite element + * \param scvf the sub control volume face + * \note used for cell-centered discretization schemes + */ + PrimaryVariables dirichlet(const Element &element, const SubControlVolumeFace &scvf) const + { + // forward it to the method which only takes the global coordinate + if (isCVFE) + { + DUNE_THROW(Dune::InvalidStateException, "dirichlet(scvf) called for CVFE method."); + } + else + return asImp_().dirichletAtPos(scvf.ipGlobal()); + } + + /*! + * \brief Evaluate the boundary conditions for a dirichlet + * control volume. + * + * \param element The finite element + * \param scv the sub control volume + * \note used for cell-centered discretization schemes + */ + PrimaryVariables dirichlet(const Element &element, const SubControlVolume &scv) const + { + // forward it to the method which only takes the global coordinate + if (!isCVFE && !isStaggered) + { + DUNE_THROW(Dune::InvalidStateException, "dirichlet(scv) called for other than CVFE or staggered method."); + } + else + return asImp_().dirichletAtPos(scv.dofPosition()); + } + + /*! + * \brief Evaluate the boundary conditions for a dirichlet + * control volume. + * + * \param globalPos The position of the center of the finite volume + * for which the dirichlet condition ought to be + * set in global coordinates + */ + PrimaryVariables dirichletAtPos(const GlobalPosition &globalPos) const + { + // Throw an exception (there is no reasonable default value + // for Dirichlet conditions) + DUNE_THROW(Dune::InvalidStateException, + "The problem specifies that some boundary " + "segments are dirichlet, but does not provide " + "a dirichlet() or a dirichletAtPos() method."); + } + + /*! + * \brief If internal Dirichlet constraints are enabled + * Enables / disables internal (non-boundary) Dirichlet constraints. If this is overloaded + * to return true, the assembler calls problem.hasInternalDirichletConstraint(element, scv). + * This means you have to implement the following member function + * + * std::bitset hasInternalDirichletConstraint(const Element& element, const SubControlVolume& scv) const; + * + * where N is the number of equations and where the return value defines for each equation if the corresponding dof associated + * with the element/scv pair is constraint. If true is returned for a dof, the assembler calls + * problem.internalDirichlet(element, scv). This means you have to additionally implement the following member function + * + * PrimaryVariables internalDirichlet(const Element& element, const SubControlVolume& scv) const; + * + * which returns the enforced Dirichlet values the dof associated with the element/scv pair. + */ + static constexpr bool enableInternalDirichletConstraints() + { return false; } + + /*! + * \brief Evaluate the boundary conditions for a neumann + * boundary segment. + * + * This is the method for the case where the Neumann condition is + * potentially solution dependent + * + * \param element The finite element + * \param fvGeometry The finite-volume geometry + * \param elemVolVars All volume variables for the element + * \param elemFluxVarsCache Flux variables caches for all faces in stencil + * \param scvf The sub control volume face + * + * Negative values mean influx. + * E.g. for the mass balance that would be the mass flux in \f$ [ kg / (m^2 \cdot s)] \f$. + */ + template + NumEqVector neumann(const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const ElementFluxVariablesCache& elemFluxVarsCache, + const SubControlVolumeFace& scvf) const + { + // forward it to the interface with only the global position + return asImp_().neumannAtPos(scvf.ipGlobal()); + } + + /*! + * \brief Evaluate the boundary conditions for a neumann + * boundary segment. + * + * \param globalPos The position of the boundary face's integration point in global coordinates + * + * Negative values mean influx. + * E.g. for the mass balance that would be the mass flux in \f$ [ kg / (m^2 \cdot s)] \f$. + */ + NumEqVector neumannAtPos(const GlobalPosition &globalPos) const + { + //! As a default, i.e. if the user's problem does not overload any neumann method + //! return no-flow Neumann boundary conditions at all Neumann boundaries + return NumEqVector(0.0); + } + + /*! + * \brief Evaluate the source term for all phases within a given + * sub-control-volume. + * + * This is the method for the case where the source term is + * potentially solution dependent and requires some quantities that + * are specific to the fully-implicit method. + * + * \param element The finite element + * \param fvGeometry The finite-volume geometry + * \param elemVolVars All volume variables for the element + * \param scv The sub control volume + * + * For this method, the return parameter stores the conserved quantity rate + * generated or annihilate per volume unit. Positive values mean + * that the conserved quantity is created, negative ones mean that it vanishes. + * E.g. for the mass balance that would be a mass rate in \f$ [ kg / (m^3 \cdot s)] \f$. + */ + template + NumEqVector source(const Element &element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const SubControlVolume &scv) const + { + // forward to solution independent, fully-implicit specific interface + return asImp_().sourceAtPos(scv.center()); + } + + /*! + * \brief Evaluate the source term for all phases within a given + * sub-control-volume. + * + * \param globalPos The position of the center of the finite volume + * for which the source term ought to be + * specified in global coordinates + * + * For this method, the values parameter stores the conserved quantity rate + * generated or annihilate per volume unit. Positive values mean + * that the conserved quantity is created, negative ones mean that it vanishes. + * E.g. for the mass balance that would be a mass rate in \f$ [ kg / (m^3 \cdot s)] \f$. + */ + NumEqVector sourceAtPos(const GlobalPosition &globalPos) const + { + //! As a default, i.e. if the user's problem does not overload any source method + //! return 0.0 (no source terms) + return NumEqVector(0.0); + } + + /*! + * \brief Applies a vector of point sources. The point sources + * are possibly solution dependent. + * + * \param pointSources A vector of PointSource s that contain + source values for all phases and space positions. + * + * For this method, the values method of the point source + * has to return the absolute rate values in units + * \f$ [ \textnormal{unit of conserved quantity} / s ] \f$. + * Positive values mean that the conserved quantity is created, negative ones mean that it vanishes. + * E.g. for the mass balance that would be a mass rate in \f$ [ kg / s ] \f$. + */ + void addPointSources(std::vector& pointSources) const {} + + /*! + * \brief Evaluate the point sources (added by addPointSources) + * for all phases within a given sub-control-volume. + * + * This is the method for the case where the point source is + * solution dependent + * + * \param source A single point source + * \param element The finite element + * \param fvGeometry The finite-volume geometry + * \param elemVolVars All volume variables for the element + * \param scv The sub control volume + * + * For this method, the values() method of the point sources returns + * the absolute conserved quantity rate generated or annihilate in + * units \f$ [ \textnormal{unit of conserved quantity} / s ] \f$. + * Positive values mean that the conserved quantity is created, negative ones mean that it vanishes. + * E.g. for the mass balance that would be a mass rate in \f$ [ kg / s ] \f$. + */ + template + void pointSource(PointSource& source, + const Element &element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const SubControlVolume &scv) const + { + // forward to space dependent interface method + asImp_().pointSourceAtPos(source, source.position()); + } + + /*! + * \brief Evaluate the point sources (added by addPointSources) + * for all phases within a given sub-control-volume. + * + * This is the method for the case where the point source is space dependent + * + * \param pointSource A single point source + * \param globalPos The point source position in global coordinates + * + * For this method, the \a values() method of the point sources returns + * the absolute conserved quantity rate generated or annihilate in + * units \f$ [ \textnormal{unit of conserved quantity} / s ] \f$. Positive values mean + * that the conserved quantity is created, negative ones mean that it vanishes. + * E.g. for the mass balance that would be a mass rate in \f$ [ kg / s ] \f$. + */ + void pointSourceAtPos(PointSource& pointSource, + const GlobalPosition &globalPos) const {} + + /*! + * \brief Add source term derivative to the Jacobian + * \note Only needed in case of analytic differentiation and solution dependent sources + */ + template + void addSourceDerivatives(MatrixBlock& block, + const Element& element, + const FVElementGeometry& fvGeometry, + const VolumeVariables& volVars, + const SubControlVolume& scv) const {} + + /*! + * \brief Adds contribution of point sources for a specific sub control volume + * to the values. + * Caution: Only overload this method in the implementation if you know + * what you are doing. + */ + template + NumEqVector scvPointSources(const Element &element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const SubControlVolume &scv) const + { + NumEqVector source(0); + auto scvIdx = scv.indexInElement(); + auto key = std::make_pair(gridGeometry_->elementMapper().index(element), scvIdx); + if (pointSourceMap_.count(key)) + { + // Add the contributions to the dof source values + // We divide by the volume. In the local residual this will be multiplied with the same + // factor again. That's because the user specifies absolute values in kg/s. + const auto volume = Extrusion::volume(fvGeometry, scv)*elemVolVars[scv].extrusionFactor(); + + for (const auto& ps : pointSourceMap_.at(key)) + { + // we make a copy of the local point source here + auto pointSource = ps; + + // Note: two concepts are implemented here. The PointSource property can be set to a + // customized point source function achieving variable point sources, + // see TimeDependentPointSource for an example. The second imitated the standard + // dumux source interface with solDependentPointSource / pointSourceAtPos, methods + // that can be overloaded in the actual problem class also achieving variable point sources. + // The first one is more convenient for simple function like a time dependent source. + // The second one might be more convenient for e.g. a solution dependent point source. + + // we do an update e.g. used for TimeDependentPointSource + pointSource.update(asImp_(), element, fvGeometry, elemVolVars, scv); + // call convenience problem interface function + asImp_().pointSource(pointSource, element, fvGeometry, elemVolVars, scv); + // at last take care about multiplying with the correct volume + pointSource /= volume*pointSource.embeddings(); + // add the point source values to the local residual + source += pointSource.values(); + } + } + + return source; + } + + /*! + * \brief Compute the point source map, i.e. which scvs have point source contributions + * \note Call this on the problem before assembly if you want to enable point sources set + * via the addPointSources member function. + */ + void computePointSourceMap() + { + // clear the given point source maps in case it's not empty + pointSourceMap_.clear(); + + // get and apply point sources if any given in the problem + std::vector sources; + asImp_().addPointSources(sources); + + // if there are point sources calculate point source locations and save them in a map + if (!sources.empty()) + PointSourceHelper::computePointSourceMap(*gridGeometry_, sources, pointSourceMap_, paramGroup()); + } + + /*! + * \brief Get the point source map. It stores the point sources per scv + */ + const PointSourceMap& pointSourceMap() const + { return pointSourceMap_; } + + /*! + * \brief Applies the initial solution for all degrees of freedom of the grid. + * \param sol the initial solution vector + */ + template + void applyInitialSolution(SolutionVector& sol) const + { + assembleInitialSolution(sol, asImp_()); + } + + /*! + * \brief Evaluate the initial value for a entity + * + * \param entity The dof entity + */ + template + PrimaryVariables initial(const Entity& entity) const + { + return asImp_().initialAtPos(entity.geometry().center()); + } + + /*! + * \brief Evaluate the initial value for a control volume. + * + * \param globalPos The global position + */ + PrimaryVariables initialAtPos(const GlobalPosition &globalPos) const + { + // Throw an exception (there is no reasonable default value + // for initial values) + DUNE_THROW(Dune::InvalidStateException, + "The problem does not provide " + "an initial() or an initialAtPos() method."); + } + + //! The finite volume grid geometry + const GridGeometry& gridGeometry() const + { return *gridGeometry_; } + + //! The parameter group in which to retrieve runtime parameters + const std::string& paramGroup() const + { return paramGroup_; } + +protected: + //! Returns the implementation of the problem (i.e. static polymorphism) + Implementation &asImp_() + { return *static_cast(this); } + + //! \copydoc asImp_() + const Implementation &asImp_() const + { return *static_cast(this); } + +private: + //! The finite volume grid geometry + std::shared_ptr gridGeometry_; + + //! The parameter group in which to retrieve runtime parameters + std::string paramGroup_; + + //! The name of the problem + std::string problemName_; + + //! A map from an scv to a vector of point sources + PointSourceMap pointSourceMap_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/fvproblemwithspatialparams.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/fvproblemwithspatialparams.hh new file mode 100644 index 0000000..808ebb6 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/fvproblemwithspatialparams.hh @@ -0,0 +1,76 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Core + * \brief Base class for all finite volume problems that are parameterized. + */ +#ifndef DUMUX_COMMON_FV_PROBLEM_WITH_SPATIAL_PARAMS_HH +#define DUMUX_COMMON_FV_PROBLEM_WITH_SPATIAL_PARAMS_HH + +#include + +#include +#include + +namespace Dumux { + +/*! + * \ingroup Core + * \brief Base class for all finite-volume problems using spatial parameters. + */ +template +class FVProblemWithSpatialParams +: public FVProblem +{ + using ParentType = FVProblem; + using GridGeometry = GetPropType; + +public: + using SpatialParams = GetPropType; + + /*! + * \brief Constructor + * \param gridGeometry The finite volume grid geometry + * \param paramGroup The parameter group in which to look for runtime parameters first (default is "") + * \note This constructor assumes the spatial parameters to be constructible from a grid geometry + */ + FVProblemWithSpatialParams(std::shared_ptr gridGeometry, + const std::string& paramGroup = "") + : ParentType(gridGeometry, paramGroup) + , spatialParams_(std::make_shared(gridGeometry)) + {} + + /*! + * \brief Constructor + * \param gridGeometry The finite volume grid geometry + * \param spatialParams The spatially varying parameters + * \param paramGroup The parameter group in which to look for runtime parameters first (default is "") + */ + FVProblemWithSpatialParams(std::shared_ptr gridGeometry, + std::shared_ptr spatialParams, + const std::string& paramGroup = "") + : ParentType(gridGeometry, paramGroup) + , spatialParams_(spatialParams) + {} + + //! Return a reference to the underlying spatial parameters + const SpatialParams& spatialParams() const + { return *spatialParams_; } + + //! Return a reference to the underlying spatial parameters + SpatialParams& spatialParams() + { return *spatialParams_; } + +private: + //! Spatially varying parameters + std::shared_ptr spatialParams_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/fvspatialparams.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/fvspatialparams.hh new file mode 100644 index 0000000..dfb795c --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/fvspatialparams.hh @@ -0,0 +1,149 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Core + * \ingroup SpatialParameters + * \brief Basic spatial parameters to be used with finite-volume schemes. + */ +#ifndef DUMUX_COMMON_FV_SPATIAL_PARAMS_HH +#define DUMUX_COMMON_FV_SPATIAL_PARAMS_HH + +#include + +#include +#include + +#include + +namespace Dumux { + +/*! + * \ingroup Core + * \ingroup SpatialParameters + * \brief The base class for spatial parameters used with finite-volume schemes. + */ +template +class FVSpatialParams +{ + using GridView = typename GridGeometry::GridView; + using Element = typename GridView::template Codim<0>::Entity; + using SubControlVolume = typename GridGeometry::SubControlVolume; + + static constexpr int dimWorld = GridView::dimensionworld; + + using GlobalPosition = typename Element::Geometry::GlobalCoordinate; + using GravityVector = Dune::FieldVector; + +public: + FVSpatialParams(std::shared_ptr gridGeometry) + : gridGeometry_(gridGeometry) + , gravity_(0.0) + { + if (getParam("Problem.EnableGravity")) + gravity_[dimWorld-1] = -9.81; + } + + /*! + * \brief Return how much the domain is extruded at a given sub-control volume. + * + * This means the factor by which a lower-dimensional (1D or 2D) + * entity needs to be expanded to get a full dimensional cell. The + * default is 1.0 which means that 1D problems are actually + * thought as pipes with a cross section of 1 m^2 and 2D problems + * are assumed to extend 1 m to the back. + */ + template + Scalar extrusionFactor(const Element& element, + const SubControlVolume& scv, + const ElementSolution& elemSol) const + { + // forward to generic interface + return asImp_().extrusionFactorAtPos(scv.center()); + } + + /*! + * \brief Return how much the domain is extruded at a given position. + */ + Scalar extrusionFactorAtPos(const GlobalPosition& globalPos) const + { + // As a default, i.e. if the user's spatial parameters do not overload + // any extrusion factor method, return 1.0 + return 1.0; + } + + /*! + * \brief Return the temperature in the given sub-control volume. + */ + template + Scalar temperature(const Element& element, + const SubControlVolume& scv, + const ElementSolution& elemSol) const + { + // forward to generic interface + return asImp_().temperatureAtPos(scv.center()); + } + + /*! + * \brief Return the temperature in the domain at the given position + * \param globalPos The position in global coordinates where the temperature should be specified. + */ + Scalar temperatureAtPos(const GlobalPosition& globalPos) const + { + static const Scalar defaultTemperature = [] () + { + Scalar defaultTemp = 293.15; // 20°C + if (!hasParam("SpatialParams.Temperature")) + { + std::cout << " -- Using the default temperature of " << defaultTemp << " in the entire domain. " + << "Overload temperatureAtPos() in your spatial params class to define a custom temperature field." + << "Or provide the preferred domain temperature via the SpatialParams.Temperature parameter." + << std::endl; + } + const Scalar temperature = getParam("SpatialParams.Temperature", defaultTemp); + return temperature; + } (); + + return defaultTemperature; + } + + /*! + * \brief Returns the acceleration due to gravity \f$\mathrm{[m/s^2]}\f$. + * + * The default behaviour is a constant gravity vector; + * if the Problem.EnableGravity parameter is true, + * \f$\boldsymbol{g} = ( 0,\dots,\ -9.81)^T \f$, + * else \f$\boldsymbol{g} = ( 0,\dots, 0)^T \f$. + * + * \param pos the spatial position at which to evaluate the gravity vector + */ + const GravityVector& gravity(const GlobalPosition& pos) const + { return gravity_; } + + //! The finite volume grid geometry + const GridGeometry& gridGeometry() const + { return *gridGeometry_; } + +protected: + //! Returns the implementation of the spatial parameters (static polymorphism) + Implementation &asImp_() + { return *static_cast(this); } + + //! \copydoc asImp_() + const Implementation &asImp_() const + { return *static_cast(this); } + +private: + std::shared_ptr gridGeometry_; + GravityVector gravity_; +}; + +} // namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/gridcapabilities.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/gridcapabilities.hh new file mode 100644 index 0000000..f0fb6c3 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/gridcapabilities.hh @@ -0,0 +1,79 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Core + * \brief dune-grid capabilities compatibility layer + */ +#ifndef DUMUX_COMMON_GRID_CAPABILITIES_HH +#define DUMUX_COMMON_GRID_CAPABILITIES_HH + +#include + +// TODO: The following is a temporary solution to make canCommunicate work. +// Once it is resolved upstream +// (https://gitlab.dune-project.org/core/dune-grid/issues/78), +// it should be guarded by a DUNE_VERSION macro and removed later. + +#if HAVE_DUNE_UGGRID +namespace Dune { +template +class UGGrid; +} // end namespace Dumux +#endif // HAVE_DUNE_UGGRID + +namespace Dumux::Temp::Capabilities { + +template +struct canCommunicate +{ + static const bool v = false; +}; + +#if HAVE_DUNE_UGGRID +template +struct canCommunicate, codim> +{ + static const bool v = true; +}; +#endif // HAVE_DUNE_UGGRID + +} // namespace Dumux::Temp::Capabilities +// end workaround + +namespace Dumux::Detail { + +template +static constexpr bool canCommunicate = + Dune::Capabilities::canCommunicate::v + || Dumux::Temp::Capabilities::canCommunicate::v; + +} // namespace Dumux + +namespace Dumux::Grid::Capabilities { + +// Default implementation +// The grid capability gives an absolute guarantee +// however this does not mean that multithreading is not +// supported at all. It might still work for some special cases. +// This knowledge is encoded in specializations for the different +// grid managers, see dumux/grid/io/gridmanager_*.hh +template +struct MultithreadingSupported +{ + template + static bool eval(const GV&) // default is independent of the grid view + { return Dune::Capabilities::viewThreadSafe::v; } +}; + +template +inline bool supportsMultithreading(const GridView& gridView) +{ return MultithreadingSupported::eval(gridView); } + +} // namespace Dumux::Grid::Capabilities + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/indextraits.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/indextraits.hh new file mode 100644 index 0000000..fc5de8e --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/indextraits.hh @@ -0,0 +1,34 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Core + * \brief Defines the index types used for grid and local indices. + */ +#ifndef DUMUX_COMMON_INDEX_TRAITS_HH +#define DUMUX_COMMON_INDEX_TRAITS_HH + +#include + +namespace Dumux { + +/*! + * \ingroup Core + * \brief Structure to define the index types used for grid and local indices. + * \tparam GridView The grid view type + */ +template +struct IndexTraits +{ + using GridIndex = typename GridView::IndexSet::IndexType; + using LocalIndex = unsigned int; + using SmallLocalIndex = std::uint_least8_t; +}; + +} // namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/initialize.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/initialize.hh new file mode 100644 index 0000000..e3bf82d --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/initialize.hh @@ -0,0 +1,109 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Core + * \brief The initialize function to be called before using Dumux + */ +#ifndef DUMUX_COMMON_INITIALIZE_HH +#define DUMUX_COMMON_INITIALIZE_HH + +#include +#include +#include + +#include + +#if HAVE_TBB +#include +#include + +#ifndef DOXYGEN +namespace Dumux::Detail { + +class TBBGlobalControl +{ +public: + static oneapi::tbb::global_control& instance(int& argc, char* argv[]) + { + int maxNumThreads = oneapi::tbb::info::default_concurrency(); + if (const char* dumuxNumThreads = std::getenv("DUMUX_NUM_THREADS")) + maxNumThreads = std::max(1, std::stoi(std::string{ dumuxNumThreads })); + + static oneapi::tbb::global_control global_limit( + oneapi::tbb::global_control::max_allowed_parallelism, maxNumThreads + ); + + return global_limit; + } +}; + +} // namespace Dumux::Detail +#endif // DOXYGEN + +#endif // HAVE_TBB + + +#if DUMUX_HAVE_OPENMP +#include +#endif // DUMUX_HAVE_OPENMP + + +#if DUMUX_HAVE_KOKKOS +#include + +#ifndef DOXYGEN +namespace Dumux::Detail { + +class KokkosScopeGuard +{ +public: + static Kokkos::ScopeGuard& instance(int& argc, char* argv[]) + { + Kokkos::InitArguments arguments; + if (const char* dumuxNumThreads = std::getenv("DUMUX_NUM_THREADS")) + arguments.num_threads = std::max(1, std::stoi(std::string{ dumuxNumThreads })); + + static Kokkos::ScopeGuard guard(arguments); + return guard; + } +}; + +} // namespace Dumux::Detail +#endif // DOXYGEN + +#endif // DUMUX_HAVE_KOKKOS + +namespace Dumux { + +void initialize(int& argc, char* argv[]) +{ + // initialize MPI if available + // otherwise this will create a sequential (fake) helper + Dune::MPIHelper::instance(argc, argv); + +#if HAVE_TBB + // initialize TBB and keep global control alive + Detail::TBBGlobalControl::instance(argc, argv); +#endif + +#if DUMUX_HAVE_OPENMP + if (const char* dumuxNumThreads = std::getenv("DUMUX_NUM_THREADS")) + omp_set_num_threads( + std::max(1, std::stoi(std::string{ dumuxNumThreads })) + ); +#endif + +#if DUMUX_HAVE_KOKKOS + // initialize Kokkos (command line / environmental variable interface) + Detail::KokkosScopeGuard::instance(argc, argv); +#endif +} + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/loggingparametertree.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/loggingparametertree.hh new file mode 100644 index 0000000..edf80ea --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/loggingparametertree.hh @@ -0,0 +1,558 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Parameter + * \brief A parameter tree that logs which parameters have been used + */ +#ifndef DUMUX_LOGGING_PARAMETER_TREE_HH +#define DUMUX_LOGGING_PARAMETER_TREE_HH + +#include +#include +#include +#include +#include + +#include +#include + +namespace Dumux { + +/*! + * \ingroup Parameter + * \brief A parameter tree that logs which parameters have been used + */ +class LoggingParameterTree +{ + +public: + /* + * \brief A logging parameter tree is always attached to an existingparameter tree + */ + LoggingParameterTree() = delete; + + /* + * \brief Create LoggingParameterTree from ParameterTree + */ + LoggingParameterTree(const Dune::ParameterTree& params, const Dune::ParameterTree& defaultParams) + : params_(params) + , defaultParams_(defaultParams) + , usedRuntimeParams_(std::make_unique()) + , usedDefaultParams_(std::make_unique()) + {} + + /** \brief test for key + * + * Tests whether given key exists. + * + * \note This ignores defaults. Hence, if the + * the specified key only exists in the defaults, this + * function returns false + * + * \param key key name + * \return true if key exists in structure, otherwise false + */ + bool hasKey(const std::string& key) const + { return params_.hasKey(key); } + + /** \brief test for key in group + * + * Tests whether given key exists in a group. + * Given a group this function starts to look from the back + * for dots. In G1.G2.G3 the function first looks if the key + * "G3.Key" exists, then "G2.Key", ... + * + * \note This ignores defaults. Hence, if the + * the specified key only exists in the defaults, this + * function returns false + * + * \param key key name + * \param groupPrefix the group prefix name + * \return true if key exists in structure, otherwise false + */ + bool hasKeyInGroup(const std::string& key, + const std::string& groupPrefix) const + { + if (groupPrefix.empty()) + return hasKey(key); + + if (hasKey(key)) + return true; + + auto compoundKey = groupPrefix + "." + key; + if (params_.hasKey(compoundKey)) + return true; + + compoundKey = findKeyInGroup(params_, key, groupPrefix); + if (compoundKey != "") + return true; + + return false; + } + + /** \brief obtain a vector of all full group names for a specified subgroup name + * + * Example: + * ------------ + * For the parameter tree + * + * [G1] + * MyParam1 = 1 + * [G2.G1] + * MyParam2 = 2 + * [G3.G2.G1] + * MyParam3 = 3 + * + * and groupPrefix="G3.G2" and subGroupName="G1" + * this returns a vector with the entries {"G3.G2.G1", "G2.G1", "G1"}. + * If groupPrefix = "G2", it returns {"G2.G1", "G1"}. + * If groupPrefix = "" the returned vector has size 1 (containing subGroupName), + * or size 0 if the subgroup does not exist in the parameter tree. + * + * \param subGroupName the sub group to look for + * \param groupPrefix the group prefix name (potentially prefixing the subgroup) + * \return a vector of fully qualified groups ordered by decreasing tree depth + */ + std::vector getSubGroups(const std::string& subGroupName, + std::string groupPrefix) const + { + std::vector groupNames; + + if (!groupPrefix.empty()) + { + auto compoundGroup = groupPrefix + "." + subGroupName; + for (std::string::size_type dotPos = 0; dotPos != std::string::npos; dotPos = groupPrefix.rfind(".")) + { + if (params_.hasSub(compoundGroup) || defaultParams_.hasSub(compoundGroup)) + groupNames.push_back(compoundGroup); + + groupPrefix.resize(dotPos); + compoundGroup = groupPrefix + "." + subGroupName; + } + } + + if (params_.hasSub(subGroupName) || defaultParams_.hasSub(subGroupName)) + groupNames.push_back(subGroupName); + + return groupNames; + } + + /** \brief print the hierarchical parameter tree to stream + * + * \param stream the output stream to print to + */ + void report(std::ostream& stream = std::cout) const + { params_.report(stream); } + + /** \brief print distinct substructure to stream + * + * Prints all entries with given prefix. + * + * \param stream Stream to print to + */ + void reportAll(std::ostream& stream = std::cout) const + { + stream << "\n# Runtime-specified parameters used:" << std::endl; + usedRuntimeParams_->report(stream); + + stream << "\n# Global default parameters used:" << std::endl; + usedDefaultParams_->report(stream); + + const auto unusedParams = getUnusedKeys(); + if (!unusedParams.empty()) + { + stream << "\n# Unused parameters:" << std::endl; + for (const auto& key : unusedParams) + stream << key << " = \"" << params_[key] << "\"" << std::endl; + } + } + + /*! \brief Do a backwards hierarchical search for a key in a group + * + * \note Given a group this function starts to look from the back + * for dots. In G1.G2.G3 the function first looks if the key + * "G3.Key" exists, then "G2.Key", ... + * The first compound key that is found is returned. If no + * compound key is found an empty string is returned. + * \param tree The tree to look in for keys + * \param key The key + * \param groupPrefix the group prefix attached to the key + */ + std::string findKeyInGroup(const Dune::ParameterTree& tree, + const std::string& key, + const std::string& groupPrefix) const + { + // search backwards until key is found + std::string prefix = groupPrefix; + auto dot = prefix.rfind("."); + while (dot != std::string::npos) + { + prefix.resize(dot); + std::string compoundKey = prefix + "." + key; + + if (tree.hasKey(compoundKey)) + return compoundKey; + + // look for the next dot in the current prefix + dot = prefix.rfind("."); + + } + + // the key was not found + return ""; + } + + /** \brief get value as string + * + * Returns pure string value for given key. + * + * \param key key name + * \param defaultValue default if key does not exist + * \return value as string + * \note This might be quite slow so call it only once, + * e.g. on initialization of the class configured by runtime parameters + */ + std::string get(const std::string& key, const std::string& defaultValue) const + { + if (params_.hasKey(key)) + { + // log that we used this parameter + const auto returnValue = params_[key]; + logUsedRuntimeParam_(key, returnValue); + return returnValue; + } + + return defaultValue; + } + + /** \brief get value as string, preferably from the sub-tree corresponding + * to a given prefix. The sub-tree is searched backwards for the parameter + * until its "first" occurrence. + * + * Returns pure string value for given key. + * + * \param groupPrefix The prefix of the sub tree the search should start in + * \param key key name + * \param defaultValue default if key does not exist + * \return value as string + * \note This might be quite slow so call it only once, + * e.g. on initialization of the class configured by runtime parameters + */ + std::string getFromGroup(const std::string& groupPrefix, + const std::string& key, + const std::string& defaultValue) const + { + if (groupPrefix.empty()) + return get(key, defaultValue); + + // first, look for the compound key + std::string compoundKey = groupPrefix + "." + key; + if (params_.hasKey(compoundKey)) + { + // log that we used this parameter + const auto returnValue = params_[compoundKey]; + logUsedRuntimeParam_(compoundKey, returnValue); + return returnValue; + } + + // search backwards until key is found + compoundKey = findKeyInGroup(params_, key, groupPrefix); + if (compoundKey != "") + { + // log that we used this parameter + const auto returnValue = params_[compoundKey]; + logUsedRuntimeParam_(compoundKey, returnValue); + return returnValue; + } + + // finally, look for the key without prefix + return get(key, defaultValue); + } + + /** \brief get value as string + * + * Returns pure string value for given key. + * + * \todo This is a hack so get("my_key", "xyz") compiles + * (without this method "xyz" resolves to bool instead of std::string) + * \param key key name + * \param defaultValue default if key does not exist + * \return value as string + * \note This might be quite slow so call it only once, + * e.g. on initialization of the class configured by runtime parameters + */ + std::string get(const std::string& key, const char* defaultValue) const + { + const std::string dv = defaultValue; + return get(key, dv); + } + + /** \brief get value as string, preferably from the sub-tree corresponding + * to a given prefix. The sub-tree is searched for the parameter + * recursively until its "first" occurrence. + * + * Returns pure string value for given key. + * + * \param groupPrefix The prefix of the sub tree the search should start in + * \param key key name + * \param defaultValue default if key does not exist + * \return value as string + * \note This might be quite slow so call it only once, + * e.g. on initialization of the class configured by runtime parameters + */ + std::string getFromGroup(const std::string& groupPrefix, + const std::string& key, + const char* defaultValue) const + { + const std::string dv = defaultValue; + return getFromGroup(groupPrefix, key, dv); + } + + + /** \brief get value converted to a certain type + * + * Returns value as type T for given key. + * + * \tparam T type of returned value. + * \param key key name + * \param defaultValue default if key does not exist + * \return value converted to T + * \note This might be quite slow so call it only once, + * e.g. on initialization of the class configured by runtime parameters + */ + template + T get(const std::string& key, const T& defaultValue) const + { + if (params_.hasKey(key)) + { + // log that we used this parameter + logUsedRuntimeParam_(key, params_[key]); + return params_.template get(key); + } + + return defaultValue; + } + + /** \brief get value as string, preferably from the sub-tree corresponding + * to a given prefix. The sub-tree is searched for the parameter + * recursively until its "first" occurrence. + * + * Returns pure string value for given key. + * + * \param groupPrefix The prefix of the sub tree the search should start in + * \param key key name + * \param defaultValue default if key does not exist + * \return value as string + * \note This might be quite slow so call it only once, + * e.g. on initialization of the class configured by runtime parameters + */ + template + T getFromGroup(const std::string& groupPrefix, + const std::string& key, + const T& defaultValue) const + { + if (groupPrefix.empty()) + return get(key, defaultValue); + + // first, look for the compound key + std::string compoundKey = groupPrefix + "." + key; + if (params_.hasKey(compoundKey)) + { + // log that we used this parameter + logUsedRuntimeParam_(compoundKey, params_[compoundKey]); + return params_.template get(compoundKey); + } + + // search backwards until key is found + compoundKey = findKeyInGroup(params_, key, groupPrefix); + if (compoundKey != "") + { + // log that we used this parameter + logUsedRuntimeParam_(compoundKey, params_[compoundKey]); + return params_.template get(compoundKey); + } + + // finally, look for the key without prefix + return get(key, defaultValue); + } + + /** \brief Get value + * + * \tparam T Type of the value + * \param key Key name + * \throws RangeError if key does not exist + * \throws NotImplemented Type is not supported + * \return value as T + * \note This might be quite slow so call it only once, + * e.g. on initialization of the class configured by runtime parameters + */ + template + T get(const std::string& key) const + { + if (params_.hasKey(key)) + { + // log that we used this parameter + logUsedRuntimeParam_(key, params_[key]); + return params_.template get(key); + } + + else if(defaultParams_.hasKey(key)) + { + // use the default + logUsedDefaultParam_(key, defaultParams_[key]); + return defaultParams_.template get(key); + } + + DUNE_THROW(Dumux::ParameterException, "Key " << key << " not found in the parameter tree"); + } + + /** \brief get value as string, preferably from the sub-tree corresponding + * to a given prefix. The sub-tree is searched for the parameter + * recursively until its "first" occurrence. + * + * Returns pure string value for given key. + * + * \param groupPrefix The prefix of the sub tree the search should start in + * \param key key name + * \return value as string + * \note This might be quite slow so call it only once, + * e.g. on initialization of the class configured by runtime parameters + */ + template + T getFromGroup(const std::string& groupPrefix, + const std::string& key) const + { + if (groupPrefix.empty()) + return get(key); + + // first, look for the compound key + std::string compoundKey = groupPrefix + "." + key; + if (params_.hasKey(compoundKey)) + { + // log that we used this parameter + logUsedRuntimeParam_(compoundKey, params_[compoundKey]); + return params_.template get(compoundKey); + } + + // search backwards until key is found + compoundKey = findKeyInGroup(params_, key, groupPrefix); + if (compoundKey != "") + { + // log that we used this parameter + logUsedRuntimeParam_(compoundKey, params_[compoundKey]); + return params_.template get(compoundKey); + } + + // reset the compoundKey + compoundKey = groupPrefix + "." + key; + + // if the backward search did not succeed, try the bare key without any prefix + if (params_.hasKey(key)) + { + // log that we used this parameter + logUsedRuntimeParam_(key, params_[key]); + return params_.template get(key); + } + + // if this did not work, repeat the procedure using the default parameters + else if(defaultParams_.hasKey(compoundKey)) + { + // use the default + logUsedDefaultParam_(compoundKey, defaultParams_[compoundKey]); + return defaultParams_.template get(compoundKey); + } + + else + { + // search backwards until key is found + compoundKey = findKeyInGroup(defaultParams_, key, groupPrefix); + if (compoundKey != "") + { + // log that we used this parameter + logUsedDefaultParam_(compoundKey, defaultParams_[compoundKey]); + return defaultParams_.template get(compoundKey); + } + + if(defaultParams_.hasKey(key)) + { + // use the default + logUsedDefaultParam_(key, defaultParams_[key]); + return defaultParams_.template get(key); + } + + DUNE_THROW(Dumux::ParameterException, "Key " << key << " not found in the parameter tree with group prefix " << groupPrefix); + } + } + + /** \brief Find the keys that haven't been used yet + * + * \return unusedParams Container storing unused keys + * \note Useful for debugging purposes + */ + std::vector getUnusedKeys() const + { + std::vector unusedParams; + findUnusedKeys_(params_, unusedParams); + return unusedParams; + } + +private: + /** \brief Find the keys that haven't been used yet recursively + * + * \param tree The tree to look in for unused keys + * \param unusedParams Container to store unused keys + * \param prefix the prefix attached to the key + */ + void findUnusedKeys_(const Dune::ParameterTree& tree, + std::vector& unusedParams, + const std::string& prefix = "") const + { + // loop over all keys of the current tree + // store keys which were not accessed + const auto& keys = tree.getValueKeys(); + for (const auto& key : keys) + if (key != "ParameterFile" && !usedRuntimeParams_->hasKey(prefix + key)) + unusedParams.push_back(prefix + key); + + // recursively loop over all subtrees + const auto& subTreeKeys = tree.getSubKeys(); + for (const auto& key : subTreeKeys) + findUnusedKeys_(tree.sub(key), unusedParams, prefix + key + "."); + } + + /** \brief Log the key value pair as used runtime param + */ + void logUsedRuntimeParam_(const std::string& key, const std::string& value) const + { + std::scoped_lock lock{ usedRuntimeMutex_ }; + usedRuntimeParams_->operator[](key) = value; + } + + /** \brief Log the key value pair as used default param + */ + void logUsedDefaultParam_(const std::string& key, const std::string& value) const + { + std::scoped_lock lock{ usedDefaultMutex_ }; + usedDefaultParams_->operator[](key) = value; + } + + const Dune::ParameterTree& params_; + const Dune::ParameterTree& defaultParams_; + + // logging caches (externally stored) + std::unique_ptr usedRuntimeParams_; + std::unique_ptr usedDefaultParams_; + + // access to the caches have to be protected for thread-safety + mutable std::mutex usedRuntimeMutex_; + mutable std::mutex usedDefaultMutex_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/math.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/math.hh new file mode 100644 index 0000000..3bcc3f7 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/math.hh @@ -0,0 +1,955 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Core + * \brief Define some often used mathematical functions + */ +#ifndef DUMUX_MATH_HH +#define DUMUX_MATH_HH + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace Dumux { + +/*! + * \ingroup Core + * \brief Calculate the (weighted) arithmetic mean of two scalar values. + * + * \param x The first input value + * \param y The second input value + * \param wx The first weight + * \param wy The second weight + */ +template +constexpr Scalar arithmeticMean(Scalar x, Scalar y, Scalar wx = 1.0, Scalar wy = 1.0) noexcept +{ + static_assert(Dune::IsNumber::value, "The arguments x, y, wx, and wy have to be numbers!"); + + if (x*y <= 0) + return 0; + return (x * wx + y * wy)/(wx + wy); +} + +/*! + * \ingroup Core + * \brief Calculate the (weighted) harmonic mean of two scalar values. + * + * \param x The first input value + * \param y The second input value + * \param wx The first weight + * \param wy The second weight + */ +template +constexpr Scalar harmonicMean(Scalar x, Scalar y, Scalar wx = 1.0, Scalar wy = 1.0) noexcept +{ + static_assert(Dune::IsNumber::value, "The arguments x, y, wx, and wy have to be numbers!"); + + if (x*y <= 0) + return 0; + return (wx + wy) * x * y / (wy * x + wx * y); +} + +/*! + * \ingroup Core + * \brief Calculate the geometric mean of two scalar values. + * + * \param x The first input value + * \param y The second input value + * \note as std::sqrt is not constexpr this function is not constexpr + */ +template +Scalar geometricMean(Scalar x, Scalar y) noexcept +{ + static_assert(Dune::IsNumber::value, "The arguments x and y have to be numbers!"); + + if (x*y <= 0) + return 0; + using std::sqrt; + return sqrt(x*y)*sign(x); +} + +/*! + * \ingroup Core + * \brief Calculate the harmonic mean of a fixed-size matrix. + * + * This is done by calculating the harmonic mean for each entry + * individually. The result is stored the first argument. + * + * \param K The matrix for storing the result + * \param Ki The first input matrix + * \param Kj The second input matrix + */ +template +void harmonicMeanMatrix(Dune::FieldMatrix &K, + const Dune::FieldMatrix &Ki, + const Dune::FieldMatrix &Kj) +{ + for (int rowIdx=0; rowIdx < m; rowIdx++){ + for (int colIdx=0; colIdx< n; colIdx++){ + if (Dune::FloatCmp::ne(Ki[rowIdx][colIdx], Kj[rowIdx][colIdx])) { + K[rowIdx][colIdx] = + harmonicMean(Ki[rowIdx][colIdx], + Kj[rowIdx][colIdx]); + } + else + K[rowIdx][colIdx] = Ki[rowIdx][colIdx]; + } + } +} + +/*! + * \ingroup Core + * \brief A smoothed minimum function (using cubic interpolation) + * \note The returned value is guaranteed to be smaller or equal to the minimum of the two input values. + * \note The smoothing kicks in if the difference between the two input values is smaller than the smoothing parameter. + * \param a The first input value + * \param b The second input value + * \param k The smoothing parameter (k > 0) + */ +template +Scalar smoothMin(const Scalar a, const Scalar b, const Scalar k) +{ + using std::max; using std::min; using std::abs; + const auto h = max(k-abs(a-b), 0.0 )/k; + return min(a, b) - h*h*h*k*(1.0/6.0); +} + +/*! + * \ingroup Core + * \brief A smoothed maximum function (using cubic interpolation) + * + * \param a The first input value + * \param b The second input value + * \param k The smoothing parameter (k > 0) + */ +template +Scalar smoothMax(const Scalar a, const Scalar b, const Scalar k) +{ return -smoothMin(-a, -b, k); } + +/*! + * \ingroup Core + * \brief Invert a linear polynomial analytically + * + * The polynomial is defined as + * \f[ p(x) = a\; x + b \f] + * + * This method Returns the number of solutions which are in the real + * numbers, i.e. 1 except if the slope of the line is 0. + * + * \param sol Container into which the solutions are written + * \param a The coefficiont for the linear term + * \param b The coefficiont for the constant term + */ +template +int invertLinearPolynomial(SolContainer &sol, + Scalar a, + Scalar b) +{ + if (a == 0.0) + return 0; + + sol[0] = -b/a; + return 1; +} + +/*! + * \ingroup Core + * \brief Invert a quadratic polynomial analytically + * + * The polynomial is defined as + * \f[ p(x) = a\; x^2 + + b\;x + c \f] + * + * This method returns the number of solutions which are in the real + * numbers. The "sol" argument contains the real roots of the parabola + * in order with the smallest root first. + * + * \param sol Container into which the solutions are written + * \param a The coefficiont for the quadratic term + * \param b The coefficiont for the linear term + * \param c The coefficiont for the constant term + */ +template +int invertQuadraticPolynomial(SolContainer &sol, + Scalar a, + Scalar b, + Scalar c) +{ + // check for a line + if (a == 0.0) + return invertLinearPolynomial(sol, b, c); + + // discriminant + Scalar Delta = b*b - 4*a*c; + if (Delta < 0) + return 0; // no real roots + + using std::sqrt; + Delta = sqrt(Delta); + sol[0] = (- b + Delta)/(2*a); + sol[1] = (- b - Delta)/(2*a); + + // sort the result + if (sol[0] > sol[1]) + { + using std::swap; + swap(sol[0], sol[1]); + } + return 2; // two real roots +} + +//! \cond false +template +void invertCubicPolynomialPostProcess_(SolContainer &sol, + int numSol, + Scalar a, Scalar b, Scalar c, Scalar d, + std::size_t numIterations = 1) +{ + const auto eval = [&](auto x){ return d + x*(c + x*(b + x*a)); }; + const auto evalDeriv = [&](auto x){ return c + x*(2*b + x*3*a); }; + + // do numIterations Newton iterations on the analytic solution + // and update result if the precision is increased + for (int i = 0; i < numSol; ++i) + { + Scalar x = sol[i]; + Scalar fCurrent = eval(x); + const Scalar fOld = fCurrent; + for (int j = 0; j < numIterations; ++j) + { + const Scalar fPrime = evalDeriv(x); + if (fPrime == 0.0) + break; + x -= fCurrent/fPrime; + fCurrent = eval(x); + } + + using std::abs; + if (abs(fCurrent) < abs(fOld)) + sol[i] = x; + } +} +//! \endcond + +/*! + * \ingroup Core + * \brief Invert a cubic polynomial analytically + * + * The polynomial is defined as + * \f[ p(x) = a\; x^3 + + b\;x^3 + c\;x + d \f] + * + * This method returns the number of solutions which are in the real + * numbers. The "sol" argument contains the real roots of the cubic + * polynomial in order with the smallest root first. + * + * \note The closer the roots are to each other the less + * precise the inversion becomes. Increase number of post-processing iterations for improved results. + * + * \param sol Container into which the solutions are written + * \param a The coefficient for the cubic term + * \param b The coefficient for the quadratic term + * \param c The coefficient for the linear term + * \param d The coefficient for the constant term + * \param numPostProcessIterations The number of iterations to increase precision of the analytical result + */ +template +int invertCubicPolynomial(SolContainer *sol, + Scalar a, Scalar b, Scalar c, Scalar d, + std::size_t numPostProcessIterations = 1) +{ + // reduces to a quadratic polynomial + if (a == 0) + return invertQuadraticPolynomial(sol, b, c, d); + + // normalize the polynomial + b /= a; + c /= a; + d /= a; + a = 1; + + // get rid of the quadratic term by substituting x = t - b/3 + Scalar p = c - b*b/3; + Scalar q = d + (2*b*b*b - 9*b*c)/27; + + // now we are at the form t^3 + p*t + q = 0. First we handle some + // special cases to avoid divisions by zero later... + if (p == 0.0 && q == 0.0) { + // t^3 = 0, i.e. triple root at t = 0 + sol[0] = sol[1] = sol[2] = 0.0 - b/3; + return 3; + } + else if (p == 0.0 && q != 0.0) { + // t^3 + q = 0, + // + // i. e. single real root at t=curt(q) + using std::cbrt; + Scalar t = cbrt(q); + sol[0] = t - b/3; + + return 1; + } + else if (p != 0.0 && q == 0.0) { + // t^3 + p*t = 0 = t*(t^2 + p), + // + // i. e. roots at t = 0, t^2 + p = 0 + if (p > 0) { + sol[0] = 0.0 - b/3; + return 1; // only a single real root at t=0 + } + + // two additional real roots at t = sqrt(-p) and t = -sqrt(-p) + using std::sqrt; + sol[0] = -sqrt(-p) - b/3; + sol[1] = 0.0 - b/3; + sol[2] = sqrt(-p) - b/3; + + return 3; + } + + // At this point + // + // t^3 + p*t + q = 0 + // + // with p != 0 and q != 0 holds. Introducing the variables u and v + // with the properties + // + // u + v = t and 3*u*v + p = 0 + // + // leads to + // + // u^3 + v^3 + q = 0 . + // + // multiplying both sides with u^3 and taking advantage of the + // fact that u*v = -p/3 leads to + // + // u^6 + q*u^3 - p^3/27 = 0 + // + // Now, substituting u^3 = w yields + // + // w^2 + q*w - p^3/27 = 0 + // + // This is a quadratic equation with the solutions + // + // w = -q/2 + sqrt(q^2/4 + p^3/27) and + // w = -q/2 - sqrt(q^2/4 + p^3/27) + // + // Since w is equivalent to u^3 it is sufficient to only look at + // one of the two cases. Then, there are still 2 cases: positive + // and negative discriminant. + Scalar wDisc = q*q/4 + p*p*p/27; + if (wDisc >= 0) { // the positive discriminant case: + // calculate the cube root of - q/2 + sqrt(q^2/4 + p^3/27) + using std::cbrt; + using std::sqrt; + + // Choose the root that is safe against the loss of precision + // that can cause wDisc to be equal to q*q/4 despite p != 0. + // We do not want u to be zero in that case. Mathematically, + // we are happy with either root. + const Scalar u = [&]{ + return q > 0 ? cbrt(-0.5*q - sqrt(wDisc)) : cbrt(-0.5*q + sqrt(wDisc)); + }(); + // at this point, u != 0 since p^3 = 0 is necessary in order + // for u = 0 to hold, so + sol[0] = u - p/(3*u) - b/3; + // does not produce a division by zero. the remaining two + // roots of u are rotated by +- 2/3*pi in the complex plane + // and thus not considered here + invertCubicPolynomialPostProcess_(sol, 1, a, b, c, d, numPostProcessIterations); + return 1; + } + else { // the negative discriminant case: + Scalar uCubedRe = - q/2; + using std::sqrt; + Scalar uCubedIm = sqrt(-wDisc); + // calculate the cube root of - q/2 + sqrt(q^2/4 + p^3/27) + using std::cbrt; + Scalar uAbs = cbrt(sqrt(uCubedRe*uCubedRe + uCubedIm*uCubedIm)); + using std::atan2; + Scalar phi = atan2(uCubedIm, uCubedRe)/3; + + // calculate the length and the angle of the primitive root + + // with the definitions from above it follows that + // + // x = u - p/(3*u) - b/3 + // + // where x and u are complex numbers. Rewritten in polar form + // this is equivalent to + // + // x = |u|*e^(i*phi) - p*e^(-i*phi)/(3*|u|) - b/3 . + // + // Factoring out the e^ terms and subtracting the additional + // terms, yields + // + // x = (e^(i*phi) + e^(-i*phi))*(|u| - p/(3*|u|)) - y - b/3 + // + // with + // + // y = - |u|*e^(-i*phi) + p*e^(i*phi)/(3*|u|) . + // + // The crucial observation is the fact that y is the conjugate + // of - x + b/3. This means that after taking advantage of the + // relation + // + // e^(i*phi) + e^(-i*phi) = 2*cos(phi) + // + // the equation + // + // x = 2*cos(phi)*(|u| - p / (3*|u|)) - conj(x) - 2*b/3 + // + // holds. Since |u|, p, b and cos(phi) are real numbers, it + // follows that Im(x) = - Im(x) and thus Im(x) = 0. This + // implies + // + // Re(x) = x = cos(phi)*(|u| - p / (3*|u|)) - b/3 . + // + // Considering the fact that u is a cubic root, we have three + // values for phi which differ by 2/3*pi. This allows to + // calculate the three real roots of the polynomial: + for (int i = 0; i < 3; ++i) { + using std::cos; + sol[i] = cos(phi)*(uAbs - p/(3*uAbs)) - b/3; + phi += 2*M_PI/3; + } + + // post process the obtained solution to increase numerical + // precision + invertCubicPolynomialPostProcess_(sol, 3, a, b, c, d, numPostProcessIterations); + + // sort the result + using std::sort; + sort(sol, sol + 3); + + return 3; + } + + // NOT REACHABLE! + return 0; +} + +/*! + * \ingroup Core + * \brief Comparison of two position vectors + * + * Compares an current position vector with a reference vector, and returns true + * if the position vector is larger. + * "Larger" in this case means that all the entries of each spatial dimension are + * larger compared to the reference vector. + * + * \param pos Vector holding the current Position that is to be checked + * \param smallerVec Reference vector, holding the minimum values for comparison. + */ +template +bool isLarger(const Dune::FieldVector &pos, + const Dune::FieldVector &smallerVec) +{ + for (int i=0; i < dim; i++) + { + if (pos[i]<= smallerVec[i]) + { + return false; + } + } + return true; +} + +/*! + * \ingroup Core + * \brief Comparison of two position vectors + * + * Compares an current position vector with a reference vector, and returns true + * if the position vector is smaller. + * "Smaller" in this case means that all the entries of each spatial dimension are + * smaller in comparison with the reference vector. + * + * \param pos Vector holding the current Position that is to be checked + * \param largerVec Reference vector, holding the maximum values for comparison. + */ +template +bool isSmaller(const Dune::FieldVector &pos, + const Dune::FieldVector &largerVec) +{ + for (int i=0; i < dim; i++) + { + if (pos[i]>= largerVec[i]) + { + return false; + } + } + return true; +} + +/*! + * \ingroup Core + * \brief Comparison of three position vectors + * + * Compares an current position vector with two reference vector, and returns true + * if the position vector lies in between them. + * "Between" in this case means that all the entries of each spatial dimension are + * smaller in comparison with the larger reference vector as well as larger compared + * to the smaller reference. + * This is comfortable to cheack weather the current position is located inside or + * outside of a lense with different properties. + * + * \param pos Vector holding the current Position that is to be checked + * \param smallerVec Reference vector, holding the minimum values for comparison. + * \param largerVec Reference vector, holding the maximum values for comparison. + */ +template +bool isBetween(const Dune::FieldVector &pos, + const Dune::FieldVector &smallerVec, + const Dune::FieldVector &largerVec) +{ + if (isLarger(pos, smallerVec) && isSmaller(pos, largerVec)) + { + return true; + } + else + return false; +} + +//! forward declaration of the linear interpolation policy (default) +namespace InterpolationPolicy { struct Linear; } + +/*! + * \ingroup Core + * \brief a generic function to interpolate given a set of parameters and an interpolation point + * \param params the parameters used for interpolation (depends on the policy used) + * \param ip the interpolation point + */ +template +Scalar interpolate(Scalar ip, Parameter&& ... params) +{ return Policy::interpolate(ip, std::forward(params) ...); } + +/*! + * \ingroup Core + * \brief Interpolation policies + */ +namespace InterpolationPolicy { + +/*! + * \ingroup Core + * \brief interpolate linearly between two given values + */ +struct Linear +{ + /*! + * \brief interpolate linearly between two given values + * \param ip the interpolation point in [0,1] + * \param params array with the lower and upper bound + */ + template + static constexpr Scalar interpolate(Scalar ip, const std::array& params) + { + return params[0]*(1.0 - ip) + params[1]*ip; + } +}; + +/*! + * \ingroup Core + * \brief interpolate linearly in a piecewise linear function (tabularized function) + */ +struct LinearTable +{ + /*! + * \brief interpolate linearly in a piecewise linear function (tabularized function) + * \param ip the interpolation point + * \param range positions of values + * \param values values to interpolate from + * \note if the interpolation point is out of bounds this will return the bounds + */ + template + static constexpr Scalar interpolate(Scalar ip, const RandomAccessContainer0& range, const RandomAccessContainer1& values) + { + // check bounds + if (ip > range[range.size()-1]) return values[values.size()-1]; + if (ip < range[0]) return values[0]; + + // if we are within bounds find the index of the lower bound + const auto lookUpIndex = std::distance(range.begin(), std::lower_bound(range.begin(), range.end(), ip)); + if (lookUpIndex == 0) + return values[0]; + + const auto ipLinear = (ip - range[lookUpIndex-1])/(range[lookUpIndex] - range[lookUpIndex-1]); + return Dumux::interpolate(ipLinear, std::array{{values[lookUpIndex-1], values[lookUpIndex]}}); + } + + template + static constexpr Scalar interpolate(Scalar ip, const std::pair& table) + { + const auto& [range, values] = table; + return interpolate(ip, range, values); + } +}; + +} // end namespace InterpolationPolicy + +/*! + * \ingroup Core + * \brief Generates linearly spaced vectors + * + * \param begin The first value in the vector + * \param end The last value in the vector + * \param samples The size of the vector + * \param endPoint if the range is including the interval's end point or not + */ +template +std::vector linspace(const Scalar begin, const Scalar end, + std::size_t samples, + bool endPoint = true) +{ + using std::max; + samples = max(std::size_t{2}, samples); // only makes sense for 2 or more samples + const Scalar divisor = endPoint ? samples-1 : samples; + const Scalar delta = (end-begin)/divisor; + std::vector vec(samples); + for (std::size_t i = 0; i < samples; ++i) + vec[i] = begin + i*delta; + return vec; +} + + +/*! + * \ingroup Core + * \brief Evaluates the Antoine equation used to calculate the vapour + * pressure of various liquids. + * + * See http://en.wikipedia.org/wiki/Antoine_equation + * + * \param temperature The temperature [K] of the fluid + * \param A The first coefficient for the Antoine equation + * \param B The first coefficient for the Antoine equation + * \param C The first coefficient for the Antoine equation + */ +template +Scalar antoine(Scalar temperature, + Scalar A, + Scalar B, + Scalar C) +{ + const Scalar ln10 = 2.3025850929940459; + using std::exp; + return exp(ln10*(A - B/(C + temperature))); +} + +/*! + * \ingroup Core + * \brief Sign or signum function. + * + * Returns 1 for a positive argument. + * Returns -1 for a negative argument. + * Returns 0 if the argument is zero. + */ +template +constexpr int sign(const ValueType& value) noexcept +{ + return (ValueType(0) < value) - (value < ValueType(0)); +} + +/*! + * \ingroup Core + * \brief Cross product of two vectors in three-dimensional Euclidean space + * + * \param vec1 The first vector + * \param vec2 The second vector + */ +template +Dune::FieldVector crossProduct(const Dune::FieldVector &vec1, + const Dune::FieldVector &vec2) +{ + return {vec1[1]*vec2[2]-vec1[2]*vec2[1], + vec1[2]*vec2[0]-vec1[0]*vec2[2], + vec1[0]*vec2[1]-vec1[1]*vec2[0]}; +} + +/*! + * \ingroup Core + * \brief Cross product of two vectors in two-dimensional Euclidean space retuning scalar + * + * \param vec1 The first vector + * \param vec2 The second vector + */ +template +Scalar crossProduct(const Dune::FieldVector &vec1, + const Dune::FieldVector &vec2) +{ return vec1[0]*vec2[1]-vec1[1]*vec2[0]; } + +/*! + * \ingroup Core + * \brief Triple product of three vectors in three-dimensional Euclidean space retuning scalar + * + * \param vec1 The first vector + * \param vec2 The second vector + * \param vec3 The third vector + */ +template +Scalar tripleProduct(const Dune::FieldVector &vec1, + const Dune::FieldVector &vec2, + const Dune::FieldVector &vec3) +{ return crossProduct(vec1, vec2)*vec3; } + +/*! + * \ingroup Core + * \brief Transpose a FieldMatrix + * + * \param M The matrix to be transposed + */ +template +Dune::FieldMatrix getTransposed(const Dune::FieldMatrix& M) +{ + Dune::FieldMatrix T; + for (std::size_t i = 0; i < m; ++i) + for (std::size_t j = 0; j < n; ++j) + T[j][i] = M[i][j]; + + return T; +} + +/*! + * \ingroup Core + * \brief Transpose a DynamicMatrix + * + * \param M The matrix to be transposed + */ +template +Dune::DynamicMatrix getTransposed(const Dune::DynamicMatrix& M) +{ + std::size_t rows_T = M.M(); + std::size_t cols_T = M.N(); + + Dune::DynamicMatrix M_T(rows_T, cols_T, 0.0); + + for (std::size_t i = 0; i < rows_T; ++i) + for (std::size_t j = 0; j < cols_T; ++j) + M_T[i][j] = M[j][i]; + + return M_T; +} + +/*! + * \ingroup Core + * \brief Multiply two dynamic matrices + * + * \param M1 The first dynamic matrix + * \param M2 The second dynamic matrix (to be multiplied to M1 from the right side) + */ +template +Dune::DynamicMatrix multiplyMatrices(const Dune::DynamicMatrix &M1, + const Dune::DynamicMatrix &M2) +{ + using size_type = typename Dune::DynamicMatrix::size_type; + const size_type rows = M1.N(); + const size_type cols = M2.M(); + + DUNE_ASSERT_BOUNDS(M1.M() == M2.N()); + + Dune::DynamicMatrix result(rows, cols, 0.0); + for (size_type i = 0; i < rows; i++) + for (size_type j = 0; j < cols; j++) + for (size_type k = 0; k < M1.M(); k++) + result[i][j] += M1[i][k]*M2[k][j]; + + return result; +} + +/*! + * \ingroup Core + * \brief Multiply two field matrices + * + * \param M1 The first field matrix + * \param M2 The second field matrix (to be multiplied to M1 from the right side) + */ +template +Dune::FieldMatrix multiplyMatrices(const Dune::FieldMatrix &M1, + const Dune::FieldMatrix &M2) +{ + using size_type = typename Dune::FieldMatrix::size_type; + + Dune::FieldMatrix result(0.0); + for (size_type i = 0; i < rows1; i++) + for (size_type j = 0; j < cols2; j++) + for (size_type k = 0; k < cols1; k++) + result[i][j] += M1[i][k]*M2[k][j]; + + return result; +} + + +/*! + * \ingroup Core + * \brief Trace of a dense matrix + * + * \param M The dense matrix + */ +template +typename Dune::DenseMatrix::field_type +trace(const Dune::DenseMatrix& M) +{ + const auto rows = M.N(); + DUNE_ASSERT_BOUNDS(rows == M.M()); // rows == cols + + using MatType = Dune::DenseMatrix; + typename MatType::field_type trace = 0.0; + + for (typename MatType::size_type i = 0; i < rows; ++i) + trace += M[i][i]; + + return trace; +} + +/*! + * \ingroup Core + * \brief Returns the result of the projection of + * a vector v with a Matrix M. + * + * Note: We use DenseVector and DenseMatrix here so that + * it can be used with the statically and dynamically + * allocated Dune Vectors/Matrices. Size mismatch + * assertions are done in the respective Dune classes. + * + * \param M The matrix + * \param v The vector + */ +template +typename Dune::DenseVector::derived_type +mv(const Dune::DenseMatrix& M, + const Dune::DenseVector& v) +{ + typename Dune::DenseVector::derived_type res(v); + M.mv(v, res); + return res; +} + +/*! + * \ingroup Core + * \brief Returns the result of a vector v multiplied by a scalar m. + * + * Note: We use DenseVector and DenseMatrix here so that + * it can be used with the statically and dynamically + * allocated Dune Vectors/Matrices. Size mismatch + * assertions are done in the respective Dune classes. + * + * \note We need the enable_if to make sure that only Scalars + * fit here. Matrix types are forced to use the above + * mv(DenseMatrix, DenseVector) instead. + * + * \param m The scale factor + * \param v The vector + */ + +template +typename std::enable_if_t::value, + typename Dune::DenseVector::derived_type> +mv(const FieldScalar m, const Dune::DenseVector& v) +{ + typename Dune::DenseVector::derived_type res(v); + res *= m; + return res; +} + +/*! + * \ingroup Core + * \brief Evaluates the scalar product of a vector v2, projected by + * a matrix M, with a vector v1. + * + * Note: We use DenseVector and DenseMatrix here so that + * it can be used with the statically and dynamically + * allocated Dune Vectors/Matrices. Size mismatch + * assertions are done in the respective Dune classes. + * + * \param v1 The first vector + * \param M The matrix + * \param v2 The second vector + */ +template +typename Dune::DenseMatrix::value_type +vtmv(const Dune::DenseVector& v1, + const Dune::DenseMatrix& M, + const Dune::DenseVector& v2) +{ + return v1*mv(M, v2); +} + +/*! + * \ingroup Core + * \brief Evaluates the scalar product of a vector v2, scaled by + * a scalar m, with a vector v1. + * + * Note: We use DenseVector and DenseMatrix here so that + * it can be used with the statically and dynamically + * allocated Dune Vectors/Matrices. Size mismatch + * assertions are done in the respective Dune classes. + * + * \note We need the enable_if to make sure that only Scalars + * fit here. Matrix types are forced to use the above + * vtmv(DenseVector, DenseMatrix, DenseVector) instead. + * + * \param v1 The first vector + * \param m The scale factor + * \param v2 The second vector + */ + +template +typename std::enable_if_t::value, FieldScalar> +vtmv(const Dune::DenseVector& v1, + const FieldScalar m, + const Dune::DenseVector& v2) +{ + return m*(v1*v2); +} + +/*! + * \ingroup Core + * \brief Returns the [intercept, slope] of the regression line + * fitted to a set of (x, y) data points. + * + * Note: We use least-square regression method to find + * the regression line. + * + * \param x x-values of the data set + * \param y y-values of the data set + */ +template +std::array linearRegression(const std::vector& x, + const std::vector& y) +{ + if (x.size() != y.size()) + DUNE_THROW(Dune::InvalidStateException, "x and y array must have the same length."); + + const Scalar averageX = std::accumulate(x.begin(), x.end(), 0.0)/x.size(); + const Scalar averageY = std::accumulate(y.begin(), y.end(), 0.0)/y.size(); + + // calculate temporary variables necessary for slope computation + const Scalar numerator = std::inner_product( + x.begin(), x.end(), y.begin(), 0.0, std::plus(), + [&](auto xx, auto yy) { return (xx - averageX) * (yy - averageY); } + ); + const Scalar denominator = std::inner_product( + x.begin(), x.end(), x.begin(), 0.0, std::plus(), + [&](auto xx, auto yy) { return (xx - averageX) * (yy - averageX); } + ); + + // compute slope and intercept of the regression line + const Scalar slope = numerator / denominator; + const Scalar intercept = averageY - slope * averageX; + + return {intercept, slope}; +} + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/numeqvector.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/numeqvector.hh new file mode 100644 index 0000000..0d447eb --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/numeqvector.hh @@ -0,0 +1,38 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Core + * \brief A helper to deduce a vector with the same size as numbers of equations + */ +#ifndef DUMUX_COMMON_NUMEQVECTOR_HH +#define DUMUX_COMMON_NUMEQVECTOR_HH + +#include + +namespace Dumux { + +template +struct NumEqVectorTraits +{ + static constexpr std::size_t numEq = PrimaryVariables::size(); + using type = PrimaryVariables; +}; + +/*! + * \ingroup Core + * \brief A vector with the same size as numbers of equations + * This is the default implementation and has to be specialized for + * all custom primary variable vector types + * \note This is based on the primary variables concept + */ +template +using NumEqVector = typename NumEqVectorTraits::type; + +} // namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/numericdifferentiation.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/numericdifferentiation.hh new file mode 100644 index 0000000..24ea92f --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/numericdifferentiation.hh @@ -0,0 +1,125 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Core + * \brief A class for numeric differentiation + * + */ +#ifndef DUMUX_NUMERIC_DIFFERENTIATION_HH +#define DUMUX_NUMERIC_DIFFERENTIATION_HH + +#include +#include +#include + +namespace Dumux { + +/*! + * \ingroup Core + * \brief A class for numeric differentiation with respect to a scalar parameter + */ +class NumericDifferentiation +{ +public: + + /*! + * \brief Computes the epsilon used for numeric differentiation + * \param value The value of the variable with respect to which we are differentiating + * \param baseEps The step width which we are using for differentiation + */ + template + static Scalar epsilon(const Scalar value, const Scalar baseEps = 1e-10) + { + assert(std::numeric_limits::epsilon()*1e4 < baseEps); + // the epsilon value used for the numeric differentiation is + // now scaled by the absolute value of the primary variable... + using std::abs; + return baseEps*(abs(value) + 1.0); + } + + /*! + * \brief Computes the derivative of a function with respect to a function parameter + * \note Overload using default epsilon computation + */ + template + static void partialDerivative(const Function& function, Scalar x0, + FunctionEvalType& derivative, + const FunctionEvalType& fx0, + const int numericDifferenceMethod = 1) + { partialDerivative(function, x0, derivative, fx0, epsilon(x0), numericDifferenceMethod); } + + /*! + * \brief Computes the derivative of a function with respect to a function parameter + * \param function The function to derive + * \param x0 The parameter at which the derivative is ought to be evaluated + * \param derivative The partial derivative (output) + * \param fx0 The result of the function evaluated at x0 + * \param eps The numeric epsilon used in the differentiation + * \param numericDifferenceMethod The numeric difference method + * (1: forward differences (default), 0: central differences, -1: backward differences, 5: five-point stencil method) + */ + template + static void partialDerivative(const Function& function, Scalar x0, + FunctionEvalType& derivative, + const FunctionEvalType& fx0, + const Scalar eps, + const int numericDifferenceMethod = 1) + { + // Five-point stencil numeric difference, + // Abramowitz & Stegun, Table 25.2. + // The error is proportional to eps^4. + if (numericDifferenceMethod == 5) + { + derivative = function(x0 + eps); + derivative -= function(x0 - eps); + derivative *= 8.0; + derivative += function(x0 - 2*eps); + derivative -= function(x0 + 2*eps); + derivative /= 12*eps; + return; + } + + // Forward, central, or backward differences + Scalar delta = 0.0; + + // we are using forward or central differences, i.e. we need to calculate f(x + \epsilon) + if (numericDifferenceMethod >= 0) + { + delta += eps; + // calculate the function evaluated with the deflected variable + derivative = function(x0 + eps); + } + + // we are using backward differences, + // i.e. we don't need to calculate f(x + \epsilon) + // we can recycle the (possibly cached) f(x) + else derivative = fx0; + + // we are using backward or central differences, + // i.e. we need to calculate f(x - \epsilon) + if (numericDifferenceMethod <= 0) + { + delta += eps; + // subtract the function evaluated with the deflected variable + derivative -= function(x0 - eps); + } + + // we are using forward differences, + // i.e. we don't need to calculate f(x - \epsilon) + // we can recycle the (possibly cached) f(x) + else derivative -= fx0; + + // divide difference in residuals by the magnitude of the + // deflections between the two function evaluation + derivative /= delta; + } +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/parameters.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/parameters.hh new file mode 100644 index 0000000..bdd59cf --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/parameters.hh @@ -0,0 +1,178 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Parameter + * \brief The infrastructure to retrieve run-time parameters from Dune::ParameterTrees. + */ +#ifndef DUMUX_PARAMETERS_HH +#define DUMUX_PARAMETERS_HH + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace Dumux { + +/*! + * \ingroup Parameter + * \brief Parameter class managing runtime input parameters + * \todo Doc me! + */ +class Parameters { + + using DefaultParams = std::function; + using Usage = std::function; + +public: + + //! Initialize the parameter tree singletons + static void init(int argc, char **argv, const Usage& usage); + + //! Initialize the parameter tree singletons + static void init(int argc, char **argv, + std::string parameterFileName, + const Usage& usage = [](const char *, const std::string &){}); + + //! Initialize the parameter tree singletons + static void init(int argc, char **argv, + const DefaultParams& defaultParams, + const Usage& usage); + + /*! + * \brief Initialize the parameter tree + * \param argc number of command line argument (forwarded from main) + * \param argv command line argument (forwarded from main) + * \param defaultParams a function that sets parameters of the default runtime parameter tree + * \param parameterFileName the file name of the input file + * \param usage the usage function to print if the help option was passed on the command line + * \note the default parameter tree is initialized in the following way + * 1) global defaults (see member function applyGlobalDefaults_) + * 2) user provided defaults (overwrite global defaults) + * the parameter tree is initialized in the following way + * 1) parameters from the input file + * 2) parameters from the command line (overwrite input file parameters) + * \note if a parameter is looked up without explicitly providing a default, the + * default tree is consulted if the parameter could not be found in the parameter tree + */ + static void init(int argc, char **argv, + const DefaultParams& defaultParams = [] (Dune::ParameterTree&) {}, + std::string parameterFileName = "", + const Usage& usage = [](const char *, const std::string &){}); + + /*! + * \brief Initialize the parameter tree + * \param params a function that sets parameters of the runtime parameter tree + * \param defaultParams a function that sets parameters of the default runtim parameter tree + * \note if a parameter is looked up without explicitly providing a default, the + * default tree is consulted if the parameter could not be found in the parameter tree + */ + static void init(const DefaultParams& params = [] (Dune::ParameterTree&) {}, + const DefaultParams& defaultParams = [] (Dune::ParameterTree&) {}); + + /*! + * \brief Initialize the parameter tree + * \param parameterFileName an input parameter file name + * \param params a parameter tree with runtime parameters + * \param inputFileOverwritesParams if set to true (default) the parameters from the input file have precedence, + * if set to false the input the parameters provided via params have precedence + * \param defaultParams a parameter tree with default parameters + * \note the params function overwrites + * \note if a parameter is looked up without explicitly providing a default, the + * default tree is consulted if the parameter could not be found in the parameter tree + */ + static void init(const std::string& parameterFileName, + const DefaultParams& params = [] (Dune::ParameterTree&) {}, + bool inputFileOverwritesParams = true, + const DefaultParams& defaultParams = [] (Dune::ParameterTree&) {}); + + //! prints all used and unused parameters + static void print(); + + //! Parse command line arguments into a parameter tree + static Dune::ParameterTree parseCommandLine(int argc, char **argv); + + /*! + * \brief Get the parameter tree + * + * The logging parameter tree recording which parameters are used during the simulation + */ + static const LoggingParameterTree& getTree(); + +private: + //! the actual internal parameter tree storing all user-specfied runtime parameters + static Dune::ParameterTree& paramTree_(); + + //! the parameter tree storing the Dumux global defaults for some parameters + static Dune::ParameterTree& defaultParamTree_(); + + //! This method puts all default arguments into the parameter tree + //! we do this once per simulation on call to Parameters::init(); + static void applyGlobalDefaults_(Dune::ParameterTree& params); + + //! merge source into target tree + static void mergeTree_(Dune::ParameterTree& target, const Dune::ParameterTree& source, bool overwrite = true); + + //! recursively merge all elements + static void mergeTreeImpl_(Dune::ParameterTree& target, const Dune::ParameterTree& source, bool overwrite, const std::string& group); +}; + +/*! + * \ingroup Parameter + * \brief A free function to get a parameter from the parameter tree singleton + * \note \code auto endTime = getParam("TimeManager.TEnd"); \endcode + * \note Once this has been called the first time, you cannot modify the parameter tree anymore + */ +template +T getParam(Args&&... args) +{ return Parameters::getTree().template get(std::forward(args)... ); } + +/*! + * \ingroup Parameter + * \brief A free function to get a parameter from the parameter tree singleton with a model group + * \note \code auto endTime = getParamFromGroup("FreeFlow", "TimeManager.TEnd"); \endcode + * \note Once this has been called the first time, you cannot modify the parameter tree anymore + */ +template +T getParamFromGroup(Args&&... args) +{ return Parameters::getTree().template getFromGroup(std::forward(args)... ); } + +/*! + * \ingroup Parameter + * \brief Check whether a key exists in the parameter tree + * \note Once this has been called the first time, you cannot modify the parameter tree anymore + */ +inline bool hasParam(const std::string& param) +{ return Parameters::getTree().hasKey(param); } + +/*! + * \ingroup Parameter + * \brief Check whether a key exists in the parameter tree with a model group prefix + * \note Once this has been called the first time, you cannot modify the parameter tree anymore + */ +inline bool hasParamInGroup(const std::string& paramGroup, const std::string& param) +{ return Parameters::getTree().hasKeyInGroup(param, paramGroup); } + +/*! + * \ingroup Parameter + * \brief Get a list of sub groups from the parameter tree sorted by relevance + * \return A vector of fully qualified subGroup names sorted by descending relevance. + */ +inline std::vector getParamSubGroups(const std::string& subGroupName, const std::string& paramGroup) +{ return Parameters::getTree().getSubGroups(subGroupName, paramGroup); } + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/pdesolver.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/pdesolver.hh new file mode 100644 index 0000000..057d6bb --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/pdesolver.hh @@ -0,0 +1,178 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Core + * \brief Defines a high-level interface for a PDESolver + */ +#ifndef DUMUX_COMMON_PDESOLVER_HH +#define DUMUX_COMMON_PDESOLVER_HH + +#include +#include + +#include +#include + +#include + +// forward declare +namespace Dune { +template +class MultiTypeBlockMatrix; +} // end namespace Dune + +namespace Dumux::Detail::PDESolver { + +template +using AssemblerVariablesType = typename Assembler::Variables; + +template +inline constexpr bool assemblerExportsVariables = Dune::Std::is_detected_v; + +template> struct VariablesChooser; +template struct VariablesChooser { using Type = AssemblerVariablesType; }; +template struct VariablesChooser { using Type = typename A::SolutionVector; }; + +template +using AssemblerVariables = typename VariablesChooser::Type; + +} // end namespace Dumux::Detail::PDESolver + +namespace Dumux { + +/*! + * \ingroup Core + * \brief A high-level interface for a PDESolver + * + * A PDESolver is constructed with an assembler and a linear solver + * and has a method solve that linearizes (if not already linear), assembles, solves and updates + * given an initial solution producing a new solution. + * + * \tparam A Assembler for linearized system of the PDE + * \tparam LS Linear system solver + */ +template +class PDESolver +{ + using Scalar = typename A::Scalar; + using TimeLoop = TimeLoopBase; + +public: + //! export the assembler and linear solver types + using Assembler = A; + using LinearSolver = LS; + + //! export the type of variables that represent a numerical solution + using Variables = Detail::PDESolver::AssemblerVariables; + + /*! + * \brief Constructor + * \param assembler pointer to the assembler of the linear system + * \param linearSolver pointer to the solver of the resulting linear system + */ + PDESolver(std::shared_ptr assembler, + std::shared_ptr linearSolver) + : assembler_(assembler) + , linearSolver_(linearSolver) + {} + + virtual ~PDESolver() = default; + + /*! + * \brief Solve the given PDE system (usually assemble + solve linear system + update) + * \param vars instance of the `Variables` class representing a numerical + * solution, defining primary and possibly secondary variables + * and information on the time level. + * \return bool true if the solver converged + * \post If converged, the given `Variables` will represent the solution. If the solver + * does not converge, it may be the case that they are in some intermediate (implementation-dependent) state. + */ + virtual bool apply(Variables& vars) = 0; + + /*! + * \brief Solve the given PDE system (usually assemble + solve linear system + update) + * \param vars instance of the `Variables` class representing a numerical + * solution, defining primary and possibly secondary variables + * and information on the time level. + */ + virtual void solve(Variables& vars) = 0; + + /*! + * \brief Solve the given PDE system with time step control + * \note This is used for solvers that are allowed to e.g. automatically reduce the + * time step if the solve was not successful + * \param vars instance of the `Variables` class representing a numerical solution + * \param timeLoop a reference to the current time loop + */ + virtual void solve(Variables& vars, TimeLoop& timeLoop) + { + // per default we just forward to the method without time step control + solve(vars); + } + + /*! + * \brief Access the assembler + */ + const Assembler& assembler() const + { return *assembler_; } + + /*! + * \brief Access the assembler + */ + Assembler& assembler() + { return *assembler_; } + + /*! + * \brief Access the linear solver + */ + const LinearSolver& linearSolver() const + { return *linearSolver_; } + +protected: + + /*! + * \brief Access the linear solver + */ + LinearSolver& linearSolver() + { return *linearSolver_; } + + /*! + * \brief Helper function to assure the MultiTypeBlockMatrix's sub-blocks have the correct sizes. + */ + template + bool checkSizesOfSubMatrices(const Dune::MultiTypeBlockMatrix& matrix) const + { + bool matrixHasCorrectSize = true; + using namespace Dune::Hybrid; + forEach(std::make_index_sequence::N()>(), [&](const auto i) + { + const auto& row = matrix[i]; + const auto numRowsLeftMostBlock = row[Dune::index_constant<0>{}].N(); + forEach(row, [&](const auto& subBlock) + { + if (subBlock.N() != numRowsLeftMostBlock) + matrixHasCorrectSize = false; + }); + }); + return matrixHasCorrectSize; + } + + /*! + * \brief Default implementation for any matrix type + */ + template + bool checkSizesOfSubMatrices(const M&) const { return true; } + +private: + std::shared_ptr assembler_; + std::shared_ptr linearSolver_; +}; + +} // namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/pointsource.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/pointsource.hh new file mode 100644 index 0000000..19a89a0 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/pointsource.hh @@ -0,0 +1,348 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Core + * \brief A point source class, + * i.e. sources located at a single point in space + */ + +#ifndef DUMUX_POINTSOURCE_HH +#define DUMUX_POINTSOURCE_HH + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace Dumux { + +/*! + * \ingroup Core + * \brief A point source base class + * \tparam PositionType the position type + * \tparam ValueType the a vector type storing the source for all equations + */ +template +class PointSource +{ +public: + //! Export the scalar type + using Scalar = std::decay_t()[0])>; + //! Export the position type + using GlobalPosition = PositionType; + //! Export the value type + using Values = ValueType; + + //! Constructor for constant point sources + PointSource(GlobalPosition pos, Values values) + : values_(values), pos_(pos), embeddings_(1) {} + + //! Constructor for sol dependent point sources, when there is no + // value known at the time of initialization + PointSource(GlobalPosition pos) + : values_(0.0), pos_(pos), embeddings_(1) {} + + //! Convenience += operator overload modifying only the values + PointSource& operator+= (Scalar s) + { + values_ += s; + return *this; + } + + //! Convenience -= operator overload modifying only the values + PointSource& operator-= (Scalar s) + { + values_ -= s; + return *this; + } + + //! Convenience *= operator overload modifying only the values + PointSource& operator*= (Scalar s) + { + values_ *= s; + return *this; + } + + //! Convenience /= operator overload modifying only the values + PointSource& operator/= (Scalar s) + { + values_ /= s; + return *this; + } + + //! Convenience = operator overload modifying only the values + PointSource& operator= (const Values& values) + { + values_ = values; + return *this; + } + + //! Convenience = operator overload modifying only the values + PointSource& operator= (Scalar s) + { + values_ = s; + return *this; + } + + //! return the source values + Values values() const + { return values_; } + + //! return the source position + const GlobalPosition& position() const + { return pos_; } + + //! an update function called before adding the value + // to the local residual in the problem in scvPointSources + // to be overloaded by derived classes + template + void update(const Problem &problem, + const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity &element, + const FVElementGeometry &fvGeometry, + const ElementVolumeVariables &elemVolVars, + const typename FVElementGeometry::SubControlVolume &scv) + {} + + //! set the number of embeddings for this point source + void setEmbeddings(std::size_t embeddings) + { + embeddings_ = embeddings; + } + + /*! + * \brief get the number of embeddings for this point source + * \note A point source might be located on the intersection between several scvs. + * If so, there are point sources for every neighboring scv with the same position. + * `embeddings` returns the number of neighboring scvs. + * Example: If I want to inject 1kg/s at a location that is on the inner face of an scv + * the point source exists in both scvs. Both have a value of 1kg/s. + * We then divide the value by the number of embeddings to not inject 2kg/s but 1kg/s. + * \note This division is done in the problem.scvPointSources() if this behaviour is not explicitly + * changed by e.g. overloading this function in the problem implementation. + */ + std::size_t embeddings() const + { + return embeddings_; + } + +protected: + Values values_; //!< value of the point source for each equation +private: + GlobalPosition pos_; //!< position of the point source + std::size_t embeddings_; //!< how many SCVs the point source is associated with +}; + +/*! + * \ingroup Core + * \brief A point source class with an identifier to attach data + * \tparam GlobalPosition the position type + * \tparam SourceValues the a vector type storing the source for all equations + * \tparam I the ID type + */ +template +class IdPointSource : public PointSource +{ + using ParentType = PointSource; + using Scalar = typename ParentType::Scalar; + +public: + //! export the id type + using IdType = I; + + //! Constructor for constant point sources + IdPointSource(GlobalPosition pos, SourceValues values, IdType id) + : ParentType(pos, values), id_(id) {} + + //! Constructor for sol dependent point sources, when there is no + // value known at the time of initialization + IdPointSource(GlobalPosition pos, IdType id) + : ParentType(pos, SourceValues(0.0)), id_(id) {} + + //! return the sources identifier + IdType id() const + { return id_; } + + //! Convenience = operator overload modifying only the values + IdPointSource& operator= (const SourceValues& values) + { + ParentType::operator=(values); + return *this; + } + + //! Convenience = operator overload modifying only the values + IdPointSource& operator= (Scalar s) + { + ParentType::operator=(s); + return *this; + } + +private: + IdType id_; +}; + +/*! + * \ingroup Core + * \brief A point source class for time dependent point sources + */ +template +class SolDependentPointSource : public PointSource::GridView::ctype, + GetPropType::GridView::dimensionworld>, + Dumux::NumEqVector>> +{ + using GridView = typename GetPropType::GridView; + using SourceValues = Dumux::NumEqVector>; + using Problem = GetPropType; + using ElementVolumeVariables = typename GetPropType::LocalView; + using FVElementGeometry = typename GetPropType::LocalView; + using SubControlVolume = typename FVElementGeometry::SubControlVolume; + using Element = typename GridView::template Codim<0>::Entity; + + static const int dimworld = GridView::dimensionworld; + using GlobalPosition = typename Dune::FieldVector; + // returns the PointSource values as PrimaryVariables + using ValueFunction = typename std::function; + + using ParentType = PointSource; + using Scalar = typename ParentType::Scalar; + +public: + //! Constructor for sol dependent point sources, when there is no + // value known at the time of initialization + SolDependentPointSource(GlobalPosition pos, + ValueFunction valueFunction) + : ParentType(pos, SourceValues(0.0)), valueFunction_(valueFunction) {} + + //! an update function called before adding the value + // to the local residual in the problem in scvPointSources + // to be overloaded by derived classes + void update(const Problem &problem, + const Element &element, + const FVElementGeometry &fvGeometry, + const ElementVolumeVariables &elemVolVars, + const SubControlVolume &scv) + { this->values_ = valueFunction_(problem, element, fvGeometry, elemVolVars, scv); } + + //! Convenience = operator overload modifying only the values + SolDependentPointSource& operator= (const SourceValues& values) + { + ParentType::operator=(values); + return *this; + } + + //! Convenience = operator overload modifying only the values + SolDependentPointSource& operator= (Scalar s) + { + ParentType::operator=(s); + return *this; + } + +private: + ValueFunction valueFunction_; +}; + +/*! + * \ingroup Core + * \brief A helper class calculating a sub control volume to point source map + * This class uses the bounding box tree implementation to identify in which + * sub control volume(s) a point source falls. + */ +class BoundingBoxTreePointSourceHelper +{ +public: + //! calculate a DOF index to point source map from given vector of point sources + template + static void computePointSourceMap(const GridGeometry& gridGeometry, + const std::vector& sources, + PointSourceMap& pointSourceMap, + const std::string& paramGroup = "") + { + const auto& boundingBoxTree = gridGeometry.boundingBoxTree(); + + for (const auto& s : sources) + { + // compute in which elements the point source falls + const auto entities = intersectingEntities(s.position(), boundingBoxTree); + + // continue with next point source if no intersection with the grid are found + if (entities.empty()) + continue; + + // make local copy of point source for the map + auto source = s; + + // split the source values equally among all concerned entities + source.setEmbeddings(entities.size()*source.embeddings()); + + if constexpr (GridGeometry::discMethod == DiscretizationMethods::box + || GridGeometry::discMethod == DiscretizationMethods::fcdiamond) + { + // loop over all concerned elements + auto fvGeometry = localView(gridGeometry); + for (const auto eIdx : entities) + { + // check in which subcontrolvolume(s) we are + const auto element = boundingBoxTree.entitySet().entity(eIdx); + fvGeometry.bindElement(element); + + const auto globalPos = source.position(); + // loop over all sub control volumes and check if the point source is inside + constexpr int dim = GridGeometry::GridView::dimension; + Dune::ReservedVector scvIndices; + for (const auto& scv : scvs(fvGeometry)) + if (intersectsPointGeometry(globalPos, fvGeometry.geometry(scv))) + scvIndices.push_back(scv.indexInElement()); + + // for all scvs that tested positive add the point sources + // to the element/scv to point source map + for (const auto scvIdx : scvIndices) + { + const auto key = std::make_pair(eIdx, scvIdx); + if (pointSourceMap.count(key)) + pointSourceMap.at(key).push_back(source); + else + pointSourceMap.insert({key, {source}}); + // split equally on the number of matched scvs + auto& s = pointSourceMap.at(key).back(); + s.setEmbeddings(scvIndices.size()*s.embeddings()); + } + } + } + else if constexpr (GridGeometry::discMethod == DiscretizationMethods::cctpfa + || GridGeometry::discMethod == DiscretizationMethods::ccmpfa) + { + for (const auto eIdx : entities) + { + // add the pointsource to the DOF map + const auto key = std::make_pair(eIdx, /*scvIdx=*/ 0); + if (pointSourceMap.count(key)) + pointSourceMap.at(key).push_back(source); + else + pointSourceMap.insert({key, {source}}); + } + } + else + DUNE_THROW(Dune::NotImplemented, "BoundingBoxTreePointSourceHelper for discretization method " << GridGeometry::discMethod); + } + } +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/properties.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/properties.hh new file mode 100644 index 0000000..97ff652 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/properties.hh @@ -0,0 +1,184 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Properties + * + * \brief _Defines_ all properties used in Dumux. + * \note Include this to forward declare properties in your headers. + * \note This is guaranteed to also include the property system + */ + +#ifndef DUMUX_PROPERTIES_HH +#define DUMUX_PROPERTIES_HH + +// explicitly guard the include so that the property system +// header doesn't need to be opened and checked all the time +// this include is guaranteed to be here for users of this header +#ifndef DUMUX_PROPERTY_SYSTEM_HH +#include +#endif // DUMUX_PROPERTY_SYSTEM_HH + +namespace Dumux::Properties { + +/////////////////////////////////////// +// Basic properties of numeric models: +/////////////////////////////////////// +DUMUX_DEFINE_PROPERTY(Scalar) //!< Property to specify the type of scalar values. +DUMUX_DEFINE_PROPERTY(Grid) //!< The DUNE grid type +DUMUX_DEFINE_PROPERTY(PrimaryVariables) //!< A vector of primary variables +DUMUX_DEFINE_PROPERTY(ModelTraits) //!< Traits class encapsulating model specifications +DUMUX_DEFINE_PROPERTY(BaseModelTraits) //!< Model traits to be used as a base for nonisothermal, mineralization ... models +DUMUX_DEFINE_PROPERTY(Problem) //!< Property to specify the type of a problem which has to be solved +DUMUX_DEFINE_PROPERTY(PointSource) //!< Property defining the type of point source used +DUMUX_DEFINE_PROPERTY(PointSourceHelper) //!< Property defining the class that computes which sub control volume point sources belong to +DUMUX_DEFINE_PROPERTY(IOFields) //!< A class helping models to define input and output fields +DUMUX_DEFINE_PROPERTY(BaseLocalResidual) //!< The type of the base class of the local residual (DEPRECATED. Will be removed after 3.10) +DUMUX_DEFINE_PROPERTY(JacobianMatrix) //!< Type of the global jacobian matrix +DUMUX_DEFINE_PROPERTY(SolutionVector) //!< Vector containing all primary variable vector of the grid + +//! The type of the local residual function, i.e. the equation to be solved +DUMUX_DEFINE_PROPERTY(LocalResidual) + +//////////////////////////////////////////////// +// Basic properties regarding balance equations +///////////////////////////////////////////////// +DUMUX_DEFINE_PROPERTY(UseMoles) //!< Property whether to use moles or kg as amount unit for balance equations +DUMUX_DEFINE_PROPERTY(ReplaceCompEqIdx) //!< The component balance index that should be replaced by the total mass/mole balance +DUMUX_DEFINE_PROPERTY(BalanceEqOpts) //!< A class that collects options for the evaluation of the balance equations +DUMUX_DEFINE_PROPERTY(EnableCompositionalDispersion) //!< Property whether to include compositional dispersion +DUMUX_DEFINE_PROPERTY(EnableThermalDispersion) //!< Property whether to include thermal dispersion + +///////////////////////////////////////////// +// Properties used by finite volume schemes: +///////////////////////////////////////////// +DUMUX_DEFINE_PROPERTY(ElementBoundaryTypes) //!< Stores the boundary types on an element +DUMUX_DEFINE_PROPERTY(GridGeometry) //!< Grid wrapper creating finite volume geometry and connectivity +DUMUX_DEFINE_PROPERTY(GridVariables) //!< The grid variables object managing variable data on the grid (volvars/fluxvars cache) +DUMUX_DEFINE_PROPERTY(VolumeVariables) //!< The secondary variables within a sub-control volume +DUMUX_DEFINE_PROPERTY(GridVolumeVariables) //!< The type for a global container for the volume variables +DUMUX_DEFINE_PROPERTY(FluxVariables) //!< Container storing the different types of flux variables +DUMUX_DEFINE_PROPERTY(FluxVariablesCache) //!< Stores data associated with flux vars +DUMUX_DEFINE_PROPERTY(FluxVariablesCacheFiller) //!< The engine behind the global flux cache (how to fill caches for the stencil) +DUMUX_DEFINE_PROPERTY(GridFluxVariablesCache) //!< The global vector of flux variable containers + +DUMUX_DEFINE_PROPERTY(EnableGridGeometryCache) //!< Whether to store finite volume geometry for the entire grid +DUMUX_DEFINE_PROPERTY(EnableGridVolumeVariablesCache) //!< If disabled, the volume variables are not stored (reduces memory, but is slower) +DUMUX_DEFINE_PROPERTY(EnableGridFluxVariablesCache) //!< Specifies if data on flux vars should be stores (faster, but more memory consuming) + +///////////////////////////////////////////////////////////////// +// Additional properties used by the cell-centered mpfa schemes: +///////////////////////////////////////////////////////////////// +DUMUX_DEFINE_PROPERTY(PrimaryInteractionVolume) //!< The primary interaction volume type +DUMUX_DEFINE_PROPERTY(SecondaryInteractionVolume) //!< The secondary interaction volume type used e.g. on the boundaries +DUMUX_DEFINE_PROPERTY(DualGridNodalIndexSet) //!< The type used for the nodal index sets of the dual grid + +///////////////////////////////////////////////////////////// +// Properties used by models involving flow in porous media: +///////////////////////////////////////////////////////////// +DUMUX_DEFINE_PROPERTY(EnergyLocalResidual) //!< The local residual of the energy equation +DUMUX_DEFINE_PROPERTY(AdvectionType) //!< The type for the calculation the advective fluxes +DUMUX_DEFINE_PROPERTY(SolutionDependentAdvection) //!< specifies if the parameters for the advective fluxes depend on the solution +DUMUX_DEFINE_PROPERTY(MolecularDiffusionType) //!< The type for the calculation of the molecular diffusion fluxes +DUMUX_DEFINE_PROPERTY(DispersionFluxType) //!< The type for the calculation of the dispersive fluxes +DUMUX_DEFINE_PROPERTY(SolutionDependentMolecularDiffusion) //!< specifies if the parameters for the diffusive fluxes depend on the solution +DUMUX_DEFINE_PROPERTY(HeatConductionType) //!< The type for the calculation of the heat conduction fluxes +DUMUX_DEFINE_PROPERTY(CompositionalDispersionModel) //!< The type for the calculation of the compositional dispersion tensor +DUMUX_DEFINE_PROPERTY(ThermalDispersionModel) //!< The type for the calculation of the thermal dispersion tensor +DUMUX_DEFINE_PROPERTY(SolutionDependentHeatConduction) //!< specifies if the parameters for the heat conduction fluxes depend on the solution + +DUMUX_DEFINE_PROPERTY(SpatialParams) //!< The type of the spatial parameters object +DUMUX_DEFINE_PROPERTY(FluidSystem) //!< The type of the fluid system to use +DUMUX_DEFINE_PROPERTY(FluidState) //!< The type of the fluid state to use +DUMUX_DEFINE_PROPERTY(SolidSystem) //!< The type of the solid system to use +DUMUX_DEFINE_PROPERTY(SolidState) //!< The type of the solid state to use +DUMUX_DEFINE_PROPERTY(EffectiveDiffusivityModel) //!< The employed model for the computation of the effective diffusivity +DUMUX_DEFINE_PROPERTY(ThermalConductivityModel) //!< Model to be used for the calculation of the effective conductivity +DUMUX_DEFINE_PROPERTY(VelocityOutput) //!< specifies the velocity calculation module to be used +DUMUX_DEFINE_PROPERTY(Formulation) //!< The formulation of the model +// TODO: is this useful? -> everything is a constraint solver just a different type +DUMUX_DEFINE_PROPERTY(UseConstraintSolver) //!< Whether to use a constraint solver for computing the secondary variables + +// When using the box method in a multi-phase context, an interface solver might be necessary +DUMUX_DEFINE_PROPERTY(EnableBoxInterfaceSolver) + +////////////////////////////////////////////////////////////// +// Additional properties used by the 2pnc and 2pncmin models: +////////////////////////////////////////////////////////////// +DUMUX_DEFINE_PROPERTY(Chemistry) //!< The chemistry class with which equilibrium reactions are solved +DUMUX_DEFINE_PROPERTY(SetMoleFractionsForFirstPhase) //!< Set the mole fraction in the wetting or nonwetting phase +////////////////////////////////////////////////////////////// +// Additional properties used by the 3pwateroil model: +////////////////////////////////////////////////////////////// +DUMUX_DEFINE_PROPERTY(OnlyGasPhaseCanDisappear) //!< reduces the phasestates to threePhases and wnPhaseOnly + +///////////////////////////////////////////////////////////// +// Properties used by geomechanical models: +///////////////////////////////////////////////////////////// +DUMUX_DEFINE_PROPERTY(StressType) //!< The type used for the evaluation of stress tensors and forces + +///////////////////////////////////////////////////////////// +// Properties used by the staggered-grid discretization method +///////////////////////////////////////////////////////////// +DUMUX_DEFINE_PROPERTY(NumEqCellCenter) //!< The number of equations for cell-centered dofs +DUMUX_DEFINE_PROPERTY(NumEqFace) //!< The number of equations for face dofs +DUMUX_DEFINE_PROPERTY(CellCenterSolutionVector) //!< The solution vector type for cell-centered dofs +DUMUX_DEFINE_PROPERTY(FaceSolutionVector) //!< The solution vector type for face dofs +DUMUX_DEFINE_PROPERTY(GridFaceVariables) //!< Global vector containing face-related data +DUMUX_DEFINE_PROPERTY(CellCenterPrimaryVariables) //!< The primary variables container type for cell-centered dofs +DUMUX_DEFINE_PROPERTY(FacePrimaryVariables) //!< The primary variables container type for face dofs +DUMUX_DEFINE_PROPERTY(IntersectionMapper) //!< Specifies the intersection mapper +DUMUX_DEFINE_PROPERTY(StaggeredPrimaryVariables) //!< The hybrid primary variables container type +DUMUX_DEFINE_PROPERTY(BaseEpsilon) //!< A base epsilon for numerical differentiation, can contain multiple values +DUMUX_DEFINE_PROPERTY(FaceVariables) //!< Class containing local face-related data +DUMUX_DEFINE_PROPERTY(BoundaryValues) //!< Class containing local boundary data +DUMUX_DEFINE_PROPERTY(StaggeredFaceSolution) //!< A vector containing the solution for a face (similar to ElementSolution) +DUMUX_DEFINE_PROPERTY(EnableGridFaceVariablesCache) //!< Switch on/off caching of face variables +DUMUX_DEFINE_PROPERTY(UpwindSchemeOrder) //!< Specifies the order of the upwinding scheme (1 == first order, 2 == second order(tvd methods)) + +///////////////////////////////////////////////////////////// +// Properties used by the mpnc model +///////////////////////////////////////////////////////////// +DUMUX_DEFINE_PROPERTY(PressureFormulation) //! the formulation of the pressure e.g most wetting first + +///////////////////////////////////////////////////////////// +// Properties used by the nonequilibrium model +///////////////////////////////////////////////////////////// +DUMUX_DEFINE_PROPERTY(EquilibriumModelTraits) +DUMUX_DEFINE_PROPERTY(EquilibriumLocalResidual) +DUMUX_DEFINE_PROPERTY(EquilibriumIndices) +DUMUX_DEFINE_PROPERTY(EquilibriumIOFields) +DUMUX_DEFINE_PROPERTY(NumEqBalance) +DUMUX_DEFINE_PROPERTY(EnableThermalNonEquilibrium) +DUMUX_DEFINE_PROPERTY(EnableChemicalNonEquilibrium) +DUMUX_DEFINE_PROPERTY(NumEnergyEqFluid) +DUMUX_DEFINE_PROPERTY(NumEnergyEqSolid) + +DUMUX_DEFINE_PROPERTY(NusseltFormulation) +DUMUX_DEFINE_PROPERTY(SherwoodFormulation) + +///////////////////////////////////////////////////////////// +// Properties used by free flow models +///////////////////////////////////////////////////////////// + +DUMUX_DEFINE_PROPERTY(NormalizePressure) //!< Returns whether to normalize the pressure term in the momentum balance or not +DUMUX_DEFINE_PROPERTY(ViscousFluxType) //!< The type for the calculation of the (turbulent) viscous (momentum) fluxes + +///////////////////////////////////////////////////////////// +// Properties used by multidomain simulations +///////////////////////////////////////////////////////////// +DUMUX_DEFINE_PROPERTY(CouplingManager) + +///////////////////////////////////////////////////////////// +// Basic properties of by old/deprecated sequential models: +// Do not use this unless you are dealing with such old code +//////////////////////////////////////////////////////////// +DUMUX_DEFINE_PROPERTY(TimeManager) + +} // end namespace Dumux::Properties + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/properties/grid.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/properties/grid.hh new file mode 100644 index 0000000..21b12b4 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/properties/grid.hh @@ -0,0 +1,48 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Properties + * \brief Defines a type tags and some fundamental grid-related properties + */ +#ifndef DUMUX_GRID_PROPERTIES_HH +#define DUMUX_GRID_PROPERTIES_HH + +#include + +#include +#include +#include + +namespace Dumux { +namespace Properties { + +namespace TTag { +//! Type tag for numeric models. +struct GridProperties {}; +} + +//! Use the minimal point source implementation as default +template +struct PointSource +{ +private: + using SourceValues = Dumux::NumEqVector>; + using GridView = typename GetPropType::GridView; + using GlobalPosition = typename Dune::FieldVector; +public: + using type = Dumux::PointSource; +}; + +//! Use the point source helper using the bounding box tree as a default +template +struct PointSourceHelper { using type = BoundingBoxTreePointSourceHelper; }; + +} // namespace Properties +} // namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/properties/model.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/properties/model.hh new file mode 100644 index 0000000..3bae782 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/properties/model.hh @@ -0,0 +1,77 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Properties + * \brief Defines a type tags and some fundamental properties for all models + */ +#ifndef DUMUX_MODEL_PROPERTIES_HH +#define DUMUX_MODEL_PROPERTIES_HH + +#include + +#include +#include +#include +#include + +// Forward declaration +namespace Dune { class ParameterTree; } + +namespace Dumux { +namespace Properties { + +//! Type tag for numeric models. +namespace TTag { +struct ModelProperties {}; +} + +//! Set the default type of scalar values to double +template +struct Scalar { using type = double; }; + +//! Set the default primary variable vector to a vector of size of number of equations +template +struct PrimaryVariables { using type = Dune::FieldVector, + GetPropType::numEq()>; }; + +//! Set the default to an implementation throwing a NotImplemented error +template +struct IOFields { using type = DefaultIOFields; }; + +//! Set the default class for the balance equation options +template +struct BalanceEqOpts { using type = BalanceEquationOptions; }; + +template +class DeprecatedBaseLocalResidual : public DiscretizationDefaultLocalOperator +{ + struct PropertyBaseLocalResidual { + [[deprecated("BaseLocalResidual property is deprecated. Will be removed after release 3.10. Use DiscretizationDefaultLocalOperator.")]] + PropertyBaseLocalResidual() = default; + int dummy = 0; + }; + using ParentType = DiscretizationDefaultLocalOperator; +public: + using ParentType::ParentType; +private: + PropertyBaseLocalResidual deprecated_; +}; + +//! Deprecation helper for BaseLocalResidual +template +struct [[deprecated("BaseLocalResidual property is deprecated. Will be removed after release 3.10. Use DiscretizationDefaultLocalOperator.")]] +BaseLocalResidual +{ + using type [[deprecated("BaseLocalResidual property is deprecated. Will be removed after release 3.10. Use DiscretizationDefaultLocalOperator.")]] + = DeprecatedBaseLocalResidual; +}; + +} // namespace Properties +} // namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/properties/propertysystem.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/properties/propertysystem.hh new file mode 100644 index 0000000..8894f03 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/properties/propertysystem.hh @@ -0,0 +1,360 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Properties + * \ingroup Typetraits + * \author Timo Koch + * \brief The Dumux property system, traits with inheritance + */ +#ifndef DUMUX_PROPERTY_SYSTEM_HH +#define DUMUX_PROPERTY_SYSTEM_HH + +#include +#include +#include + +namespace Dumux::Properties { + +/*! + * \ingroup Properties + * \brief a tag to mark properties as undefined + */ +struct UndefinedProperty {}; + +/*! + * \ingroup Properties + * \brief a tag to specify a direct alias for property extraction + */ +template struct PropertyAlias; + +} // end namespace Dumux::Properties + + +// hide from doxygen +#ifndef DOXYGEN + +//! implementation details for template meta programming +namespace Dumux::Properties::Detail { + +//! check if a property P is defined +template +constexpr auto isDefinedProperty(int) +-> decltype(std::integral_constant>{}) +{ return {}; } + +//! fall back if a Property is defined +template +constexpr std::true_type isDefinedProperty(...) { return {}; } + +//! check if a TypeTag inherits from other TypeTags +template +constexpr auto hasParentTypeTag(int) +-> decltype(std::declval(), std::true_type{}) +{ return {}; } + +//! fall back if a TypeTag doesn't inherit +template +constexpr std::false_type hasParentTypeTag(...) +{ return {}; } + + +//! detect if the tag T has an alias with the same name as that of the property P +template +using TypeAliasPropertyDetector = typename PropertyAlias

::template Alias; + +//! a property imitating the actual property P that extracts type and value +//! from alias members of tag T instead of from a property specialization for tag T +template +struct TypeAliasProperty +{ using type = Dune::Std::detected_or_t; }; + +//! detect if the tag T has a template alias with the same name as that of the property P +template +using TemplateAliasPropertyDetector = typename PropertyAlias

::template TemplateAlias; + +//! a property imitating the actual property P that extracts type +//! from template alias members of tag T instead of from a property specialization for tag T +template +struct TemplateAliasProperty +{ using type = Dune::Std::detected_or_t; }; + + +//! detector for value members +template +using ValueMemberDetector = decltype(T::value); + +//! specialization if there is no value member +template> +struct ValueMember { static constexpr bool value = false; }; + +//! specialization if there is a value member +template +struct ValueMember { static constexpr auto value = T::value; }; + +//! extract values from properties specializations +template +struct GetPropValue { static constexpr auto value = ValueMember::value; }; + +//! extract values from type alias properties +template +struct GetPropValue> +{ static constexpr auto value = ValueMember::type>::value; }; + +//! extract values from template alias properties +template +struct GetPropValue> +{ static constexpr auto value = ValueMember::type>::value; }; + + +//! helper alias to concatenate multiple tuples +template +using ConCatTuples = decltype(std::tuple_cat(std::declval()...)); + +//! helper struct to get the first property that is defined in the TypeTag hierarchy +template class Property, class TTagList> +struct GetDefined; + +//! helper struct to iteratre over the TypeTag hierarchy +template class Property, class TTagList, class Enable> +struct GetNextTypeTag; + +template class Property, class LastTypeTag> +struct GetNextTypeTag, std::enable_if_t(int{}), void>> +{ using type = typename GetDefined::type; }; + +template class Property, class LastTypeTag> +struct GetNextTypeTag, std::enable_if_t(int{}), void>> +{ using type = UndefinedProperty; }; + +template class Property, class FirstTypeTag, class ...Args> +struct GetNextTypeTag, std::enable_if_t(int{}), void>> +{ using type = typename GetDefined>>::type; }; + +template class Property, class FirstTypeTag, class ...Args> +struct GetNextTypeTag, std::enable_if_t(int{}), void>> +{ using type = typename GetDefined>::type; }; + +template class Property, class LastTypeTag> +struct GetDefined> +{ +// For clang, the following alias triggers compiler warnings if instantiated +// from something like `GetPropType<..., DeprecatedProperty>`, even if that is +// contained in a diagnostic pragma construct that should prevent these warnings. +// As a workaround, also add the pragmas around this line. +// See the discussion in MR 1647 for more details. +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + using LastType = Property; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + using DirectType = TypeAliasProperty; + using DirectTemplateType = TemplateAliasProperty; + // See below for an explanation of this + using type = std::conditional_t< + isDefinedProperty(int{}), LastType, + std::conditional_t< + isDefinedProperty(int{}), DirectTemplateType, + std::conditional_t< + isDefinedProperty(int{}), DirectType, + typename GetNextTypeTag, void>::type + > + > + >; +}; + +template class Property, class FirstTypeTag, class ...Args> +struct GetDefined> +{ +// See the comment above. +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + using FirstType = Property; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + using DirectType = TypeAliasProperty; + using DirectTemplateType = TemplateAliasProperty; + // First we check if the property is specialized for the current type tag + // If yes, we found the correct specialization, if no we keep searching. + // Second, we check if the type tag contains an alias with the property name. + // If yes, we found the correct specialization, if no we keep searching. + // Third, we check if the type tag contains a template alias with the property name (template argument is TypeTag) + // If yes, we found the correct specialization, if no we the property is undefined (default definition) + using type = std::conditional_t< + isDefinedProperty(int{}), FirstType, + std::conditional_t< + isDefinedProperty(int{}), DirectTemplateType, + std::conditional_t< + isDefinedProperty(int{}), DirectType, + typename GetNextTypeTag, void>::type + > + > + >; +}; + +//! helper struct to extract get the Property specialization given a TypeTag, asserts that the property is defined +template class Property> +struct GetPropImpl +{ + using type = typename Detail::GetDefined>::type; + static_assert(!std::is_same_v, "Property is undefined!"); +}; + +template class Property, class T> +struct GetPropOrImpl +{ + using PT = typename Detail::GetDefined>::type; + struct WrapperT { using type = T; }; // fake property wrapper + using type = std::conditional_t, WrapperT, PT>; +}; + +template(int{})> +struct InheritsFrom; + +template +struct InheritsFrom { + static constexpr bool value = std::is_same_v; +}; + +template +struct InheritsFrom { + static constexpr bool value = std::is_same_v + || InheritsFrom::value; +}; + +template +struct InheritsFrom, false> { + static constexpr bool value = (InheritsFrom::value || ...); +}; + +} // end namespace Dumux::Properties::Detail + +#endif // DOXYGEN + +namespace Dumux::Properties { + +/*! + * \ingroup Properties + * \brief whether the property is defined/specialized for TypeTag + */ +template class Property> +inline constexpr bool hasDefinedType() +{ + using type = typename Detail::GetDefined>::type; + return !std::is_same_v; +} + +/*! + * \ingroup Properties + * \brief Return true if the given type tag inherits from the given parent type tag + */ +template +inline constexpr bool inheritsFrom() +{ + return Detail::InheritsFrom::value; +} + +} // end namespace Dumux::Properties + +namespace Dumux { + +/*! + * \ingroup Properties + * \brief get the type of a property + */ +template class Property> +using GetProp = typename Properties::Detail::GetPropImpl::type; + +/*! + * \ingroup Properties + * \brief get the type of a property or the type T if the property is undefined + */ +template class Property, class T> +using GetPropOr = typename Properties::Detail::GetPropOrImpl::type; + +// See the comment above. +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + +/*! + * \ingroup Properties + * \brief get the type alias defined in the property + */ +template class Property> +using GetPropType = typename GetProp::type; + +/*! + * \ingroup Properties + * \brief get the type alias defined in the property or the type T if the property is undefined + */ +template class Property, class T> +using GetPropTypeOr = typename GetPropOr::type; + +/*! + * \ingroup Properties + * \brief get the value data member of a property + */ +template class Property> +inline constexpr auto getPropValue() { return Properties::Detail::GetPropValue>::value; } +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +} // end namespace Dumux + +/*! + * \ingroup Properties + * \brief A preprocessor macro to define properties + * \note Every property can only be defined once (names have to be unique to the program) + * \note Properties should be defined in the namespace Dumux::Properties + * \details The macro defines two components for each property. + * The first is the definition of the property. For example for + * a property Scalar we get + * + * \code{.cpp} + template \ + struct Scalar { using type = UndefinedProperty; }; + \endcode + * + * The second is the specialization of the PropertyAlias template + * for the newly defined property. For a property Scalar, we get + * + * \code{.cpp} + template // specialization for property Scalar + struct PropertyAlias> { + template using Alias = typename MyTypeTag::Scalar; + template using TemplateAlias = typename MyTypeTag::template Scalar; + }; + \endcode + * + * The specialization contains the template alias "Alias" that + * can be used to check if a given type tag "MyTypeTag" has an alias member + * "Scalar", and a template alias "TemplateAlias" that can be used to check + * if a given type tag "MyTypeTag" has a template alias Scalar and can be + * instantiated with a given type tag "TypeTag" (this will be the user- + * end type tag). + */ +#define DUMUX_DEFINE_PROPERTY(Prop) \ + template \ + struct Prop { \ + using type = UndefinedProperty; \ + }; \ + template \ + struct PropertyAlias> { \ + template using Alias = typename MyTypeTag::Prop; \ + template using TemplateAlias = typename MyTypeTag::template Prop; \ + }; + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/reservedblockvector.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/reservedblockvector.hh new file mode 100644 index 0000000..648b105 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/reservedblockvector.hh @@ -0,0 +1,88 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Core + * \brief A arithmetic block vector type based on DUNE's reserved vector + */ +#ifndef DUMUX_RESERVED_BLOCK_VECTOR_HH +#define DUMUX_RESERVED_BLOCK_VECTOR_HH + +#include +#include + +namespace Dumux { + +/*! + * \ingroup Core + * \brief A arithmetic block vector type based on DUNE's reserved vector + */ +template +class ReservedBlockVector : public Dune::ReservedVector +{ + using Base = Dune::ReservedVector; +public: + + using size_type = typename Base::size_type; + using value_type = BlockType; + + using Base::Base; + + explicit ReservedBlockVector() : Base() {} + explicit ReservedBlockVector(size_type size) : Base() { this->resize(size); } + + ReservedBlockVector(const ReservedBlockVector&) = default; + ReservedBlockVector(ReservedBlockVector&&) = default; + + ReservedBlockVector& operator= (const ReservedBlockVector&) = default; + ReservedBlockVector& operator= (ReservedBlockVector&&) = default; + + ~ReservedBlockVector() = default; + + //! assignment from scalar + ReservedBlockVector& operator= (const typename BlockType::field_type& v) + { + std::fill(this->begin(), this->end(), v); + return *this; + } + + //! vector space addition + ReservedBlockVector& operator+= (const ReservedBlockVector& other) + { + for (size_type i = 0; i < this->size(); ++i) + (*this)[i] += other[i]; + return *this; + } + + //! vector space subtraction + ReservedBlockVector& operator-= (const ReservedBlockVector& other) + { + for (size_type i = 0; i < this->size(); ++i) + (*this)[i] -= other[i]; + return *this; + } + + //! division by scalar + ReservedBlockVector& operator/= (const typename BlockType::field_type& v) + { + for (size_type i = 0; i < this->size(); ++i) + (*this)[i] /= v; + return *this; + } + + //! multiplication by scalar + ReservedBlockVector& operator*= (const typename BlockType::field_type& v) + { + for (size_type i = 0; i < this->size(); ++i) + (*this)[i] *= v; + return *this; + } +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/tag.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/tag.hh new file mode 100644 index 0000000..db15d0e --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/tag.hh @@ -0,0 +1,69 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Core + * \brief Helper class to create (named and comparable) tagged types + */ +#ifndef DUMUX_COMMON_TAG_HH +#define DUMUX_COMMON_TAG_HH + +#include +#include +#include +#include +#include + +namespace Dumux::Utility { + +/*! + * \ingroup Core + * \brief Helper class to create (named and comparable) tagged types + * Tags any given type. The tagged type is equality comparable and can be written to streams. + * A custom name can be provided by implementing the `name()` member function. + */ +template +struct Tag {}; + +//! Tags are equality comparable and return true if the tagged types are equal +template +inline constexpr bool operator==(Tag, Tag) +{ return std::is_same_v; } + +template +inline constexpr bool operator!=(Tag, Tag) +{ return !std::is_same_v; } + +namespace Detail { +// cppcheck-suppress internalAstError +constexpr auto hasName = isValid([](auto&& t) -> decltype(t.name(), void()) {}); +} // end namespace Detail + +//! Return the class name of the tagged type calling t.name() +template, T>, int> = 0> +auto operator<<(std::ostream& os, const T& t) +-> std::enable_if_t +{ os << t.name(); return os; } + +//! Return the class name of the tagged type calling Dune::className if t.name() doesn't exist +template, T>, int> = 0> +auto operator<<(std::ostream& os, const T& t) +-> std::enable_if_t +{ + const auto fullName = Dune::className(); + + // strip all namespace qualifiers + const auto pos = fullName.rfind("::"); + const auto name = pos != std::string::npos ? fullName.substr(pos+2) : fullName; + + os << name; + return os; +} + +} // end namespace Dumux::Utility + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/timeloop.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/timeloop.hh new file mode 100644 index 0000000..ad25632 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/timeloop.hh @@ -0,0 +1,785 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Core + * \brief Manages the handling of time dependent problems + */ +#ifndef DUMUX_TIME_LOOP_HH +#define DUMUX_TIME_LOOP_HH + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include + +#include +#include +#include + +namespace Dumux { + + +#ifndef DOXYGEN +namespace Detail::TimeLoop { + +template +Scalar toSeconds(std::chrono::duration duration) +{ + using Second = std::chrono::duration>; + return std::chrono::duration_cast(duration).count(); +} + +template, bool> = true> +Scalar toSeconds(T duration) +{ return static_cast(duration); } + +// overload for nice static_assert message in case of unuspported type +template, bool> = true> +Scalar toSeconds(T duration) +{ static_assert(AlwaysFalse::value, "Given type not supported for representation of time values"); } + +} // namespace Detail::TimeLoop +#endif // DOXYGEN + +/*! + * \ingroup Core + * \brief Manages the handling of time dependent problems. + * + * This class facilitates the time management of the simulation. + * It doesn't manage any user data, but keeps track of what the + * current time, time step size and "episode" of the + * simulation is. It triggers the initialization of the problem and + * is responsible for the time control of a simulation run. + * + * The time manager allows to specify a sequence of "episodes" which + * determine the boundary conditions of a problem. This approach + * is handy if the problem is not static, i.e. the boundary + * conditions change over time. + * + * An episode is a span of simulated time in which + * the problem behaves in a specific way. It is characterized by + * the (simulation) time it starts, its length and a consecutive + * index starting at 0. + * + * \note Time and time step sizes are in units of seconds + */ +template +class TimeLoopBase +{ +public: + using Scalar = S; + + //! Abstract base class needs virtual constructor + virtual ~TimeLoopBase() {}; + + /*! + * \brief Return the time \f$\mathrm{[s]}\f$ before the time integration. + * To get the time after the time integration you have to add timeStepSize() to + * time(). + */ + virtual Scalar time() const = 0; + + /*! + * \brief Returns the suggested time step length \f$\mathrm{[s]}\f$ + */ + virtual Scalar timeStepSize() const = 0; + + /*! + * \brief Get the maximum possible time step size \f$\mathrm{[s]}\f$ + */ + virtual Scalar maxTimeStepSize() const = 0; + + /*! + * \brief Advance to the next time step. + */ + virtual void advanceTimeStep() = 0; + + /*! + * \brief Set the current time step size to a given value. + * \param dt The new value for the time step size \f$\mathrm{[s]}\f$ + */ + virtual void setTimeStepSize(Scalar dt) = 0; + + /*! + * \brief Set the current time step size to a given value. + * \param dt The new value for the time step size \f$\mathrm{[s]}\f$ + */ + template + void setTimeStepSize(std::chrono::duration dt) + { setTimeStepSize(Detail::TimeLoop::toSeconds(dt)); } + + /*! + * \brief Returns true if the simulation is finished. + */ + virtual bool finished() const = 0; +}; + +/*! + * \ingroup Core + * \brief The default time loop for instationary simulations + */ +template +class TimeLoop : public TimeLoopBase +{ +public: + TimeLoop(Scalar startTime, Scalar dt, Scalar tEnd, bool verbose = true) + : timer_(false) + { + reset(startTime, dt, tEnd, verbose); + } + + template + TimeLoop(std::chrono::duration startTime, + std::chrono::duration dt, + std::chrono::duration tEnd, + bool verbose = true) + : TimeLoop( + Detail::TimeLoop::toSeconds(startTime), + Detail::TimeLoop::toSeconds(dt), + Detail::TimeLoop::toSeconds(tEnd), + verbose + ){} + + /*! + * \name Simulated time and time step management + * @{ + */ + + /*! + * \brief Tells the time loop to start tracking the time. + */ + void start() + { + timer_.start(); + } + + /*! + * \brief Tells the time loop to stop tracking the time. + * \return the wall clock time (CPU time) spent until now + */ + double stop() + { + return timer_.stop(); + } + + /*! + * \brief Reset the timer + */ + void resetTimer() + { + timer_.reset(); + } + + /*! + * \brief Reset the time loop + */ + template + void reset(std::chrono::duration startTime, + std::chrono::duration dt, + std::chrono::duration tEnd, + bool verbose = true) + { + reset( + Detail::TimeLoop::toSeconds(startTime), + Detail::TimeLoop::toSeconds(dt), + Detail::TimeLoop::toSeconds(tEnd) + ); + } + + /*! + * \brief Reset the time loop + */ + void reset(Scalar startTime, Scalar dt, Scalar tEnd, bool verbose = true) + { + verbose_ = + verbose && + Dune::MPIHelper::getCommunication().rank() == 0; + + startTime_ = startTime; + time_ = startTime; + endTime_ = tEnd; + + previousTimeStepSize_ = 0.0; + userSetMaxTimeStepSize_ = std::numeric_limits::max(); + timeStepIdx_ = 0; + finished_ = false; + timeAfterLastTimeStep_ = 0.0; + timeStepWallClockTime_ = 0.0; + + // ensure that dt is not greater than tEnd-startTime + setTimeStepSize(dt); + + timer_.stop(); + timer_.reset(); + } + + /*! + * \brief Advance time step. + */ + void advanceTimeStep() override + { + timeStepIdx_++; + time_ += timeStepSize_; + previousTimeStepSize_ = timeStepSize_; + + // compute how long the last time step took + const auto cpuTime = wallClockTime(); + timeStepWallClockTime_ = cpuTime - timeAfterLastTimeStep_; + timeAfterLastTimeStep_ = cpuTime; + + // ensure that using current dt we don't exceed tEnd in next time step + setTimeStepSize(timeStepSize_); + } + + /*! + * \brief Set the current simulated time, don't change the current + * time step index. + * + * \param t The time \f$\mathrm{[s]}\f$ which should be jumped to + */ + template + void setTime(ScalarOrDuration t) + { time_ = Detail::TimeLoop::toSeconds(t); } + + /*! + * \brief Set the current simulated time and the time step index. + * + * \param t The time \f$\mathrm{[s]}\f$ which should be jumped to + * \param stepIdx The new time step index + */ + template + void setTime(ScalarOrDuration t, int stepIdx) + { + time_ = Detail::TimeLoop::toSeconds(t); + timeStepIdx_ = stepIdx; + } + + /*! + * \brief Return the time \f$\mathrm{[s]}\f$ before the time integration. + * To get the time after the time integration you have to add timeStepSize() to + * time(). + */ + Scalar time() const final + { return time_; } + + /*! + * \brief Returns the number of (simulated) seconds which the simulation runs. + */ + Scalar endTime() const + { return endTime_; } + + /*! + * \brief Set the time of simulated seconds at which the simulation runs. + * + * \param t The time \f$\mathrm{[s]}\f$ at which the simulation is finished + */ + void setEndTime(Scalar t) + { + endTime_ = t; + if (verbose_) + std::cout << Fmt::format("Set new end time to t = {:.5g} seconds.\n", t); + } + + /*! + * \brief Returns the current wall clock time (cpu time) spend in this time loop + */ + double wallClockTime() const + { return timer_.elapsed(); } + + using TimeLoopBase::setTimeStepSize; + /*! + * \brief Set the current time step size to a given value. + * + * If the step size would exceed the length of the current + * episode, the timeStep() method will take care that the step + * size won't exceed the episode or the end of the simulation, + * though. + * + * \param dt The new value for the time step size \f$\mathrm{[s]}\f$ + */ + void setTimeStepSize(Scalar dt) final + { + using std::min; + timeStepSize_ = min(dt, maxTimeStepSize()); + // Warn if dt is so small w.r.t. current time that it renders float addition meaningless + // For instance, consider (may depend on architecture): + // double cien = 100; + // double mil = 1000; + // if (cien + 1e-14 == cien) std::cout << "Will not be printed" << std::endl; + // if (mil + 1e-14 == mil) std::cout << "Will be printed" << std::endl; + if (!finished() && (time_ + timeStepSize_ == time_)) + std::cerr << Fmt::format("You have set a very small timestep size (dt = {:.5g}).", timeStepSize_) + << " This might lead to numerical problems!\n"; + } + + /*! + * \brief Set the maximum time step size to a given value. + * + * \param maxDt The new value for the maximum time step size \f$\mathrm{[s]}\f$ + * \note This may also reduce the currently set timestep size if needed to comply with the set maximum + */ + template + void setMaxTimeStepSize(ScalarOrDuration maxDt) + { + userSetMaxTimeStepSize_ = Detail::TimeLoop::toSeconds(maxDt); + setTimeStepSize(timeStepSize_); + } + + /*! + * \brief Returns the suggested time step length \f$\mathrm{[s]}\f$ so that we + * don't miss the beginning of the next episode or cross + * the end of the simulation. + */ + Scalar timeStepSize() const final + { return timeStepSize_; } + + /*! + * \brief Returns number of time steps which have been + * executed since the beginning of the simulation. + */ + int timeStepIndex() const + { return timeStepIdx_; } + + /*! + * \brief The previous time step size + */ + Scalar previousTimeStepSize() const + { return previousTimeStepSize_; } + + /*! + * \brief Specify whether the simulation is finished + * + * \param finished If true the simulation is considered finished + * before the end time is reached, else it is only + * considered finished if the end time is reached. + */ + void setFinished(bool finished = true) + { finished_ = finished; } + + /*! + * \brief Returns true if the simulation is finished. + * + * This is the case if either setFinished(true) has been called or + * if the end time is reached. + */ + bool finished() const override + { + return finished_ || (endTime_ - time_) < baseEps_*(time_ - startTime_); + } + + /*! + * \brief Returns true if the simulation is finished after the + * time level is incremented by the current time step size. + */ + bool willBeFinished() const + { + return finished() || (endTime_ - time_ - timeStepSize_) < baseEps_*timeStepSize_; + } + + /*! + * \brief The current maximum time step size + * \note This gets aligned on every setTimeStepSize call to end time + * and other possible check points + */ + Scalar maxTimeStepSize() const override + { + if (finished()) + return 0.0; + + using std::min; using std::max; + return min(userSetMaxTimeStepSize_, max(0.0, endTime_ - time_)); + } + + /*! + * \brief State info on cpu time. + * \note Always call this after TimeLoop::advanceTimeStep() + */ + void reportTimeStep() const + { + if (verbose_) + { + const auto cpuTime = wallClockTime(); + using std::round; + const auto percent = round( (time_ - startTime_) / (endTime_ - startTime_) * 100 ); + std::cout << Fmt::format("[{:3.0f}%] ", percent) + << Fmt::format("Time step {} done in {:.2g} seconds. ", timeStepIdx_, timeStepWallClockTime_) + << Fmt::format("Wall clock time: {:.5g}, time: {:.5g}, time step size: {:.5g}\n", cpuTime, time_, previousTimeStepSize_); + } + } + + /*! + * \brief Print final status and stops tracking the time. + */ + template< class Communicator = Dune::Communication > + void finalize(const Communicator& comm = Dune::MPIHelper::getCommunication()) + { + auto cpuTime = timer_.stop(); + + if (verbose_) + std::cout << Fmt::format("Simulation took {:.5g} seconds on {} processes.\n", cpuTime, comm.size()); + + if (comm.size() > 1) + cpuTime = comm.sum(cpuTime); + + if (verbose_) + std::cout << Fmt::format("The cumulative CPU time was {:.5g} seconds.\n", cpuTime); + } + + //! If the time loop has verbose output + bool verbose() const + { return verbose_; } + + //! Sets time loop verbosity + void setVerbose(bool verbose = true) + { verbose_ = verbose; } + + /* + * @} + */ + +protected: + static constexpr Scalar baseEps_ = 1e-10; + + Dune::Timer timer_; + Scalar time_; + Scalar endTime_; + Scalar startTime_; + + Scalar timeStepSize_; + Scalar previousTimeStepSize_; + Scalar userSetMaxTimeStepSize_; + Scalar timeAfterLastTimeStep_, timeStepWallClockTime_; + int timeStepIdx_; + bool finished_; + bool verbose_; +}; + +// always fall back to floating-point representation +template +TimeLoop(std::chrono::duration, + std::chrono::duration, + std::chrono::duration, + bool verbose = true) -> TimeLoop>, + std::common_type_t, + double +>>; + +/*! + * \ingroup Core + * \brief A time loop with a check point mechanism + */ +template +class CheckPointTimeLoop : public TimeLoop +{ + class CheckPointType { + static constexpr std::size_t manualIdx = 0; + static constexpr std::size_t periodicIdx = 1; + + public: + bool isPeriodic() const { return set_[periodicIdx]; } + bool isManual() const { return set_[manualIdx]; } + bool isAny() const { return set_.any(); } + + CheckPointType& withPeriodic(bool value) { set_[periodicIdx] = value; return *this; } + CheckPointType& withManual(bool value) { set_[manualIdx] = value; return *this; } + + private: + std::bitset<2> set_; + }; + +public: + template + CheckPointTimeLoop(Args&&... args) + : TimeLoop(std::forward(args)...) + { + periodicCheckPoints_ = false; + deltaPeriodicCheckPoint_ = 0.0; + lastPeriodicCheckPoint_ = this->startTime_; + isCheckPoint_ = false; + } + + /*! + * \brief Advance time step. + */ + void advanceTimeStep() override + { + const auto dt = this->timeStepSize(); + const auto newTime = this->time()+dt; + + //! Check point management, TimeLoop::isCheckPoint() has to be called after this! + const auto cpType = nextCheckPointType_(newTime); + if (cpType.isManual()) checkPoints_.pop(); + if (cpType.isPeriodic()) lastPeriodicCheckPoint_ += deltaPeriodicCheckPoint_; + isCheckPoint_ = cpType.isAny(); + + const auto previousTimeStepSize = this->previousTimeStepSize(); + + // advance the time step like in the parent class + TimeLoop::advanceTimeStep(); + + // if this is a check point we might have reduced the time step to reach this check point + // reset the time step size to the time step size before this time step + if (!this->willBeFinished()) + { + using std::max; + if (isCheckPoint_) + this->setTimeStepSize(max(dt, previousTimeStepSize)); + + // if there is a check point soon check if the time step after the next time step would be smaller + // than 20% of the next time step, if yes increase the suggested next time step to exactly reach the check point + // (in the limits of the maximum time step size) + auto nextDt = this->timeStepSize(); + const auto threshold = 0.2*nextDt; + const auto nextTime = this->time() + nextDt; + + const auto nextDtToCheckPoint = maxDtToCheckPoint_(nextTime); + if (nextDtToCheckPoint > Scalar{0} && Dune::FloatCmp::le(nextDtToCheckPoint, threshold)) + nextDt += nextDtToCheckPoint; + + assert(nextDt > 0.0); + this->setTimeStepSize(nextDt); + } + } + + /*! + * \brief The current maximum time step size + * \note This gets aligned on every setTimeStepSize call to end time + * and other possible check points + */ + Scalar maxTimeStepSize() const override + { + using std::min; + const auto maxCheckPointDt = timeStepSizeToNextCheckPoint(); + const auto maxDtParent = TimeLoop::maxTimeStepSize(); + return min(maxDtParent, maxCheckPointDt); + } + + /*! + * \brief Set a periodic check point + * \note You can query if we are at a time check point with isCheckPoint() + * \param interval Set a periodic checkout every [interval] seconds + * \param offset time from which the periodic check points are supposed to start (simulation time) + * the first checkpoint will be at time = offset. + * \note If offset is in the past the first check point will be at the next + * periodic check point greater or equal than time + * \note This also updates the time step size and potentially reduces the time step size to meet the next check point + */ + template + void setPeriodicCheckPoint(ScalarOrDuration1 interval, ScalarOrDuration2 offset = 0.0) + { + setPeriodicCheckPoint_( + Detail::TimeLoop::toSeconds(interval), + Detail::TimeLoop::toSeconds(offset) + ); + } + + //! disable periodic check points + void disablePeriodicCheckPoints() + { periodicCheckPoints_ = false; } + + //! remove all check points + void removeAllCheckPoints() + { + periodicCheckPoints_ = false; + while (!checkPoints_.empty()) + checkPoints_.pop(); + } + + /*! + * \brief Whether now is a time checkpoint + * \note has to be called after TimeLoop::advanceTimeStep() + */ + bool isCheckPoint() const + { return isCheckPoint_; } + + /*! + * \brief add a checkpoint to the queue + * \note checkpoints have to be provided in ascending order + * \param t the check point (in seconds) + * \note This also updates the time step size and potentially reduces the time step size to meet the next check point + */ + template + void setCheckPoint(ScalarOrDuration t) + { + // set the check point + setCheckPoint_(Detail::TimeLoop::toSeconds(t)); + + // make sure we respect this check point on the next time step + this->setTimeStepSize(this->timeStepSize()); + } + + /*! + * \brief add checkpoints to the queue from a list of time points + * \note checkpoints have to be provided in ascending order + * \param checkPoints the list of check points + * \note This also updates the time step size and potentially reduces the time step size to meet the next check point + */ + template + void setCheckPoint(const std::initializer_list& checkPoints) + { setCheckPoint(checkPoints.begin(), checkPoints.end()); } + + /*! + * \brief add checkpoints to the queue from a container from the first iterator to the last iterator + * \note checkpoints have to be provided in ascending order + * \param first iterator to the first element to be inserted + * \param last iterator to the one-after-last element to be inserted + * \note This also updates the time step size and potentially reduces the time step size to meet the next check point + */ + template + void setCheckPoint(ForwardIterator first, ForwardIterator last) + { + // set the check points + for (; first != last; ++first) + setCheckPoint_(Detail::TimeLoop::toSeconds(*first)); + + // make sure we respect this check point on the next time step + this->setTimeStepSize(this->timeStepSize()); + } + + /*! + * \brief Return the time step size to exactly reach the next check point + * In case there is no check point in the future, the largest representable scalar is returned + */ + Scalar timeStepSizeToNextCheckPoint() const + { return maxDtToCheckPoint_(this->time()); } + +private: + bool fuzzyEqual_(const Scalar t0, const Scalar t1) const + { return Dune::FloatCmp::eq(t0, t1, this->baseEps_*this->timeStepSize()); } + + void setPeriodicCheckPoint_(Scalar interval, Scalar offset = 0.0) + { + using std::signbit; + if (signbit(interval)) + DUNE_THROW(Dune::InvalidStateException, "Interval has to be positive!"); + + periodicCheckPoints_ = true; + deltaPeriodicCheckPoint_ = interval; + lastPeriodicCheckPoint_ = offset; + while (lastPeriodicCheckPoint_ + interval - this->time() < 1e-14*interval) + lastPeriodicCheckPoint_ += interval; + + if (this->verbose()) + std::cout << Fmt::format("Enabled periodic check points every {:.5g} seconds ", interval) + << Fmt::format("with the next check point at {:.5g} seconds.\n", lastPeriodicCheckPoint_ + interval); + + // check if the current time point is a periodic check point + if (nextCheckPointType_(this->time() + deltaPeriodicCheckPoint_).isPeriodic()) + isCheckPoint_ = true; + + // make sure we respect this check point on the next time step + this->setTimeStepSize(this->timeStepSize()); + } + + //! Adds a check point to the queue + void setCheckPoint_(Scalar t) + { + if (Dune::FloatCmp::le(t - this->time(), 0.0, this->timeStepSize()*this->baseEps_)) + { + if (this->verbose()) + std::cerr << Fmt::format("Couldn't insert checkpoint at t = {:.5g} ", t) + << Fmt::format("because that's not in the future! (current simulation time is {:.5g})\n", this->time()); + return; + } + + if (!checkPoints_.empty()) + { + if (t <= checkPoints_.back()) + { + if (this->verbose()) + std::cerr << Fmt::format("Couldn't insert checkpoint at t = {:.5g} ", t) + << Fmt::format("because it's earlier than or equal to the last check point (t = {:.5g}) in the queue.\n", checkPoints_.back()) + << "Checkpoints can only be inserted in ascending order." << std::endl; + return; + } + } + + checkPoints_.push(t); + if (this->verbose()) + std::cout << Fmt::format("Set check point at t = {:.5g} seconds.\n", t); + } + + /*! + * \brief Return the type of (next) check point at the given time + */ + CheckPointType nextCheckPointType_(Scalar t) + { + return CheckPointType{} + .withPeriodic(periodicCheckPoints_ && fuzzyEqual_(t - lastPeriodicCheckPoint_, deltaPeriodicCheckPoint_)) + .withManual(!checkPoints_.empty() && fuzzyEqual_(t - checkPoints_.front(), 0.0)); + } + + /*! + * \brief Compute a time step size respecting upcoming checkpoints, starting from the given time t. + */ + Scalar maxDtToCheckPoint_(Scalar t) const + { + static constexpr auto unset = std::numeric_limits::max(); + const auto dtToPeriodic = dtToNextPeriodicCheckPoint_(t); + const auto dtToManual = dtToNextManualCheckPoint_(t); + + using std::min; + return min( + dtToPeriodic.value_or(unset), + dtToManual.value_or(unset) + ); + } + + /*! + * \brief Compute the time step size to the next periodic check point. + */ + std::optional dtToNextPeriodicCheckPoint_(Scalar t) const + { + if (periodicCheckPoints_) + return lastPeriodicCheckPoint_ + deltaPeriodicCheckPoint_ - t; + return {}; + } + + /*! + * \brief Compute a time step size respecting the next manual check point. + */ + std::optional dtToNextManualCheckPoint_(Scalar t) const + { + if (!checkPoints_.empty()) + return checkPoints_.front() - t; + return {}; + } + + bool periodicCheckPoints_; + Scalar deltaPeriodicCheckPoint_; + Scalar lastPeriodicCheckPoint_; + std::queue checkPoints_; + bool isCheckPoint_; +}; + +template +CheckPointTimeLoop(Args&&... args) -> CheckPointTimeLoop< + typename decltype(TimeLoop{std::forward(args)...})::Scalar +>; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/typetraits/isvalid.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/typetraits/isvalid.hh new file mode 100644 index 0000000..630dafb --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/typetraits/isvalid.hh @@ -0,0 +1,86 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Typetraits + * \brief A helper function for class member function introspection + * \note Follows the description by Jean Guegant on + * https://jguegant.github.io/blogs/tech/sfinae-introduction.html + */ +#ifndef DUMUX_TYPETRAITS_ISVALID_HH +#define DUMUX_TYPETRAITS_ISVALID_HH + +#include + +namespace Dumux { + +namespace Detail { + +// the functor testing an expression for validity +// Expression: can be for example a lambda expression +template +struct ValidityTestFunctor +{ +private: + // std::declval creates an object of expression + // the expression, i.e. a lambda expression gets an object as a parameter and does checks on it. + // so we create also an object of the parameter usiung std::declval + // if decltype can evaluate the type, i.e. the object parameter is a valid argument for the expression + // we return std::true_type + // note: the int is used to give the first overload always precedence + // note: the last argument in decltype determines the deduced type but all types need to be valid + template + constexpr auto testArgument_(int /* unused int to make this overload the priority choice */) const + -> decltype(std::declval()(std::declval()), std::true_type()) + { return std::true_type(); } + + // otherwise we return std::false_type, i.e. this is the fallback + template + constexpr std::false_type testArgument_(...) const + { return std::false_type(); } + +public: + // the operator () takes the argument we want to use as argument to the test expression + // note we use the int to prefer the "valid"-result overload of test_ if possible + template + constexpr auto operator() (const Argument& arg) const + { return testArgument_(int()); } + + // check function takes the template argument explicitly + template + constexpr auto check () const + { return testArgument_(int()); } +}; + +} // end namespace Detail + + +/*! + * \ingroup Typetraits + * \brief A function that creates a test functor to do class member introspection at compile time + * \return a functor that returns true if the expression is valid with a given type / object + * Usage: + * If you want to test if a class has the member function resize(std::size_t) create a test functor + * \code + * auto hasResize = isValid([](auto&& c) -> decltype(c.resize(std::size_t(1))) {}; }); + * \endcode + * \note hasResize can be constexpr in C++17 which allows lambdas in constexpr functions + * The you can use the test in compile time expressions + * \code + * template + * auto otherFunc(const T& t) + * -> typename std::enable_if_t + * { return 4.0; } + * \endcode + */ +template +constexpr auto isValid(const Expression& t) +{ return Detail::ValidityTestFunctor(); } + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/typetraits/matrix.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/typetraits/matrix.hh new file mode 100644 index 0000000..054458b --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/typetraits/matrix.hh @@ -0,0 +1,44 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Typetraits + * \brief Type traits to be used with matrix types + */ +#ifndef DUMUX_TYPETRAITS_MATRIX_HH +#define DUMUX_TYPETRAITS_MATRIX_HH + +#include + +// Forward declare to avoid includes +namespace Dune { +template +class BCRSMatrix; + +template +class MultiTypeBlockMatrix; +} // end namespace Dune + +namespace Dumux { + +//! Helper type to determine whether a given type is a Dune::BCRSMatrix +template +struct isBCRSMatrix : public std::false_type {}; + +template +struct isBCRSMatrix> : public std::true_type {}; + +//! Helper type to determine whether a given type is a Dune::MultiTypeBlockMatrix +template +struct isMultiTypeBlockMatrix : public std::false_type {}; + +template +struct isMultiTypeBlockMatrix> : public std::true_type {}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/typetraits/periodic.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/typetraits/periodic.hh new file mode 100644 index 0000000..6358b5e --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/typetraits/periodic.hh @@ -0,0 +1,29 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Typetraits + * \brief Type traits to detect periodicity support + */ +#ifndef DUMUX_TYPETRAITS_PERIODIC_HH +#define DUMUX_TYPETRAITS_PERIODIC_HH + +#include + +namespace Dumux::Detail { + +//! helper struct detecting if a gridGeometry object has a periodicDofMap() function +template +using GGPeriodicMapDetector = decltype(std::declval().periodicDofMap()); + +template +constexpr inline bool hasPeriodicDofMap() +{ return Dune::Std::is_detected::value; } + +} // end namespace Dumux::Detail + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/typetraits/problem.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/typetraits/problem.hh new file mode 100644 index 0000000..d8fdbc2 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/typetraits/problem.hh @@ -0,0 +1,39 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Typetraits + * \brief Type traits for problem classes + */ +#ifndef DUMUX_TYPETRAITS_PROBLEM_HH +#define DUMUX_TYPETRAITS_PROBLEM_HH + +#include +#include + +namespace Dumux { + +// forward declare +namespace Detail { +template +struct ProblemTraits; +} // end namespace Detail + +/*! + * \ingroup Typetraits + * \brief Type traits for problem classes. + */ +template +struct ProblemTraits +{ + using GridGeometry = std::decay_t().gridGeometry())>; + using BoundaryTypes = typename Detail::template ProblemTraits::BoundaryTypes; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/typetraits/state.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/typetraits/state.hh new file mode 100644 index 0000000..d856768 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/typetraits/state.hh @@ -0,0 +1,37 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Typetraits + * \brief Type traits to be used with matrix types + */ +#ifndef DUMUX_TYPETRAITS_STATE_HH +#define DUMUX_TYPETRAITS_STATE_HH + +#include + +namespace Dumux::Detail { + +//! helper struct detecting if a PrimaryVariables object has a state() function +struct hasState +{ + template + auto operator()(PrimaryVariables&& priVars) + -> decltype(priVars.state()) + {} +}; + +template +using DetectPriVarsHaveState = decltype(std::declval

().state()); + +template +constexpr inline bool priVarsHaveState() +{ return Dune::Std::is_detected::value; } + +} // end namespace Dumux::Detail + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/typetraits/typetraits.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/typetraits/typetraits.hh new file mode 100644 index 0000000..cc62bb0 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/typetraits/typetraits.hh @@ -0,0 +1,41 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Typetraits + * \brief Type traits. + */ +#ifndef DUMUX_TYPE_TRAITS_HH +#define DUMUX_TYPE_TRAITS_HH + +#include + +namespace Dumux { + +/*! + * \brief Template which always yields a false value + * \tparam T Some type. + */ +template +struct AlwaysFalse : public std::false_type {}; + +/*! + * \brief Function that performs no operation. + */ +inline constexpr auto noop = [] (auto...) {}; +using Noop = decltype(noop); + +/*! + * \brief Helper template to select type T if it is not void + * or fall back to the given default type otherwise. + */ +template +using NonVoidOr = std::conditional_t, T, Default>; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/typetraits/utility.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/typetraits/utility.hh new file mode 100644 index 0000000..0dd2b61 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/typetraits/utility.hh @@ -0,0 +1,91 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Typetraits + * \brief Utilities for template meta programming + */ +#ifndef DUMUX_COMMON_TYPETRAITS_UTILITY_HH +#define DUMUX_COMMON_TYPETRAITS_UTILITY_HH + +#include +#include + +namespace Dumux { + +/* + * \ingroup Typetraits + * \brief create a variadic template from indexed types + * \tparam V a variadic template that we want to create + * \tparam T an indexed type (type that gets an index as template parameter) + * \tparam U the list of indices + */ +template class Variadic, template class Indexed, class U> +struct makeFromIndexedType; + +template class Variadic, template class Indexed, std::size_t... IndexSeq> +struct makeFromIndexedType> +{ + using type = Variadic...>; +}; + +namespace Detail { + template struct ConcatSeq; + + template + struct ConcatSeq, offset, std::index_sequence> + { + using type = std::index_sequence; + }; +} + +/* + * \ingroup Typetraits + * \brief create an integer sequence from 0 to n-1, omitting one specific number e + * \tparam n number of integers in complete sequence before omitting + * \tparam e value of integer to be omitted + * + * example: makeIncompleteIntegerSequence<3, 1> = [0, 2] + * example: makeIncompleteIntegerSequence<4, 4> = [0, 1, 2, 3] + * + * see https://stackoverflow.com/questions/27124920/compile-time-generate-integer-sequence-with-one-left-out for details + */ +template +using makeIncompleteIntegerSequence = + typename Detail::ConcatSeq{}), e + 1, decltype(std::make_index_sequence<(n > e) ? (n - e - 1) : 0>{})>::type; + +/* + * \ingroup Typetraits + * \brief add an offset to an index sequence + * \tparam offset the offset + * \tparam is the index sequence + * + * see https://stackoverflow.com/a/35625414 + */ +template +constexpr std::index_sequence<(offset + is)...> addOffsetToIndexSequence(std::index_sequence) +{ return {}; } + +/* + * \ingroup Typetraits + * \brief create an index sequence starting from an offset + * \tparam offset the offset + * \tparam n the length of the sequence + * + * example: makeIndexSequenceWithOffset<2, 3> = [2,3,4] + * + * see https://stackoverflow.com/a/35625414 + */ +template +constexpr auto makeIndexSequenceWithOffset() +{ + return addOffsetToIndexSequence(std::make_index_sequence{}); +} + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/typetraits/vector.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/typetraits/vector.hh new file mode 100644 index 0000000..f444e08 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/typetraits/vector.hh @@ -0,0 +1,30 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Typetraits + * \brief Type traits to be used with vector types + */ +#ifndef DUMUX_TYPETRAITS_VECTOR_HH +#define DUMUX_TYPETRAITS_VECTOR_HH + +#include +#include + + +namespace Dumux { + + //! Helper type to determine whether a given type is a Dune::MultiTypeBlockVector + template struct isMultiTypeBlockVector : public std::false_type {}; + + //! Helper type to determine whether a given type is a Dune::MultiTypeBlockVector + template + struct isMultiTypeBlockVector > : public std::true_type {}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/variablesbackend.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/variablesbackend.hh new file mode 100644 index 0000000..afd2edc --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/common/variablesbackend.hh @@ -0,0 +1,257 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Core + * \brief Backends for operations on different solution vector types + * or more generic variable classes to be used in places where + * several different types/layouts should be supported. + */ +#ifndef DUMUX_COMMON_VARIABLES_BACKEND_HH +#define DUMUX_COMMON_VARIABLES_BACKEND_HH + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +// forward declaration +namespace Dune { + +template +class MultiTypeBlockVector; + +} // end namespace Dune + +namespace Dumux::Detail::DofBackend { + +struct HasResize +{ + template + auto operator()(const V& v) -> decltype(std::declval().resize(0)) + {} +}; + +template +static constexpr auto hasResize() +{ return decltype( isValid(HasResize())(std::declval()) )::value; } + +} // end namespace Dumux::Detail::VariablesBackend + +namespace Dumux { + +/*! + * \ingroup Core + * \brief Class providing operations with primary variable vectors + */ +template::value> +class DofBackend; + +/*! + * \ingroup Core + * \brief Specialization providing operations for scalar/number types + */ +template +class DofBackend +{ +public: + using DofVector = Scalar; //!< the type of the dofs parametrizing the variables object + using SizeType = std::size_t; + + //! Return the number of entries in the dof vector + static SizeType size(const DofVector& d) + { return 1; } + + //! Make a zero-initialized dof vector instance + static DofVector zeros(SizeType size) + { return 0.0; } + + //! Perform axpy operation (y += a * x) + template + static void axpy(Scalar a, const OtherDofVector& x, DofVector& y) + { y += a*x; } +}; + +/*! + * \ingroup Core + * \brief Specialization providing operations for block vectors + * \tparam Vector a type that is + * - default-constructible + * - has size() member + * - has resize(0) member + * - has axpy(a, x) member + */ +template +class DofBackend +{ +public: + using DofVector = Vector; //!< the type of the dofs parametrizing the variables object + using SizeType = std::size_t; + + //! Return the number of entries in the dof vector + static SizeType size(const DofVector& d) + { return d.size(); } + + //! Make a zero-initialized dof vector instance + static DofVector zeros(SizeType size) + { + DofVector d; + if constexpr (Detail::DofBackend::hasResize()) + d.resize(size); + return d; + } + + //! Perform axpy operation (y += a * x) + template + static void axpy(typename DofVector::field_type a, const OtherDofVector& x, DofVector& y) + { + for (typename DofVector::size_type i = 0; i < y.size(); ++i) + { + if constexpr (Dune::IsNumber>::value) + y[i] += a*x[i]; + else + y[i].axpy(a, x[i]); + } + } +}; + +/*! + * \ingroup Core + * \brief Specialization providing operations for multitype block vectors + */ +template +class DofBackend, false> +{ + using DV = Dune::MultiTypeBlockVector; + static constexpr auto numBlocks = DV::size(); + + using VectorSizeInfo = std::array; + +public: + using DofVector = DV; //!< the type of the dofs parametrizing the variables object + using SizeType = VectorSizeInfo; + + //! Return the number of entries in the sub-dof-vectors + static SizeType size(const DofVector& d) + { + VectorSizeInfo result; + using namespace Dune::Hybrid; + forEach(std::make_index_sequence{}, [&](auto i) { + result[i] = d[Dune::index_constant{}].size(); + }); + return result; + } + + //! Make a zero-initialized dof vector instance + static DofVector zeros(const SizeType& size) + { + DofVector result; + using namespace Dune::Hybrid; + forEach(std::make_index_sequence{}, [&](auto i) { + result[Dune::index_constant{}].resize(size[i]); + }); + return result; + } + + //! Perform axpy operation (y += a * x) + template::value, int> = 0> + static void axpy(Scalar a, const OtherDofVector& x, DofVector& y) + { + using namespace Dune::Hybrid; + forEach(std::make_index_sequence{}, [&](auto i) { + DofBackend{}])>>::axpy( + a, x[Dune::index_constant{}], y[Dune::index_constant{}] + ); + }); + } +}; + +namespace Detail { + +template +using SolutionVectorType = typename Vars::SolutionVector; + +template +class VariablesBackend; + +/*! + * \ingroup Core + * \brief Class providing operations for primary variable vector/scalar types + * \note We assume the variables being simply a dof vector if we + * do not find the variables class to export `SolutionVector`. + */ +template +class VariablesBackend +: public Dumux::DofBackend +{ + using ParentType = Dumux::DofBackend; + +public: + using Variables = Vars; + using typename ParentType::DofVector; + + //! update to new solution vector + static void update(Variables& v, const DofVector& dofs) + { v = dofs; } + + //! return const reference to dof vector + static const DofVector& dofs(const Variables& v) + { return v; } + + //! return reference to dof vector + static DofVector& dofs(Variables& v) + { return v; } +}; + +/*! + * \ingroup Core + * \brief Class providing operations for generic variable classes, + * containing primary and possibly also secondary variables. + */ +template +class VariablesBackend +: public Dumux::DofBackend +{ +public: + using DofVector = typename Vars::SolutionVector; + using Variables = Vars; //!< the type of the variables object + + //! update to new solution vector + static void update(Variables& v, const DofVector& dofs) + { v.update(dofs); } + + //! return const reference to dof vector + static const DofVector& dofs(const Variables& v) + { return v.dofs(); } + + //! return reference to dof vector + static DofVector& dofs(Variables& v) + { return v.dofs(); } +}; +} // end namespace Detail + +/*! + * \ingroup Core + * \brief Class providing operations for generic variable classes + * that represent the state of a numerical solution, possibly + * consisting of primary/secondary variables and information on + * the time level. + */ +template +using VariablesBackend = Detail::VariablesBackend>; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/basegridgeometry.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/basegridgeometry.hh new file mode 100644 index 0000000..50f8b85 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/basegridgeometry.hh @@ -0,0 +1,181 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Discretization + * \brief Base class for grid geometries + */ +#ifndef DUMUX_DISCRETIZATION_BASE_GRID_GEOMETRY_HH +#define DUMUX_DISCRETIZATION_BASE_GRID_GEOMETRY_HH + +#include +#include +#include +#include + +// make the local view function available whenever we use the grid geometry +#include + +namespace Dumux { + +namespace Detail { +template +using SpecifiesBaseGridGeometry = typename T::BasicGridGeometry; + +template +using SpecifiesGeometryHelper = typename T::GeometryHelper; +} // end namespace Detail + +/*! + * \ingroup Discretization + * \brief Type of the basic grid geometry implementation used as backend + */ +template +using BasicGridGeometry_t = Dune::Std::detected_or_t< + Dumux::BasicGridGeometry, + Detail::SpecifiesBaseGridGeometry, + T +>; + +/*! + * \ingroup Discretization + * \brief Base class for all grid geometries + * \tparam GV the grid view type + * \tparam Traits traits class that specifies mappers and basic grid geometry + */ +template +class BaseGridGeometry +{ + using GridIndexType = typename IndexTraits::GridIndex; + using Element = typename GV::template Codim<0>::Entity; + using BaseImplementation = BasicGridGeometry_t; +public: + //! export the grid type + using Grid = typename BaseImplementation::Grid; + //! export the grid view type + using GridView = typename BaseImplementation::GridView; + //! export the global coordinate type + using GlobalCoordinate = typename BaseImplementation::GlobalCoordinate; + //! export the element mapper type + using ElementMapper = typename BaseImplementation::ElementMapper; + //! export the vertex mapper type + using VertexMapper = typename BaseImplementation::VertexMapper; + + /*! + * \ingroup Discretization + * \brief Constructor from a BaseImplementation + */ + BaseGridGeometry(std::shared_ptr impl) + : impl_(std::move(impl)) + {} + + /*! + * \ingroup Discretization + * \brief Constructor from a grid view + * \param gridView the grid view on which to construct the grid geometry + */ + BaseGridGeometry(const GridView& gridView) + : BaseGridGeometry(std::make_shared(gridView)) + {} + + /*! + * \brief Update all fvElementGeometries (call this after grid adaption) + */ + void update(const GridView& gridView) + { impl_->update(gridView); } + + /*! + * \brief Update all fvElementGeometries (call this after grid adaption) + */ + void update(GridView&& gridView) + { impl_->update(std::move(gridView)); } + + /*! + * \brief Return the gridView this grid geometry object lives on + */ + const GridView& gridView() const + { return impl_->gridView(); } + + /*! + * \brief Returns the mapper for vertices to indices for constant grids. + */ + const VertexMapper &vertexMapper() const + { return impl_->vertexMapper(); } + + /*! + * \brief Returns the mapper for elements to indices for constant grids. + */ + const ElementMapper &elementMapper() const + { return impl_->elementMapper(); } + + /*! + * \brief Returns the mapper for vertices to indices for possibly adaptive grids. + */ + VertexMapper &vertexMapper() + { return impl_->vertexMapper(); } + + /*! + * \brief Returns the mapper for elements to indices for possibly adaptive grids. + */ + ElementMapper &elementMapper() + { return impl_->elementMapper(); } + + /*! + * \brief Returns the bounding box tree of the grid + */ + decltype(auto) boundingBoxTree() const + { return impl_->boundingBoxTree(); } + + /*! + * \brief Returns the element index to element map + */ + decltype(auto) elementMap() const + { return impl_->elementMap(); } + + /*! + * \brief Get an element from a global element index + */ + Element element(GridIndexType eIdx) const + { return impl_->element(eIdx); } + + /*! + * \brief The coordinate of the corner of the GridView's bounding + * box with the smallest values. + */ + const GlobalCoordinate &bBoxMin() const + { return impl_->bBoxMin(); } + + /*! + * \brief The coordinate of the corner of the GridView's bounding + * box with the largest values. + */ + const GlobalCoordinate &bBoxMax() const + { return impl_->bBoxMax(); } + + /*! + * \brief Returns if the grid geometry is periodic (at all) + */ + bool isPeriodic() const + { return periodic_; } + +protected: + /*! + * \brief Set the periodicity of the grid geometry + */ + void setPeriodic(bool value = true) + { periodic_ = value; } + +private: + std::shared_ptr impl_; + + //! if the grid geometry has periodic boundaries + bool periodic_ = false; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/basicgridgeometry.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/basicgridgeometry.hh new file mode 100644 index 0000000..ff84be9 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/basicgridgeometry.hh @@ -0,0 +1,241 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Discretization + * \brief A basic implementation of a grid geometry with some common interfaces + */ +#ifndef DUMUX_DISCRETIZATION_BASIC_GRID_GEOMETRY_HH +#define DUMUX_DISCRETIZATION_BASIC_GRID_GEOMETRY_HH + +#include +#include +#include + +#include + +#include +#include +#include +#include + +namespace Dumux { + +/*! + * \ingroup Discretization + * \brief An implementation of a grid geometry with some basic features + * \tparam GV the grid view type + * \tparam EM the type of the element mapper + * \tparam VM the type of the vertex mapper + */ +template +class BasicGridGeometry +{ + using ElementMap = EntityMap; + using ElementSet = GridViewGeometricEntitySet; + using BoundingBoxTree = Dumux::BoundingBoxTree; + + static constexpr int dim = GV::dimension; + static constexpr int dimWorld = GV::dimensionworld; + + using GridIndexType = typename IndexTraits::GridIndex; + using Element = typename GV::template Codim<0>::Entity; + +public: + //! export the grid type + using Grid = typename GV::Grid; + //! export the grid view type + using GridView = GV; + //! export the global coordinate type + using GlobalCoordinate = typename Element::Geometry::GlobalCoordinate; + //! export the element mapper type + using ElementMapper = EM; + //! export the vertex mapper type + using VertexMapper = VM; + + /*! + * \ingroup Discretization + * \brief Constructor computes the bounding box of the entire domain, for e.g. setting boundary conditions + * \param gridView the grid view on which to construct the grid geometry + */ + BasicGridGeometry(const GridView& gridView) + : gridView_(gridView) + , elementMapper_(makeElementMapper_(gridView)) + , vertexMapper_(makeVertexMapper_(gridView)) + , bBoxMin_(std::numeric_limits::max()) + , bBoxMax_(-std::numeric_limits::max()) + { + computeGlobalBoundingBox_(); + update_(); + } + + /*! + * \brief Update internal state after grid changed + */ + void update(const GridView& gridView) + { + gridView_ = gridView; + update_(); + } + + /*! + * \brief Update internal state after grid changed + */ + void update(GridView&& gridView) + { + gridView_ = std::move(gridView); + update_(); + } + + /*! + * \brief Return the gridView this grid geometry object lives on + */ + const GridView& gridView() const + { return gridView_; } + + /*! + * \brief Returns the mapper for vertices to indices for constant grids. + */ + const VertexMapper &vertexMapper() const + { return vertexMapper_; } + + /*! + * \brief Returns the mapper for elements to indices for constant grids. + */ + const ElementMapper &elementMapper() const + { return elementMapper_; } + + /*! + * \brief Returns the mapper for vertices to indices for possibly adaptive grids. + */ + VertexMapper &vertexMapper() + { return vertexMapper_; } + + /*! + * \brief Returns the mapper for elements to indices for possibly adaptive grids. + */ + ElementMapper &elementMapper() + { return elementMapper_; } + + /*! + * \brief Returns the bounding box tree of the grid + */ + const BoundingBoxTree& boundingBoxTree() const + { return *boundingBoxTree_; } + + /*! + * \brief Returns the element index to element map + */ + const ElementMap& elementMap() const + { return *elementMap_; } + + /*! + * \brief Get an element from a global element index + */ + Element element(GridIndexType eIdx) const + { return elementMap()[eIdx]; } + + /*! + * \brief The coordinate of the corner of the GridView's bounding + * box with the smallest values. + */ + const GlobalCoordinate &bBoxMin() const + { return bBoxMin_; } + + /*! + * \brief The coordinate of the corner of the GridView's bounding + * box with the largest values. + */ + const GlobalCoordinate &bBoxMax() const + { return bBoxMax_; } + +private: + + //! Return an instance of the element mapper + ElementMapper makeElementMapper_(const GridView& gridView) const + { + if constexpr (std::is_constructible()) + return ElementMapper(gridView, Dune::mcmgElementLayout()); + else + return ElementMapper(gridView); + } + + //! Return an instance of the vertex mapper + VertexMapper makeVertexMapper_(const GridView& gridView) const + { + if constexpr (std::is_constructible()) + return VertexMapper(gridView, Dune::mcmgVertexLayout()); + else + return VertexMapper(gridView); + } + + //! Compute the bounding box of the entire domain, for e.g. setting boundary conditions + void computeGlobalBoundingBox_() + { + // calculate the bounding box of the local partition of the grid view + for (const auto& vertex : vertices(gridView_)) + { + for (int i=0; i 1) + { + for (int i = 0; i < dimWorld; ++i) + { + bBoxMin_[i] = gridView_.comm().min(bBoxMin_[i]); + bBoxMax_[i] = gridView_.comm().max(bBoxMax_[i]); + } + } + } + + void update_() + { + // Update the mappers + elementMapper_.update(gridView_); + vertexMapper_.update(gridView_); + + // Compute the bounding box of the entire domain, for e.g. setting boundary conditions + computeGlobalBoundingBox_(); + + // update element map and bounding box tree + // always building these comes at a memory overhead but improved + // performance and thread-safe element level access (e.g. during assembly) + // for all simulation that use these features + elementMap_ = std::make_shared(gridView_.grid(), elementMapper_); + boundingBoxTree_ = std::make_unique( + std::make_shared(gridView_, elementMapper(), elementMap_) + ); + } + + //! the process grid view + GridView gridView_; + + //! entity mappers + ElementMapper elementMapper_; + VertexMapper vertexMapper_; + + //! the bounding box tree of the grid view for efficient element intersections + std::unique_ptr boundingBoxTree_; + + //! a map from element index to elements + std::shared_ptr elementMap_; + + //! the bounding box of the whole domain + GlobalCoordinate bBoxMin_; + GlobalCoordinate bBoxMax_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/box.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/box.hh new file mode 100644 index 0000000..7fe4042 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/box.hh @@ -0,0 +1,152 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Discretization + * \brief Defines a type tag and some properties for models using the box scheme. + */ + +#ifndef DUMUX_DISCRETIZTAION_BOX_HH +#define DUMUX_DISCRETIZTAION_BOX_HH + +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +namespace Dumux::Properties { + +//! Type tag for the box scheme. +// Create new type tags +namespace TTag { +struct BoxModel { using InheritsFrom = std::tuple; }; +} // end namespace TTag + +//! Set the default for the grid geometry +template +struct GridGeometry +{ +private: + static constexpr bool enableCache = getPropValue(); + using GridView = typename GetPropType::LeafGridView; + using Scalar = GetPropType; +public: + using type = BoxFVGridGeometry; +}; + +//! The grid volume variables vector class +template +struct GridVolumeVariables +{ +private: + static constexpr bool enableCache = getPropValue(); + using Problem = GetPropType; + using VolumeVariables = GetPropType; + using Traits = CVFEDefaultGridVolumeVariablesTraits; +public: + using type = CVFEGridVolumeVariables; +}; + +//! The grid flux variables cache vector class +template +struct GridFluxVariablesCache +{ +private: + static constexpr bool enableCache = getPropValue(); + using Problem = GetPropType; + + using Scalar = GetPropType; + using FluxVariablesCache = GetPropTypeOr + >; +public: + using type = CVFEGridFluxVariablesCache; +}; + +//! The flux variables cache type +template +struct FluxVariablesCache +{ +private: + using GridGeometry = GetPropType; + using Scalar = GetPropType; +public: + using type = CVFEFluxVariablesCache; +}; + +//! Set the default for the ElementBoundaryTypes +template +struct ElementBoundaryTypes +{ +private: + using Problem = GetPropType; + using BoundaryTypes = typename ProblemTraits::BoundaryTypes; +public: + using type = CVFEElementBoundaryTypes; +}; + +} // namespace Dumux::Properties + +namespace Dumux::Detail { + +template +struct ProblemTraits +{ +private: + using GG = std::decay_t().gridGeometry())>; + using Element = typename GG::GridView::template Codim<0>::Entity; + using SubControlVolume = typename GG::SubControlVolume; +public: + using GridGeometry = GG; + // BoundaryTypes is whatever the problem returns from boundaryTypes(element, scv) + using BoundaryTypes = std::decay_t().boundaryTypes(std::declval(), std::declval()))>; +}; + +template +struct LocalDofTraits +{ + static constexpr int dim = GridView::dimension; + // Dofs are located at the corners + static constexpr int numCubeElementDofs = (1< +concept BoxModel = std::is_same_v< + typename GetPropType::DiscretizationMethod, + DiscretizationMethods::Box +>; + +template +struct DiscretizationDefaultLocalOperator +{ + using type = CVFELocalResidual; +}; + +} // end namespace Dumux:Detail + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/box/boxgeometryhelper.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/box/boxgeometryhelper.hh new file mode 100644 index 0000000..6393dc7 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/box/boxgeometryhelper.hh @@ -0,0 +1,607 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup BoxDiscretization + * \brief Helper class constructing the dual grid finite volume geometries + * for the box discretizazion method + */ +#ifndef DUMUX_DISCRETIZATION_BOX_GEOMETRY_HELPER_HH +#define DUMUX_DISCRETIZATION_BOX_GEOMETRY_HELPER_HH + +#include + +#include + +#include +#include +#include +#include + +#include + +namespace Dumux { + +//! Traits for an efficient corner storage for box method sub control volumes +template +struct BoxMLGeometryTraits : public Dune::MultiLinearGeometryTraits +{ + // we use static vectors to store the corners as we know + // the number of corners in advance (2^(mydim) corners (1<<(mydim)) + template< int mydim, int cdim > + struct CornerStorage + { + using Type = std::array< Dune::FieldVector< ct, cdim >, (1<<(mydim)) >; + }; + + // we know all scvfs will have the same geometry type + template< int mydim > + struct hasSingleGeometryType + { + static const bool v = true; + static const unsigned int topologyId = Dune::GeometryTypes::cube(mydim).id(); + }; +}; + +namespace Detail::Box { + +template +struct ScvCorners; + +template<> +struct ScvCorners +{ + using Key = std::pair; // (i, codim) + static constexpr std::array, 2> keys = {{ + { Key{0, 1}, Key{0, 0} }, + { Key{1, 1}, Key{0, 0} } + }}; +}; + +template<> +struct ScvCorners +{ + using Key = std::pair; // (i, codim) + static constexpr std::array, 3> keys = {{ + { Key{0, 2}, Key{0, 1}, Key{1, 1}, Key{0, 0} }, + { Key{1, 2}, Key{2, 1}, Key{0, 1}, Key{0, 0} }, + { Key{2, 2}, Key{1, 1}, Key{2, 1}, Key{0, 0} } + }}; +}; + +template<> +struct ScvCorners +{ + using Key = std::pair; // (i, codim) + static constexpr std::array, 4> keys = {{ + { Key{0, 2}, Key{2, 1}, Key{0, 1}, Key{0, 0} }, + { Key{1, 2}, Key{1, 1}, Key{2, 1}, Key{0, 0} }, + { Key{2, 2}, Key{0, 1}, Key{3, 1}, Key{0, 0} }, + { Key{3, 2}, Key{3, 1}, Key{1, 1}, Key{0, 0} } + }}; +}; + +template<> +struct ScvCorners +{ + using Key = std::pair; // (i, codim) + static constexpr std::array, 4> keys = {{ + { Key{0, 3}, Key{0, 2}, Key{1, 2}, Key{0, 1}, Key{3, 2}, Key{1, 1}, Key{2, 1}, Key{0, 0} }, + { Key{1, 3}, Key{2, 2}, Key{0, 2}, Key{0, 1}, Key{4, 2}, Key{3, 1}, Key{1, 1}, Key{0, 0} }, + { Key{2, 3}, Key{1, 2}, Key{2, 2}, Key{0, 1}, Key{5, 2}, Key{2, 1}, Key{3, 1}, Key{0, 0} }, + { Key{3, 3}, Key{3, 2}, Key{5, 2}, Key{2, 1}, Key{4, 2}, Key{1, 1}, Key{3, 1}, Key{0, 0} } + }}; +}; + +template<> +struct ScvCorners +{ + using Key = std::pair; // (i, codim) + static constexpr std::array, 6> keys = {{ + { Key{0, 3}, Key{3, 2}, Key{4, 2}, Key{3, 1}, Key{0, 2}, Key{0, 1}, Key{1, 1}, Key{0, 0} }, + { Key{1, 3}, Key{5, 2}, Key{3, 2}, Key{3, 1}, Key{1, 2}, Key{2, 1}, Key{0, 1}, Key{0, 0} }, + { Key{2, 3}, Key{4, 2}, Key{5, 2}, Key{3, 1}, Key{2, 2}, Key{1, 1}, Key{2, 1}, Key{0, 0} }, + { Key{3, 3}, Key{7, 2}, Key{6, 2}, Key{4, 1}, Key{0, 2}, Key{1, 1}, Key{0, 1}, Key{0, 0} }, + { Key{4, 3}, Key{6, 2}, Key{8, 2}, Key{4, 1}, Key{1, 2}, Key{0, 1}, Key{2, 1}, Key{0, 0} }, + { Key{5, 3}, Key{8, 2}, Key{7, 2}, Key{4, 1}, Key{2, 2}, Key{2, 1}, Key{1, 1}, Key{0, 0} } + }}; +}; + +template<> +struct ScvCorners +{ + using Key = std::pair; // (i, codim) + static constexpr std::array, 8> keys = {{ + { Key{0, 3}, Key{6, 2}, Key{4, 2}, Key{4, 1}, Key{0, 2}, Key{2, 1}, Key{0, 1}, Key{0, 0} }, + { Key{1, 3}, Key{5, 2}, Key{6, 2}, Key{4, 1}, Key{1, 2}, Key{1, 1}, Key{2, 1}, Key{0, 0} }, + { Key{2, 3}, Key{4, 2}, Key{7, 2}, Key{4, 1}, Key{2, 2}, Key{0, 1}, Key{3, 1}, Key{0, 0} }, + { Key{3, 3}, Key{7, 2}, Key{5, 2}, Key{4, 1}, Key{3, 2}, Key{3, 1}, Key{1, 1}, Key{0, 0} }, + { Key{4, 3}, Key{8, 2}, Key{10, 2}, Key{5, 1}, Key{0, 2}, Key{0, 1}, Key{2, 1}, Key{0, 0} }, + { Key{5, 3}, Key{10, 2}, Key{9, 2}, Key{5, 1}, Key{1, 2}, Key{2, 1}, Key{1, 1}, Key{0, 0} }, + { Key{6, 3}, Key{11, 2}, Key{8, 2}, Key{5, 1}, Key{2, 2}, Key{3, 1}, Key{0, 1}, Key{0, 0} }, + { Key{7, 3}, Key{9, 2}, Key{11, 2}, Key{5, 1}, Key{3, 2}, Key{1, 1}, Key{3, 1}, Key{0, 0} } + }}; +}; + +template +struct ScvfCorners; + +template<> +struct ScvfCorners +{ + using Key = std::pair; // (i, codim) + static constexpr std::array, 1> keys = {{ + { Key{0, 0} } + }}; +}; + +template<> +struct ScvfCorners +{ + using Key = std::pair; // (i, codim) + static constexpr std::array, 3> keys = {{ + { Key{0, 0}, Key{0, 1} }, + { Key{1, 1}, Key{0, 0} }, + { Key{0, 0}, Key{2, 1} } + }}; +}; + +template<> +struct ScvfCorners +{ + using Key = std::pair; // (i, codim) + static constexpr std::array, 4> keys = {{ + { Key{0, 1}, Key{0, 0} }, + { Key{0, 0}, Key{1, 1} }, + { Key{0, 0}, Key{2, 1} }, + { Key{3, 1}, Key{0, 0} } + }}; +}; + +template<> +struct ScvfCorners +{ + using Key = std::pair; // (i, codim) + static constexpr std::array, 6> keys = {{ + { Key{0, 2}, Key{0, 1}, Key{1, 1}, Key{0, 0} }, + { Key{0, 1}, Key{1, 2}, Key{0, 0}, Key{2, 1} }, + { Key{2, 2}, Key{0, 1}, Key{3, 1}, Key{0, 0} }, + { Key{2, 1}, Key{3, 2}, Key{0, 0}, Key{1, 1} }, + { Key{3, 1}, Key{0, 0}, Key{4, 2}, Key{1, 1} }, + { Key{5, 2}, Key{2, 1}, Key{3, 1}, Key{0, 0} } + }}; +}; + +template<> +struct ScvfCorners +{ + using Key = std::pair; // (i, codim) + static constexpr std::array, 9> keys = {{ + { Key{0, 2}, Key{0, 1}, Key{1, 1}, Key{0, 0} }, + { Key{1, 2}, Key{2, 1}, Key{0, 1}, Key{0, 0} }, + { Key{2, 2}, Key{1, 1}, Key{2, 1}, Key{0, 0} }, + { Key{3, 2}, Key{0, 1}, Key{3, 1}, Key{0, 0} }, + { Key{4, 2}, Key{3, 1}, Key{1, 1}, Key{0, 0} }, + { Key{5, 2}, Key{2, 1}, Key{3, 1}, Key{0, 0} }, + { Key{6, 2}, Key{4, 1}, Key{0, 1}, Key{0, 0} }, + { Key{7, 2}, Key{1, 1}, Key{4, 1}, Key{0, 0} }, + { Key{8, 2}, Key{4, 1}, Key{2, 1}, Key{0, 0} } + }}; +}; + +template<> +struct ScvfCorners +{ + using Key = std::pair; // (i, codim) + static constexpr std::array, 12> keys = {{ + { Key{0, 1}, Key{0, 2}, Key{0, 0}, Key{2, 1} }, + { Key{1, 1}, Key{0, 0}, Key{1, 2}, Key{2, 1} }, + { Key{3, 1}, Key{2, 2}, Key{0, 0}, Key{0, 1} }, + { Key{3, 2}, Key{3, 1}, Key{1, 1}, Key{0, 0} }, + { Key{4, 1}, Key{4, 2}, Key{0, 0}, Key{0, 1} }, + { Key{5, 2}, Key{4, 1}, Key{1, 1}, Key{0, 0} }, + { Key{6, 2}, Key{4, 1}, Key{2, 1}, Key{0, 0} }, + { Key{4, 1}, Key{7, 2}, Key{0, 0}, Key{3, 1} }, + { Key{0, 0}, Key{0, 1}, Key{5, 1}, Key{8, 2} }, + { Key{9, 2}, Key{1, 1}, Key{5, 1}, Key{0, 0} }, + { Key{10, 2}, Key{2, 1}, Key{5, 1}, Key{0, 0} }, + { Key{11, 2}, Key{5, 1}, Key{3, 1}, Key{0, 0} } + }}; +}; + +// convert key array to global corner storage +template +S keyToCornerStorageImpl(const Geo& geo, const KeyArray& key, std::index_sequence) +{ + using Dune::referenceElement; + const auto ref = referenceElement(geo); + // key is a pair of a local sub-entity index (first) and the sub-entity's codim (second) + return { geo.global(ref.position(key[I].first, key[I].second))... }; +} + +// convert key array to global corner storage +template> +S keyToCornerStorage(const Geo& geo, const std::array& key) +{ + return keyToCornerStorageImpl(geo, key, Indices{}); +} + +// convert key array to global corner storage +// for the i-th sub-entity of codim c (e.g. the i-th facet/codim-1-entity for boundaries) +template +S subEntityKeyToCornerStorageImpl(const Geo& geo, unsigned int i, unsigned int c, const KeyArray& key, std::index_sequence) +{ + using Dune::referenceElement; + const auto ref = referenceElement(geo); + // subEntity gives the subEntity number with respect to the codim-0 reference element + // key is a pair of a local sub-entity index (first) and the sub-entity's codim (second) but here w.r.t. the sub-entity i/c + return { geo.global(ref.position(ref.subEntity(i, c, key[I].first, c+key[I].second), c+key[I].second))... }; +} + +// convert key array to global corner storage +// for the i-th sub-entity of codim c (e.g. the i-th facet/codim-1-entity for boundaries) +template> +S subEntityKeyToCornerStorage(const Geo& geo, unsigned int i, unsigned int c, const std::array& key) +{ + return subEntityKeyToCornerStorageImpl(geo, i, c, key, Indices{}); +} + +} // end namespace Detail::Box + +//! Create sub control volumes and sub control volume face geometries +template +class BoxGeometryHelper; + +//! A class to create sub control volume and sub control volume face geometries per element +template +class BoxGeometryHelper +{ +private: + using Scalar = typename GridView::ctype; + using GlobalPosition = typename Dune::FieldVector; + using ScvCornerStorage = typename ScvType::Traits::CornerStorage; + using ScvfCornerStorage = typename ScvfType::Traits::CornerStorage; + using ScvGeometry = typename ScvType::Traits::Geometry; + using ScvfGeometry = typename ScvfType::Traits::Geometry; + + using Element = typename GridView::template Codim<0>::Entity; + using Intersection = typename GridView::Intersection; + + static constexpr int dim = 1; +public: + + explicit BoxGeometryHelper(const typename Element::Geometry& geometry) + : geo_(geometry) + {} + + //! Create a vector with the scv corners + ScvCornerStorage getScvCorners(unsigned int localScvIdx) const + { + using Corners = Detail::Box::ScvCorners; + return Detail::Box::keyToCornerStorage(geo_, Corners::keys[localScvIdx]); + } + + ScvGeometry scvGeometry(unsigned int localScvIdx) const + { + return { Dune::GeometryTypes::line, getScvCorners(localScvIdx) }; + } + + //! Create a vector with the corners of sub control volume faces + ScvfCornerStorage getScvfCorners(unsigned int localScvfIdx) const + { + using Corners = Detail::Box::ScvfCorners; + return Detail::Box::keyToCornerStorage(geo_, Corners::keys[localScvfIdx]); + } + + //! Create the sub control volume face geometries on the boundary + ScvfCornerStorage getBoundaryScvfCorners(unsigned int localFacetIndex, + unsigned int) const + { + return ScvfCornerStorage{{ geo_.corner(localFacetIndex) }}; + } + + //! get scvf normal vector + GlobalPosition normal(const ScvfCornerStorage& scvfCorners, + const std::vector& scvIndices) const + { + auto normal = geo_.corner(1) - geo_.corner(0); + normal /= normal.two_norm(); + return normal; + } + + //! number of sub control volume faces (number of edges) + std::size_t numInteriorScvf() const + { + return referenceElement(geo_).size(dim-1); + } + + //! number of sub control volumes (number of vertices) + std::size_t numScv() const + { + return referenceElement(geo_).size(dim); + } + + //! the wrapped element geometry + const typename Element::Geometry& elementGeometry() const + { return geo_; } + +private: + const typename Element::Geometry& geo_; //!< Reference to the element geometry +}; + +//! A class to create sub control volume and sub control volume face geometries per element +template +class BoxGeometryHelper +{ + using Scalar = typename GridView::ctype; + using GlobalPosition = typename Dune::FieldVector; + using ScvCornerStorage = typename ScvType::Traits::CornerStorage; + using ScvfCornerStorage = typename ScvfType::Traits::CornerStorage; + + using Element = typename GridView::template Codim<0>::Entity; + using Intersection = typename GridView::Intersection; + + static constexpr auto dim = GridView::dimension; + static constexpr auto dimWorld = GridView::dimensionworld; +public: + + explicit BoxGeometryHelper(const typename Element::Geometry& geometry) + : geo_(geometry) + {} + + //! Create a vector with the scv corners + ScvCornerStorage getScvCorners(unsigned int localScvIdx) const + { + // proceed according to number of corners of the element + const auto type = geo_.type(); + if (type == Dune::GeometryTypes::triangle) + { + using Corners = Detail::Box::ScvCorners; + return Detail::Box::keyToCornerStorage(geo_, Corners::keys[localScvIdx]); + } + else if (type == Dune::GeometryTypes::quadrilateral) + { + using Corners = Detail::Box::ScvCorners; + return Detail::Box::keyToCornerStorage(geo_, Corners::keys[localScvIdx]); + } + else + DUNE_THROW(Dune::NotImplemented, "Box scv geometries for dim=" << dim + << " dimWorld=" << dimWorld + << " type=" << type); + } + + //! Create a vector with the corners of sub control volume faces + ScvfCornerStorage getScvfCorners(unsigned int localScvfIdx) const + { + // proceed according to number of corners + const auto type = geo_.type(); + if (type == Dune::GeometryTypes::triangle) + { + using Corners = Detail::Box::ScvfCorners; + return Detail::Box::keyToCornerStorage(geo_, Corners::keys[localScvfIdx]); + } + else if (type == Dune::GeometryTypes::quadrilateral) + { + using Corners = Detail::Box::ScvfCorners; + return Detail::Box::keyToCornerStorage(geo_, Corners::keys[localScvfIdx]); + } + else + DUNE_THROW(Dune::NotImplemented, "Box scvf geometries for dim=" << dim + << " dimWorld=" << dimWorld + << " type=" << type); + } + + //! Create the sub control volume face geometries on the boundary + ScvfCornerStorage getBoundaryScvfCorners(unsigned int localFacetIndex, + unsigned int indexInFacet) const + { + // we have to use the corresponding facet geometry as the intersection geometry + // might be rotated or flipped. This makes sure that the corners (dof location) + // and corresponding scvfs are sorted in the same way + using Corners = Detail::Box::ScvCorners; + constexpr int facetCodim = 1; + return Detail::Box::subEntityKeyToCornerStorage(geo_, localFacetIndex, facetCodim, Corners::keys[indexInFacet]); + } + + //! get scvf normal vector for dim == 2, dimworld == 3 + template + typename std::enable_if::type + normal(const ScvfCornerStorage& scvfCorners, + const std::vector& scvIndices) const + { + const auto v1 = geo_.corner(1) - geo_.corner(0); + const auto v2 = geo_.corner(2) - geo_.corner(0); + const auto v3 = Dumux::crossProduct(v1, v2); + const auto t = scvfCorners[1] - scvfCorners[0]; + GlobalPosition normal = Dumux::crossProduct(v3, t); + normal /= normal.two_norm(); + + //! ensure the right direction of the normal + const auto v = geo_.corner(scvIndices[1]) - geo_.corner(scvIndices[0]); + const auto s = v*normal; + if (std::signbit(s)) + normal *= -1; + + return normal; + } + + //! get scvf normal vector for dim == 2, dimworld == 2 + template + typename std::enable_if::type + normal(const ScvfCornerStorage& scvfCorners, + const std::vector& scvIndices) const + { + //! obtain normal vector by 90° counter-clockwise rotation of t + const auto t = scvfCorners[1] - scvfCorners[0]; + GlobalPosition normal({-t[1], t[0]}); + normal /= normal.two_norm(); + + //! ensure the right direction of the normal + const auto v = geo_.corner(scvIndices[1]) - geo_.corner(scvIndices[0]); + const auto s = v*normal; + if (std::signbit(s)) + normal *= -1; + + return normal; + } + + //! number of sub control volume faces (number of edges) + std::size_t numInteriorScvf() const + { + return referenceElement(geo_).size(dim-1); + } + + //! number of sub control volumes (number of vertices) + std::size_t numScv() const + { + return referenceElement(geo_).size(dim); + } + + //! the wrapped element geometry + const typename Element::Geometry& elementGeometry() const + { return geo_; } + +private: + const typename Element::Geometry& geo_; //!< Reference to the element geometry +}; + +//! A class to create sub control volume and sub control volume face geometries per element +template +class BoxGeometryHelper +{ + using Scalar = typename GridView::ctype; + using GlobalPosition = typename Dune::FieldVector; + using ScvCornerStorage = typename ScvType::Traits::CornerStorage; + using ScvfCornerStorage = typename ScvfType::Traits::CornerStorage; + + using Element = typename GridView::template Codim<0>::Entity; + using Intersection = typename GridView::Intersection; + + static constexpr auto dim = GridView::dimension; + static constexpr auto dimWorld = GridView::dimensionworld; + +public: + explicit BoxGeometryHelper(const typename Element::Geometry& geometry) + : geo_(geometry) + {} + + //! Create a vector with the scv corners + ScvCornerStorage getScvCorners(unsigned int localScvIdx) const + { + const auto type = geo_.type(); + if (type == Dune::GeometryTypes::tetrahedron) + { + using Corners = Detail::Box::ScvCorners; + return Detail::Box::keyToCornerStorage(geo_, Corners::keys[localScvIdx]); + } + else if (type == Dune::GeometryTypes::prism) + { + using Corners = Detail::Box::ScvCorners; + return Detail::Box::keyToCornerStorage(geo_, Corners::keys[localScvIdx]); + } + else if (type == Dune::GeometryTypes::hexahedron) + { + using Corners = Detail::Box::ScvCorners; + return Detail::Box::keyToCornerStorage(geo_, Corners::keys[localScvIdx]); + } + else + DUNE_THROW(Dune::NotImplemented, "Box scv geometries for dim=" << dim + << " dimWorld=" << dimWorld + << " type=" << type); + } + + //! Create a vector with the scvf corners + ScvfCornerStorage getScvfCorners(unsigned int localScvfIdx) const + { + // proceed according to number of corners + const auto type = geo_.type(); + if (type == Dune::GeometryTypes::tetrahedron) + { + using Corners = Detail::Box::ScvfCorners; + return Detail::Box::keyToCornerStorage(geo_, Corners::keys[localScvfIdx]); + } + else if (type == Dune::GeometryTypes::prism) + { + using Corners = Detail::Box::ScvfCorners; + return Detail::Box::keyToCornerStorage(geo_, Corners::keys[localScvfIdx]); + } + else if (type == Dune::GeometryTypes::hexahedron) + { + using Corners = Detail::Box::ScvfCorners; + return Detail::Box::keyToCornerStorage(geo_, Corners::keys[localScvfIdx]); + } + else + DUNE_THROW(Dune::NotImplemented, "Box scvf geometries for dim=" << dim + << " dimWorld=" << dimWorld + << " type=" << type); + } + + //! Create the sub control volume face geometries on the boundary + ScvfCornerStorage getBoundaryScvfCorners(unsigned localFacetIndex, + unsigned int indexInFacet) const + { + constexpr int facetCodim = 1; + + // we have to use the corresponding facet geometry as the intersection geometry + // might be rotated or flipped. This makes sure that the corners (dof location) + // and corresponding scvfs are sorted in the same way + using Dune::referenceElement; + const auto type = referenceElement(geo_).type(localFacetIndex, facetCodim); + if (type == Dune::GeometryTypes::triangle) + { + using Corners = Detail::Box::ScvCorners; + return Detail::Box::subEntityKeyToCornerStorage(geo_, localFacetIndex, facetCodim, Corners::keys[indexInFacet]); + } + else if (type == Dune::GeometryTypes::quadrilateral) + { + using Corners = Detail::Box::ScvCorners; + return Detail::Box::subEntityKeyToCornerStorage(geo_, localFacetIndex, facetCodim, Corners::keys[indexInFacet]); + } + else + DUNE_THROW(Dune::NotImplemented, "Box boundary scvf geometries for dim=" << dim + << " dimWorld=" << dimWorld + << " type=" << type); + } + + //! get scvf normal vector + GlobalPosition normal(const ScvfCornerStorage& p, + const std::vector& scvIndices) const + { + auto normal = Dumux::crossProduct(p[1]-p[0], p[2]-p[0]); + normal /= normal.two_norm(); + + const auto v = geo_.corner(scvIndices[1]) - geo_.corner(scvIndices[0]); + const auto s = v*normal; + if (std::signbit(s)) + normal *= -1; + + return normal; + } + + //! number of sub control volume faces (number of edges) + std::size_t numInteriorScvf() const + { + return referenceElement(geo_).size(dim-1); + } + + //! number of sub control volumes (number of vertices) + std::size_t numScv() const + { + return referenceElement(geo_).size(dim); + } + + //! the wrapped element geometry + const typename Element::Geometry& elementGeometry() const + { return geo_; } + +private: + const typename Element::Geometry& geo_; //!< Reference to the element geometry +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/box/fvelementgeometry.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/box/fvelementgeometry.hh new file mode 100644 index 0000000..2232e3b --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/box/fvelementgeometry.hh @@ -0,0 +1,495 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup BoxDiscretization + * \brief Base class for the local finite volume geometry for box models + * This builds up the sub control volumes and sub control volume faces + * for an element. + */ +#ifndef DUMUX_DISCRETIZATION_BOX_FV_ELEMENT_GEOMETRY_HH +#define DUMUX_DISCRETIZATION_BOX_FV_ELEMENT_GEOMETRY_HH + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace Dumux { + +/*! + * \ingroup BoxDiscretization + * \brief Base class for the finite volume geometry vector for box models + * This builds up the sub control volumes and sub control volume faces + * for each element. + * \tparam GG the finite volume grid geometry type + * \tparam enableGridGeometryCache if the grid geometry is cached or not + */ +template +class BoxFVElementGeometry; + +//! specialization in case the FVElementGeometries are stored +template +class BoxFVElementGeometry +{ + using GridView = typename GG::GridView; + static constexpr int dim = GridView::dimension; + static constexpr int dimWorld = GridView::dimensionworld; + using GridIndexType = typename IndexTraits::GridIndex; + using LocalIndexType = typename IndexTraits::LocalIndex; + using CoordScalar = typename GridView::ctype; + using FeLocalBasis = typename GG::FeCache::FiniteElementType::Traits::LocalBasisType; + using GGCache = typename GG::Cache; + using GeometryHelper = typename GGCache::GeometryHelper; +public: + //! export the element type + using Element = typename GridView::template Codim<0>::Entity; + //! export type of subcontrol volume + using SubControlVolume = typename GG::SubControlVolume; + //! export type of subcontrol volume face + using SubControlVolumeFace = typename GG::SubControlVolumeFace; + //! export type of finite volume grid geometry + using GridGeometry = GG; + //! the maximum number of scvs per element (2^dim for cubes) + static constexpr std::size_t maxNumElementScvs = (1<scvs(eIdx_)[scvIdx]; + } + + //! Get a sub control volume face with a local scvf index + const SubControlVolumeFace& scvf(LocalIndexType scvfIdx) const + { + return ggCache_->scvfs(eIdx_)[scvfIdx]; + } + + //! iterator range for sub control volumes. Iterates over + //! all scvs of the bound element. + //! This is a free function found by means of ADL + //! To iterate over all sub control volumes of this FVElementGeometry use + //! for (auto&& scv : scvs(fvGeometry)) + friend inline Dune::IteratorRange::const_iterator> + scvs(const BoxFVElementGeometry& fvGeometry) + { + using Iter = typename std::vector::const_iterator; + const auto& s = fvGeometry.ggCache_->scvs(fvGeometry.eIdx_); + return Dune::IteratorRange(s.begin(), s.end()); + } + + //! iterator range for sub control volumes faces. Iterates over + //! all scvfs of the bound element. + //! This is a free function found by means of ADL + //! To iterate over all sub control volume faces of this FVElementGeometry use + //! for (auto&& scvf : scvfs(fvGeometry)) + friend inline Dune::IteratorRange::const_iterator> + scvfs(const BoxFVElementGeometry& fvGeometry) + { + using Iter = typename std::vector::const_iterator; + const auto& s = fvGeometry.ggCache_->scvfs(fvGeometry.eIdx_); + return Dune::IteratorRange(s.begin(), s.end()); + } + + //! Get a local finite element basis + const FeLocalBasis& feLocalBasis() const + { + return gridGeometry().feCache().get(element_->type()).localBasis(); + } + + //! The total number of sub control volumes + std::size_t numScv() const + { + return ggCache_->scvs(eIdx_).size(); + } + + //! The total number of sub control volume faces + std::size_t numScvf() const + { + return ggCache_->scvfs(eIdx_).size(); + } + + /*! + * \brief bind the local view (r-value overload) + * This overload is called when an instance of this class is a temporary in the usage context + * This allows a usage like this: `const auto view = localView(...).bind(element);` + */ + BoxFVElementGeometry bind(const Element& element) && + { + this->bindElement(element); + return std::move(*this); + } + + //! this function is for compatibility reasons with cc methods + //! The box stencil is always element-local so bind and bindElement + //! are identical. + void bind(const Element& element) & + { this->bindElement(element); } + + /*! + * \brief bind the local view (r-value overload) + * This overload is called when an instance of this class is a temporary in the usage context + * This allows a usage like this: `const auto view = localView(...).bindElement(element);` + */ + BoxFVElementGeometry bindElement(const Element& element) && + { + this->bindElement(element); + return std::move(*this); + } + + //! Binding of an element, has to be called before using the fvgeometries + //! Prepares all the volume variables within the element + //! For compatibility reasons with the FVGeometry cache being disabled + void bindElement(const Element& element) & + { + element_ = element; + // cache element index + eIdx_ = gridGeometry().elementMapper().index(element); + } + + //! Returns true if bind/bindElement has already been called + bool isBound() const + { return static_cast(element_); } + + //! The bound element + const Element& element() const + { return *element_; } + + //! The bound element's index in the grid view + GridIndexType elementIndex() const + { return eIdx_; } + + //! The grid geometry we are a restriction of + const GridGeometry& gridGeometry() const + { return ggCache_->gridGeometry(); } + + //! Returns whether one of the geometry's scvfs lies on a boundary + bool hasBoundaryScvf() const + { return ggCache_->hasBoundaryScvf(eIdx_); } + + //! Geometry of a sub control volume + typename SubControlVolume::Traits::Geometry geometry(const SubControlVolume& scv) const + { + assert(isBound()); + const auto geo = element().geometry(); + return { Dune::GeometryTypes::cube(dim), GeometryHelper(geo).getScvCorners(scv.indexInElement()) }; + } + + //! Geometry of a sub control volume face + typename SubControlVolumeFace::Traits::Geometry geometry(const SubControlVolumeFace& scvf) const + { + assert(isBound()); + const auto geo = element().geometry(); + const GeometryHelper geometryHelper(geo); + if (scvf.boundary()) + { + const auto localBoundaryIndex = scvf.index() - geometryHelper.numInteriorScvf(); + const auto& key = ggCache_->scvfBoundaryGeometryKeys(eIdx_)[localBoundaryIndex]; + return { Dune::GeometryTypes::cube(dim-1), geometryHelper.getBoundaryScvfCorners(key[0], key[1]) }; + } + else + return { Dune::GeometryTypes::cube(dim-1), geometryHelper.getScvfCorners(scvf.index()) }; + } + +private: + const GGCache* ggCache_; + GridIndexType eIdx_; + + std::optional element_; +}; + +//! specialization in case the FVElementGeometries are not stored +template +class BoxFVElementGeometry +{ + using GridView = typename GG::GridView; + static constexpr int dim = GridView::dimension; + static constexpr int dimWorld = GridView::dimensionworld; + using GridIndexType = typename IndexTraits::GridIndex; + using LocalIndexType = typename IndexTraits::LocalIndex; + using CoordScalar = typename GridView::ctype; + using FeLocalBasis = typename GG::FeCache::FiniteElementType::Traits::LocalBasisType; + using GGCache = typename GG::Cache; + using GeometryHelper = typename GGCache::GeometryHelper; +public: + //! export the element type + using Element = typename GridView::template Codim<0>::Entity; + //! export type of subcontrol volume + using SubControlVolume = typename GG::SubControlVolume; + //! export type of subcontrol volume face + using SubControlVolumeFace = typename GG::SubControlVolumeFace; + //! export type of finite volume grid geometry + using GridGeometry = GG; + //! the maximum number of scvs per element (2^dim for cubes) + static constexpr std::size_t maxNumElementScvs = (1<::const_iterator> + scvs(const BoxFVElementGeometry& fvGeometry) + { + using Iter = typename std::vector::const_iterator; + return Dune::IteratorRange(fvGeometry.scvs_.begin(), fvGeometry.scvs_.end()); + } + + //! iterator range for sub control volumes faces. Iterates over + //! all scvfs of the bound element. + //! This is a free function found by means of ADL + //! To iterate over all sub control volume faces of this FVElementGeometry use + //! for (auto&& scvf : scvfs(fvGeometry)) + friend inline Dune::IteratorRange::const_iterator> + scvfs(const BoxFVElementGeometry& fvGeometry) + { + using Iter = typename std::vector::const_iterator; + return Dune::IteratorRange(fvGeometry.scvfs_.begin(), fvGeometry.scvfs_.end()); + } + + //! Get a local finite element basis + const FeLocalBasis& feLocalBasis() const + { + return gridGeometry().feCache().get(element_->type()).localBasis(); + } + + //! The total number of sub control volumes + std::size_t numScv() const + { + return scvs_.size(); + } + + //! The total number of sub control volume faces + std::size_t numScvf() const + { + return scvfs_.size(); + } + + /*! + * \brief bind the local view (r-value overload) + * This overload is called when an instance of this class is a temporary in the usage context + * This allows a usage like this: `const auto view = localView(...).bind(element);` + */ + BoxFVElementGeometry bind(const Element& element) && + { + this->bindElement(element); + return std::move(*this); + } + + //! this function is for compatibility reasons with cc methods + //! The box stencil is always element-local so bind and bindElement + //! are identical. + void bind(const Element& element) & + { this->bindElement(element); } + + /*! + * \brief bind the local view (r-value overload) + * This overload is called when an instance of this class is a temporary in the usage context + * This allows a usage like this: `const auto view = localView(...).bindElement(element);` + */ + BoxFVElementGeometry bindElement(const Element& element) && + { + this->bindElement(element); + return std::move(*this); + } + + //! Binding of an element, has to be called before using the fvgeometries + //! Prepares all the volume variables within the element + //! For compatibility reasons with the FVGeometry cache being disabled + void bindElement(const Element& element) & + { + element_ = element; + eIdx_ = gridGeometry().elementMapper().index(element); + makeElementGeometries_(); + } + + //! Returns true if bind/bindElement has already been called + bool isBound() const + { return static_cast(element_); } + + //! The bound element + const Element& element() const + { return *element_; } + + //! The bound element's index in the grid view + GridIndexType elementIndex() const + { return eIdx_; } + + //! The grid geometry we are a restriction of + const GridGeometry& gridGeometry() const + { return ggCache_->gridGeometry(); } + + //! Returns whether one of the geometry's scvfs lies on a boundary + bool hasBoundaryScvf() const + { return hasBoundaryScvf_; } + + //! Geometry of a sub control volume + typename SubControlVolume::Traits::Geometry geometry(const SubControlVolume& scv) const + { + assert(isBound()); + const auto geo = element().geometry(); + return { Dune::GeometryTypes::cube(dim), GeometryHelper(geo).getScvCorners(scv.indexInElement()) }; + } + + //! Geometry of a sub control volume face + typename SubControlVolumeFace::Traits::Geometry geometry(const SubControlVolumeFace& scvf) const + { + assert(isBound()); + const auto geo = element().geometry(); + const GeometryHelper geometryHelper(geo); + if (scvf.boundary()) + { + const auto localBoundaryIndex = scvf.index() - geometryHelper.numInteriorScvf(); + const auto& key = scvfBoundaryGeometryKeys_[localBoundaryIndex]; + return { Dune::GeometryTypes::cube(dim-1), geometryHelper.getBoundaryScvfCorners(key[0], key[1]) }; + } + else + return { Dune::GeometryTypes::cube(dim-1), geometryHelper.getScvfCorners(scvf.index()) }; + } + +private: + void makeElementGeometries_() + { + hasBoundaryScvf_ = false; + + // get the element geometry + const auto& element = *element_; + const auto elementGeometry = element.geometry(); + const auto refElement = referenceElement(elementGeometry); + + // get the sub control volume geometries of this element + GeometryHelper geometryHelper(elementGeometry); + + // construct the sub control volumes + scvs_.resize(elementGeometry.corners()); + for (LocalIndexType scvLocalIdx = 0; scvLocalIdx < elementGeometry.corners(); ++scvLocalIdx) + { + // get associated dof index + const auto dofIdxGlobal = gridGeometry().vertexMapper().subIndex(element, scvLocalIdx, dim); + + // add scv to the local container + scvs_[scvLocalIdx] = SubControlVolume( + geometryHelper.getScvCorners(scvLocalIdx), + scvLocalIdx, + eIdx_, + dofIdxGlobal + ); + } + + // construct the sub control volume faces + const auto numInnerScvf = geometryHelper.numInteriorScvf(); + scvfs_.resize(numInnerScvf); + scvfBoundaryGeometryKeys_.clear(); + + LocalIndexType scvfLocalIdx = 0; + for (; scvfLocalIdx < numInnerScvf; ++scvfLocalIdx) + { + // find the local scv indices this scvf is connected to + std::vector localScvIndices({static_cast(refElement.subEntity(scvfLocalIdx, dim-1, 0, dim)), + static_cast(refElement.subEntity(scvfLocalIdx, dim-1, 1, dim))}); + + const auto& corners = geometryHelper.getScvfCorners(scvfLocalIdx); + scvfs_[scvfLocalIdx] = SubControlVolumeFace( + corners, + geometryHelper.normal(corners, localScvIndices), + element, + elementGeometry, + scvfLocalIdx, + std::move(localScvIndices), + false + ); + } + + // construct the sub control volume faces on the domain boundary + for (const auto& intersection : intersections(gridGeometry().gridView(), element)) + { + if (intersection.boundary() && !intersection.neighbor()) + { + const auto isGeometry = intersection.geometry(); + hasBoundaryScvf_ = true; + + for (unsigned int isScvfLocalIdx = 0; isScvfLocalIdx < isGeometry.corners(); ++isScvfLocalIdx) + { + // find the scv this scvf is connected to + const LocalIndexType insideScvIdx = static_cast(refElement.subEntity(intersection.indexInInside(), 1, isScvfLocalIdx, dim)); + std::vector localScvIndices = {insideScvIdx, insideScvIdx}; + + scvfs_.emplace_back( + geometryHelper.getBoundaryScvfCorners(intersection.indexInInside(), isScvfLocalIdx), + intersection.centerUnitOuterNormal(), + intersection, + isGeometry, + isScvfLocalIdx, + scvfLocalIdx, + std::move(localScvIndices), + true + ); + + scvfBoundaryGeometryKeys_.emplace_back(std::array{{ + static_cast(intersection.indexInInside()), + static_cast(isScvfLocalIdx) + }}); + + // increment local counter + scvfLocalIdx++; + } + } + } + } + + //! The bound element + GridIndexType eIdx_; + std::optional element_; + + //! The global geometry cache + const GGCache* ggCache_; + + //! vectors to store the geometries locally after binding an element + std::vector scvs_; + std::vector scvfs_; + std::vector> scvfBoundaryGeometryKeys_; + + bool hasBoundaryScvf_ = false; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/box/fvgridgeometry.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/box/fvgridgeometry.hh new file mode 100644 index 0000000..e43bfc4 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/box/fvgridgeometry.hh @@ -0,0 +1,679 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup BoxDiscretization + * \brief Base class for the finite volume geometry vector for box models + * This builds up the sub control volumes and sub control volume faces + * for each element of the grid partition. + */ +#ifndef DUMUX_DISCRETIZATION_BOX_GRID_FVGEOMETRY_HH +#define DUMUX_DISCRETIZATION_BOX_GRID_FVGEOMETRY_HH + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace Dumux { + +namespace Detail { +template +using BoxGeometryHelper_t = Dune::Std::detected_or_t< + Dumux::BoxGeometryHelper, + SpecifiesGeometryHelper, + T +>; +} // end namespace Detail + +/*! + * \ingroup BoxDiscretization + * \brief The default traits for the box finite volume grid geometry + * Defines the scv and scvf types and the mapper types + * \tparam the grid view type + */ +template> +struct BoxDefaultGridGeometryTraits +: public MapperTraits +{ + using SubControlVolume = BoxSubControlVolume; + using SubControlVolumeFace = BoxSubControlVolumeFace; + + template + using LocalView = BoxFVElementGeometry; +}; + +/*! + * \ingroup BoxDiscretization + * \brief Base class for the finite volume geometry vector for box schemes + * This builds up the sub control volumes and sub control volume faces + * \note This class is specialized for versions with and without caching the fv geometries on the grid view + */ +template > +class BoxFVGridGeometry; + +/*! + * \ingroup BoxDiscretization + * \brief Base class for the finite volume geometry vector for box schemes + * This builds up the sub control volumes and sub control volume faces + * \note For caching enabled we store the fv geometries for the whole grid view which is memory intensive but faster + */ +template +class BoxFVGridGeometry +: public BaseGridGeometry +{ + using ThisType = BoxFVGridGeometry; + using ParentType = BaseGridGeometry; + using GridIndexType = typename IndexTraits::GridIndex; + using LocalIndexType = typename IndexTraits::LocalIndex; + + using Element = typename GV::template Codim<0>::Entity; + using CoordScalar = typename GV::ctype; + static const int dim = GV::dimension; + static const int dimWorld = GV::dimensionworld; + +public: + //! export the discretization method this geometry belongs to + using DiscretizationMethod = DiscretizationMethods::Box; + static constexpr DiscretizationMethod discMethod{}; + + //! export basic grid geometry type for the alternative constructor + using BasicGridGeometry = BasicGridGeometry_t; + //! export the type of the fv element geometry (the local view type) + using LocalView = typename Traits::template LocalView; + //! export the type of sub control volume + using SubControlVolume = typename Traits::SubControlVolume; + //! export the type of sub control volume + using SubControlVolumeFace = typename Traits::SubControlVolumeFace; + //! export the type of extrusion + using Extrusion = Extrusion_t; + //! export dof mapper type + using DofMapper = typename Traits::VertexMapper; + //! export the finite element cache type + using FeCache = Dune::LagrangeLocalFiniteElementCache; + //! export the grid view type + using GridView = GV; + //! export whether the grid(geometry) supports periodicity + using SupportsPeriodicity = typename PeriodicGridTraits::SupportsPeriodicity; + + //! Constructor with basic grid geometry used to share state with another grid geometry on the same grid view + BoxFVGridGeometry(std::shared_ptr gg) + : ParentType(std::move(gg)) + , cache_(*this) + , periodicGridTraits_(this->gridView().grid()) + { + update_(); + } + + //! Constructor + BoxFVGridGeometry(const GridView& gridView) + : BoxFVGridGeometry(std::make_shared(gridView)) + {} + + //! the vertex mapper is the dofMapper + //! this is convenience to have better chance to have the same main files for box/tpfa/mpfa... + const DofMapper& dofMapper() const + { return this->vertexMapper(); } + + //! The total number of sub control volumes + std::size_t numScv() const + { return numScv_; } + + //! The total number of sun control volume faces + std::size_t numScvf() const + { return numScvf_; } + + //! The total number of boundary sub control volume faces + //! For compatibility reasons with cc methods + std::size_t numBoundaryScvf() const + { return numBoundaryScvf_; } + + //! The total number of degrees of freedom + std::size_t numDofs() const + { return this->vertexMapper().size(); } + + + //! update all fvElementGeometries (call this after grid adaption) + void update(const GridView& gridView) + { + ParentType::update(gridView); + update_(); + } + + //! update all fvElementGeometries (call this after grid adaption) + void update(GridView&& gridView) + { + ParentType::update(std::move(gridView)); + update_(); + } + + //! The finite element cache for creating local FE bases + const FeCache& feCache() const + { return feCache_; } + + //! If a vertex / d.o.f. is on the boundary + bool dofOnBoundary(GridIndexType dofIdx) const + { return boundaryDofIndices_[dofIdx]; } + + //! If a vertex / d.o.f. is on a periodic boundary + bool dofOnPeriodicBoundary(GridIndexType dofIdx) const + { return periodicDofMap_.count(dofIdx); } + + //! The index of the vertex / d.o.f. on the other side of the periodic boundary + GridIndexType periodicallyMappedDof(GridIndexType dofIdx) const + { return periodicDofMap_.at(dofIdx); } + + //! Returns the map between dofs across periodic boundaries + const std::unordered_map& periodicDofMap() const + { return periodicDofMap_; } + + //! local view of this object (constructed with the internal cache) + friend inline LocalView localView(const BoxFVGridGeometry& gg) + { return { gg.cache_ }; } + +private: + + class BoxGridGeometryCache + { + friend class BoxFVGridGeometry; + public: + //! export the geometry helper type + using GeometryHelper = Detail::BoxGeometryHelper_t; + + explicit BoxGridGeometryCache(const BoxFVGridGeometry& gg) + : gridGeometry_(&gg) + {} + + const BoxFVGridGeometry& gridGeometry() const + { return *gridGeometry_; } + + //! Get the global sub control volume indices of an element + const std::vector& scvs(GridIndexType eIdx) const + { return scvs_[eIdx]; } + + //! Get the global sub control volume face indices of an element + const std::vector& scvfs(GridIndexType eIdx) const + { return scvfs_[eIdx]; } + + //! Returns whether one of the geometry's scvfs lies on a boundary + bool hasBoundaryScvf(GridIndexType eIdx) const + { return hasBoundaryScvf_[eIdx]; } + + //! Returns local mappings for constructing boundary scvf geometries + const std::vector>& scvfBoundaryGeometryKeys(GridIndexType eIdx) const + { return scvfBoundaryGeometryKeys_.at(eIdx); } + + private: + void clear_() + { + scvs_.clear(); + scvfs_.clear(); + hasBoundaryScvf_.clear(); + scvfBoundaryGeometryKeys_.clear(); + } + + std::vector> scvs_; + std::vector> scvfs_; + std::vector hasBoundaryScvf_; + std::unordered_map>> scvfBoundaryGeometryKeys_; + + const BoxFVGridGeometry* gridGeometry_; + }; + +public: + //! the cache type (only the caching implementation has this) + //! this alias should only be used by the local view implementation + using Cache = BoxGridGeometryCache; + +private: + using GeometryHelper = typename Cache::GeometryHelper; + + void update_() + { + cache_.clear_(); + + const auto numElements = this->gridView().size(0); + cache_.scvs_.resize(numElements); + cache_.scvfs_.resize(numElements); + cache_.hasBoundaryScvf_.resize(numElements, false); + + boundaryDofIndices_.assign(numDofs(), false); + + numScv_ = 0; + numScvf_ = 0; + numBoundaryScvf_ = 0; + // Build the SCV and SCV faces + for (const auto& element : elements(this->gridView())) + { + // fill the element map with seeds + const auto eIdx = this->elementMapper().index(element); + + // count + numScv_ += element.subEntities(dim); + numScvf_ += element.subEntities(dim-1); + + // get the element geometry + auto elementGeometry = element.geometry(); + const auto refElement = referenceElement(elementGeometry); + + // instantiate the geometry helper + GeometryHelper geometryHelper(elementGeometry); + + // construct the sub control volumes + cache_.scvs_[eIdx].resize(elementGeometry.corners()); + for (LocalIndexType scvLocalIdx = 0; scvLocalIdx < elementGeometry.corners(); ++scvLocalIdx) + { + const auto dofIdxGlobal = this->vertexMapper().subIndex(element, scvLocalIdx, dim); + + cache_.scvs_[eIdx][scvLocalIdx] = SubControlVolume( + geometryHelper.getScvCorners(scvLocalIdx), + scvLocalIdx, + eIdx, + dofIdxGlobal + ); + } + + // construct the sub control volume faces + LocalIndexType scvfLocalIdx = 0; + cache_.scvfs_[eIdx].resize(element.subEntities(dim-1)); + for (; scvfLocalIdx < element.subEntities(dim-1); ++scvfLocalIdx) + { + // find the global and local scv indices this scvf is belonging to + std::vector localScvIndices({static_cast(refElement.subEntity(scvfLocalIdx, dim-1, 0, dim)), + static_cast(refElement.subEntity(scvfLocalIdx, dim-1, 1, dim))}); + + const auto& corners = geometryHelper.getScvfCorners(scvfLocalIdx); + cache_.scvfs_[eIdx][scvfLocalIdx] = SubControlVolumeFace( + corners, + geometryHelper.normal(corners, localScvIndices), + element, + elementGeometry, + scvfLocalIdx, + std::move(localScvIndices), + false + ); + } + + // construct the sub control volume faces on the domain boundary + for (const auto& intersection : intersections(this->gridView(), element)) + { + if (intersection.boundary() && !intersection.neighbor()) + { + const auto isGeometry = intersection.geometry(); + cache_.hasBoundaryScvf_[eIdx] = true; + + // count + numScvf_ += isGeometry.corners(); + numBoundaryScvf_ += isGeometry.corners(); + + for (unsigned int isScvfLocalIdx = 0; isScvfLocalIdx < isGeometry.corners(); ++isScvfLocalIdx) + { + // find the scvs this scvf is belonging to + const LocalIndexType insideScvIdx = static_cast(refElement.subEntity(intersection.indexInInside(), 1, isScvfLocalIdx, dim)); + std::vector localScvIndices = {insideScvIdx, insideScvIdx}; + + cache_.scvfs_[eIdx].emplace_back( + geometryHelper.getBoundaryScvfCorners(intersection.indexInInside(), isScvfLocalIdx), + intersection.centerUnitOuterNormal(), + intersection, + isGeometry, + isScvfLocalIdx, + scvfLocalIdx, + std::move(localScvIndices), + true + ); + + cache_.scvfBoundaryGeometryKeys_[eIdx].emplace_back(std::array{{ + static_cast(intersection.indexInInside()), + static_cast(isScvfLocalIdx) + }}); + + // increment local counter + scvfLocalIdx++; + } + + // add all vertices on the intersection to the set of + // boundary vertices + const auto fIdx = intersection.indexInInside(); + const auto numFaceVerts = refElement.size(fIdx, 1, dim); + for (int localVIdx = 0; localVIdx < numFaceVerts; ++localVIdx) + { + const auto vIdx = refElement.subEntity(fIdx, 1, localVIdx, dim); + const auto vIdxGlobal = this->vertexMapper().subIndex(element, vIdx, dim); + boundaryDofIndices_[vIdxGlobal] = true; + } + } + + // inform the grid geometry if we have periodic boundaries + else if (periodicGridTraits_.isPeriodic(intersection)) + { + this->setPeriodic(); + + // find the mapped periodic vertex of all vertices on periodic boundaries + const auto fIdx = intersection.indexInInside(); + const auto numFaceVerts = refElement.size(fIdx, 1, dim); + const auto eps = 1e-7*(elementGeometry.corner(1) - elementGeometry.corner(0)).two_norm(); + for (int localVIdx = 0; localVIdx < numFaceVerts; ++localVIdx) + { + const auto vIdx = refElement.subEntity(fIdx, 1, localVIdx, dim); + const auto vIdxGlobal = this->vertexMapper().subIndex(element, vIdx, dim); + const auto vPos = elementGeometry.corner(vIdx); + + const auto& outside = intersection.outside(); + const auto outsideGeometry = outside.geometry(); + for (const auto& isOutside : intersections(this->gridView(), outside)) + { + // only check periodic vertices of the periodic neighbor + if (periodicGridTraits_.isPeriodic(isOutside)) + { + const auto fIdxOutside = isOutside.indexInInside(); + const auto numFaceVertsOutside = refElement.size(fIdxOutside, 1, dim); + for (int localVIdxOutside = 0; localVIdxOutside < numFaceVertsOutside; ++localVIdxOutside) + { + const auto vIdxOutside = refElement.subEntity(fIdxOutside, 1, localVIdxOutside, dim); + const auto vPosOutside = outsideGeometry.corner(vIdxOutside); + const auto shift = std::abs((this->bBoxMax()-this->bBoxMin())*intersection.centerUnitOuterNormal()); + if (std::abs((vPosOutside-vPos).two_norm() - shift) < eps) + periodicDofMap_[vIdxGlobal] = this->vertexMapper().subIndex(outside, vIdxOutside, dim); + } + } + } + } + } + } + } + + // error check: periodic boundaries currently don't work for box in parallel + if (this->isPeriodic() && this->gridView().comm().size() > 1) + DUNE_THROW(Dune::NotImplemented, "Periodic boundaries for box method for parallel simulations!"); + } + + const FeCache feCache_; + + std::size_t numScv_; + std::size_t numScvf_; + std::size_t numBoundaryScvf_; + + // vertices on the boundary + std::vector boundaryDofIndices_; + + // a map for periodic boundary vertices + std::unordered_map periodicDofMap_; + + Cache cache_; + + PeriodicGridTraits periodicGridTraits_; +}; + +/*! + * \ingroup BoxDiscretization + * \brief Base class for the finite volume geometry vector for box schemes + * This builds up the sub control volumes and sub control volume faces + * \note For caching disabled we store only some essential index maps to build up local systems on-demand in + * the corresponding FVElementGeometry + */ +template +class BoxFVGridGeometry +: public BaseGridGeometry +{ + using ThisType = BoxFVGridGeometry; + using ParentType = BaseGridGeometry; + using GridIndexType = typename IndexTraits::GridIndex; + + static const int dim = GV::dimension; + static const int dimWorld = GV::dimensionworld; + + using Element = typename GV::template Codim<0>::Entity; + using CoordScalar = typename GV::ctype; + +public: + //! export the discretization method this geometry belongs to + using DiscretizationMethod = DiscretizationMethods::Box; + static constexpr DiscretizationMethod discMethod{}; + + //! export basic grid geometry type for the alternative constructor + using BasicGridGeometry = BasicGridGeometry_t; + //! export the type of the fv element geometry (the local view type) + using LocalView = typename Traits::template LocalView; + //! export the type of sub control volume + using SubControlVolume = typename Traits::SubControlVolume; + //! export the type of sub control volume + using SubControlVolumeFace = typename Traits::SubControlVolumeFace; + //! export the type of extrusion + using Extrusion = Extrusion_t; + //! export dof mapper type + using DofMapper = typename Traits::VertexMapper; + //! export the finite element cache type + using FeCache = Dune::LagrangeLocalFiniteElementCache; + //! export the grid view type + using GridView = GV; + //! export whether the grid(geometry) supports periodicity + using SupportsPeriodicity = typename PeriodicGridTraits::SupportsPeriodicity; + + //! Constructor with basic grid geometry used to share state with another grid geometry on the same grid view + BoxFVGridGeometry(std::shared_ptr gg) + : ParentType(std::move(gg)) + , cache_(*this) + , periodicGridTraits_(this->gridView().grid()) + { + update_(); + } + + //! Constructor + BoxFVGridGeometry(const GridView& gridView) + : BoxFVGridGeometry(std::make_shared(gridView)) + {} + + //! the vertex mapper is the dofMapper + //! this is convenience to have better chance to have the same main files for box/tpfa/mpfa... + const DofMapper& dofMapper() const + { return this->vertexMapper(); } + + //! The total number of sub control volumes + std::size_t numScv() const + { return numScv_; } + + //! The total number of sun control volume faces + std::size_t numScvf() const + { return numScvf_; } + + //! The total number of boundary sub control volume faces + //! For compatibility reasons with cc methods + std::size_t numBoundaryScvf() const + { return numBoundaryScvf_; } + + //! The total number of degrees of freedom + std::size_t numDofs() const + { return this->vertexMapper().size(); } + + + //! update all fvElementGeometries (call this after grid adaption) + void update(const GridView& gridView) + { + ParentType::update(gridView); + update_(); + } + + //! update all fvElementGeometries (call this after grid adaption) + void update(GridView&& gridView) + { + ParentType::update(std::move(gridView)); + update_(); + } + + //! The finite element cache for creating local FE bases + const FeCache& feCache() const + { return feCache_; } + + //! If a vertex / d.o.f. is on the boundary + bool dofOnBoundary(GridIndexType dofIdx) const + { return boundaryDofIndices_[dofIdx]; } + + //! If a vertex / d.o.f. is on a periodic boundary + bool dofOnPeriodicBoundary(GridIndexType dofIdx) const + { return periodicDofMap_.count(dofIdx); } + + //! The index of the vertex / d.o.f. on the other side of the periodic boundary + GridIndexType periodicallyMappedDof(GridIndexType dofIdx) const + { return periodicDofMap_.at(dofIdx); } + + //! Returns the map between dofs across periodic boundaries + const std::unordered_map& periodicDofMap() const + { return periodicDofMap_; } + + //! local view of this object (constructed with the internal cache) + friend inline LocalView localView(const BoxFVGridGeometry& gg) + { return { gg.cache_ }; } + +private: + + class BoxGridGeometryCache + { + friend class BoxFVGridGeometry; + public: + //! export the geometry helper type + using GeometryHelper = Detail::BoxGeometryHelper_t; + + explicit BoxGridGeometryCache(const BoxFVGridGeometry& gg) + : gridGeometry_(&gg) + {} + + const BoxFVGridGeometry& gridGeometry() const + { return *gridGeometry_; } + + private: + const BoxFVGridGeometry* gridGeometry_; + }; + +public: + //! the cache type (only the caching implementation has this) + //! this alias should only be used by the local view implementation + using Cache = BoxGridGeometryCache; + +private: + + void update_() + { + boundaryDofIndices_.assign(numDofs(), false); + + // save global data on the grid's scvs and scvfs + // TODO do we need those information? + numScv_ = 0; + numScvf_ = 0; + numBoundaryScvf_ = 0; + for (const auto& element : elements(this->gridView())) + { + numScv_ += element.subEntities(dim); + numScvf_ += element.subEntities(dim-1); + + const auto elementGeometry = element.geometry(); + const auto refElement = referenceElement(elementGeometry); + + // store the sub control volume face indices on the domain boundary + for (const auto& intersection : intersections(this->gridView(), element)) + { + if (intersection.boundary() && !intersection.neighbor()) + { + const auto isGeometry = intersection.geometry(); + numScvf_ += isGeometry.corners(); + numBoundaryScvf_ += isGeometry.corners(); + + // add all vertices on the intersection to the set of + // boundary vertices + const auto fIdx = intersection.indexInInside(); + const auto numFaceVerts = refElement.size(fIdx, 1, dim); + for (int localVIdx = 0; localVIdx < numFaceVerts; ++localVIdx) + { + const auto vIdx = refElement.subEntity(fIdx, 1, localVIdx, dim); + const auto vIdxGlobal = this->vertexMapper().subIndex(element, vIdx, dim); + boundaryDofIndices_[vIdxGlobal] = true; + } + } + + // inform the grid geometry if we have periodic boundaries + else if (periodicGridTraits_.isPeriodic(intersection)) + { + this->setPeriodic(); + + // find the mapped periodic vertex of all vertices on periodic boundaries + const auto fIdx = intersection.indexInInside(); + const auto numFaceVerts = refElement.size(fIdx, 1, dim); + const auto eps = 1e-7*(elementGeometry.corner(1) - elementGeometry.corner(0)).two_norm(); + for (int localVIdx = 0; localVIdx < numFaceVerts; ++localVIdx) + { + const auto vIdx = refElement.subEntity(fIdx, 1, localVIdx, dim); + const auto vIdxGlobal = this->vertexMapper().subIndex(element, vIdx, dim); + const auto vPos = elementGeometry.corner(vIdx); + + const auto& outside = intersection.outside(); + const auto outsideGeometry = outside.geometry(); + for (const auto& isOutside : intersections(this->gridView(), outside)) + { + // only check periodic vertices of the periodic neighbor + if (periodicGridTraits_.isPeriodic(isOutside)) + { + const auto fIdxOutside = isOutside.indexInInside(); + const auto numFaceVertsOutside = refElement.size(fIdxOutside, 1, dim); + for (int localVIdxOutside = 0; localVIdxOutside < numFaceVertsOutside; ++localVIdxOutside) + { + const auto vIdxOutside = refElement.subEntity(fIdxOutside, 1, localVIdxOutside, dim); + const auto vPosOutside = outsideGeometry.corner(vIdxOutside); + const auto shift = std::abs((this->bBoxMax()-this->bBoxMin())*intersection.centerUnitOuterNormal()); + if (std::abs((vPosOutside-vPos).two_norm() - shift) < eps) + periodicDofMap_[vIdxGlobal] = this->vertexMapper().subIndex(outside, vIdxOutside, dim); + } + } + } + } + } + } + } + + // error check: periodic boundaries currently don't work for box in parallel + if (this->isPeriodic() && this->gridView().comm().size() > 1) + DUNE_THROW(Dune::NotImplemented, "Periodic boundaries for box method for parallel simulations!"); + } + + const FeCache feCache_; + + // Information on the global number of geometries + // TODO do we need those information? + std::size_t numScv_; + std::size_t numScvf_; + std::size_t numBoundaryScvf_; + + // vertices on the boundary + std::vector boundaryDofIndices_; + + // a map for periodic boundary vertices + std::unordered_map periodicDofMap_; + + Cache cache_; + + PeriodicGridTraits periodicGridTraits_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/box/subcontrolvolume.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/box/subcontrolvolume.hh new file mode 100644 index 0000000..eb40e22 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/box/subcontrolvolume.hh @@ -0,0 +1,151 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup BoxDiscretization + * \brief the sub control volume for the box scheme + */ +#ifndef DUMUX_DISCRETIZATION_BOX_SUBCONTROLVOLUME_HH +#define DUMUX_DISCRETIZATION_BOX_SUBCONTROLVOLUME_HH + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace Dumux { + +/*! + * \ingroup BoxDiscretization + * \brief Default traits class to be used for the sub-control volumes + * for the box scheme + * \tparam GV the type of the grid view + */ +template +struct BoxDefaultScvGeometryTraits +{ + using Grid = typename GridView::Grid; + + static const int dim = Grid::dimension; + static const int dimWorld = Grid::dimensionworld; + + using GridIndexType = typename IndexTraits::GridIndex; + using LocalIndexType = typename IndexTraits::LocalIndex; + using Scalar = typename Grid::ctype; + using GeometryTraits = BoxMLGeometryTraits; + using Geometry = Dune::MultiLinearGeometry; + using CornerStorage = typename GeometryTraits::template CornerStorage::Type; + using GlobalPosition = typename Geometry::GlobalCoordinate; +}; + +/*! + * \ingroup BoxDiscretization + * \brief the sub control volume for the box scheme + * \tparam GV the type of the grid view + * \tparam T the scvf geometry traits + */ +template > +class BoxSubControlVolume +: public SubControlVolumeBase, T> +{ + using ThisType = BoxSubControlVolume; + using ParentType = SubControlVolumeBase; + using Geometry = typename T::Geometry; + using GridIndexType = typename T::GridIndexType; + using LocalIndexType = typename T::LocalIndexType; + using Scalar = typename T::Scalar; + static constexpr int dim = Geometry::mydimension; + +public: + //! export the type used for global coordinates + using GlobalPosition = typename T::GlobalPosition; + //! state the traits public and thus export all types + using Traits = T; + + //! The default constructor + BoxSubControlVolume() = default; + + // the constructor in the box case + template + BoxSubControlVolume(const Corners& corners, + LocalIndexType scvIdx, + GridIndexType elementIndex, + GridIndexType dofIndex) + : dofPosition_(corners[0]) + , center_(Dumux::center(corners)) + , elementIndex_(elementIndex) + , localDofIdx_(scvIdx) + , dofIndex_(dofIndex) + { + // The corner list is defined such that the first entry is the vertex itself + volume_ = Dumux::convexPolytopeVolume( + Dune::GeometryTypes::cube(dim), + [&](unsigned int i){ return corners[i]; } + ); + } + + //! The center of the sub control volume + const GlobalPosition& center() const + { + return center_; + } + + //! The volume of the sub control volume + Scalar volume() const + { + return volume_; + } + + //! The element-local index of the dof this scv is embedded in + LocalIndexType localDofIndex() const + { + return localDofIdx_; + } + + //! The element-local index of this scv. + //! For the standard box scheme this is the local dof index. + LocalIndexType indexInElement() const + { + return localDofIdx_; + } + + //! The index of the dof this scv is embedded in + GridIndexType dofIndex() const + { + return dofIndex_; + } + + // The position of the dof this scv is embedded in + const GlobalPosition& dofPosition() const + { + return dofPosition_; + } + + //! The global index of the element this scv is embedded in + GridIndexType elementIndex() const + { + return elementIndex_; + } + +private: + GlobalPosition dofPosition_; + GlobalPosition center_; + Scalar volume_; + GridIndexType elementIndex_; + LocalIndexType localDofIdx_; + GridIndexType dofIndex_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/box/subcontrolvolumeface.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/box/subcontrolvolumeface.hh new file mode 100644 index 0000000..8674a8f --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/box/subcontrolvolumeface.hh @@ -0,0 +1,199 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup BoxDiscretization + * \brief Base class for a sub control volume face + */ +#ifndef DUMUX_DISCRETIZATION_BOX_SUBCONTROLVOLUMEFACE_HH +#define DUMUX_DISCRETIZATION_BOX_SUBCONTROLVOLUMEFACE_HH + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace Dumux { + +/*! + * \ingroup BoxDiscretization + * \brief Default traits class to be used for the sub-control volume faces + * for the box scheme + * \tparam GV the type of the grid view + */ +template +struct BoxDefaultScvfGeometryTraits +{ + using Grid = typename GridView::Grid; + static constexpr int dim = Grid::dimension; + static constexpr int dimWorld = Grid::dimensionworld; + using GridIndexType = typename IndexTraits::GridIndex; + using LocalIndexType = typename IndexTraits::LocalIndex; + using Scalar = typename Grid::ctype; + using GeometryTraits = BoxMLGeometryTraits; + using Geometry = Dune::MultiLinearGeometry; + using CornerStorage = typename GeometryTraits::template CornerStorage::Type; + using GlobalPosition = typename Geometry::GlobalCoordinate; + using BoundaryFlag = Dumux::BoundaryFlag; +}; + +/*! + * \ingroup BoxDiscretization + * \brief Class for a sub control volume face in the box method, i.e a part of the boundary + * of a sub control volume we compute fluxes on. We simply use the base class here. + * \tparam GV the type of the grid view + * \tparam T the scvf geometry traits + */ +template > +class BoxSubControlVolumeFace +: public SubControlVolumeFaceBase, T> +{ + using ThisType = BoxSubControlVolumeFace; + using ParentType = SubControlVolumeFaceBase; + using GridIndexType = typename T::GridIndexType; + using LocalIndexType = typename T::LocalIndexType; + using Scalar = typename T::Scalar; + using Geometry = typename T::Geometry; + using BoundaryFlag = typename T::BoundaryFlag; + static constexpr int dim = Geometry::mydimension; + +public: + //! export the type used for global coordinates + using GlobalPosition = typename T::GlobalPosition; + //! state the traits public and thus export all types + using Traits = T; + + //! The default constructor + BoxSubControlVolumeFace() = default; + + //! Constructor for inner scvfs + template + BoxSubControlVolumeFace(const Corners& corners, + const GlobalPosition& normal, + const Element& element, + const typename Element::Geometry& elemGeometry, + GridIndexType scvfIndex, + std::vector&& scvIndices, + bool boundary = false) + : center_(Dumux::center(corners)) + , unitOuterNormal_(normal) + , scvfIndex_(scvfIndex) + , scvIndices_(std::move(scvIndices)) + , boundary_(boundary) + , boundaryFlag_{} + { + area_ = Dumux::convexPolytopeVolume( + Dune::GeometryTypes::cube(dim), + [&](unsigned int i){ return corners[i]; } + ); + } + + //! Constructor for boundary scvfs + template + BoxSubControlVolumeFace(const Corners& corners, + const GlobalPosition& normal, + const Intersection& intersection, + const typename Intersection::Geometry& isGeometry, + LocalIndexType indexInIntersection, + GridIndexType scvfIndex, + std::vector&& scvIndices, + bool boundary = false) + : center_(Dumux::center(corners)) + , unitOuterNormal_(normal) + , scvfIndex_(scvfIndex) + , scvIndices_(std::move(scvIndices)) + , boundary_(boundary) + , boundaryFlag_{intersection} + { + area_ = Dumux::convexPolytopeVolume( + Dune::GeometryTypes::cube(dim), + [&](unsigned int i){ return corners[i]; } + ); + } + + //! The center of the sub control volume face + const GlobalPosition& center() const + { + return center_; + } + + //! The integration point for flux evaluations in global coordinates + const GlobalPosition& ipGlobal() const + { + return center_; + } + + //! The area of the sub control volume face + Scalar area() const + { + return area_; + } + + //! returns true if the sub control volume face is on the boundary + bool boundary() const + { + return boundary_; + } + + const GlobalPosition& unitOuterNormal() const + { + return unitOuterNormal_; + } + + //! index of the inside sub control volume + LocalIndexType insideScvIdx() const + { + return scvIndices_[0]; + } + + //! Index of the i-th outside sub control volume or boundary scv index. + // Results in undefined behaviour if i >= numOutsideScvs() + LocalIndexType outsideScvIdx(int i = 0) const + { + assert(!boundary()); + return scvIndices_[1]; + } + + //! The number of scvs on the outside of this face + std::size_t numOutsideScvs() const + { + return static_cast(!boundary()); + } + + //! The local index of this sub control volume face + GridIndexType index() const + { + return scvfIndex_; + } + + //! Return the boundary flag + typename BoundaryFlag::value_type boundaryFlag() const + { + return boundaryFlag_.get(); + } + +private: + GlobalPosition center_; + GlobalPosition unitOuterNormal_; + Scalar area_; + GridIndexType scvfIndex_; + std::vector scvIndices_; + bool boundary_; + BoundaryFlag boundaryFlag_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cctpfa.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cctpfa.hh new file mode 100644 index 0000000..155ad82 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cctpfa.hh @@ -0,0 +1,127 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Discretization + * \brief Properties for all models using cell-centered finite volume scheme with TPFA + * \note Inherit from these properties to use a cell-centered finite volume scheme with TPFA + */ + +#ifndef DUMUX_DISCRETIZATION_CC_TPFA_HH +#define DUMUX_DISCRETIZATION_CC_TPFA_HH + +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +namespace Dumux { +namespace Properties { + +//! Type tag for the cell-centered tpfa scheme. +// Create new type tags +namespace TTag { +struct CCTpfaModel { using InheritsFrom = std::tuple; }; +} // end namespace TTag + +//! Set the default for the grid geometry +template +struct GridGeometry +{ +private: + static constexpr bool enableCache = getPropValue(); + using GridView = typename GetPropType::LeafGridView; +public: + using type = CCTpfaFVGridGeometry; +}; + +//! The grid volume variables vector class +template +struct GridVolumeVariables +{ +private: + static constexpr bool enableCache = getPropValue(); + using Problem = GetPropType; + using VolumeVariables = GetPropType; +public: + using type = CCTpfaGridVolumeVariables; +}; + +//! The grid flux variables cache vector class +template +struct GridFluxVariablesCache +{ +private: + static constexpr bool enableCache = getPropValue(); + using Problem = GetPropType; + + using Scalar = GetPropType; + using FluxVariablesCache = GetPropTypeOr + >; + using FluxVariablesCacheFiller = GetPropTypeOr; +public: + using type = CCTpfaGridFluxVariablesCache; +}; + +//! Set the default for the ElementBoundaryTypes +template +struct ElementBoundaryTypes { using type = CCElementBoundaryTypes; }; + +} // namespace Properties + +namespace Detail { + +template +struct ProblemTraits +{ +private: + using GG = std::decay_t().gridGeometry())>; + using Element = typename GG::GridView::template Codim<0>::Entity; + using SubControlVolumeFace = typename GG::SubControlVolumeFace; +public: + using GridGeometry = GG; + // BoundaryTypes is whatever the problem returns from boundaryTypes(element, scvf) + using BoundaryTypes = std::decay_t().boundaryTypes(std::declval(), std::declval()))>; +}; + +template +concept CCTpfaModel = std::is_same_v< + typename GetPropType::DiscretizationMethod, + DiscretizationMethods::CCTpfa +>; + +template +struct DiscretizationDefaultLocalOperator +{ + using type = CCLocalResidual; +}; + +} // end namespace Detail + +} // namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/connectivitymap.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/connectivitymap.hh new file mode 100644 index 0000000..6604de3 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/connectivitymap.hh @@ -0,0 +1,125 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup CCDiscretization + * \brief Stores the face indices corresponding to the neighbors of an element + * that contribute to the derivative calculation. This is used for + * finite-volume schemes with symmetric sparsity pattern in the global matrix. + */ +#ifndef DUMUX_CC_CONNECTIVITY_MAP_HH +#define DUMUX_CC_CONNECTIVITY_MAP_HH + +#include +#include +#include + +#include +#include + +#include +#include + +namespace Dumux { + +/*! + * \ingroup CCDiscretization + * \brief A simple version of the connectivity map for cellcentered schemes. + * This implementation works for schemes in which for a given cell I only + * those cells J have to be prepared in whose stencil the cell I appears. + * This means that for the flux calculations in the cells J (in order to compute + * the derivatives with respect to cell I), we do not need data on any additional cells J + * to compute these fluxes. The same holds for scvfs in the cells J, i.e. we need only those + * scvfs in the cells J in which the cell I is in the stencil. + */ +template +class CCSimpleConnectivityMap +{ + using FVElementGeometry = typename GridGeometry::LocalView; + using GridView = typename GridGeometry::GridView; + using GridIndexType = typename IndexTraits::GridIndex; + using FluxStencil = Dumux::FluxStencil; + static constexpr int maxElemStencilSize = GridGeometry::maxElementStencilSize; + + struct DataJ + { + GridIndexType globalJ; + typename FluxStencil::ScvfStencilIForJ scvfsJ; + // A list of additional scvfs is needed for compatibility + // reasons with more complex connectivity maps (see mpfa) + typename FluxStencil::ScvfStencilIForJ additionalScvfs; + }; + + using Map = std::vector>; + +public: + + /*! + * \brief Initialize the ConnectivityMap object. + * + * \param gridGeometry The grid's finite volume geometry. + */ + void update(const GridGeometry& gridGeometry) + { + map_.clear(); + map_.resize(gridGeometry.gridView().size(0)); + + // container to store for each element J the elements I which have J in their flux stencil + Dune::ReservedVector, maxElemStencilSize> dataJForI; + auto fvGeometry = localView(gridGeometry); + for (const auto& element : elements(gridGeometry.gridView())) + { + // We are looking for the elements I, for which this element J is in the flux stencil + const auto globalJ = gridGeometry.elementMapper().index(element); + fvGeometry.bindElement(element); + + // obtain the data of J in elements I + dataJForI.clear(); + + // loop over sub control faces + for (auto&& scvf : scvfs(fvGeometry)) + { + const auto& stencil = FluxStencil::stencil(element, fvGeometry, scvf); + + // insert our index in the neighbor stencils of the elements in the flux stencil + for (auto globalI : stencil) + { + if (globalI == globalJ) + continue; + + auto it = std::find_if(dataJForI.begin(), dataJForI.end(), + [globalI](const auto& pair) { return pair.first == globalI; }); + + if (it != dataJForI.end()) + it->second.scvfsJ.push_back(scvf.index()); + else + { + if (dataJForI.size() > maxElemStencilSize - 1) + DUNE_THROW(Dune::InvalidStateException, "Maximum admissible stencil size (" << maxElemStencilSize-1 + << ") is surpassed (" << dataJForI.size() << "). " + << "Please adjust the GridGeometry traits accordingly!"); + + dataJForI.push_back(std::make_pair(globalI, DataJ({globalJ, {scvf.index()}, {}}))); + } + } + } + + for (auto&& pair : dataJForI) + map_[pair.first].emplace_back(std::move(pair.second)); + } + } + + const std::vector& operator[] (const GridIndexType globalI) const + { return map_[globalI]; } + +private: + Map map_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/elementboundarytypes.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/elementboundarytypes.hh new file mode 100644 index 0000000..fd7ba5a --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/elementboundarytypes.hh @@ -0,0 +1,45 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup CCDiscretization + * \brief Boundary types gathered on an element + */ +#ifndef DUMUX_CC_ELEMENT_BOUNDARY_TYPES_HH +#define DUMUX_CC_ELEMENT_BOUNDARY_TYPES_HH + +namespace Dumux { + +/*! + * \ingroup CCDiscretization + * \brief Boundary types gathered on an element + * \note This class exists only for compatibility purposes with the + * box scheme. The cell-centered schemes and the box scheme use + * a common base local residual, which passes an ElementBoundaryTypes + * object to the implemented interfaces. + */ +class CCElementBoundaryTypes +{ +public: + /*! + * \brief Update the boundary types for all vertices of an element. + * + * \param problem The problem object which needs to be simulated + * \param element The DUNE Codim<0> entity for which the boundary + * types should be collected + * \param fvGeometry The element finite volume geometry + */ + template + void update(const Problem &problem, + const Element &element, + const FVElementGeometry &fvGeometry) + {} +}; + +} // namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/elementsolution.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/elementsolution.hh new file mode 100644 index 0000000..c4e4c8d --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/elementsolution.hh @@ -0,0 +1,156 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup CCDiscretization + * \brief The local element solution class for cell-centered methods + */ +#ifndef DUMUX_CC_ELEMENT_SOLUTION_HH +#define DUMUX_CC_ELEMENT_SOLUTION_HH + +#include +#include +#include +#include + +namespace Dumux { + +/*! + * \ingroup CCDiscretization + * \brief The element solution vector + */ +template +class CCElementSolution +{ + using GridGeometry = typename FVElementGeometry::GridGeometry; + using GridView = typename GridGeometry::GridView; + using Element = typename GridView::template Codim<0>::Entity; + +public: + //! export the primary variables type + using PrimaryVariables = PV; + + //! default constructor + CCElementSolution() = default; + + //! Constructor with element, solution vector and grid geometry + template + CCElementSolution(const Element& element, const SolutionVector& sol, + const GridGeometry& gridGeometry) + : CCElementSolution(sol[gridGeometry.elementMapper().index(element)]) + {} + + //! Constructor with element, element volume variables and fv element geometry + template + CCElementSolution(const Element& element, const ElementVolumeVariables& elemVolVars, + const FVElementGeometry& fvGeometry) + { + for (const auto& scv : scvs(fvGeometry)) + priVars_ = elemVolVars[scv].priVars(); + } + + //! Constructor with a primary variable object + CCElementSolution(PrimaryVariables&& priVars) + : priVars_(std::move(priVars)) {} + + //! Constructor with a primary variable object + CCElementSolution(const PrimaryVariables& priVars) + : priVars_(priVars) {} + + //! extract the element solution from the solution vector using a mapper + template + void update(const Element& element, const SolutionVector& sol, + const GridGeometry& gridGeometry) + { + priVars_ = sol[gridGeometry.elementMapper().index(element)]; + } + + //! return the size of the element solution + constexpr std::size_t size() const + { return 1; } + + //! bracket operator const access + template + const PrimaryVariables& operator [](IndexType i) const + { + assert(i == 0 && "Index exceeds valid range!"); + return priVars_; + } + + //! bracket operator access + template + PrimaryVariables& operator [](IndexType i) + { + assert(i == 0 && "Index exceeds valid range!"); + return priVars_; + } + +private: + PrimaryVariables priVars_; +}; + +/*! + * \ingroup Discretization + * \brief Make an element solution for cell-centered schemes + */ +template +auto elementSolution(const Element& element, const SolutionVector& sol, const GridGeometry& gg) +-> std::enable_if_t()[0])>> + > +{ + using PrimaryVariables = std::decay_t()[0])>; + return CCElementSolution(element, sol, gg); +} + +/*! + * \ingroup Discretization + * \brief Make an element solution for cell-centered schemes + */ +template +auto elementSolution(const Element& element, const ElementVolumeVariables& elemVolVars, const FVElementGeometry& gg) +-> std::enable_if_t> +{ + using PrimaryVariables = typename ElementVolumeVariables::VolumeVariables::PrimaryVariables; + return CCElementSolution(element, elemVolVars, gg); +} + +/*! + * \ingroup Discretization + * \brief Make an element solution for cell-centered schemes + * \note This is e.g. used to construct an element solution at Dirichlet boundaries + */ +template +auto elementSolution(PrimaryVariables&& priVars) +-> std::enable_if_t> +{ + return CCElementSolution(std::move(priVars)); +} + +/*! + * \ingroup Discretization + * \brief Make an element solution for cell-centered schemes + * \note This is e.g. used to construct an element solution at Dirichlet boundaries + */ +template +auto elementSolution(const PrimaryVariables& priVars) +-> std::enable_if_t> +{ + return CCElementSolution(priVars); +} + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/gridvolumevariables.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/gridvolumevariables.hh new file mode 100644 index 0000000..7ccfc6e --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/gridvolumevariables.hh @@ -0,0 +1,139 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup CCDiscretization + * \brief The grid volume variables class for cell centered models + */ +#ifndef DUMUX_DISCRETIZATION_CC_GRID_VOLUMEVARIABLES_HH +#define DUMUX_DISCRETIZATION_CC_GRID_VOLUMEVARIABLES_HH + +#include +#include + +#include + +// make the local view function available whenever we use this class +#include +#include + +namespace Dumux { + +/*! + * \ingroup CCDiscretization + * \brief Base class for the grid volume variables + * \note This class has a cached version and a non-cached version + * \tparam Traits the traits class injecting the problem, volVar and elemVolVars type + * \tparam cachingEnabled if the cache is enabled + */ +template +class CCGridVolumeVariables {}; + +//! specialization in case of storing the volume variables +template +class CCGridVolumeVariables +{ + using ThisType = CCGridVolumeVariables; + +public: + //! export the problem type + using Problem = typename Traits::Problem; + + //! export the volume variables type + using VolumeVariables = typename Traits::VolumeVariables; + + //! make it possible to query if caching is enabled + static constexpr bool cachingEnabled = true; + + //! export the type of the local view + using LocalView = typename Traits::template LocalView; + + CCGridVolumeVariables(const Problem& problem) : problemPtr_(&problem) {} + + template + void update(const GridGeometry& gridGeometry, const SolutionVector& sol) + { + volumeVariables_.resize(gridGeometry.numScv()); + Dumux::parallelFor(gridGeometry.gridView().size(0), [&, &problem = problem()](const std::size_t eIdx) + { + const auto element = gridGeometry.element(eIdx); + const auto fvGeometry = localView(gridGeometry).bindElement(element); + for (const auto& scv : scvs(fvGeometry)) + { + const auto elemSol = elementSolution(element, sol, gridGeometry); + volumeVariables_[scv.dofIndex()].update(elemSol, problem, element, scv); + } + }); + } + + const VolumeVariables& volVars(const std::size_t scvIdx) const + { return volumeVariables_[scvIdx]; } + + VolumeVariables& volVars(const std::size_t scvIdx) + { return volumeVariables_[scvIdx]; } + + template::value, int> = 0> + const VolumeVariables& volVars(const SubControlVolume& scv) const + { return volumeVariables_[scv.dofIndex()]; } + + template::value, int> = 0> + VolumeVariables& volVars(const SubControlVolume& scv) + { return volumeVariables_[scv.dofIndex()]; } + + // required for compatibility with the box method + const VolumeVariables& volVars(const std::size_t scvIdx, const std::size_t localIdx) const + { return volumeVariables_[scvIdx]; } + + // required for compatibility with the box method + VolumeVariables& volVars(const std::size_t scvIdx, const std::size_t localIdx) + { return volumeVariables_[scvIdx]; } + + //! The problem we are solving + const Problem& problem() const + { return *problemPtr_; } + +private: + const Problem* problemPtr_; + std::vector volumeVariables_; +}; + + +//! Specialization when the current volume variables are not stored globally +template +class CCGridVolumeVariables +{ + using ThisType = CCGridVolumeVariables; + +public: + //! export the problem type + using Problem = typename Traits::Problem; + + //! export the volume variables type + using VolumeVariables = typename Traits::VolumeVariables; + + //! make it possible to query if caching is enabled + static constexpr bool cachingEnabled = false; + + //! export the type of the local view + using LocalView = typename Traits::template LocalView; + + CCGridVolumeVariables(const Problem& problem) : problemPtr_(&problem) {} + + template + void update(const GridGeometry& gridGeometry, const SolutionVector& sol) {} + + //! The problem we are solving + const Problem& problem() const + { return *problemPtr_;} + +private: + const Problem* problemPtr_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/subcontrolvolume.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/subcontrolvolume.hh new file mode 100644 index 0000000..aa954ad --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/subcontrolvolume.hh @@ -0,0 +1,121 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup CCDiscretization + * \brief Sub control volumes for cell-centered discretization schemes + */ +#ifndef DUMUX_DISCRETIZATION_CC_SUBCONTROLVOLUME_HH +#define DUMUX_DISCRETIZATION_CC_SUBCONTROLVOLUME_HH + +#include +#include + +namespace Dumux { + +/*! + * \ingroup CCDiscretization + * \brief Default traits class to be used for the sub-control volumes + * for the cell-centered finite volume scheme using TPFA + * \tparam GV the type of the grid view + */ +template +struct CCDefaultScvGeometryTraits +{ + using Geometry = typename GridView::template Codim<0>::Geometry; + using GridIndexType = typename IndexTraits::GridIndex; + using LocalIndexType = typename IndexTraits::LocalIndex; + using Scalar = typename GridView::ctype; + using Element = typename GridView::template Codim<0>::Entity; + using GlobalPosition = typename Element::Geometry::GlobalCoordinate; +}; + +/*! + * \ingroup CCDiscretization + * \brief Sub control volumes for cell-centered discretization schemes + * \tparam GV the type of the grid view + * \tparam T the scv geometry traits + */ +template > +class CCSubControlVolume +: public SubControlVolumeBase, T> +{ + using ThisType = CCSubControlVolume; + using ParentType = SubControlVolumeBase; + using GridIndexType = typename T::GridIndexType; + using LocalIndexType = typename T::LocalIndexType; + using Scalar = typename T::Scalar; +public: + //! export the type used for global coordinates + using GlobalPosition = typename T::GlobalPosition; + //! state the traits public and thus export all types + using Traits = T; + + CCSubControlVolume() = default; + + template + CCSubControlVolume(Geometry&& geometry, + GridIndexType elementIndex) + : ParentType() + , volume_(geometry.volume()) + , center_(geometry.center()) + , elementIndex_(elementIndex) + {} + + //! The center of the sub control volume + const GlobalPosition& center() const + { + return center_; + } + + //! The volume of the sub control volume + Scalar volume() const + { + return volume_; + } + + //! The index of the dof this scv is embedded in (the global index of this scv) + GridIndexType dofIndex() const + { + return elementIndex(); + } + + //! The element-local index of the dof this scv is embedded in + LocalIndexType localDofIndex() const + { + return 0; + } + + //! The element-local index of this scv. + //! In cell-centered schemes there is always only one scv per element. + LocalIndexType indexInElement() const + { + return 0; + } + + // The position of the dof this scv is embedded in + const GlobalPosition& dofPosition() const + { + return center_; + } + + //! The global index of the element this scv is embedded in + GridIndexType elementIndex() const + { + return elementIndex_; + } + +private: + Scalar volume_; + GlobalPosition center_; + GridIndexType elementIndex_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/tpfa/computetransmissibility.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/tpfa/computetransmissibility.hh new file mode 100644 index 0000000..575b2ed --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/tpfa/computetransmissibility.hh @@ -0,0 +1,84 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup CCTpfaDiscretization + * \brief Free functions to evaluate the transmissibilities + * associated with flux evaluations across sub-control volume faces + * in the context of the cell-centered TPFA scheme. + */ +#ifndef DUMUX_DISCRETIZATION_CC_TPFA_COMPUTE_TRANSMISSIBILITY_HH +#define DUMUX_DISCRETIZATION_CC_TPFA_COMPUTE_TRANSMISSIBILITY_HH + +#include +#include + +namespace Dumux { + +/*! + * \ingroup CCTpfaDiscretization + * \brief Free function to evaluate the Tpfa transmissibility + * associated with the flux (in the form of flux = T*gradU) across a + * sub-control volume face stemming from a given sub-control + * volume with corresponding tensor T. + * + * \param fvGeometry The element-centered control volume geometry + * \param scvf The sub-control volume face + * \param scv The neighboring sub-control volume + * \param T The tensor living in the neighboring scv + * \param extrusionFactor The extrusion factor of the scv + */ +template< class FVElementGeometry, class Tensor > +typename Tensor::field_type computeTpfaTransmissibility(const FVElementGeometry& fvGeometry, + const typename FVElementGeometry::SubControlVolumeFace& scvf, + const typename FVElementGeometry::SubControlVolume& scv, + const Tensor& T, + typename FVElementGeometry::SubControlVolume::Traits::Scalar extrusionFactor) +{ + using GlobalPosition = typename FVElementGeometry::SubControlVolumeFace::Traits::GlobalPosition; + GlobalPosition Knormal; + T.mv(scvf.unitOuterNormal(), Knormal); + + auto distanceVector = scvf.ipGlobal(); + distanceVector -= scv.center(); + distanceVector /= distanceVector.two_norm2(); + + return (Knormal*distanceVector) * extrusionFactor; +} + +/*! + * \ingroup CCTpfaDiscretization + * \brief Free function to evaluate the Tpfa transmissibility + * associated with the flux (in the form of flux = T*gradU) across a + * sub-control volume face stemming from a given sub-control + * volume for the case where T is just a scalar + * + * \param fvGeometry The element-centered control volume geometry + * \param scvf The sub-control volume face + * \param scv The neighboring sub-control volume + * \param t The scalar quantity living in the neighboring scv + * \param extrusionFactor The extrusion factor of the scv + */ +template< class FVElementGeometry, + class Tensor, + typename std::enable_if_t::value, int> = 0 > +Tensor computeTpfaTransmissibility(const FVElementGeometry& fvGeometry, + const typename FVElementGeometry::SubControlVolumeFace& scvf, + const typename FVElementGeometry::SubControlVolume& scv, + Tensor t, + typename FVElementGeometry::SubControlVolumeFace::Traits::Scalar extrusionFactor) +{ + auto distanceVector = scvf.ipGlobal(); + distanceVector -= scv.center(); + distanceVector /= distanceVector.two_norm2(); + + return t * extrusionFactor * (distanceVector * scvf.unitOuterNormal()); +} + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/tpfa/elementfluxvariablescache.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/tpfa/elementfluxvariablescache.hh new file mode 100644 index 0000000..d2eb2a8 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/tpfa/elementfluxvariablescache.hh @@ -0,0 +1,383 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup CCTpfaDiscretization + * \brief The flux variables caches for an element + */ +#ifndef DUMUX_DISCRETIZATION_CCTPFA_ELEMENT_FLUXVARSCACHE_HH +#define DUMUX_DISCRETIZATION_CCTPFA_ELEMENT_FLUXVARSCACHE_HH + +#include +#include +#include +#include + +#include + +namespace Dumux { + +/*! + * \ingroup CCTpfaDiscretization + * \brief The flux variables caches for an element + * \note The class is specialized for a version with and without caching + * If grid caching is enabled the flux caches are stored for the whole gridview in the corresponding + * GridFluxVariablesCache which is memory intensive but faster. For caching disabled the + * flux caches are locally computed for each element whenever needed. + */ +template +class CCTpfaElementFluxVariablesCache; + +/*! + * \ingroup CCTpfaDiscretization + * \brief The flux variables caches for an element with caching enabled + */ +template +class CCTpfaElementFluxVariablesCache +{ + //! the type of the flux variables cache filler + using FluxVariablesCacheFiller = typename GFVC::Traits::FluxVariablesCacheFiller; + +public: + //! export the type of the grid flux variables cache + using GridFluxVariablesCache = GFVC; + + //! export the type of the flux variables cache + using FluxVariablesCache = typename GFVC::FluxVariablesCache; + + CCTpfaElementFluxVariablesCache(const GridFluxVariablesCache& global) + : gridFluxVarsCachePtr_(&global) {} + + /*! + * \brief bind the local view (r-value overload) + * This overload is called when an instance of this class is a temporary in the usage context + * This allows a usage like this: `const auto view = localView(...).bind(element);` + */ + template + CCTpfaElementFluxVariablesCache bindElement(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars) && + { + this->bindElement(element, fvGeometry, elemVolVars); + return std::move(*this); + } + + //! Specialization for the global caching being enabled - do nothing here + template + void bindElement(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars) & {} + + /*! + * \brief bind the local view (r-value overload) + * This overload is called when an instance of this class is a temporary in the usage context + * This allows a usage like this: `const auto view = localView(...).bind(element);` + */ + template + CCTpfaElementFluxVariablesCache bind(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars) && + { + this->bind(element, fvGeometry, elemVolVars); + return std::move(*this); + } + + //! Specialization for the global caching being enabled - do nothing here + template + void bind(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars) & {} + + /*! + * \brief bind the local view (r-value overload) + * This overload is called when an instance of this class is a temporary in the usage context + * This allows a usage like this: `const auto view = localView(...).bind(element);` + */ + template + CCTpfaElementFluxVariablesCache bindScvf(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const typename FVElementGeometry::SubControlVolumeFace& scvf) && + { + this->bindScvf(element, fvGeometry, elemVolVars, scvf); + return std::move(*this); + } + + //! Specialization for the global caching being enabled - do nothing here + template + void bindScvf(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const typename FVElementGeometry::SubControlVolumeFace& scvf) & {} + + //! Specialization for the global caching being enabled - do nothing here + template + void update(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars) {} + + //! access operators in the case of caching + template + const FluxVariablesCache& operator [](const SubControlVolumeFace& scvf) const + { return gridFluxVarsCache()[scvf]; } + + //! The global object we are a restriction of + const GridFluxVariablesCache& gridFluxVarsCache() const + { return *gridFluxVarsCachePtr_; } + +private: + const GridFluxVariablesCache* gridFluxVarsCachePtr_; +}; + +/*! + * \ingroup CCTpfaDiscretization + * \brief The flux variables caches for an element with caching disabled + */ +template +class CCTpfaElementFluxVariablesCache +{ + //! the type of the flux variables cache filler + using FluxVariablesCacheFiller = typename GFVC::Traits::FluxVariablesCacheFiller; + +public: + //! export the type of the grid flux variables cache + using GridFluxVariablesCache = GFVC; + + //! export the type of the flux variables cache + using FluxVariablesCache = typename GFVC::FluxVariablesCache; + + CCTpfaElementFluxVariablesCache(const GridFluxVariablesCache& global) + : gridFluxVarsCachePtr_(&global) {} + + /*! + * \brief bind the local view (r-value overload) + * This overload is called when an instance of this class is a temporary in the usage context + * This allows a usage like this: `const auto view = localView(...).bind(element);` + */ + template + CCTpfaElementFluxVariablesCache bindElement(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars) && + { + this->bindElement_(element, fvGeometry, elemVolVars); + return std::move(*this); + } + + //! Specialization for the global caching being enabled - do nothing here + template + void bindElement(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars) & + { this->bindElement_(element, fvGeometry, elemVolVars); } + + /*! + * \brief bind the local view (r-value overload) + * This overload is called when an instance of this class is a temporary in the usage context + * This allows a usage like this: `const auto view = localView(...).bind(element);` + */ + template + CCTpfaElementFluxVariablesCache bind(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars) && + { + this->bind_(element, fvGeometry, elemVolVars); + return std::move(*this); + } + + //! Specialization for the global caching being enabled - do nothing here + template + void bind(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars) & + { this->bind_(element, fvGeometry, elemVolVars); } + + /*! + * \brief bind the local view (r-value overload) + * This overload is called when an instance of this class is a temporary in the usage context + * This allows a usage like this: `const auto view = localView(...).bind(element);` + */ + template + CCTpfaElementFluxVariablesCache bindScvf(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const typename FVElementGeometry::SubControlVolumeFace& scvf) && + { + this->bindScvf_(element, fvGeometry, elemVolVars, scvf); + return std::move(*this); + } + + //! Specialization for the global caching being enabled - do nothing here + template + void bindScvf(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const typename FVElementGeometry::SubControlVolumeFace& scvf) & + { this->bindScvf_(element, fvGeometry, elemVolVars, scvf); } + + /*! + * \brief Update the transmissibilities if the volume variables have changed + * \note Results in undefined behaviour if called before bind() or with a different element + */ + template + void update(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars) + { + if (FluxVariablesCacheFiller::isSolDependent) + { + const auto& problem = gridFluxVarsCache().problem(); + const auto globalI = fvGeometry.gridGeometry().elementMapper().index(element); + + // instantiate filler class + FluxVariablesCacheFiller filler(problem); + + // let the filler class update the caches + for (unsigned int localScvfIdx = 0; localScvfIdx < fluxVarsCache_.size(); ++localScvfIdx) + { + const auto& scvf = fvGeometry.scvf(globalScvfIndices_[localScvfIdx]); + + const auto scvfInsideScvIdx = scvf.insideScvIdx(); + const auto& insideElement = scvfInsideScvIdx == globalI ? + element : + fvGeometry.gridGeometry().element(scvfInsideScvIdx); + + filler.fill(*this, fluxVarsCache_[localScvfIdx], insideElement, fvGeometry, elemVolVars, scvf); + } + } + } + + //! access operators in the case of no caching + template + const FluxVariablesCache& operator [](const SubControlVolumeFace& scvf) const + { return fluxVarsCache_[getLocalScvfIdx_(scvf.index())]; } + + //! access operators in the case of no caching + template + FluxVariablesCache& operator [](const SubControlVolumeFace& scvf) + { return fluxVarsCache_[getLocalScvfIdx_(scvf.index())]; } + + //! The global object we are a restriction of + const GridFluxVariablesCache& gridFluxVarsCache() const + { return *gridFluxVarsCachePtr_; } + +private: + + /*! + * \brief Prepares the transmissibilities of the scv faces in an element + * \note the fvGeometry is assumed to be bound to the same element + * \note this function has to be called prior to flux calculations on the element. + */ + template + void bindElement_(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars) + { + // resizing of the cache + const auto numScvf = fvGeometry.numScvf(); + fluxVarsCache_.resize(numScvf); + globalScvfIndices_.resize(numScvf); + + // instantiate helper class to fill the caches + FluxVariablesCacheFiller filler(gridFluxVarsCache().problem()); + + std::size_t localScvfIdx = 0; + // fill the containers + for (auto&& scvf : scvfs(fvGeometry)) + { + filler.fill(*this, fluxVarsCache_[localScvfIdx], element, fvGeometry, elemVolVars, scvf, true); + globalScvfIndices_[localScvfIdx] = scvf.index(); + localScvfIdx++; + } + } + + /*! + * \brief Prepares the transmissibilities of the scv faces in the stencil of an element + * \note the fvGeometry is assumed to be bound to the same element + * \note this function has to be called prior to flux calculations on the element. + */ + template + void bind_(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars) + { + const auto& problem = gridFluxVarsCache().problem(); + const auto& gridGeometry = fvGeometry.gridGeometry(); + const auto globalI = gridGeometry.elementMapper().index(element); + const auto& connectivityMapI = gridGeometry.connectivityMap()[globalI]; + const auto numNeighbors = connectivityMapI.size(); + + // instantiate helper class to fill the caches + FluxVariablesCacheFiller filler(problem); + + // find the number of scv faces that need to be prepared + auto numScvf = fvGeometry.numScvf(); + for (unsigned int localIdxJ = 0; localIdxJ < numNeighbors; ++localIdxJ) + numScvf += connectivityMapI[localIdxJ].scvfsJ.size(); + + // fill the containers with the data on the scv faces inside the actual element + fluxVarsCache_.resize(numScvf); + globalScvfIndices_.resize(numScvf); + unsigned int localScvfIdx = 0; + for (auto&& scvf : scvfs(fvGeometry)) + { + filler.fill(*this, fluxVarsCache_[localScvfIdx], element, fvGeometry, elemVolVars, scvf, true); + globalScvfIndices_[localScvfIdx] = scvf.index(); + localScvfIdx++; + } + + // add required data on the scv faces in the neighboring elements + for (unsigned int localIdxJ = 0; localIdxJ < numNeighbors; ++localIdxJ) + { + const auto elementJ = gridGeometry.element(connectivityMapI[localIdxJ].globalJ); + for (auto scvfIdx : connectivityMapI[localIdxJ].scvfsJ) + { + auto&& scvfJ = fvGeometry.scvf(scvfIdx); + filler.fill(*this, fluxVarsCache_[localScvfIdx], elementJ, fvGeometry, elemVolVars, scvfJ, true); + globalScvfIndices_[localScvfIdx] = scvfJ.index(); + localScvfIdx++; + } + } + } + + /*! + * \brief Prepares the transmissibilities of a single scv face + * \note the fvGeometry is assumed to be bound to the same element + * \note this function has to be called prior to flux calculations on the element. + */ + template + void bindScvf_(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const typename FVElementGeometry::SubControlVolumeFace& scvf) + { + fluxVarsCache_.resize(1); + globalScvfIndices_.resize(1); + + // instantiate helper class to fill the caches + FluxVariablesCacheFiller filler(gridFluxVarsCache().problem()); + + filler.fill(*this, fluxVarsCache_[0], element, fvGeometry, elemVolVars, scvf, true); + globalScvfIndices_[0] = scvf.index(); + } + + const GridFluxVariablesCache* gridFluxVarsCachePtr_; + + //! get index of scvf in the local container + int getLocalScvfIdx_(const int scvfIdx) const + { + auto it = std::find(globalScvfIndices_.begin(), globalScvfIndices_.end(), scvfIdx); + assert(it != globalScvfIndices_.end() && "Could not find the flux vars cache for scvfIdx"); + return std::distance(globalScvfIndices_.begin(), it); + } + + std::vector fluxVarsCache_; + std::vector globalScvfIndices_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/tpfa/elementvolumevariables.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/tpfa/elementvolumevariables.hh new file mode 100644 index 0000000..861e0c0 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/tpfa/elementvolumevariables.hh @@ -0,0 +1,402 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup CCTpfaDiscretization + * \brief The local (stencil) volume variables class for cell centered tpfa models + */ +#ifndef DUMUX_DISCRETIZATION_CCTPFA_ELEMENT_VOLUMEVARIABLES_HH +#define DUMUX_DISCRETIZATION_CCTPFA_ELEMENT_VOLUMEVARIABLES_HH + +#include +#include +#include +#include + +#include + +namespace Dumux { + +/*! + * \ingroup CCTpfaDiscretization + * \brief The local (stencil) volume variables class for cell centered tpfa models + * \note The class is specialized for versions with and without caching + * \tparam GVV the grid volume variables type + * \tparam cachingEnabled if the cache is enabled + */ +template +class CCTpfaElementVolumeVariables +{}; + +/*! + * \ingroup CCTpfaDiscretization + * \brief The local (stencil) volume variables class for cell centered tpfa models with caching + * \note the volume variables are stored for the whole grid view in the corresponding GridVolumeVariables class + */ +template +class CCTpfaElementVolumeVariables +{ +public: + //! export type of the grid volume variables + using GridVolumeVariables = GVV; + + //! export type of the volume variables + using VolumeVariables = typename GridVolumeVariables::VolumeVariables; + + //! Constructor + CCTpfaElementVolumeVariables(const GridVolumeVariables& gridVolVars) + : gridVolVarsPtr_(&gridVolVars) + , numScv_(gridVolVars.problem().gridGeometry().numScv()) + {} + + //! operator for the access with an scv + template::value, int> = 0> + const VolumeVariables& operator [](const SubControlVolume& scv) const + { + if (scv.dofIndex() < numScv_) + return gridVolVars().volVars(scv.dofIndex()); + else + return boundaryVolumeVariables_[getLocalIdx_(scv.dofIndex())]; + } + + //! operator for the access with an index + const VolumeVariables& operator [](const std::size_t scvIdx) const + { + if (scvIdx < numScv_) + return gridVolVars().volVars(scvIdx); + else + return boundaryVolumeVariables_[getLocalIdx_(scvIdx)]; + } + + /*! + * \brief bind the local view (r-value overload) + * This overload is called when an instance of this class is a temporary in the usage context + * This allows a usage like this: `const auto view = localView(...).bind(element);` + */ + template + CCTpfaElementVolumeVariables bind(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const SolutionVector& sol) && + { + this->bind_(element, fvGeometry, sol); + return std::move(*this); + } + + template + void bind(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const SolutionVector& sol) & + { this->bind_(element, fvGeometry, sol); } + + /*! + * \brief bind the local view (r-value overload) + * This overload is called when an instance of this class is a temporary in the usage context + * This allows a usage like this: `const auto view = localView(...).bind(element);` + */ + template + CCTpfaElementVolumeVariables bindElement(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const SolutionVector& sol) && + { + this->bindElement_(element, fvGeometry, sol); + return std::move(*this); + } + + template + void bindElement(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const SolutionVector& sol) & + { this->bindElement_(element, fvGeometry, sol); } + + //! The global volume variables object we are a restriction of + const GridVolumeVariables& gridVolVars() const + { return *gridVolVarsPtr_; } + +private: + + //! Clear all local storage + void clear_() + { + boundaryVolVarIndices_.clear(); + boundaryVolumeVariables_.clear(); + } + + //! precompute all boundary volume variables in a stencil of an element, the remaining ones are cached + template + void bind_(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const SolutionVector& sol) + { + if (!fvGeometry.hasBoundaryScvf()) + return; + + clear_(); + boundaryVolVarIndices_.reserve(fvGeometry.numScvf()); + boundaryVolumeVariables_.reserve(fvGeometry.numScvf()); + + for (const auto& scvf : scvfs(fvGeometry)) + { + if (!scvf.boundary()) + continue; + + // check if boundary is a pure dirichlet boundary + const auto& problem = gridVolVars().problem(); + const auto bcTypes = problem.boundaryTypes(element, scvf); + if (bcTypes.hasOnlyDirichlet()) + { + const auto dirichletPriVars = elementSolution(problem.dirichlet(element, scvf)); + auto&& scvI = fvGeometry.scv(scvf.insideScvIdx()); + + VolumeVariables volVars; + volVars.update(dirichletPriVars, + problem, + element, + scvI); + + boundaryVolumeVariables_.emplace_back(std::move(volVars)); + boundaryVolVarIndices_.push_back(scvf.outsideScvIdx()); + } + } + } + + //! precompute the volume variables of an element - do nothing: volVars are cached + template + void bindElement_(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const SolutionVector& sol) + {} + + const GridVolumeVariables* gridVolVarsPtr_; + + //! map a global scv index to the local storage index + int getLocalIdx_(const int volVarIdx) const + { + auto it = std::find(boundaryVolVarIndices_.begin(), boundaryVolVarIndices_.end(), volVarIdx); + assert(it != boundaryVolVarIndices_.end() && "Could not find the current volume variables for volVarIdx!"); + return std::distance(boundaryVolVarIndices_.begin(), it); + } + + std::vector boundaryVolVarIndices_; + std::vector boundaryVolumeVariables_; + const std::size_t numScv_; +}; + +/*! + * \ingroup CCTpfaDiscretization + * \brief The local (stencil) volume variables class for cell centered tpfa models with caching + */ +template +class CCTpfaElementVolumeVariables +{ +public: + //! export type of the grid volume variables + using GridVolumeVariables = GVV; + + //! export type of the volume variables + using VolumeVariables = typename GridVolumeVariables::VolumeVariables; + + //! Constructor + CCTpfaElementVolumeVariables(const GridVolumeVariables& gridVolVars) + : gridVolVarsPtr_(&gridVolVars) {} + + /*! + * \brief bind the local view (r-value overload) + * This overload is called when an instance of this class is a temporary in the usage context + * This allows a usage like this: `const auto view = localView(...).bind(element);` + */ + template + CCTpfaElementVolumeVariables bind(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const SolutionVector& sol) && + { + this->bind_(element, fvGeometry, sol); + return std::move(*this); + } + + template + void bind(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const SolutionVector& sol) & + { this->bind_(element, fvGeometry, sol); } + + /*! + * \brief bind the local view (r-value overload) + * This overload is called when an instance of this class is a temporary in the usage context + * This allows a usage like this: `const auto view = localView(...).bind(element);` + */ + template + CCTpfaElementVolumeVariables bindElement(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const SolutionVector& sol) && + { + this->bindElement_(element, fvGeometry, sol); + return std::move(*this); + } + + template + void bindElement(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const SolutionVector& sol) & + { this->bindElement_(element, fvGeometry, sol); } + + //! access operator with scv + template::value, int> = 0> + const VolumeVariables& operator [](const SubControlVolume& scv) const + { return volumeVariables_[getLocalIdx_(scv.dofIndex())]; } + + //! access operator with scv + template::value, int> = 0> + VolumeVariables& operator [](const SubControlVolume& scv) + { return volumeVariables_[getLocalIdx_(scv.dofIndex())]; } + + //! access operator with scv index + const VolumeVariables& operator [](std::size_t scvIdx) const + { return volumeVariables_[getLocalIdx_(scvIdx)]; } + + //! access operator with scv index + VolumeVariables& operator [](std::size_t scvIdx) + { return volumeVariables_[getLocalIdx_(scvIdx)]; } + + //! The global volume variables object we are a restriction of + const GridVolumeVariables& gridVolVars() const + { return *gridVolVarsPtr_; } + +private: + //! Clear all local storage + void clear_() + { + volVarIndices_.clear(); + volumeVariables_.clear(); + } + + //! Prepares the volume variables within the element stencil + template + void bind_(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const SolutionVector& sol) + { + clear_(); + + const auto& problem = gridVolVars().problem(); + const auto& gridGeometry = fvGeometry.gridGeometry(); + const auto globalI = gridGeometry.elementMapper().index(element); + const auto& connectivityMapI = gridGeometry.connectivityMap()[globalI]; + const auto numDofs = connectivityMapI.size() + 1; + + // resize local containers to the required size (for internal elements) + volumeVariables_.resize(numDofs); + volVarIndices_.resize(numDofs); + int localIdx = 0; + + // update the volume variables of the element at hand + auto&& scvI = fvGeometry.scv(globalI); + volumeVariables_[localIdx].update(elementSolution(element, sol, gridGeometry), + problem, + element, + scvI); + volVarIndices_[localIdx] = scvI.dofIndex(); + ++localIdx; + + // Update the volume variables of the neighboring elements + for (const auto& dataJ : connectivityMapI) + { + const auto& elementJ = gridGeometry.element(dataJ.globalJ); + auto&& scvJ = fvGeometry.scv(dataJ.globalJ); + volumeVariables_[localIdx].update(elementSolution(elementJ, sol, gridGeometry), + problem, + elementJ, + scvJ); + volVarIndices_[localIdx] = scvJ.dofIndex(); + ++localIdx; + } + + if (fvGeometry.hasBoundaryScvf()) + { + // Update boundary volume variables + for (auto&& scvf : scvfs(fvGeometry)) + { + // if we are not on a boundary, skip to the next scvf + if (!scvf.boundary()) + continue; + + // check if boundary is a pure dirichlet boundary + const auto bcTypes = problem.boundaryTypes(element, scvf); + if (bcTypes.hasOnlyDirichlet()) + { + const auto dirichletPriVars = elementSolution(problem.dirichlet(element, scvf)); + + volumeVariables_.resize(localIdx+1); + volVarIndices_.resize(localIdx+1); + volumeVariables_[localIdx].update(dirichletPriVars, + problem, + element, + scvI); + volVarIndices_[localIdx] = scvf.outsideScvIdx(); + ++localIdx; + } + } + } + + //! Check if user added additional DOF dependencies, i.e. the residual of DOF globalI depends + //! on additional DOFs not included in the discretization schemes' occupation pattern + // const auto& additionalDofDependencies = problem.getAdditionalDofDependencies(globalI); + // if (!additionalDofDependencies.empty()) + // { + // volumeVariables_.resize(volumeVariables_.size() + additionalDofDependencies.size()); + // volVarIndices_.resize(volVarIndices_.size() + additionalDofDependencies.size()); + // for (auto globalJ : additionalDofDependencies) + // { + // const auto& elementJ = gridGeometry.element(globalJ); + // auto&& scvJ = fvGeometry.scv(globalJ); + + // volumeVariables_[localIdx].update(elementSolution(elementJ, sol, gridGeometry), + // problem, + // elementJ, + // scvJ); + // volVarIndices_[localIdx] = scvJ.dofIndex(); + // ++localIdx; + // } + // } + } + + template + void bindElement_(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const SolutionVector& sol) + { + clear_(); + + const auto eIdx = fvGeometry.gridGeometry().elementMapper().index(element); + volumeVariables_.resize(1); + volVarIndices_.resize(1); + + // update the volume variables of the element + auto&& scv = fvGeometry.scv(eIdx); + volumeVariables_[0].update(elementSolution(element, sol, fvGeometry.gridGeometry()), + gridVolVars().problem(), + element, + scv); + volVarIndices_[0] = scv.dofIndex(); + } + + const GridVolumeVariables* gridVolVarsPtr_; + + //! map a global scv index to the local storage index + int getLocalIdx_(const int volVarIdx) const + { + auto it = std::find(volVarIndices_.begin(), volVarIndices_.end(), volVarIdx); + assert(it != volVarIndices_.end() && "Could not find the current volume variables for volVarIdx!"); + return std::distance(volVarIndices_.begin(), it); + } + + std::vector volVarIndices_; + std::vector volumeVariables_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/tpfa/fvelementgeometry.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/tpfa/fvelementgeometry.hh new file mode 100644 index 0000000..ea8e8c0 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/tpfa/fvelementgeometry.hh @@ -0,0 +1,695 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup CCTpfaDiscretization + * \brief Stencil-local finite volume geometry (scvs and scvfs) for cell-centered TPFA models + * This builds up the sub control volumes and sub control volume faces + * for each element in the local scope we are restricting to, e.g. stencil or element. + */ +#ifndef DUMUX_DISCRETIZATION_CCTPFA_FV_ELEMENT_GEOMETRY_HH +#define DUMUX_DISCRETIZATION_CCTPFA_FV_ELEMENT_GEOMETRY_HH + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace Dumux { + +namespace Detail::Tpfa { + +template +auto findLocalIndex(const GridIndexType idx, + const std::vector& indices) +{ + auto it = std::find(indices.begin(), indices.end(), idx); + assert(it != indices.end() && "Could not find the scv/scvf! Make sure to properly bind this class!"); + return std::distance(indices.begin(), it); +} + +} // end namespace Detail::Tpfa + +/*! + * \ingroup CCTpfaDiscretization + * \brief Stencil-local finite volume geometry (scvs and scvfs) for cell-centered TPFA models + * This builds up the sub control volumes and sub control volume faces + * for each element in the local scope we are restricting to, e.g. stencil or element. + * \tparam GG the finite volume grid geometry type + * \tparam enableGridGeometryCache if the grid geometry is cached or not + * \note This class is specialized for versions with and without caching the fv geometries on the grid view + */ +template +class CCTpfaFVElementGeometry; + +/*! + * \ingroup CCTpfaDiscretization + * \brief Stencil-local finite volume geometry (scvs and scvfs) for cell-centered TPFA models + * Specialization for grid caching enabled + * \note The finite volume geometries are stored in the corresponding GridGeometry + */ +template +class CCTpfaFVElementGeometry +{ + using ThisType = CCTpfaFVElementGeometry; + using GridView = typename GG::GridView; + using GridIndexType = typename IndexTraits::GridIndex; + using LocalIndexType = typename IndexTraits::LocalIndex; + +public: + //! export type of the element + using Element = typename GridView::template Codim<0>::Entity; + //! export type of subcontrol volume + using SubControlVolume = typename GG::SubControlVolume; + //! export type of subcontrol volume face + using SubControlVolumeFace = typename GG::SubControlVolumeFace; + //! export type of finite volume grid geometry + using GridGeometry = GG; + + //! the maximum number of scvs per element + static constexpr std::size_t maxNumElementScvs = 1; + //! the maximum number of scvfs per element (use cubes for maximum) + static constexpr std::size_t maxNumElementScvfs = 2*GridView::dimension; + + //! Constructor + CCTpfaFVElementGeometry(const GridGeometry& gridGeometry) + : gridGeometryPtr_(&gridGeometry) {} + + //! Get an element sub control volume with a global scv index + //! We separate element and neighbor scvs to speed up mapping + const SubControlVolume& scv(GridIndexType scvIdx) const + { + return gridGeometry().scv(scvIdx); + } + + //! Get an element sub control volume face with a global scvf index + //! We separate element and neighbor scvfs to speed up mapping + const SubControlVolumeFace& scvf(GridIndexType scvfIdx) const + { + return gridGeometry().scvf(scvfIdx); + } + + //! Get the scvf on the same face but from the other side + //! Note that e.g. the normals might be different in the case of surface grids + const SubControlVolumeFace& flipScvf(GridIndexType scvfIdx, unsigned int outsideScvIdx = 0) const + { + return gridGeometry().flipScvf(scvfIdx, outsideScvIdx); + } + + //! iterator range for sub control volumes. Iterates over + //! all scvs of the bound element (not including neighbor scvs) + //! This is a free function found by means of ADL + //! To iterate over all sub control volumes of this FVElementGeometry use + //! for (auto&& scv : scvs(fvGeometry)) + friend inline Dune::IteratorRange< ScvIterator, ThisType> > + scvs(const CCTpfaFVElementGeometry& fvGeometry) + { + using ScvIterator = Dumux::ScvIterator, ThisType>; + return Dune::IteratorRange(ScvIterator(fvGeometry.scvIndices_.begin(), fvGeometry), + ScvIterator(fvGeometry.scvIndices_.end(), fvGeometry)); + } + + //! iterator range for sub control volumes faces. Iterates over + //! all scvfs of the bound element (not including neighbor scvfs) + //! This is a free function found by means of ADL + //! To iterate over all sub control volume faces of this FVElementGeometry use + //! for (auto&& scvf : scvfs(fvGeometry)) + friend inline Dune::IteratorRange< ScvfIterator, ThisType> > + scvfs(const CCTpfaFVElementGeometry& fvGeometry) + { + const auto& g = fvGeometry.gridGeometry(); + const auto scvIdx = fvGeometry.scvIndices_[0]; + using ScvfIterator = Dumux::ScvfIterator, ThisType>; + return Dune::IteratorRange(ScvfIterator(g.scvfIndicesOfScv(scvIdx).begin(), fvGeometry), + ScvfIterator(g.scvfIndicesOfScv(scvIdx).end(), fvGeometry)); + } + + //! number of sub control volumes in this fv element geometry + std::size_t numScv() const + { + return scvIndices_.size(); + } + + //! number of sub control volumes in this fv element geometry + std::size_t numScvf() const + { + return gridGeometry().scvfIndicesOfScv(scvIndices_[0]).size(); + } + + /*! + * \brief bind the local view (r-value overload) + * This overload is called when an instance of this class is a temporary in the usage context + * This allows a usage like this: `const auto view = localView(...).bind(element);` + */ + CCTpfaFVElementGeometry bind(const Element& element) && + { + this->bindElement(element); + return std::move(*this); + } + + void bind(const Element& element) & + { + this->bindElement(element); + } + + /*! + * \brief bind the local view (r-value overload) + * This overload is called when an instance of this class is a temporary in the usage context + * This allows a usage like this: `const auto view = localView(...).bindElement(element);` + */ + CCTpfaFVElementGeometry bindElement(const Element& element) && + { + this->bindElement(element); + return std::move(*this); + } + + //! Bind only element-local + void bindElement(const Element& element) & + { + element_ = element; + scvIndices_[0] = gridGeometry().elementMapper().index(*element_); + } + + //! Returns true if bind/bindElement has already been called + bool isBound() const + { return static_cast(element_); } + + //! The bound element + const Element& element() const + { return *element_; } + + //! The global finite volume geometry we are a restriction of + const GridGeometry& gridGeometry() const + { return *gridGeometryPtr_; } + + //! Returns whether one of the geometry's scvfs lies on a boundary + bool hasBoundaryScvf() const + { return gridGeometry().hasBoundaryScvf(scvIndices_[0]); } + + typename Element::Geometry geometry(const SubControlVolume& scv) const + { return gridGeometryPtr_->element(scv.dofIndex()).geometry(); } + + typename GridView::Intersection::Geometry geometry(const SubControlVolumeFace& scvf) const + { + const auto element = gridGeometryPtr_->element(scvf.insideScvIdx()); + const auto& scvfIndices = gridGeometryPtr_->scvfIndicesOfScv(scvf.insideScvIdx()); + const LocalIndexType localScvfIdx = Detail::Tpfa::findLocalIndex(scvf.index(), scvfIndices); + LocalIndexType localIdx = 0; + for (const auto& intersection : intersections(gridGeometryPtr_->gridView(), element)) + { + if (intersection.neighbor() || intersection.boundary()) + { + if (localIdx == localScvfIdx) + return intersection.geometry(); + else + ++localIdx; + } + } + + DUNE_THROW(Dune::InvalidStateException, "Could not find scvf geometry"); + } + +private: + + std::optional element_; + std::array scvIndices_; + const GridGeometry* gridGeometryPtr_; +}; + +/*! + * \ingroup CCTpfaDiscretization + * \brief Stencil-local finite volume geometry (scvs and scvfs) for cell-centered TPFA models + * Specialization for grid caching disabled + */ +template +class CCTpfaFVElementGeometry +{ + using ThisType = CCTpfaFVElementGeometry; + using GridView = typename GG::GridView; + using GridIndexType = typename IndexTraits::GridIndex; + using LocalIndexType = typename IndexTraits::LocalIndex; + + static const int dim = GridView::dimension; + static const int dimWorld = GridView::dimensionworld; + +public: + //! export type of the element + using Element = typename GridView::template Codim<0>::Entity; + //! export type of subcontrol volume + using SubControlVolume = typename GG::SubControlVolume; + //! export type of subcontrol volume face + using SubControlVolumeFace = typename GG::SubControlVolumeFace; + //! export type of finite volume grid geometry + using GridGeometry = GG; + //! the maximum number of scvs per element + static constexpr std::size_t maxNumElementScvs = 1; + //! the maximum number of scvfs per element (use cubes for maximum) + static constexpr std::size_t maxNumElementScvfs = 2*dim; + + //! Constructor + CCTpfaFVElementGeometry(const GridGeometry& gridGeometry) + : gridGeometryPtr_(&gridGeometry) {} + + //! Get an element sub control volume with a global scv index + //! We separate element and neighbor scvs to speed up mapping + const SubControlVolume& scv(GridIndexType scvIdx) const + { + if (scvIdx == scvIndices_[0]) + return scvs_[0]; + else + return neighborScvs_[Detail::Tpfa::findLocalIndex(scvIdx, neighborScvIndices_)]; + } + + //! Get an element sub control volume face with a global scvf index + //! We separate element and neighbor scvfs to speed up mapping + const SubControlVolumeFace& scvf(GridIndexType scvfIdx) const + { + auto it = std::find(scvfIndices_.begin(), scvfIndices_.end(), scvfIdx); + if (it != scvfIndices_.end()) + return scvfs_[std::distance(scvfIndices_.begin(), it)]; + else + return neighborScvfs_[Detail::Tpfa::findLocalIndex(scvfIdx, neighborScvfIndices_)]; + } + + //! Get the scvf on the same face but from the other side + //! Note that e.g. the normals might be different in the case of surface grids + const SubControlVolumeFace& flipScvf(GridIndexType scvfIdx, unsigned int outsideScvIdx = 0) const + { + auto it = std::find(scvfIndices_.begin(), scvfIndices_.end(), scvfIdx); + if (it != scvfIndices_.end()) + { + const auto localScvfIdx = std::distance(scvfIndices_.begin(), it); + return neighborScvfs_[flippedScvfIndices_[localScvfIdx][outsideScvIdx]]; + } + else + { + const auto localScvfIdx = Detail::Tpfa::findLocalIndex(scvfIdx, neighborScvfIndices_); + const auto localFlippedIndex = flippedNeighborScvfIndices_[localScvfIdx][outsideScvIdx]; + if (localFlippedIndex < scvfs_.size()) + return scvfs_[localFlippedIndex]; + else + return neighborScvfs_[localFlippedIndex - scvfs_.size()]; + } + } + + //! iterator range for sub control volumes. Iterates over + //! all scvs of the bound element (not including neighbor scvs) + //! This is a free function found by means of ADL + //! To iterate over all sub control volumes of this FVElementGeometry use + //! for (auto&& scv : scvs(fvGeometry)) + friend inline Dune::IteratorRange::const_iterator> + scvs(const ThisType& g) + { + using IteratorType = typename std::array::const_iterator; + return Dune::IteratorRange(g.scvs_.begin(), g.scvs_.end()); + } + + //! iterator range for sub control volumes faces. Iterates over + //! all scvfs of the bound element (not including neighbor scvfs) + //! This is a free function found by means of ADL + //! To iterate over all sub control volume faces of this FVElementGeometry use + //! for (auto&& scvf : scvfs(fvGeometry)) + friend inline Dune::IteratorRange::const_iterator> + scvfs(const ThisType& g) + { + using IteratorType = typename std::vector::const_iterator; + return Dune::IteratorRange(g.scvfs_.begin(), g.scvfs_.end()); + } + + //! number of sub control volumes in this fv element geometry + std::size_t numScv() const + { return scvs_.size(); } + + //! number of sub control volumes in this fv element geometry + std::size_t numScvf() const + { return scvfs_.size(); } + + /*! + * \brief bind the local view (r-value overload) + * This overload is called when an instance of this class is a temporary in the usage context + * This allows a usage like this: `const auto view = localView(...).bind(element);` + */ + CCTpfaFVElementGeometry bind(const Element& element) && + { + this->bind_(element); + return std::move(*this); + } + + void bind(const Element& element) & + { + this->bind_(element); + } + + /*! + * \brief bind the local view (r-value overload) + * This overload is called when an instance of this class is a temporary in the usage context + * This allows a usage like this: `const auto view = localView(...).bindElement(element);` + */ + CCTpfaFVElementGeometry bindElement(const Element& element) && + { + this->bindElement_(element); + return std::move(*this); + } + + void bindElement(const Element& element) & + { + this->bindElement_(element); + } + + //! Returns true if bind/bindElement has already been called + bool isBound() const + { return static_cast(element_); } + + //! The bound element + const Element& element() const + { return *element_; } + + //! The global finite volume geometry we are a restriction of + const GridGeometry& gridGeometry() const + { return *gridGeometryPtr_; } + + //! Returns whether one of the geometry's scvfs lies on a boundary + bool hasBoundaryScvf() const + { return hasBoundaryScvf_; } + + typename Element::Geometry geometry(const SubControlVolume& scv) const + { return gridGeometryPtr_->element(scv.dofIndex()).geometry(); } + + typename GridView::Intersection::Geometry geometry(const SubControlVolumeFace& scvf) const + { + const auto element = gridGeometryPtr_->element(scvf.insideScvIdx()); + const auto& scvfIndices = gridGeometryPtr_->scvfIndicesOfScv(scvf.insideScvIdx()); + const LocalIndexType localScvfIdx = Detail::Tpfa::findLocalIndex(scvf.index(), scvfIndices); + LocalIndexType localIdx = 0; + for (const auto& intersection : intersections(gridGeometryPtr_->gridView(), element)) + { + if (intersection.neighbor() || intersection.boundary()) + { + if (localIdx == localScvfIdx) + return intersection.geometry(); + else + ++localIdx; + } + } + + DUNE_THROW(Dune::InvalidStateException, "Could not find scvf geometry"); + } + +private: + //! Binding of an element preparing the geometries of the whole stencil + //! called by the local jacobian to prepare element assembly + void bind_(const Element& element) + { + bindElement_(element); + + neighborScvs_.reserve(element.subEntities(1)); + neighborScvfIndices_.reserve(element.subEntities(1)); + neighborScvfs_.reserve(element.subEntities(1)); + + std::vector handledNeighbors; + handledNeighbors.reserve(element.subEntities(1)); + + for (const auto& intersection : intersections(gridGeometry().gridView(), element)) + { + // for inner intersections and periodic (according to grid interface) intersections make neighbor geometry + if (intersection.neighbor()) + { + const auto outside = intersection.outside(); + const auto outsideIdx = gridGeometry().elementMapper().index(outside); + + // make outside geometries only if not done yet (could happen on non-conforming grids) + if ( std::find(handledNeighbors.begin(), handledNeighbors.end(), outsideIdx) == handledNeighbors.end() ) + { + makeNeighborGeometries(outside, outsideIdx); + handledNeighbors.push_back(outsideIdx); + } + } + } + + // build flip index set for network, surface, and periodic grids + if (dim < dimWorld || gridGeometry().isPeriodic()) + { + flippedScvfIndices_.resize(scvfs_.size()); + for (unsigned int localScvfIdx = 0; localScvfIdx < scvfs_.size(); ++localScvfIdx) + { + const auto& scvf = scvfs_[localScvfIdx]; + if (scvf.boundary()) + continue; + + flippedScvfIndices_[localScvfIdx].resize(scvf.numOutsideScvs()); + for (unsigned int localOutsideScvIdx = 0; localOutsideScvIdx < scvf.numOutsideScvs(); ++localOutsideScvIdx) + { + const auto globalOutsideScvIdx = scvf.outsideScvIdx(localOutsideScvIdx); + for (unsigned int localNeighborScvfIdx = 0; localNeighborScvfIdx < neighborScvfs_.size(); ++localNeighborScvfIdx) + { + if (neighborScvfs_[localNeighborScvfIdx].insideScvIdx() == globalOutsideScvIdx) + { + flippedScvfIndices_[localScvfIdx][localOutsideScvIdx] = localNeighborScvfIdx; + break; + } + } + } + } + + flippedNeighborScvfIndices_.resize(neighborScvfs_.size()); + for (unsigned int localScvfIdx = 0; localScvfIdx < neighborScvfs_.size(); ++localScvfIdx) + { + const auto& neighborScvf = neighborScvfs_[localScvfIdx]; + flippedNeighborScvfIndices_[localScvfIdx].resize(neighborScvf.numOutsideScvs()); + for (unsigned int localOutsideScvIdx = 0; localOutsideScvIdx < neighborScvf.numOutsideScvs(); ++localOutsideScvIdx) + { + flippedNeighborScvfIndices_[localScvfIdx][localOutsideScvIdx] = findFlippedScvfIndex_(neighborScvf.insideScvIdx(), neighborScvf.outsideScvIdx(localOutsideScvIdx)); + } + } + } + } + + //! Binding of an element preparing the geometries only inside the element + void bindElement_(const Element& element) + { + clear(); + element_ = element; + scvfs_.reserve(element.subEntities(1)); + scvfIndices_.reserve(element.subEntities(1)); + makeElementGeometries(element); + } + + GridIndexType findFlippedScvfIndex_(GridIndexType insideScvIdx, GridIndexType globalOutsideScvIdx) + { + for (unsigned int localNeighborScvfIdx = 0; localNeighborScvfIdx < neighborScvfs_.size(); ++localNeighborScvfIdx) + { + if (neighborScvfs_[localNeighborScvfIdx].insideScvIdx() == globalOutsideScvIdx) + { + return scvfs_.size() + localNeighborScvfIdx; + } + } + + // go over all potential scvfs of the outside scv + for (unsigned int localOutsideScvfIdx = 0; localOutsideScvfIdx < scvfs_.size(); ++localOutsideScvfIdx) + { + const auto& outsideScvf = scvfs_[localOutsideScvfIdx]; + for (unsigned int j = 0; j < outsideScvf.numOutsideScvs(); ++j) + { + if (outsideScvf.outsideScvIdx(j) == insideScvIdx) + { + return localOutsideScvfIdx; + } + } + } + + DUNE_THROW(Dune::InvalidStateException, "No flipped version of this scvf found!"); + } + + //! create scvs and scvfs of the bound element + void makeElementGeometries(const Element& element) + { + using ScvfGridIndexStorage = typename SubControlVolumeFace::Traits::GridIndexStorage; + + const auto eIdx = gridGeometry().elementMapper().index(element); + scvs_[0] = SubControlVolume(element.geometry(), eIdx); + scvIndices_[0] = eIdx; + + const auto& scvFaceIndices = gridGeometry().scvfIndicesOfScv(eIdx); + const auto& neighborVolVarIndices = gridGeometry().neighborVolVarIndices(eIdx); + + // for network grids there might be multiple intersection with the same geometryInInside + // we identify those by the indexInInside for now (assumes conforming grids at branching facets) + // here we keep track of them + std::vector handledScvf; + if (dim < dimWorld) + handledScvf.resize(element.subEntities(1), false); + + int scvfCounter = 0; + for (const auto& intersection : intersections(gridGeometry().gridView(), element)) + { + if (dim < dimWorld) + if (handledScvf[intersection.indexInInside()]) + continue; + + const auto& scvfNeighborVolVarIndices = neighborVolVarIndices[scvfCounter]; + if (intersection.neighbor() || intersection.boundary()) + { + ScvfGridIndexStorage scvIndices; + scvIndices.resize(scvfNeighborVolVarIndices.size() + 1); + scvIndices[0] = eIdx; + std::copy(scvfNeighborVolVarIndices.begin(), scvfNeighborVolVarIndices.end(), scvIndices.begin()+1); + + const bool onBoundary = intersection.boundary() && !intersection.neighbor(); + hasBoundaryScvf_ = (hasBoundaryScvf_ || onBoundary); + + scvfs_.emplace_back(intersection, + intersection.geometry(), + scvFaceIndices[scvfCounter], + scvIndices, + onBoundary); + scvfIndices_.emplace_back(scvFaceIndices[scvfCounter]); + scvfCounter++; + + // for surface and network grids mark that we handled this face + if (dim < dimWorld) + handledScvf[intersection.indexInInside()] = true; + } + } + } + + //! create the necessary scvs and scvfs of the neighbor elements to the bound elements + void makeNeighborGeometries(const Element& element, const GridIndexType eIdx) + { + using ScvfGridIndexStorage = typename SubControlVolumeFace::Traits::GridIndexStorage; + + // create the neighbor scv + neighborScvs_.emplace_back(element.geometry(), eIdx); + neighborScvIndices_.push_back(eIdx); + + const auto& scvFaceIndices = gridGeometry().scvfIndicesOfScv(eIdx); + const auto& neighborVolVarIndices = gridGeometry().neighborVolVarIndices(eIdx); + + // for network grids there might be multiple intersection with the same geometryInInside + // we identify those by the indexInInside for now (assumes conforming grids at branching facets) + // here we keep track of them + std::vector handledScvf; + if (dim < dimWorld) + handledScvf.resize(element.subEntities(1), false); + + int scvfCounter = 0; + for (const auto& intersection : intersections(gridGeometry().gridView(), element)) + { + if (dim < dimWorld) + if (handledScvf[intersection.indexInInside()]) + continue; + + if (intersection.neighbor()) + { + // this catches inner and periodic scvfs + const auto& scvfNeighborVolVarIndices = neighborVolVarIndices[scvfCounter]; + if (scvfNeighborVolVarIndices[0] < gridGeometry().gridView().size(0)) + { + // only create subcontrol faces where the outside element is the bound element + if (dim == dimWorld) + { + if (scvfNeighborVolVarIndices[0] == gridGeometry().elementMapper().index(*element_)) + { + ScvfGridIndexStorage scvIndices({eIdx, scvfNeighborVolVarIndices[0]}); + neighborScvfs_.emplace_back(intersection, + intersection.geometry(), + scvFaceIndices[scvfCounter], + scvIndices, + false); + + neighborScvfIndices_.push_back(scvFaceIndices[scvfCounter]); + } + } + // for network grids we can't use the intersection.outside() index as we can't assure that the + // first intersection with this indexInInside is the one that has our bound element as outside + // instead we check if the bound element's index is in the outsideScvIndices of the candidate scvf + // (will be optimized away for dim == dimWorld) + else + { + for (unsigned outsideScvIdx = 0; outsideScvIdx < scvfNeighborVolVarIndices.size(); ++outsideScvIdx) + { + if (scvfNeighborVolVarIndices[outsideScvIdx] == gridGeometry().elementMapper().index(*element_)) + { + ScvfGridIndexStorage scvIndices; + scvIndices.resize(scvfNeighborVolVarIndices.size() + 1); + scvIndices[0] = eIdx; + std::copy(scvfNeighborVolVarIndices.begin(), scvfNeighborVolVarIndices.end(), scvIndices.begin()+1); + neighborScvfs_.emplace_back(intersection, + intersection.geometry(), + scvFaceIndices[scvfCounter], + scvIndices, + false); + + neighborScvfIndices_.push_back(scvFaceIndices[scvfCounter]); + break; + } + } + } + + // for surface and network grids mark that we handled this face + if (dim < dimWorld) + handledScvf[intersection.indexInInside()] = true; + scvfCounter++; + } + } + + // only increase counter for boundary intersections + // (exclude periodic boundaries according to dune grid interface, they have been handled in neighbor==true) + else if (intersection.boundary()) + { + if (dim < dimWorld) + handledScvf[intersection.indexInInside()] = true; + scvfCounter++; + } + } + } + + //! Clear all local data + void clear() + { + scvfIndices_.clear(); + scvfs_.clear(); + flippedScvfIndices_.clear(); + + neighborScvIndices_.clear(); + neighborScvfIndices_.clear(); + neighborScvs_.clear(); + neighborScvfs_.clear(); + flippedNeighborScvfIndices_.clear(); + + hasBoundaryScvf_ = false; + } + + std::optional element_; //!< the element to which this fvgeometry is bound + const GridGeometry* gridGeometryPtr_; //!< the grid fvgeometry + + // local storage after binding an element + std::array scvIndices_; + std::array scvs_; + + std::vector scvfIndices_; + std::vector scvfs_; + std::vector> flippedScvfIndices_; + + std::vector neighborScvIndices_; + std::vector neighborScvs_; + + std::vector neighborScvfIndices_; + std::vector neighborScvfs_; + std::vector> flippedNeighborScvfIndices_; + + bool hasBoundaryScvf_ = false; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/tpfa/fvgridgeometry.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/tpfa/fvgridgeometry.hh new file mode 100644 index 0000000..6b77de6 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/tpfa/fvgridgeometry.hh @@ -0,0 +1,611 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup CCTpfaDiscretization + * \brief The finite volume geometry (scvs and scvfs) for cell-centered TPFA models on a grid view + * This builds up the sub control volumes and sub control volume faces + * for each element of the grid partition. + */ +#ifndef DUMUX_DISCRETIZATION_CCTPFA_FV_GRID_GEOMETRY_HH +#define DUMUX_DISCRETIZATION_CCTPFA_FV_GRID_GEOMETRY_HH + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace Dumux { + +/*! + * \ingroup CCTpfaDiscretization + * \brief The default traits for the tpfa finite volume grid geometry + * Defines the scv and scvf types and the mapper types + * \tparam the grid view type + */ +template> +struct CCTpfaDefaultGridGeometryTraits +: public MapperTraits +{ + using SubControlVolume = CCSubControlVolume; + using SubControlVolumeFace = CCTpfaSubControlVolumeFace; + + template + using ConnectivityMap = CCSimpleConnectivityMap; + + template + using LocalView = CCTpfaFVElementGeometry; + + //! State the maximum admissible number of neighbors per scvf + //! Per default, we allow for 8 branches on network/surface grids, where + //! conformity is assumed. For normal grids, we allow a maximum of one + //! hanging node per scvf. Use different traits if you need more. + static constexpr int maxNumScvfNeighbors = int(GridView::dimension) > +class CCTpfaFVGridGeometry; + +/*! + * \ingroup CCTpfaDiscretization + * \brief The finite volume geometry (scvs and scvfs) for cell-centered TPFA models on a grid view + * This builds up the sub control volumes and sub control volume faces + * \note For caching enabled we store the fv geometries for the whole grid view which is memory intensive but faster + */ +template +class CCTpfaFVGridGeometry +: public BaseGridGeometry +{ + using ThisType = CCTpfaFVGridGeometry; + using ParentType = BaseGridGeometry; + using ConnectivityMap = typename Traits::template ConnectivityMap; + using GridIndexType = typename IndexTraits::GridIndex; + using Element = typename GV::template Codim<0>::Entity; + + static const int dim = GV::dimension; + static const int dimWorld = GV::dimensionworld; + +public: + //! export basic grid geometry type for the alternative constructor + using BasicGridGeometry = BasicGridGeometry_t; + //! export the type of the fv element geometry (the local view type) + using LocalView = typename Traits::template LocalView; + //! export the type of sub control volume + using SubControlVolume = typename Traits::SubControlVolume; + //! export the type of sub control volume + using SubControlVolumeFace = typename Traits::SubControlVolumeFace; + //! export the type of extrusion + using Extrusion = Extrusion_t; + //! export dof mapper type + using DofMapper = typename Traits::ElementMapper; + //! export whether the grid(geometry) supports periodicity + using SupportsPeriodicity = typename PeriodicGridTraits::SupportsPeriodicity; + + //! export the discretization method this geometry belongs to + using DiscretizationMethod = DiscretizationMethods::CCTpfa; + static constexpr DiscretizationMethod discMethod{}; + + //! The maximum admissible stencil size (used for static memory allocation during assembly) + static constexpr int maxElementStencilSize = LocalView::maxNumElementScvfs*Traits::maxNumScvfNeighbors + 1; + + //! export the grid view type + using GridView = GV; + + //! Constructor with basic grid geometry used to share state with another grid geometry on the same grid view + CCTpfaFVGridGeometry(std::shared_ptr gg) + : ParentType(std::move(gg)) + , periodicGridTraits_(this->gridView().grid()) + { + // Check if the overlap size is what we expect + if (!CheckOverlapSize::isValid(this->gridView())) + DUNE_THROW(Dune::InvalidStateException, "The cctpfa discretization method needs at least an overlap of 1 for parallel computations. " + << " Set the parameter \"Grid.Overlap\" in the input file."); + + update_(); + } + + //! Constructor from gridView + CCTpfaFVGridGeometry(const GridView& gridView) + : CCTpfaFVGridGeometry(std::make_shared(gridView)) + {} + + //! the element mapper is the dofMapper + //! this is convenience to have better chance to have the same main files for box/tpfa/mpfa... + const DofMapper& dofMapper() const + { return this->elementMapper(); } + + //! The total number of sub control volumes + std::size_t numScv() const + { + return scvs_.size(); + } + + //! The total number of sub control volume faces + std::size_t numScvf() const + { + return scvfs_.size(); + } + + //! The total number of boundary sub control volume faces + std::size_t numBoundaryScvf() const + { + return numBoundaryScvf_; + } + + //! The total number of degrees of freedom + std::size_t numDofs() const + { return this->gridView().size(0); } + + + //! update all fvElementGeometries (call this after grid adaption) + void update(const GridView& gridView) + { + ParentType::update(gridView); + update_(); + } + + //! update all fvElementGeometries (call this after grid adaption) + void update(GridView&& gridView) + { + ParentType::update(std::move(gridView)); + update_(); + } + + //! Get a sub control volume with a global scv index + const SubControlVolume& scv(GridIndexType scvIdx) const + { + return scvs_[scvIdx]; + } + + //! Get a sub control volume face with a global scvf index + const SubControlVolumeFace& scvf(GridIndexType scvfIdx) const + { + return scvfs_[scvfIdx]; + } + + //! Get the scvf on the same face but from the other side + //! Note that e.g. the normals might be different in the case of surface grids + const SubControlVolumeFace& flipScvf(GridIndexType scvfIdx, unsigned int outsideScvfIdx = 0) const + { + return scvfs_[flipScvfIndices_[scvfIdx][outsideScvfIdx]]; + } + + //! Get the sub control volume face indices of an scv by global index + const std::vector& scvfIndicesOfScv(GridIndexType scvIdx) const + { + return scvfIndicesOfScv_[scvIdx]; + } + + /*! + * \brief Returns the connectivity map of which dofs have derivatives with respect + * to a given dof. + */ + const ConnectivityMap &connectivityMap() const + { return connectivityMap_; } + + //! Returns whether one of the geometry's scvfs lies on a boundary + bool hasBoundaryScvf(GridIndexType eIdx) const + { return hasBoundaryScvf_[eIdx]; } + +private: + + void update_() + { + // clear containers (necessary after grid refinement) + scvs_.clear(); + scvfs_.clear(); + scvfIndicesOfScv_.clear(); + flipScvfIndices_.clear(); + + // determine size of containers + std::size_t numScvs = numDofs(); + std::size_t numScvf = 0; + for (const auto& element : elements(this->gridView())) + numScvf += element.subEntities(1); + + // reserve memory + scvs_.resize(numScvs); + scvfs_.reserve(numScvf); + scvfIndicesOfScv_.resize(numScvs); + hasBoundaryScvf_.assign(numScvs, false); + + // Build the scvs and scv faces + GridIndexType scvfIdx = 0; + numBoundaryScvf_ = 0; + for (const auto& element : elements(this->gridView())) + { + const auto eIdx = this->elementMapper().index(element); + scvs_[eIdx] = SubControlVolume(element.geometry(), eIdx); + + // the element-wise index sets for finite volume geometry + std::vector scvfsIndexSet; + scvfsIndexSet.reserve(element.subEntities(1)); + + // for network grids there might be multiple intersection with the same geometryInInside + // we identify those by the indexInInside for now (assumes conforming grids at branching facets) + using ScvfGridIndexStorage = typename SubControlVolumeFace::Traits::GridIndexStorage; + std::vector outsideIndices; + if (dim < dimWorld) + { + //! first, push inside index in all neighbor sets + outsideIndices.resize(element.subEntities(1)); + std::for_each(outsideIndices.begin(), outsideIndices.end(), [eIdx] (auto& nIndices) { nIndices.push_back(eIdx); }); + + // second, insert neighbors + for (const auto& intersection : intersections(this->gridView(), element)) + { + if (intersection.neighbor()) + { + const auto nIdx = this->elementMapper().index( intersection.outside() ); + outsideIndices[intersection.indexInInside()].push_back(nIdx); + } + } + } + + for (const auto& intersection : intersections(this->gridView(), element)) + { + // inner sub control volume faces (includes periodic boundaries) + if (intersection.neighbor()) + { + // update the grid geometry if we have periodic boundaries + if (periodicGridTraits_.isPeriodic(intersection)) + this->setPeriodic(); + + if (dim == dimWorld) + { + const auto nIdx = this->elementMapper().index(intersection.outside()); + scvfs_.emplace_back(intersection, + intersection.geometry(), + scvfIdx, + ScvfGridIndexStorage({eIdx, nIdx}), + false); + scvfsIndexSet.push_back(scvfIdx++); + } + // this is for network grids + // (will be optimized away of dim == dimWorld) + else + { + auto indexInInside = intersection.indexInInside(); + // check if we already handled this facet + if (outsideIndices[indexInInside].empty()) + continue; + else + { + scvfs_.emplace_back(intersection, + intersection.geometry(), + scvfIdx, + outsideIndices[indexInInside], + false); + scvfsIndexSet.push_back(scvfIdx++); + outsideIndices[indexInInside].clear(); + } + } + } + // boundary sub control volume faces + else if (intersection.boundary()) + { + scvfs_.emplace_back(intersection, + intersection.geometry(), + scvfIdx, + ScvfGridIndexStorage({eIdx, static_cast(this->gridView().size(0) + numBoundaryScvf_++)}), + true); + scvfsIndexSet.push_back(scvfIdx++); + + hasBoundaryScvf_[eIdx] = true; + } + } + + // Save the scvf indices belonging to this scv to build up fv element geometries fast + scvfIndicesOfScv_[eIdx] = scvfsIndexSet; + } + + // Make the flip index set for network, surface, and periodic grids + if (dim < dimWorld || this->isPeriodic()) + { + flipScvfIndices_.resize(scvfs_.size()); + for (auto&& scvf : scvfs_) + { + if (scvf.boundary()) + continue; + + flipScvfIndices_[scvf.index()].resize(scvf.numOutsideScvs()); + const auto insideScvIdx = scvf.insideScvIdx(); + // check which outside scvf has the insideScvIdx index in its outsideScvIndices + for (unsigned int i = 0; i < scvf.numOutsideScvs(); ++i) + flipScvfIndices_[scvf.index()][i] = findFlippedScvfIndex_(insideScvIdx, scvf.outsideScvIdx(i)); + } + } + + // build the connectivity map for an efficient assembly + connectivityMap_.update(*this); + } + + // find the scvf that has insideScvIdx in its outsideScvIdx list and outsideScvIdx as its insideScvIdx + GridIndexType findFlippedScvfIndex_(GridIndexType insideScvIdx, GridIndexType outsideScvIdx) + { + // go over all potential scvfs of the outside scv + for (auto outsideScvfIndex : scvfIndicesOfScv_[outsideScvIdx]) + { + const auto& outsideScvf = this->scvf(outsideScvfIndex); + for (unsigned int j = 0; j < outsideScvf.numOutsideScvs(); ++j) + if (outsideScvf.outsideScvIdx(j) == insideScvIdx) + return outsideScvf.index(); + } + + DUNE_THROW(Dune::InvalidStateException, "No flipped version of this scvf found!"); + } + + //! connectivity map for efficient assembly + ConnectivityMap connectivityMap_; + + //! containers storing the global data + std::vector scvs_; + std::vector scvfs_; + std::vector> scvfIndicesOfScv_; + std::size_t numBoundaryScvf_; + std::vector hasBoundaryScvf_; + + //! needed for embedded surface and network grids (dim < dimWorld) + std::vector> flipScvfIndices_; + + PeriodicGridTraits periodicGridTraits_; +}; + +/*! + * \ingroup CCTpfaDiscretization + * \brief The finite volume geometry (scvs and scvfs) for cell-centered TPFA models on a grid view + * This builds up the sub control volumes and sub control volume faces + * \note For caching disabled we store only some essential index maps to build up local systems on-demand in + * the corresponding FVElementGeometry + */ +template +class CCTpfaFVGridGeometry +: public BaseGridGeometry +{ + using ThisType = CCTpfaFVGridGeometry; + using ParentType = BaseGridGeometry; + using ConnectivityMap = typename Traits::template ConnectivityMap; + + using GridIndexType = typename IndexTraits::GridIndex; + using Element = typename GV::template Codim<0>::Entity; + + static const int dim = GV::dimension; + static const int dimWorld = GV::dimensionworld; + + using ScvfGridIndexStorage = typename Traits::SubControlVolumeFace::Traits::GridIndexStorage; + using NeighborVolVarIndices = typename std::conditional_t< (dim >; + +public: + //! export basic grid geometry type for the alternative constructor + using BasicGridGeometry = BasicGridGeometry_t; + //! export the type of the fv element geometry (the local view type) + using LocalView = typename Traits::template LocalView; + //! export the type of sub control volume + using SubControlVolume = typename Traits::SubControlVolume; + //! export the type of sub control volume + using SubControlVolumeFace = typename Traits::SubControlVolumeFace; + //! export the type of extrusion + using Extrusion = Extrusion_t; + //! export dof mapper type + using DofMapper = typename Traits::ElementMapper; + //! export whether the grid(geometry) supports periodicity + using SupportsPeriodicity = typename PeriodicGridTraits::SupportsPeriodicity; + + //! Export the discretization method this geometry belongs to + using DiscretizationMethod = DiscretizationMethods::CCTpfa; + static constexpr DiscretizationMethod discMethod{}; + + //! The maximum admissible stencil size (used for static memory allocation during assembly) + static constexpr int maxElementStencilSize = LocalView::maxNumElementScvfs*Traits::maxNumScvfNeighbors + 1; + + //! Export the type of the grid view + using GridView = GV; + + //! Constructor with basic grid geometry used to share state with another grid geometry on the same grid view + CCTpfaFVGridGeometry(std::shared_ptr gg) + : ParentType(std::move(gg)) + , periodicGridTraits_(this->gridView().grid()) + { + // Check if the overlap size is what we expect + if (!CheckOverlapSize::isValid(this->gridView())) + DUNE_THROW(Dune::InvalidStateException, "The cctpfa discretization method needs at least an overlap of 1 for parallel computations. " + << " Set the parameter \"Grid.Overlap\" in the input file."); + + update_(); + } + + //! Constructor from gridView + CCTpfaFVGridGeometry(const GridView& gridView) + : CCTpfaFVGridGeometry(std::make_shared(gridView)) + {} + + //! the element mapper is the dofMapper + //! this is convenience to have better chance to have the same main files for box/tpfa/mpfa... + const DofMapper& dofMapper() const + { return this->elementMapper(); } + + //! The total number of sub control volumes + std::size_t numScv() const + { + return numScvs_; + } + + //! The total number of sub control volume faces + std::size_t numScvf() const + { + return numScvf_; + } + + //! The total number of boundary sub control volume faces + std::size_t numBoundaryScvf() const + { + return numBoundaryScvf_; + } + + //! The total number of degrees of freedom + std::size_t numDofs() const + { return this->gridView().size(0); } + + //! update all fvElementGeometries (call this after grid adaption) + void update(const GridView& gridView) + { + ParentType::update(gridView); + update_(); + } + + //! update all fvElementGeometries (call this after grid adaption) + void update(GridView&& gridView) + { + ParentType::update(std::move(gridView)); + update_(); + } + + const std::vector& scvfIndicesOfScv(GridIndexType scvIdx) const + { return scvfIndicesOfScv_[scvIdx]; } + + //! Return the neighbor volVar indices for all scvfs in the scv with index scvIdx + const std::vector& neighborVolVarIndices(GridIndexType scvIdx) const + { return neighborVolVarIndices_[scvIdx]; } + + /*! + * \brief Returns the connectivity map of which dofs have derivatives with respect + * to a given dof. + */ + const ConnectivityMap &connectivityMap() const + { return connectivityMap_; } + +private: + + void update_() + { + // clear local data + scvfIndicesOfScv_.clear(); + neighborVolVarIndices_.clear(); + + // reserve memory or resize the containers + numScvs_ = numDofs(); + numScvf_ = 0; + numBoundaryScvf_ = 0; + scvfIndicesOfScv_.resize(numScvs_); + neighborVolVarIndices_.resize(numScvs_); + + // Build the SCV and SCV face + for (const auto& element : elements(this->gridView())) + { + const auto eIdx = this->elementMapper().index(element); + + // the element-wise index sets for finite volume geometry + auto numLocalFaces = element.subEntities(1); + std::vector scvfsIndexSet; + std::vector neighborVolVarIndexSet; + scvfsIndexSet.reserve(numLocalFaces); + neighborVolVarIndexSet.reserve(numLocalFaces); + + // for network grids there might be multiple intersection with the same geometryInInside + // we identify those by the indexInInside for now (assumes conforming grids at branching facets) + std::vector outsideIndices; + if (dim < dimWorld) + { + outsideIndices.resize(numLocalFaces); + for (const auto& intersection : intersections(this->gridView(), element)) + { + if (intersection.neighbor()) + { + const auto nIdx = this->elementMapper().index(intersection.outside()); + outsideIndices[intersection.indexInInside()].push_back(nIdx); + } + } + } + + for (const auto& intersection : intersections(this->gridView(), element)) + { + // inner sub control volume faces (includes periodic boundaries) + if (intersection.neighbor()) + { + // update the grid geometry if we have periodic boundaries + if (periodicGridTraits_.isPeriodic(intersection)) + this->setPeriodic(); + + if (dim == dimWorld) + { + scvfsIndexSet.push_back(numScvf_++); + const auto nIdx = this->elementMapper().index(intersection.outside()); + neighborVolVarIndexSet.emplace_back(NeighborVolVarIndices({nIdx})); + } + // this is for network grids + // (will be optimized away of dim == dimWorld) + else + { + auto indexInInside = intersection.indexInInside(); + // check if we already handled this facet + if (outsideIndices[indexInInside].empty()) + continue; + else + { + scvfsIndexSet.push_back(numScvf_++); + neighborVolVarIndexSet.emplace_back(std::move(outsideIndices[indexInInside])); + outsideIndices[indexInInside].clear(); + } + } + } + // boundary sub control volume faces + else if (intersection.boundary()) + { + scvfsIndexSet.push_back(numScvf_++); + neighborVolVarIndexSet.emplace_back(NeighborVolVarIndices({static_cast(numScvs_ + numBoundaryScvf_++)})); + } + } + + // store the sets of indices in the data container + scvfIndicesOfScv_[eIdx] = scvfsIndexSet; + neighborVolVarIndices_[eIdx] = neighborVolVarIndexSet; + } + + // build the connectivity map for an efficient assembly + connectivityMap_.update(*this); + } + + //! Information on the global number of geometries + std::size_t numScvs_; + std::size_t numScvf_; + std::size_t numBoundaryScvf_; + + //! connectivity map for efficient assembly + ConnectivityMap connectivityMap_; + + //! vectors that store the global data + std::vector> scvfIndicesOfScv_; + std::vector> neighborVolVarIndices_; + + PeriodicGridTraits periodicGridTraits_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/tpfa/gridfluxvariablescache.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/tpfa/gridfluxvariablescache.hh new file mode 100644 index 0000000..99246bb --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/tpfa/gridfluxvariablescache.hh @@ -0,0 +1,207 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup CCTpfaDiscretization + * \brief Flux variable caches on a gridview + */ +#ifndef DUMUX_DISCRETIZATION_CCTPFA_GRID_FLUXVARSCACHE_HH +#define DUMUX_DISCRETIZATION_CCTPFA_GRID_FLUXVARSCACHE_HH + +#include + +// make the local view function available whenever we use this class +#include +#include + +namespace Dumux { + +/*! + * \ingroup CCTpfaDiscretization + * \brief Flux variable caches traits + */ +template +struct CCTpfaDefaultGridFVCTraits +{ + using Problem = P; + using FluxVariablesCache = FVC; + using FluxVariablesCacheFiller = FVCF; + + template + using LocalView = CCTpfaElementFluxVariablesCache; +}; + +/*! + * \ingroup CCTpfaDiscretization + * \brief Flux variable caches on a gridview + * \note The class is specialized for a version with and without grid caching + */ +template > +class CCTpfaGridFluxVariablesCache; + +/*! + * \ingroup CCTpfaDiscretization + * \brief Flux variable caches on a gridview with grid caching enabled + * \note The flux caches of the gridview are stored which is memory intensive but faster + */ +template +class CCTpfaGridFluxVariablesCache +{ + using Problem = typename TheTraits::Problem; + using ThisType = CCTpfaGridFluxVariablesCache; + + //! the flux variable cache filler type + using FluxVariablesCacheFiller = typename TheTraits::FluxVariablesCacheFiller; +public: + //! the flux variables cache traits + using Traits = TheTraits; + + //! export the flux variable cache type + using FluxVariablesCache = typename Traits::FluxVariablesCache; + + //! make it possible to query if caching is enabled + static constexpr bool cachingEnabled = true; + + //! export the type of the local view + using LocalView = typename Traits::template LocalView; + + // The constructor + CCTpfaGridFluxVariablesCache(const Problem& problem) : problemPtr_(&problem) {} + + // When global caching is enabled, precompute transmissibilities and stencils for all the scv faces + template + void update(const GridGeometry& gridGeometry, + const GridVolumeVariables& gridVolVars, + const SolutionVector& sol, + bool forceUpdate = false) + { + // only do the update if fluxes are solution dependent or if update is forced + if (FluxVariablesCacheFiller::isSolDependent || forceUpdate) + { + // instantiate helper class to fill the caches + FluxVariablesCacheFiller filler(problem()); + + fluxVarsCache_.resize(gridGeometry.numScvf()); + + Dumux::parallelFor(gridGeometry.gridView().size(0), [&](const std::size_t eIdx) + { + // Prepare the geometries within the elements of the stencil + const auto element = gridGeometry.element(eIdx); + const auto fvGeometry = localView(gridGeometry).bind(element); + const auto elemVolVars = localView(gridVolVars).bind(element, fvGeometry, sol); + + for (auto&& scvf : scvfs(fvGeometry)) + { + filler.fill(*this, fluxVarsCache_[scvf.index()], element, fvGeometry, elemVolVars, scvf, forceUpdate); + } + }); + } + } + + template + void updateElement(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars) + { + if (FluxVariablesCacheFiller::isSolDependent) + { + const auto globalI = fvGeometry.gridGeometry().elementMapper().index(element); + + // instantiate filler class + FluxVariablesCacheFiller filler(problem()); + + // update the caches inside this element + for (const auto& scvf : scvfs(fvGeometry)) + filler.fill(*this, fluxVarsCache_[scvf.index()], element, fvGeometry, elemVolVars, scvf); + + // update the caches in the neighbors + for (const auto& dataJ : fvGeometry.gridGeometry().connectivityMap()[globalI]) + { + const auto elementJ = fvGeometry.gridGeometry().element(dataJ.globalJ); + for (const auto scvfIdxJ : dataJ.scvfsJ) + { + const auto& scvfJ = fvGeometry.scvf(scvfIdxJ); + filler.fill(*this, fluxVarsCache_[scvfJ.index()], elementJ, fvGeometry, elemVolVars, scvfJ); + } + } + } + } + + // access operators in the case of caching + template + const FluxVariablesCache& operator [](const SubControlVolumeFace& scvf) const + { return fluxVarsCache_[scvf.index()]; } + + template + FluxVariablesCache& operator [](const SubControlVolumeFace& scvf) + { return fluxVarsCache_[scvf.index()]; } + + const Problem& problem() const + { return *problemPtr_; } + +private: + const Problem* problemPtr_; + + std::vector fluxVarsCache_; + std::vector globalScvfIndices_; +}; + +/*! + * \ingroup CCTpfaDiscretization + * \brief Flux variable caches on a gridview with grid caching disabled + */ +template +class CCTpfaGridFluxVariablesCache +{ + using Problem = typename TheTraits::Problem; + using ThisType = CCTpfaGridFluxVariablesCache; + + //! the flux variable cache filler type + using FluxVariablesCacheFiller = typename TheTraits::FluxVariablesCacheFiller; +public: + //! the flux variables cache traits + using Traits = TheTraits; + + //! export the flux variable cache type + using FluxVariablesCache = typename Traits::FluxVariablesCache; + + //! make it possible to query if caching is enabled + static constexpr bool cachingEnabled = false; + + //! export the type of the local view + using LocalView = typename Traits::template LocalView; + + // The constructor + CCTpfaGridFluxVariablesCache(const Problem& problem) : problemPtr_(&problem) {} + + //! When global flux variables caching is disabled, we don't need to update the cache + template + void update(const GridGeometry& gridGeometry, + const GridVolumeVariables& gridVolVars, + const SolutionVector& sol, + bool forceUpdate = false) {} + + //! When global flux variables caching is disabled, we don't need to update the cache + template + void updateElement(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars) {} + + const Problem& problem() const + { return *problemPtr_; } + +private: + const Problem* problemPtr_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/tpfa/gridvolumevariables.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/tpfa/gridvolumevariables.hh new file mode 100644 index 0000000..02dd0fa --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/tpfa/gridvolumevariables.hh @@ -0,0 +1,52 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup CCTpfaDiscretization + * \brief The grid volume variables class for cell centered tpfa models + */ +#ifndef DUMUX_DISCRETIZATION_CC_TPFA_GRID_VOLUMEVARIABLES_HH +#define DUMUX_DISCRETIZATION_CC_TPFA_GRID_VOLUMEVARIABLES_HH + +#include +#include + +namespace Dumux { + +template +struct CCTpfaDefaultGridVolumeVariablesTraits +{ + using Problem = P; + using VolumeVariables = VV; + + template + using LocalView = CCTpfaElementVolumeVariables; +}; + +/*! + * \ingroup CCTpfaDiscretization + * \brief Base class for the grid volume variables + * \note This class has a cached version and a non-cached version + * \tparam Problem the type of problem we are solving + * \tparam VolumeVariables the type of volume variables we are using for the model + * \tparam Traits the traits class injecting the problem, volVar and elemVolVars type + * \tparam cachingEnabled if the cache is enabled + */ +template > +class CCTpfaGridVolumeVariables : public CCGridVolumeVariables +{ +public: + using ParentType = CCGridVolumeVariables; + using ParentType::ParentType; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/tpfa/subcontrolvolumeface.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/tpfa/subcontrolvolumeface.hh new file mode 100644 index 0000000..f5503b2 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cellcentered/tpfa/subcontrolvolumeface.hh @@ -0,0 +1,195 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup CCTpfaDiscretization + * \brief The sub control volume face + */ +#ifndef DUMUX_DISCRETIZATION_CC_TPFA_SUBCONTROLVOLUMEFACE_HH +#define DUMUX_DISCRETIZATION_CC_TPFA_SUBCONTROLVOLUMEFACE_HH + +#include +#include + +#include +#include +#include + +#include +#include +#include + +namespace Dumux { + +/*! + * \ingroup CCTpfaDiscretization + * \brief Default traits class to be used for the sub-control volume faces + * for the cell-centered finite volume scheme using TPFA + * \tparam GV the type of the grid view + */ +template +struct CCTpfaDefaultScvfGeometryTraits +{ + using Grid = typename GridView::Grid; + + static constexpr int dim = Grid::dimension; + static constexpr int dimWorld = Grid::dimensionworld; + + using Scalar = typename Grid::ctype; + using GridIndexType = typename IndexTraits::GridIndex; + using LocalIndexType = typename IndexTraits::LocalIndex; + using GridIndexStorage = typename std::conditional_t< (dim, + Dune::ReservedVector >; + + // we use geometry traits that use static corner vectors to and a fixed geometry type + template + struct ScvfMLGTraits : public Dune::MultiLinearGeometryTraits + { + // we use static vectors to store the corners as we know + // the number of corners in advance (2^(dim-1) corners (1<<(dim-1)) + template< int mydim, int cdim > + struct CornerStorage + { + using Type = Dune::ReservedVector< Dune::FieldVector< ct, cdim >, (1<<(dim-1)) >; + }; + }; + + using Geometry = Dune::MultiLinearGeometry >; + using CornerStorage = typename ScvfMLGTraits::template CornerStorage::Type; + using GlobalPosition = typename CornerStorage::value_type; + using BoundaryFlag = Dumux::BoundaryFlag; +}; + +/*! + * \ingroup CCTpfaDiscretization + * \brief The sub control volume face + * \tparam GV the type of the grid view + * \tparam T the scvf geometry traits + */ +template > +class CCTpfaSubControlVolumeFace +: public SubControlVolumeFaceBase, T> +{ + using ThisType = CCTpfaSubControlVolumeFace; + using ParentType = SubControlVolumeFaceBase; + using GridIndexType = typename T::GridIndexType; + using Scalar = typename T::Scalar; + using CornerStorage = typename T::CornerStorage; + using GridIndexStorage = typename T::GridIndexStorage; + using BoundaryFlag = typename T::BoundaryFlag; + +public: + //! export the type used for global coordinates + using GlobalPosition = typename T::GlobalPosition; + //! state the traits public and thus export all types + using Traits = T; + + // the default constructor + CCTpfaSubControlVolumeFace() = default; + + /*! + * \brief Constructor with intersection + * + * \param is The intersection + * \param isGeometry The geometry of the intersection + * \param scvfIndex The global index of this scv face + * \param scvIndices The inside/outside scv indices connected to this face + * \param isBoundary Bool to specify whether or not the scvf is on an interior or the domain boundary + */ + template + CCTpfaSubControlVolumeFace(const Intersection& is, + const typename Intersection::Geometry& isGeometry, + GridIndexType scvfIndex, + const GridIndexStorage& scvIndices, + bool isBoundary) + : ParentType() + , area_(isGeometry.volume()) + , center_(isGeometry.center()) + , unitOuterNormal_(is.centerUnitOuterNormal()) + , scvfIndex_(scvfIndex) + , scvIndices_(scvIndices) + , boundary_(isBoundary) + , boundaryFlag_{is} + {} + + //! The center of the sub control volume face + const GlobalPosition& center() const + { + return center_; + } + + //! The integration point for flux evaluations in global coordinates + const GlobalPosition& ipGlobal() const + { + // Return center for now + return center_; + } + + //! The area of the sub control volume face + Scalar area() const + { + return area_; + } + + //! returns true if the sub control volume face is on the boundary + bool boundary() const + { + return boundary_; + } + + //! The unit outer normal of the sub control volume face + const GlobalPosition& unitOuterNormal() const + { + return unitOuterNormal_; + } + + //! index of the inside sub control volume + GridIndexType insideScvIdx() const + { + return scvIndices_[0]; + } + + //! Index of the i-th outside sub control volume or boundary scv index. + // Results in undefined behaviour if i >= numOutsideScvs() + GridIndexType outsideScvIdx(int i = 0) const + { + return scvIndices_[i+1]; + } + + //! The number of scvs on the outside of this face + std::size_t numOutsideScvs() const + { + return scvIndices_.size()-1; + } + + //! The global index of this sub control volume face + GridIndexType index() const + { + return scvfIndex_; + } + + //! Return the boundary flag + typename BoundaryFlag::value_type boundaryFlag() const + { + return boundaryFlag_.get(); + } + +private: + Scalar area_; + GlobalPosition center_; + GlobalPosition unitOuterNormal_; + GridIndexType scvfIndex_; + GridIndexStorage scvIndices_; + bool boundary_; + BoundaryFlag boundaryFlag_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/checkoverlapsize.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/checkoverlapsize.hh new file mode 100644 index 0000000..24931f9 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/checkoverlapsize.hh @@ -0,0 +1,64 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Discretization + * \brief Check the overlap size for different discretization methods + */ +#ifndef DUMUX_DISCRETIZATION_CHECK_OVERLAP_SIZE_HH +#define DUMUX_DISCRETIZATION_CHECK_OVERLAP_SIZE_HH + +#include + +namespace Dumux { + +/*! + * \ingroup Discretization + * \brief Check if the overlap size is valid for a given discretization method + * \note the default checks if the grid has at least an overlap of one if there are no ghosts + * \note for sequential grids every overlap is fine + * \note specialize this for your discretization method if the default doesn't apply + */ +template +struct CheckOverlapSize +{ + template + static bool isValid(const GridView& gridView) noexcept + { return gridView.comm().size() <= 1 || gridView.overlapSize(0) + gridView.ghostSize(0) > 0; } +}; + +//! specialization for the box method which requires an overlap size of 0 +template<> +struct CheckOverlapSize +{ + template + static bool isValid(const GridView& gridView) noexcept + { return gridView.comm().size() <= 1 || gridView.overlapSize(0) == 0; } +}; + +//! specialization for the finite element method which requires an overlap size of 0 +//! \note Overloads for bases that require overlap regions can be defined in the future +template<> +struct CheckOverlapSize +{ + template + static bool isValid(const FEBasis& feBasis) noexcept + { return feBasis.gridView().comm().size() <= 1 || feBasis.gridView().overlapSize(0) == 0; } +}; + +// fc staggered requires an overlap of exactly 1 +template<> +struct CheckOverlapSize +{ + template + static bool isValid(const GridView& gridView) noexcept + { return gridView.comm().size() <= 1 || gridView.overlapSize(0) == 1; } +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cvfe/elementboundarytypes.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cvfe/elementboundarytypes.hh new file mode 100644 index 0000000..8c53f86 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cvfe/elementboundarytypes.hh @@ -0,0 +1,113 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup CVFEDiscretization + * \brief Boundary types gathered on an element + */ +#ifndef DUMUX_CVFE_ELEMENT_BOUNDARY_TYPES_HH +#define DUMUX_CVFE_ELEMENT_BOUNDARY_TYPES_HH + +#include +#include + +#include + +namespace Dumux { + +/*! + * \ingroup CVFEDiscretization + * \brief This class stores an array of BoundaryTypes objects + */ +template +class CVFEElementBoundaryTypes +{ +public: + using BoundaryTypes = BTypes; + + /*! + * \brief Update the boundary types for all vertices of an element. + * + * \param problem The problem object which needs to be simulated + * \param element The DUNE Codim<0> entity for which the boundary + * types should be collected + * \param fvGeometry The element's finite volume geometry + */ + template + void update(const Problem& problem, + const typename FVElementGeometry::Element& element, + const FVElementGeometry& fvGeometry) + { + bcTypes_.resize(fvGeometry.numScv()); + + hasDirichlet_ = false; + hasNeumann_ = false; + + for (const auto& scv : scvs(fvGeometry)) + { + const auto scvIdxLocal = scv.localDofIndex(); + bcTypes_[scvIdxLocal].reset(); + + if (fvGeometry.gridGeometry().dofOnBoundary(scv.dofIndex())) + { + bcTypes_[scvIdxLocal] = problem.boundaryTypes(element, scv); + hasDirichlet_ = hasDirichlet_ || bcTypes_[scvIdxLocal].hasDirichlet(); + hasNeumann_ = hasNeumann_ || bcTypes_[scvIdxLocal].hasNeumann(); + } + } + } + + /*! + * \brief Returns whether the element has a vertex which contains + * a Dirichlet value. + */ + bool hasDirichlet() const + { return hasDirichlet_; } + + /*! + * \brief Returns whether the element potentially features a + * Neumann boundary segment. + */ + bool hasNeumann() const + { return hasNeumann_; } + + /* + * \brief Access operator + * \return BoundaryTypes + * \note yields undefined behaviour of the scv is not on the boundary + */ + template + const BoundaryTypes& get(const FVElementGeometry& fvGeometry, const typename FVElementGeometry::SubControlVolumeFace& scvf) const + { + assert(scvf.boundary()); + const auto localDofIdx = fvGeometry.scv(scvf.insideScvIdx()).localDofIndex(); + assert(localDofIdx < bcTypes_.size()); + return bcTypes_[localDofIdx]; + } + + /* + * \brief Access operator + * \return BoundaryTypes + * \note yields undefined behaviour of the scv is not on the boundary + */ + template + const BoundaryTypes& get(const FVElementGeometry&, const typename FVElementGeometry::SubControlVolume& scv) const + { + const auto localDofIdx = scv.localDofIndex(); + assert(localDofIdx < bcTypes_.size()); + return bcTypes_[localDofIdx]; + } + +private: + std::vector bcTypes_; + bool hasDirichlet_ = false; + bool hasNeumann_ = false; +}; + +} // namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cvfe/elementfluxvariablescache.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cvfe/elementfluxvariablescache.hh new file mode 100644 index 0000000..e15c83e --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cvfe/elementfluxvariablescache.hh @@ -0,0 +1,263 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup CVFEDiscretization + * \brief Global flux variable cache + */ +#ifndef DUMUX_DISCRETIZATION_CVFE_ELEMENT_FLUXVARSCACHE_HH +#define DUMUX_DISCRETIZATION_CVFE_ELEMENT_FLUXVARSCACHE_HH + +#include +#include +#include + +namespace Dumux { + +/*! + * \ingroup CVFEDiscretization + * \brief The flux variables caches for an element + * \note The class is specialized for a version with and without caching + * If grid caching is enabled the flux caches are stored for the whole gridview in the corresponding + * GridFluxVariablesCache which is memory intensive but faster. For caching disabled the + * flux caches are locally computed for each element whenever needed. + */ +template +class CVFEElementFluxVariablesCache +{}; + +/*! + * \ingroup CVFEDiscretization + * \brief The flux variables caches for an element with caching enabled + */ +template +class CVFEElementFluxVariablesCache +{ +public: + //! export the type of the grid flux variables cache + using GridFluxVariablesCache = GFVC; + + //! export the type of the flux variables cache + using FluxVariablesCache = typename GFVC::FluxVariablesCache; + + CVFEElementFluxVariablesCache(const GridFluxVariablesCache& global) + : gridFluxVarsCachePtr_(&global) {} + + // Function is called by the BoxLocalJacobian prior to flux calculations on the element. + // We assume the FVGeometries to be bound at this point + template + void bind(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars) & + { bindElement(element, fvGeometry, elemVolVars); } + + /*! + * \brief bind the local view (r-value overload) + * This overload is called when an instance of this class is a temporary in the usage context + * This allows a usage like this: `const auto view = localView(...).bind(element);` + */ + template + CVFEElementFluxVariablesCache bind(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars) && + { + this->bind(element, fvGeometry, elemVolVars); + return std::move(*this); + } + + template + void bindElement(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars) & + { eIdx_ = fvGeometry.gridGeometry().elementMapper().index(element); } + + /*! + * \brief bind the local view (r-value overload) + * This overload is called when an instance of this class is a temporary in the usage context + * This allows a usage like this: `const auto view = localView(...).bind(element);` + */ + template + CVFEElementFluxVariablesCache bindElement(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars) && + { + this->bindElement(element, fvGeometry, elemVolVars); + return std::move(*this); + } + + template + void bindScvf(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const typename FVElementGeometry::SubControlVolumeFace& scvf) & + { bindElement(element, fvGeometry, elemVolVars); } + + /*! + * \brief bind the local view (r-value overload) + * This overload is called when an instance of this class is a temporary in the usage context + * This allows a usage like this: `const auto view = localView(...).bind(element);` + */ + template + CVFEElementFluxVariablesCache bindScvf(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const typename FVElementGeometry::SubControlVolumeFace& scvf) && + { + this->bindScvf(element, fvGeometry, elemVolVars, scvf); + return std::move(*this); + } + + //! Specialization for the global caching being enabled - do nothing here + template + void update(const typename FVElementGeometry::Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars) {} + + // access operator + template + const FluxVariablesCache& operator [](const SubControlVolumeFace& scvf) const + { return gridFluxVarsCache().cache(eIdx_, scvf.index()); } + + //! The global object we are a restriction of + const GridFluxVariablesCache& gridFluxVarsCache() const + { return *gridFluxVarsCachePtr_; } + +private: + const GridFluxVariablesCache* gridFluxVarsCachePtr_; + std::size_t eIdx_; //!< currently bound element +}; + +/*! + * \ingroup CVFEDiscretization + * \brief The flux variables caches for an element with caching disabled + */ +template +class CVFEElementFluxVariablesCache +{ +public: + //! export the type of the grid flux variables cache + using GridFluxVariablesCache = GFVC; + + //! export the type of the flux variables cache + using FluxVariablesCache = typename GFVC::FluxVariablesCache; + + CVFEElementFluxVariablesCache(const GridFluxVariablesCache& global) + : gridFluxVarsCachePtr_(&global) {} + + // Function is called by the BoxLocalJacobian prior to flux calculations on the element. + // We assume the FVGeometries to be bound at this point + template + void bind(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars) & + { + bindElement(element, fvGeometry, elemVolVars); + } + + /*! + * \brief bind the local view (r-value overload) + * This overload is called when an instance of this class is a temporary in the usage context + * This allows a usage like this: `const auto view = localView(...).bind(element);` + */ + template + CVFEElementFluxVariablesCache bind(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars) && + { + this->bind(element, fvGeometry, elemVolVars); + return std::move(*this); + } + + template + void bindElement(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars) & + { + // temporary resizing of the cache + fluxVarsCache_.resize(fvGeometry.numScvf()); + for (auto&& scvf : scvfs(fvGeometry)) + (*this)[scvf].update(gridFluxVarsCache().problem(), element, fvGeometry, elemVolVars, scvf); + } + + /*! + * \brief bind the local view (r-value overload) + * This overload is called when an instance of this class is a temporary in the usage context + * This allows a usage like this: `const auto view = localView(...).bind(element);` + */ + template + CVFEElementFluxVariablesCache bindElement(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars) && + { + this->bindElement(element, fvGeometry, elemVolVars); + return std::move(*this); + } + + template + void bindScvf(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const typename FVElementGeometry::SubControlVolumeFace& scvf) & + { + fluxVarsCache_.resize(fvGeometry.numScvf()); + (*this)[scvf].update(gridFluxVarsCache().problem(), element, fvGeometry, elemVolVars, scvf); + } + + /*! + * \brief bind the local view (r-value overload) + * This overload is called when an instance of this class is a temporary in the usage context + * This allows a usage like this: `const auto view = localView(...).bind(element);` + */ + template + CVFEElementFluxVariablesCache bindScvf(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const typename FVElementGeometry::SubControlVolumeFace& scvf) && + { + this->bindScvf(element, fvGeometry, elemVolVars, scvf); + return std::move(*this); + } + + /*! + * \brief Update the caches if the volume variables have changed and the cache is solution-dependent + * \note Results in undefined behaviour if called before bind() or with a different element + */ + template + void update(const typename FVElementGeometry::Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars) + { + if constexpr (FluxVariablesCache::isSolDependent) + { + fluxVarsCache_.resize(fvGeometry.numScvf()); + for (const auto& scvf : scvfs(fvGeometry)) + (*this)[scvf].update(gridFluxVarsCache().problem(), element, fvGeometry, elemVolVars, scvf); + } + } + + // access operator + template + const FluxVariablesCache& operator [](const SubControlVolumeFace& scvf) const + { return fluxVarsCache_[scvf.index()]; } + + // access operator + template + FluxVariablesCache& operator [](const SubControlVolumeFace& scvf) + { return fluxVarsCache_[scvf.index()]; } + + //! The global object we are a restriction of + const GridFluxVariablesCache& gridFluxVarsCache() const + { return *gridFluxVarsCachePtr_; } + +private: + const GridFluxVariablesCache* gridFluxVarsCachePtr_; + std::vector fluxVarsCache_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cvfe/elementsolution.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cvfe/elementsolution.hh new file mode 100644 index 0000000..7d27229 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cvfe/elementsolution.hh @@ -0,0 +1,139 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup CVFEDiscretization + * \brief The local element solution class for control-volume finite element methods + */ +#ifndef DUMUX_CVFE_ELEMENT_SOLUTION_HH +#define DUMUX_CVFE_ELEMENT_SOLUTION_HH + +#include +#include +#include +#include + +namespace Dumux { + +/*! + * \ingroup CVFEDiscretization + * \brief The element solution vector + */ +template +class CVFEElementSolution +{ + using GridGeometry = typename FVElementGeometry::GridGeometry; + using GridView = typename GridGeometry::GridView; + using Element = typename GridView::template Codim<0>::Entity; + + static constexpr int dim = GridView::dimension; + + // Dofs are located at corners and in the element center + static constexpr int numCubeDofs = Detail::LocalDofTraits< + GridView, typename GridGeometry::DiscretizationMethod + >::numCubeElementDofs; + +public: + //! export the primary variables type + using PrimaryVariables = PV; + + //! Default constructor + CVFEElementSolution() = default; + + //! Constructor with element and solution and grid geometry + template + CVFEElementSolution(const Element& element, const SolutionVector& sol, + const GridGeometry& gridGeometry) + { + update(element, sol, gridGeometry); + } + + //! Constructor with element and elemVolVars and fvGeometry + template + CVFEElementSolution(const Element& element, const ElementVolumeVariables& elemVolVars, + const FVElementGeometry& fvGeometry) + { + priVars_.resize(fvGeometry.numScv()); + for (const auto& scv : scvs(fvGeometry)) + priVars_[scv.localDofIndex()] = elemVolVars[scv].priVars(); + } + + //! extract the element solution from the solution vector using a mapper + template + void update(const Element& element, const SolutionVector& sol, + const GridGeometry& gridGeometry) + { + // TODO: this implementation only works if there is only one dof per codim/entity + // As local-global mappings are provided by the grid geometry + // this logic should maybe become part of the grid geometry. + const auto& localCoeff = gridGeometry.feCache().get(element.type()).localCoefficients(); + priVars_.resize(localCoeff.size()); + for (int localDofIdx = 0; localDofIdx < localCoeff.size(); ++localDofIdx) + { + const auto& localKey = localCoeff.localKey(localDofIdx); + priVars_[localDofIdx] = sol[ + gridGeometry.dofMapper().subIndex( + element, localKey.subEntity(), localKey.codim() + ) + ]; + } + } + + //! extract the element solution from the solution vector using a local fv geometry + template + void update(const Element& element, const SolutionVector& sol, + const FVElementGeometry& fvGeometry) + { + priVars_.resize(fvGeometry.numScv()); + for (const auto& scv : scvs(fvGeometry)) + priVars_[scv.localDofIndex()] = sol[scv.dofIndex()]; + } + + //! return the size of the element solution + std::size_t size() const + { return priVars_.size(); } + + //! bracket operator const access + template + const PrimaryVariables& operator [](IndexType i) const + { return priVars_[i]; } + + //! bracket operator access + template + PrimaryVariables& operator [](IndexType i) + { return priVars_[i]; } + +private: + Dune::ReservedVector priVars_; +}; + +//! Make an element solution for control-volume finite element schemes +template +auto elementSolution(const Element& element, const SolutionVector& sol, const GridGeometry& gg) +-> std::enable_if_t, + CVFEElementSolution()[0])>> + > +{ + using PrimaryVariables = std::decay_t()[0])>; + return CVFEElementSolution(element, sol, gg); +} + +//! Make an element solution for control-volume finite element schemes +template +auto elementSolution(const Element& element, const ElementVolumeVariables& elemVolVars, const FVElementGeometry& gg) +-> std::enable_if_t, + CVFEElementSolution> +{ + using PrimaryVariables = typename ElementVolumeVariables::VolumeVariables::PrimaryVariables; + return CVFEElementSolution(element, elemVolVars, gg); +} + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cvfe/elementvolumevariables.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cvfe/elementvolumevariables.hh new file mode 100644 index 0000000..d2e5957 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cvfe/elementvolumevariables.hh @@ -0,0 +1,211 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup CVFEDiscretization + * \brief The local volume variables class + */ +#ifndef DUMUX_DISCRETIZATION_CVFE_ELEMENT_VOLUMEVARIABLES_HH +#define DUMUX_DISCRETIZATION_CVFE_ELEMENT_VOLUMEVARIABLES_HH + +#include +#include +#include + +#include + +namespace Dumux { + +/*! + * \ingroup CVFEDiscretization + * \brief The local (stencil) volume variables class for control-volume finite element + * \note The class is specialized for versions with and without caching + * \tparam GVV the grid volume variables type + * \tparam cachingEnabled if the cache is enabled + */ +template +class CVFEElementVolumeVariables; + +/*! + * \ingroup CVFEDiscretization + * \brief The local (stencil) volume variables class for control-volume finite element with caching + * \note the volume variables are stored for the whole grid view in the corresponding GridVolumeVariables class + */ +template +class CVFEElementVolumeVariables +{ +public: + //! export type of the grid volume variables + using GridVolumeVariables = GVV; + + //! export type of the volume variables + using VolumeVariables = typename GridVolumeVariables::VolumeVariables; + + //! Constructor + CVFEElementVolumeVariables(const GridVolumeVariables& gridVolVars) + : gridVolVarsPtr_(&gridVolVars) {} + + const VolumeVariables& operator [](std::size_t scvIdx) const + { return gridVolVars().volVars(eIdx_, scvIdx); } + + template::value, int> = 0> + const VolumeVariables& operator [](const SubControlVolume& scv) const + { return gridVolVars().volVars(eIdx_, scv.indexInElement()); } + + /*! + * \brief bind the local view (r-value overload) + * This overload is called when an instance of this class is a temporary in the usage context + * This allows a usage like this: `const auto view = localView(...).bind(element);` + */ + template + CVFEElementVolumeVariables bind(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const SolutionVector& sol) && + { + this->bindElement(element, fvGeometry, sol); + return std::move(*this); + } + + // For compatibility reasons with the case of not storing the vol vars. + // function to be called before assembling an element, preparing the vol vars within the stencil + template + void bind(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const SolutionVector& sol) & + { + bindElement(element, fvGeometry, sol); + } + + /*! + * \brief bind the local view (r-value overload) + * This overload is called when an instance of this class is a temporary in the usage context + * This allows a usage like this: `const auto view = localView(...).bind(element);` + */ + template + CVFEElementVolumeVariables bindElement(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const SolutionVector& sol) && + { + this->bindElement(element, fvGeometry, sol); + return std::move(*this); + } + + // function to prepare the vol vars within the element + template + void bindElement(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const SolutionVector& sol) & + { + eIdx_ = fvGeometry.gridGeometry().elementMapper().index(element); + } + + //! The global volume variables object we are a restriction of + const GridVolumeVariables& gridVolVars() const + { return *gridVolVarsPtr_; } + +private: + const GridVolumeVariables* gridVolVarsPtr_; + std::size_t eIdx_; +}; + + +/*! + * \ingroup CVFEDiscretization + * \brief The local (stencil) volume variables class for control-volume finite element without caching + */ +template +class CVFEElementVolumeVariables +{ +public: + //! export type of the grid volume variables + using GridVolumeVariables = GVV; + + //! export type of the volume variables + using VolumeVariables = typename GridVolumeVariables::VolumeVariables; + + //! Constructor + CVFEElementVolumeVariables(const GridVolumeVariables& gridVolVars) + : gridVolVarsPtr_(&gridVolVars) {} + + /*! + * \brief bind the local view (r-value overload) + * This overload is called when an instance of this class is a temporary in the usage context + * This allows a usage like this: `const auto view = localView(...).bind(element);` + */ + template + CVFEElementVolumeVariables bind(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const SolutionVector& sol) && + { + this->bindElement(element, fvGeometry, sol); + return std::move(*this); + } + + // specialization for control-volume finite element, simply forwards to the bindElement method + template + void bind(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const SolutionVector& sol) & + { + bindElement(element, fvGeometry, sol); + } + + /*! + * \brief bind the local view (r-value overload) + * This overload is called when an instance of this class is a temporary in the usage context + * This allows a usage like this: `const auto view = localView(...).bind(element);` + */ + template + CVFEElementVolumeVariables bindElement(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const SolutionVector& sol) && + { + this->bindElement(element, fvGeometry, sol); + return std::move(*this); + } + + // specialization for control-volume finite element + template + void bindElement(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, + const FVElementGeometry& fvGeometry, + const SolutionVector& sol) & + { + // get the solution at the dofs of the element + auto elemSol = elementSolution(element, sol, fvGeometry.gridGeometry()); + + // resize volume variables to the required size + volumeVariables_.resize(fvGeometry.numScv()); + for (auto&& scv : scvs(fvGeometry)) + volumeVariables_[scv.indexInElement()].update(elemSol, gridVolVars().problem(), element, scv); + } + + const VolumeVariables& operator [](std::size_t scvIdx) const + { return volumeVariables_[scvIdx]; } + + VolumeVariables& operator [](std::size_t scvIdx) + { return volumeVariables_[scvIdx]; } + + template::value, int> = 0> + const VolumeVariables& operator [](const SubControlVolume& scv) const + { return volumeVariables_[scv.indexInElement()]; } + + template::value, int> = 0> + VolumeVariables& operator [](const SubControlVolume& scv) + { return volumeVariables_[scv.indexInElement()]; } + + //! The global volume variables object we are a restriction of + const GridVolumeVariables& gridVolVars() const + { return *gridVolVarsPtr_; } + +private: + const GridVolumeVariables* gridVolVarsPtr_; + std::vector volumeVariables_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cvfe/fluxvariablescache.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cvfe/fluxvariablescache.hh new file mode 100644 index 0000000..edc59b0 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cvfe/fluxvariablescache.hh @@ -0,0 +1,104 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup CVFEDiscretization + * \brief Flux variables cache class for control-volume finite element schemes + */ +#ifndef DUMUX_DISCRETIZATION_CVFE_FLUXVARIABLES_CACHE_HH +#define DUMUX_DISCRETIZATION_CVFE_FLUXVARIABLES_CACHE_HH + +#include + +namespace Dumux { + +/*! + * \ingroup CVFEDiscretization + * \brief Flux variables cache class for control-volume finite element schemes. + * For control-volume finite element schemes, this class does not contain any physics-/process-dependent + * data. It solely stores disretization-/grid-related data. + */ +template< class Scalar, class GridGeometry > +class CVFEFluxVariablesCache +{ + using GridView = typename GridGeometry::GridView; + using Element = typename GridView::template Codim<0>::Entity; + using GlobalPosition = typename Element::Geometry::GlobalCoordinate; + + using FVElementGeometry = typename GridGeometry::LocalView; + using SubControlVolumeFace = typename GridGeometry::SubControlVolumeFace; + + static const int dim = GridView::dimension; + static const int dimWorld = GridView::dimensionworld; + + using CoordScalar = typename GridView::ctype; + using FeLocalBasis = typename GridGeometry::FeCache::FiniteElementType::Traits::LocalBasisType; + using ShapeJacobian = typename FeLocalBasis::Traits::JacobianType; + using ShapeValue = typename Dune::FieldVector; + using JacobianInverseTransposed = typename Element::Geometry::JacobianInverseTransposed; + +public: + //! whether the cache needs an update when the solution changes + static bool constexpr isSolDependent = false; + + //! update the cache for an scvf + template< class Problem, class ElementVolumeVariables > + void update(const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const SubControlVolumeFace& scvf) + { + update(problem, element, fvGeometry, elemVolVars, scvf.ipGlobal()); + } + + //! update the cache for a given global position + template< class Problem, class ElementVolumeVariables > + void update(const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const GlobalPosition& globalPos) + { + const auto geometry = element.geometry(); + const auto& localBasis = fvGeometry.feLocalBasis(); + + // evaluate shape functions and gradients at the integration point + ipGlobal_ = globalPos; + const auto ipLocal = geometry.local(ipGlobal_); + jacInvT_ = geometry.jacobianInverseTransposed(ipLocal); + localBasis.evaluateJacobian(ipLocal, shapeJacobian_); + localBasis.evaluateFunction(ipLocal, shapeValues_); // shape values for rho + + // compute the gradN at for every scv/dof + gradN_.resize(fvGeometry.numScv()); + for (const auto& scv: scvs(fvGeometry)) + jacInvT_.mv(shapeJacobian_[scv.localDofIndex()][0], gradN_[scv.indexInElement()]); + } + + //! returns the global position for which this cache has been updated + const GlobalPosition& ipGlobal() const { return ipGlobal_; } + //! returns the shape function gradients in local coordinates at the integration point + const std::vector& shapeJacobian() const { return shapeJacobian_; } + //! returns the shape function values at the integration point + const std::vector& shapeValues() const { return shapeValues_; } + //! returns inverse transposed jacobian at the integration point + const JacobianInverseTransposed& jacInvT() const { return jacInvT_; } + //! returns the shape function gradients in global coordinates at the integration point + const GlobalPosition& gradN(unsigned int scvIdxInElement) const { return gradN_[scvIdxInElement]; } + +private: + GlobalPosition ipGlobal_; + std::vector gradN_; + std::vector shapeJacobian_; + std::vector shapeValues_; + JacobianInverseTransposed jacInvT_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cvfe/gridfluxvariablescache.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cvfe/gridfluxvariablescache.hh new file mode 100644 index 0000000..5576855 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cvfe/gridfluxvariablescache.hh @@ -0,0 +1,164 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup CVFEDiscretization + * \brief Global flux variable cache + */ +#ifndef DUMUX_DISCRETIZATION_CVFE_GRID_FLUXVARSCACHE_HH +#define DUMUX_DISCRETIZATION_CVFE_GRID_FLUXVARSCACHE_HH + +#include + +// make the local view function available whenever we use this class +#include +#include + +namespace Dumux { + +/*! + * \ingroup CVFEDiscretization + * \brief Flux variable caches traits + */ +template +struct CVFEDefaultGridFVCTraits +{ + using Problem = P; + using FluxVariablesCache = FVC; + + template + using LocalView = CVFEElementFluxVariablesCache; +}; + +/*! + * \ingroup CVFEDiscretization + * \brief Flux variable caches on a gridview + * \note The class is specialized for a version with and without grid caching + */ +template > +class CVFEGridFluxVariablesCache; + +/*! + * \ingroup CVFEDiscretization + * \brief Flux variable caches on a gridview with grid caching enabled + * \note The flux caches of the gridview are stored which is memory intensive but faster + */ +template +class CVFEGridFluxVariablesCache +{ + using Problem = typename Traits::Problem; + using ThisType = CVFEGridFluxVariablesCache; + +public: + //! export the flux variable cache type + using FluxVariablesCache = typename Traits::FluxVariablesCache; + + //! make it possible to query if caching is enabled + static constexpr bool cachingEnabled = true; + + //! export the type of the local view + using LocalView = typename Traits::template LocalView; + + CVFEGridFluxVariablesCache(const Problem& problem) : problemPtr_(&problem) {} + + template + void update(const GridGeometry& gridGeometry, + const GridVolumeVariables& gridVolVars, + const SolutionVector& sol, + bool forceUpdate = false) + { + // Here, we do not do anything unless it is a forced update + if (forceUpdate) + { + fluxVarsCache_.resize(gridGeometry.gridView().size(0)); + Dumux::parallelFor(gridGeometry.gridView().size(0), [&, &problem = problem()](const std::size_t eIdx) + { + // Prepare the geometries within the elements of the stencil + const auto element = gridGeometry.element(eIdx); + const auto fvGeometry = localView(gridGeometry).bind(element); + const auto elemVolVars = localView(gridVolVars).bind(element, fvGeometry, sol); + + // only update shape functions for fluxes if update is forced + fluxVarsCache_[eIdx].resize(fvGeometry.numScvf()); + for (const auto& scvf : scvfs(fvGeometry)) + cache(eIdx, scvf.index()).update(problem, element, fvGeometry, elemVolVars, scvf); + }); + } + } + + template + void updateElement(const typename FVElementGeometry::Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars) + { + if constexpr (FluxVariablesCache::isSolDependent) + { + const auto eIdx = fvGeometry.gridGeometry().elementMapper().index(element); + fluxVarsCache_[eIdx].resize(fvGeometry.numScvf()); + for (const auto& scvf : scvfs(fvGeometry)) + cache(eIdx, scvf.index()).update(problem(), element, fvGeometry, elemVolVars, scvf); + } + } + + const Problem& problem() const + { return *problemPtr_; } + + // access operator + const FluxVariablesCache& cache(std::size_t eIdx, std::size_t scvfIdx) const + { return fluxVarsCache_[eIdx][scvfIdx]; } + + // access operator + FluxVariablesCache& cache(std::size_t eIdx, std::size_t scvfIdx) + { return fluxVarsCache_[eIdx][scvfIdx]; } + +private: + // currently bound element + const Problem* problemPtr_; + std::vector> fluxVarsCache_; +}; + +/*! + * \ingroup CVFEDiscretization + * \brief Flux variable caches on a gridview with grid caching disabled + */ +template +class CVFEGridFluxVariablesCache +{ + using Problem = typename Traits::Problem; + using ThisType = CVFEGridFluxVariablesCache; + +public: + //! export the flux variable cache type + using FluxVariablesCache = typename Traits::FluxVariablesCache; + + //! make it possible to query if caching is enabled + static constexpr bool cachingEnabled = false; + + //! export the type of the local view + using LocalView = typename Traits::template LocalView; + + CVFEGridFluxVariablesCache(const Problem& problem) : problemPtr_(&problem) {} + + template + void update(const GridGeometry& gridGeometry, + const GridVolumeVariables& gridVolVars, + const SolutionVector& sol, + bool forceUpdate = false) {} + + const Problem& problem() const + { return *problemPtr_; } + +private: + const Problem* problemPtr_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cvfe/gridvolumevariables.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cvfe/gridvolumevariables.hh new file mode 100644 index 0000000..02c3408 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/cvfe/gridvolumevariables.hh @@ -0,0 +1,140 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup CVFEDiscretization + * \brief The grid volume variables class for control-volume finite element methods + */ +#ifndef DUMUX_DISCRETIZATION_CVFE_GRID_VOLUMEVARIABLES_HH +#define DUMUX_DISCRETIZATION_CVFE_GRID_VOLUMEVARIABLES_HH + +#include +#include + +#include + +// make the local view function available whenever we use this class +#include +#include +#include + +namespace Dumux { + +template +struct CVFEDefaultGridVolumeVariablesTraits +{ + using Problem = P; + using VolumeVariables = VV; + + template + using LocalView = CVFEElementVolumeVariables; +}; + +/*! + * \ingroup CVFEDiscretization + * \brief Base class for the grid volume variables + */ +template +class CVFEGridVolumeVariables; + +// specialization in case of storing the volume variables +template +class CVFEGridVolumeVariables +{ + using ThisType = CVFEGridVolumeVariables; + +public: + //! export the problem type + using Problem = typename Traits::Problem; + + //! export the volume variables type + using VolumeVariables = typename Traits::VolumeVariables; + + //! make it possible to query if caching is enabled + static constexpr bool cachingEnabled = true; + + //! export the type of the local view + using LocalView = typename Traits::template LocalView; + + CVFEGridVolumeVariables(const Problem& problem) : problemPtr_(&problem) {} + + template + void update(const GridGeometry& gridGeometry, const SolutionVector& sol) + { + volumeVariables_.resize(gridGeometry.gridView().size(0)); + Dumux::parallelFor(gridGeometry.gridView().size(0), [&, &problem = problem()](const std::size_t eIdx) + { + const auto element = gridGeometry.element(eIdx); + const auto fvGeometry = localView(gridGeometry).bindElement(element); + + // get the element solution + auto elemSol = elementSolution(element, sol, gridGeometry); + + // update the volvars of the element + volumeVariables_[eIdx].resize(fvGeometry.numScv()); + for (const auto& scv : scvs(fvGeometry)) + volumeVariables_[eIdx][scv.indexInElement()].update(elemSol, problem, element, scv); + }); + } + + template::value, int> = 0> + const VolumeVariables& volVars(const SubControlVolume& scv) const + { return volumeVariables_[scv.elementIndex()][scv.indexInElement()]; } + + template::value, int> = 0> + VolumeVariables& volVars(const SubControlVolume& scv) + { return volumeVariables_[scv.elementIndex()][scv.indexInElement()]; } + + const VolumeVariables& volVars(const std::size_t eIdx, const std::size_t scvIdx) const + { return volumeVariables_[eIdx][scvIdx]; } + + VolumeVariables& volVars(const std::size_t eIdx, const std::size_t scvIdx) + { return volumeVariables_[eIdx][scvIdx]; } + + const Problem& problem() const + { return *problemPtr_; } + +private: + const Problem* problemPtr_; + std::vector> volumeVariables_; +}; + + +// Specialization when the current volume variables are not stored +template +class CVFEGridVolumeVariables +{ + using ThisType = CVFEGridVolumeVariables; + +public: + //! export the problem type + using Problem = typename Traits::Problem; + + //! export the volume variables type + using VolumeVariables = typename Traits::VolumeVariables; + + //! make it possible to query if caching is enabled + static constexpr bool cachingEnabled = false; + + //! export the type of the local view + using LocalView = typename Traits::template LocalView; + + CVFEGridVolumeVariables(const Problem& problem) : problemPtr_(&problem) {} + + template + void update(const GridGeometry& gridGeometry, const SolutionVector& sol) {} + + const Problem& problem() const + { return *problemPtr_;} + +private: + const Problem* problemPtr_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/defaultlocaloperator.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/defaultlocaloperator.hh new file mode 100644 index 0000000..a72c77a --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/defaultlocaloperator.hh @@ -0,0 +1,31 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightInfo: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Discretization + * \brief The default local operator than can be specialized for each discretization scheme + */ + +#ifndef DUMUX_DISCRETIZATION_DEFAULT_LOCAL_OPERTAOR_HH +#define DUMUX_DISCRETIZATION_DEFAULT_LOCAL_OPERTAOR_HH + +namespace Dumux::Detail { + +template +struct DiscretizationDefaultLocalOperator; + +} // end namespace Dumux::Detail + +namespace Dumux { + +template +using DiscretizationDefaultLocalOperator + = typename Detail::DiscretizationDefaultLocalOperator::type; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/elementsolution.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/elementsolution.hh new file mode 100644 index 0000000..a3f2df7 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/elementsolution.hh @@ -0,0 +1,25 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Discretization + * \brief Element solution classes and factory functions + */ +#ifndef DUMUX_DISCRETIZATION_ELEMENT_SOLUTION_HH +#define DUMUX_DISCRETIZATION_ELEMENT_SOLUTION_HH + +#include +#include +#include + +namespace Dumux { + +struct EmptyElementSolution {}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/evalgradients.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/evalgradients.hh new file mode 100644 index 0000000..4c6869b --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/evalgradients.hh @@ -0,0 +1,150 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Discretization + * \brief free functions for the evaluation of primary variable gradients inside elements. + */ +#ifndef DUMUX_DISCRETIZATION_EVAL_GRADIENTS_HH +#define DUMUX_DISCRETIZATION_EVAL_GRADIENTS_HH + +#include + +#include +#include +#include +#include + +#include "evalsolution.hh" + +namespace Dumux { + +// some implementation details +namespace Detail{ +/*! + * \brief Evaluates the gradient of a control-volume finite element solution to a given global position. + * \ingroup Discretization + * + * \param element The element + * \param geometry The element geometry + * \param gridGeometry The finite volume grid geometry + * \param elemSol The primary variables at the dofs of the element + * \param globalPos The global position + * \param ignoreState If true, the state of primary variables is ignored + * + * \return Dune::FieldVector with as many entries as dimension of + * the PrimaryVariables object (i.e. numEq). Each entry is + * a GlobalCoordinate object holding the priVar gradient. + */ +template +auto evalCVFEGradients(const Element& element, + const typename Element::Geometry& geometry, + const GridGeometry& gridGeometry, + const CVFEElemSol& elemSol, + const typename Element::Geometry::GlobalCoordinate& globalPos, + bool ignoreState = false) +{ + // determine if all states are the same at all vertices + using HasState = decltype(isValid(Detail::hasState())(elemSol[0])); + bool allStatesEqual = ignoreState || Detail::allStatesEqual(elemSol, HasState{}); + + if (allStatesEqual) + { + using GlobalPosition = typename Element::Geometry::GlobalCoordinate; + + // evaluate gradients using the local finite element basis + const auto& localBasis = gridGeometry.feCache().get(geometry.type()).localBasis(); + + // evaluate the shape function gradients at the scv center + using ShapeJacobian = typename std::decay_t< decltype(localBasis) >::Traits::JacobianType; + const auto localPos = geometry.local(globalPos); + std::vector< ShapeJacobian > shapeJacobian; + localBasis.evaluateJacobian(localPos, shapeJacobian); + + // the inverse transposed of the jacobian matrix + const auto jacInvT = geometry.jacobianInverseTransposed(localPos); + + // interpolate the gradients + Dune::FieldVector result( GlobalPosition(0.0) ); + for (int i = 0; i < shapeJacobian.size(); ++i) + { + // the global shape function gradient + GlobalPosition gradN; + jacInvT.mv(shapeJacobian[i][0], gradN); + + // add gradient to global privar gradients + for (unsigned int pvIdx = 0; pvIdx < CVFEElemSol::PrimaryVariables::dimension; ++pvIdx) + { + GlobalPosition tmp(gradN); + tmp *= elemSol[i][pvIdx]; + result[pvIdx] += tmp; + } + } + + return result; + } + else + { + DUNE_THROW(Dune::NotImplemented, "Element dofs have different phase states. Enforce calculation by setting ignoreState to true."); + } +} + +} // end namespace Detail + +/*! + * \brief Evaluates the gradient of a given CVFE element solution to a given global position. + * \ingroup Discretization + * + * \param element The element + * \param geometry The element geometry + * \param gridGeometry The finite volume grid geometry + * \param elemSol The primary variables at the dofs of the element + * \param globalPos The global position + * \param ignoreState If true, the state of primary variables is ignored + */ +template +auto evalGradients(const Element& element, + const typename Element::Geometry& geometry, + const typename FVElementGeometry::GridGeometry& gridGeometry, + const CVFEElementSolution& elemSol, + const typename Element::Geometry::GlobalCoordinate& globalPos, + bool ignoreState = false) +{ + return Detail::evalCVFEGradients(element, geometry, gridGeometry, elemSol, globalPos, ignoreState); +} + +/*! + * \ingroup Discretization + * \brief Evaluates the gradient of a given CCElementSolution to a given global position. + * This function is only here for (compilation) compatibility reasons with the box scheme. + * The solution within the control volumes is constant and thus gradients are zero. + * One can compute gradients towards the sub-control volume faces after reconstructing + * the solution on the faces. + * + * \param element The element + * \param geometry The element geometry + * \param gridGeometry The finite volume grid geometry + * \param elemSol The primary variables at the dofs of the element + * \param globalPos The global position + * \throws Dune::NotImplemented + * + * \return Dune::FieldVector with as many entries as dimension of + * the PrimaryVariables object (i.e. numEq). Each entry is + * a GlobalCoordinate object holding the priVar gradient. + */ +template +Dune::FieldVector +evalGradients(const Element& element, + const typename Element::Geometry& geometry, + const typename FVElementGeometry::GridGeometry& gridGeometry, + const CCElementSolution& elemSol, + const typename Element::Geometry::GlobalCoordinate& globalPos) +{ DUNE_THROW(Dune::NotImplemented, "General gradient evaluation for cell-centered methods"); } + +} // namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/evalsolution.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/evalsolution.hh new file mode 100644 index 0000000..b5f99c7 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/evalsolution.hh @@ -0,0 +1,285 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Discretization + * \brief free functions for the evaluation of primary variables inside elements. + */ +#ifndef DUMUX_DISCRETIZATION_EVAL_SOLUTION_HH +#define DUMUX_DISCRETIZATION_EVAL_SOLUTION_HH + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +namespace Dumux { + +// some implementation details +namespace Detail { + +//! returns true if all states in an element solution are the same +template +bool allStatesEqual(const ElementSolution& elemSol, std::true_type hasState) +{ + // determine if all states are the same at all vertices + auto firstState = elemSol[0].state(); + for (std::size_t i = 1; i < elemSol.size(); ++i) + if (elemSol[i].state() != firstState) + return false; + + return true; +} + +//! overload if the solution is stateless +template +bool allStatesEqual(const ElementSolution& elemSol, std::false_type hasState) +{ + return true; +} + +//! return the solution at the closest dof +template +auto minDistVertexSol(const Geometry& geometry, const typename Geometry::GlobalCoordinate& globalPos, + const ElementSolution& elemSol) +{ + // calculate the distances from the evaluation point to the vertices + std::vector distances(geometry.corners()); + for (int i = 0; i < geometry.corners(); ++i) + distances[i] = (geometry.corner(i) - globalPos).two_norm2(); + + // determine the minimum distance and corresponding vertex + auto minDistanceIt = std::min_element(distances.begin(), distances.end()); + return elemSol[std::distance(distances.begin(), minDistanceIt)]; +} + +/*! + * \brief Interpolates a given control-volume finite element solution at a given global position. + * Uses the finite element cache of the grid geometry. + * \ingroup Discretization + * + * \return the interpolated primary variables + * \param element The element + * \param geometry The element geometry + * \param gridGeometry The finite volume grid geometry + * \param elemSol The primary variables at the dofs of the element + * \param globalPos The global position + * \param ignoreState If true, the state of primary variables is ignored + */ +template +typename CVFEElemSol::PrimaryVariables +evalCVFESolution(const Element& element, + const typename Element::Geometry& geometry, + const GridGeometry& gridGeometry, + const CVFEElemSol& elemSol, + const typename Element::Geometry::GlobalCoordinate& globalPos, + bool ignoreState = false) +{ + // determine if all states are the same at all vertices + using HasState = decltype(isValid(Detail::hasState())(elemSol[0])); + bool allStatesEqual = ignoreState || Detail::allStatesEqual(elemSol, HasState{}); + + if (allStatesEqual) + { + using Scalar = typename CVFEElemSol::PrimaryVariables::value_type; + + // interpolate the solution + const auto& localBasis = gridGeometry.feCache().get(geometry.type()).localBasis(); + + // evaluate the shape functions at the scv center + const auto localPos = geometry.local(globalPos); + std::vector< Dune::FieldVector > shapeValues; + localBasis.evaluateFunction(localPos, shapeValues); + + typename CVFEElemSol::PrimaryVariables result(0.0); + for (int i = 0; i < shapeValues.size(); ++i) + { + auto value = elemSol[i]; + value *= shapeValues[i][0]; + result += value; + } + + // set an arbitrary state if the model requires a state (models constexpr if) + if constexpr (HasState{}) + if (!ignoreState) + result.setState(elemSol[0].state()); + + return result; + } + else + { + static bool warnedAboutUsingMinDist = false; + if (!warnedAboutUsingMinDist) + { + std::cout << "Warning: Using nearest-neighbor interpolation in evalSolution" + << "\nbecause not all states are equal and ignoreState is false!" + << std::endl; + warnedAboutUsingMinDist = true; + } + + return Detail::minDistVertexSol(geometry, globalPos, elemSol); + } +} + +} // end namespace Detail + +/*! + * \brief Interpolates a given box element solution at a given global position. + * Uses the finite element cache of the grid geometry. + * \ingroup Discretization + * + * \return the interpolated primary variables + * \param element The element + * \param geometry The element geometry + * \param gridGeometry The finite volume grid geometry + * \param elemSol The primary variables at the dofs of the element + * \param globalPos The global position + * \param ignoreState If true, the state of primary variables is ignored + */ +template +PrimaryVariables evalSolution(const Element& element, + const typename Element::Geometry& geometry, + const typename FVElementGeometry::GridGeometry& gridGeometry, + const CVFEElementSolution& elemSol, + const typename Element::Geometry::GlobalCoordinate& globalPos, + bool ignoreState = false) +{ + return Detail::evalCVFESolution(element, geometry, gridGeometry, elemSol, globalPos, ignoreState); +} + +/*! + * \ingroup Discretization + * \brief Interpolates a given box element solution at a given global position. + * + * Overload of the above evalSolution() function without a given gridGeometry. + * The local basis is computed on the fly. + * + * \return the interpolated primary variables + * \param element The element + * \param geometry The element geometry + * \param elemSol The primary variables at the dofs of the element + * \param globalPos The global position + * \param ignoreState If true, the state of primary variables is ignored + */ +template +PrimaryVariables evalSolution(const Element& element, + const typename Element::Geometry& geometry, + const CVFEElementSolution& elemSol, + const typename Element::Geometry::GlobalCoordinate& globalPos, + bool ignoreState = false) +{ + static_assert(FVElementGeometry::GridGeometry::discMethod == DiscretizationMethods::box, "Only implemented for the Box method"); + + // determine if all states are the same at all vertices + using HasState = decltype(isValid(Detail::hasState())(elemSol[0])); + bool allStatesEqual = ignoreState || Detail::allStatesEqual(elemSol, HasState{}); + + if (allStatesEqual) + { + using Scalar = typename PrimaryVariables::value_type; + using CoordScalar = typename Element::Geometry::GlobalCoordinate::value_type; + static constexpr int dim = Element::Geometry::mydimension; + + // The box scheme always uses linear Ansatz functions + using FEFactory = Dune::PQkLocalFiniteElementFactory; + using FiniteElement = typename FEFactory::FiniteElementType; + std::unique_ptr fe(FEFactory::create(geometry.type())); + const auto& localBasis = fe->localBasis(); + + // evaluate the shape functions at the scv center + const auto localPos = geometry.local(globalPos); + using ShapeValue = typename FiniteElement::Traits::LocalBasisType::Traits::RangeType; + std::vector< ShapeValue > shapeValues; + localBasis.evaluateFunction(localPos, shapeValues); + + PrimaryVariables result(0.0); + for (int i = 0; i < geometry.corners(); ++i) + { + auto value = elemSol[i]; + value *= shapeValues[i][0]; + result += value; + } + + // set an arbitrary state if the model requires a state (models constexpr if) + if constexpr (HasState{}) + if (!ignoreState) + result.setState(elemSol[0].state()); + + return result; + } + else + { + static bool warnedAboutUsingMinDist = false; + if (!warnedAboutUsingMinDist) + { + std::cout << "Warning: Using nearest-neighbor interpolation in evalSolution" + << "\nbecause not all states are equal and ignoreState is false!" + << std::endl; + warnedAboutUsingMinDist = true; + } + + return Detail::minDistVertexSol(geometry, globalPos, elemSol); + } +} + +/*! + * \ingroup Discretization + * \brief Interpolates a given cell-centered element solution at a given global position. + * + * \return the primary variables (constant over the element) + * \param element The element + * \param geometry The element geometry + * \param gridGeometry The finite volume grid geometry + * \param elemSol The primary variables at the dofs of the element + * \param globalPos The global position + * \param ignoreState If true, the state of primary variables is ignored + */ +template +PrimaryVariables evalSolution(const Element& element, + const typename Element::Geometry& geometry, + const typename FVElementGeometry::GridGeometry& gridGeometry, + const CCElementSolution& elemSol, + const typename Element::Geometry::GlobalCoordinate& globalPos, + bool ignoreState = false) +{ + return elemSol[0]; +} + +/*! + * \brief Interpolates a given cell-centered element solution at a given global position. + * Overload of the above evalSolution() function without a given gridGeometry. + * For compatibility reasons with the box scheme. + * \ingroup Discretization + * + * \return the primary variables (constant over the element) + * \param element The element + * \param geometry The element geometry + * \param elemSol The primary variables at the dofs of the element + * \param globalPos The global position + * \param ignoreState If true, the state of primary variables is ignored + */ +template< class Element, class FVElementGeometry, class PrimaryVariables> +PrimaryVariables evalSolution(const Element& element, + const typename Element::Geometry& geometry, + const CCElementSolution& elemSol, + const typename Element::Geometry::GlobalCoordinate& globalPos, + bool ignoreState = false) +{ + return elemSol[0]; +} + +} // namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/extrusion.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/extrusion.hh new file mode 100644 index 0000000..179b8b5 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/extrusion.hh @@ -0,0 +1,182 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Discretization + * \brief Helper classes to compute the integration elements + * + * Provides area, volume, and integration elements for integration formulas. + * Modifying these quantities is useful to realize extrusions of the computational domain. + */ +#ifndef DUMUX_DISCRETIZATION_EXTRUSION_HH +#define DUMUX_DISCRETIZATION_EXTRUSION_HH + +#include + +namespace Dumux { + +/*! + * \ingroup Discretization + * \brief Default implementation that performs no extrusion (extrusion with identity) + */ +struct NoExtrusion +{ + template + static constexpr auto area(const FVGeo&, const SCVF& scvf) + { return scvf.area(); } + + template + static constexpr auto volume(const FVGeo&, const SCV& scv) + { return scv.volume(); } + + template + static constexpr auto integrationElement(const Geometry& geo, const typename Geometry::LocalCoordinate& x) + { return geo.integrationElement(x); } +}; + +/*! + * \ingroup Discretization + * \brief Rotation symmetric extrusion policy for rotating about an external axis + * \tparam radAx The radial axis perpendicular to the symmetry axis (0 = x, 1 = y) + */ +template +struct RotationalExtrusion +{ + static constexpr int radialAxis = radAx; + + /*! + * \brief Transformed sub-control-volume face area + * \note Mid-point rule integrals are only exact for constants + */ + template + static constexpr auto area(const FVGeo&, const SCVF& scvf) + { + static_assert(int(SCVF::Traits::Geometry::mydimension) == int(SCVF::Traits::Geometry::coorddimension-1), "Area element to be called with a codim-1-entity!"); + static_assert(SCVF::Traits::Geometry::coorddimension <= 2, "Axis rotation only makes sense for geometries up to 2D!"); + static_assert(radialAxis < int(SCVF::Traits::Geometry::coorddimension), "Illegal radial axis!"); + + // Guldinus theorem + return scvf.area()*2.0*M_PI*scvf.center()[radialAxis]; + } + + /*! + * \brief Transformed sub-control-volume volume + * \note Mid-point rule integrals are only exact for constants + */ + template + static constexpr auto volume(const FVGeo&, const SCV& scv) + { + static_assert(int(SCV::Traits::Geometry::mydimension) == int(SCV::Traits::Geometry::coorddimension), "Volume element to be called with a codim-0-entity!"); + static_assert(SCV::Traits::Geometry::coorddimension <= 2, "Axis rotation only makes sense for geometries up to 2D!"); + static_assert(radialAxis < int(SCV::Traits::Geometry::coorddimension), "Illegal radial axis!"); + + // Guldinus theorem + return scv.volume()*2.0*M_PI*scv.center()[radialAxis]; + } + + /*! + * \brief Integration element for quadrature rules on the reference element + */ + template + static constexpr auto integrationElement(const Geometry& geo, const typename Geometry::LocalCoordinate& x) + { + static_assert(Geometry::coorddimension <= 2, "Axis rotation only makes sense for geometries up to 2D!"); + static_assert(radialAxis < int(Geometry::coorddimension), "Illegal radial axis!"); + + // Multiply with the polar extrusion factor (2*pi) and the determinant of the transformation Jacobian (radius) + return geo.integrationElement(x)*2.0*M_PI*geo.global(x)[radialAxis]; + } +}; + +/*! + * \ingroup Discretization + * \brief Rotation symmetric extrusion policy for spherical rotation + */ +struct SphericalExtrusion +{ + /*! + * \brief Transformed sub-control-volume face area + * \note Mid-point rule integrals are only exact for constants + */ + template + static constexpr auto area(const FVGeo&, const SCVF& scvf) + { + static_assert(int(SCVF::Traits::Geometry::mydimension) == int(SCVF::Traits::Geometry::coorddimension-1), "Area element to be called with a codim-1-entity!"); + static_assert(SCVF::Traits::Geometry::coorddimension == 1, "Spherical rotation only makes sense for 1D geometries!"); + + // sphere surface area + const auto radius = scvf.center()[0]; + return 4.0*M_PI*radius*radius; + } + + /*! + * \brief Transformed sub-control-volume volume + * \note Mid-point rule integrals are only exact for constants + */ + template + static constexpr auto volume(const FVGeo& fvGeometry, const SCV& scv) + { + static_assert(int(SCV::Traits::Geometry::mydimension) == int(SCV::Traits::Geometry::coorddimension), "Volume element to be called with a codim-0-entity!"); + static_assert(SCV::Traits::Geometry::coorddimension == 1, "Spherical rotation only makes sense for 1D geometries!"); + + // subtract two balls + const auto geo = fvGeometry.geometry(scv); + const auto radius0 = geo.corner(0)[0]; + const auto radius1 = geo.corner(1)[0]; + using std::abs; + return 4.0/3.0*M_PI*abs(radius1*radius1*radius1 - radius0*radius0*radius0); + } + + /*! + * \brief Integration element for quadrature rules on the reference element + */ + template + static constexpr auto integrationElement(const Geometry& geo, const typename Geometry::LocalCoordinate& x) + { + static_assert(Geometry::coorddimension == 1, "Spherical rotation only makes sense for 1D geometries!"); + + // Multiply with the constant spherical extrusion factor (int_0^2pi int_0^pi sin(phi) dphi dtheta = 4pi) + // and the remaining (radius-dependent) part of determinant of the transformation Jacobian (radius*radius) + const auto radius = geo.global(x)[0]; + return geo.integrationElement(x)*4.0*M_PI*radius*radius; + } +}; + +/*! + * \brief Traits extracting the public Extrusion type from T + * Defaults to NoExtrusion if no such type is found + */ +template +class Extrusion +{ + template + using E = typename G::Extrusion; +public: + using type = typename Dune::Std::detected_or::type; +}; + +/*! + * \brief Convenience alias for obtaining the extrusion type + */ +template +using Extrusion_t = typename Extrusion::type; + +/*! + * \brief Convenience trait to check whether the extrusion is rotational + */ +template +inline constexpr bool isRotationalExtrusion = false; + +/*! + * \brief Convenience trait to check whether the extrusion is rotational + */ +template +inline constexpr bool isRotationalExtrusion> = true; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/facecentered/diamond/fvelementgeometry.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/facecentered/diamond/fvelementgeometry.hh new file mode 100644 index 0000000..364f410 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/facecentered/diamond/fvelementgeometry.hh @@ -0,0 +1,211 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup DiamondDiscretization + * \copydoc Dumux::FaceCenteredDiamondFVElementGeometry + */ +#ifndef DUMUX_DISCRETIZATION_FACECENTERED_DIAMOND_FV_ELEMENT_GEOMETRY_HH +#define DUMUX_DISCRETIZATION_FACECENTERED_DIAMOND_FV_ELEMENT_GEOMETRY_HH + +#include +#include + +#include +#include + +#include +#include +#include + +namespace Dumux { + +/*! + * \ingroup DiamondDiscretization + * \brief Element-wise grid geometry (local view) + */ +template +class FaceCenteredDiamondFVElementGeometry; + +/*! + * \ingroup DiamondDiscretization + * \brief Element-wise grid geometry (local view) + */ +template +class FaceCenteredDiamondFVElementGeometry +{ + using ThisType = FaceCenteredDiamondFVElementGeometry; + using GridView = typename GG::GridView; + using GridIndexType = typename IndexTraits::GridIndex; + using LocalIndexType = typename IndexTraits::SmallLocalIndex; + using FeLocalBasis = typename GG::FeCache::FiniteElementType::Traits::LocalBasisType; + using GGCache = typename GG::Cache; + using GeometryHelper = typename GGCache::GeometryHelper; + +public: + //! export type of subcontrol volume face + using SubControlVolume = typename GG::SubControlVolume; + using SubControlVolumeFace = typename GG::SubControlVolumeFace; + using Element = typename GridView::template Codim<0>::Entity; + using GridGeometry = GG; + + //! the maximum number of scvs per element + static constexpr std::size_t maxNumElementScvs = 2*GridView::dimension; + + FaceCenteredDiamondFVElementGeometry(const GGCache& ggCache) + : ggCache_(&ggCache) + {} + + //! Get a sub control volume with a local scv index + const SubControlVolume& scv(LocalIndexType scvIdx) const + { return ggCache_->scvs(eIdx_)[scvIdx]; } + + //! Get a sub control volume face with a local scvf index + const SubControlVolumeFace& scvf(LocalIndexType scvfIdx) const + { return ggCache_->scvf(eIdx_)[scvfIdx]; } + + //! iterator range for sub control volumes. Iterates over + //! all scvs of the bound element (not including neighbor scvs) + //! This is a free function found by means of ADL + //! To iterate over all sub control volumes of this FVElementGeometry use + //! for (auto&& scv : scvs(fvGeometry)) + friend inline auto + scvs(const FaceCenteredDiamondFVElementGeometry& fvGeometry) + { + using Iter = typename std::vector::const_iterator; + const auto& s = fvGeometry.ggCache_->scvs(fvGeometry.eIdx_); + return Dune::IteratorRange(s.begin(), s.end()); + } + + //! iterator range for sub control volumes faces. Iterates over + //! all scvfs of the bound element. + //! This is a free function found by means of ADL + //! To iterate over all sub control volume faces of this FVElementGeometry use + //! for (auto&& scvf : scvfs(fvGeometry)) + friend inline auto + scvfs(const FaceCenteredDiamondFVElementGeometry& fvGeometry) + { + using Iter = typename std::vector::const_iterator; + const auto& s = fvGeometry.ggCache_->scvfs(fvGeometry.eIdx_); + return Dune::IteratorRange(s.begin(), s.end()); + } + + //! Get a local finite element basis + const FeLocalBasis& feLocalBasis() const + { + return gridGeometry().feCache().get(element().type()).localBasis(); + } + + //! number of sub control volumes in this fv element geometry + std::size_t numScv() const + { + return ggCache_->scvs(eIdx_).size(); + } + + //! number of sub control volumes in this fv element geometry + std::size_t numScvf() const + { + return ggCache_->scvfs(eIdx_).size(); + } + + //! Returns whether one of the geometry's scvfs lies on a boundary + bool hasBoundaryScvf() const + { return ggCache_->hasBoundaryScvf(eIdx_); } + + /*! + * \brief bind the local view (r-value overload) + * This overload is called when an instance of this class is a temporary in the usage context + * This allows a usage like this: `const auto view = localView(...).bind(element);` + */ + FaceCenteredDiamondFVElementGeometry bind(const Element& element) && + { + this->bindElement(element); + return std::move(*this); + } + + void bind(const Element& element) & + { + this->bindElement(element); + } + + /*! + * \brief bind the local view (r-value overload) + * This overload is called when an instance of this class is a temporary in the usage context + * This allows a usage like this: `const auto view = localView(...).bindElement(element);` + */ + FaceCenteredDiamondFVElementGeometry bindElement(const Element& element) && + { + this->bindElement(element); + return std::move(*this); + } + + //! Bind only element-local + void bindElement(const Element& element) & + { + element_ = element; + eIdx_ = gridGeometry().elementMapper().index(element); + } + + //! Returns true if bind/bindElement has already been called + bool isBound() const + { return static_cast(element_); } + + //! The bound element + const Element& element() const + { return *element_; } + + //! The grid geometry we are a restriction of + const GridGeometry& gridGeometry() const + { return ggCache_->gridGeometry(); } + + //! The bound element index + std::size_t elementIndex() const + { return eIdx_; } + + //! Geometry of a sub control volume + typename SubControlVolume::Traits::Geometry geometry(const SubControlVolume& scv) const + { + assert(isBound()); + const auto geo = element().geometry(); + return { + SubControlVolume::Traits::geometryType(geo.type()), + GeometryHelper(geo).getScvCorners(scv.indexInElement()) + }; + } + + //! Geometry of a sub control volume face + typename SubControlVolumeFace::Traits::Geometry geometry(const SubControlVolumeFace& scvf) const + { + assert(isBound()); + const auto geo = element().geometry(); + if (scvf.boundary()) + { + // use the information that each boundary scvf corresponds to one scv constructed around the same facet + const auto localFacetIndex = scvf.insideScvIdx(); + return { + referenceElement(geo).type(localFacetIndex, 1), + GeometryHelper(geo).getBoundaryScvfCorners(localFacetIndex) + }; + } + else + { + return { + SubControlVolumeFace::Traits::interiorGeometryType(geo.type()), + GeometryHelper(geo).getScvfCorners(scvf.index()) + }; + } + } + +private: + std::optional element_; + GridIndexType eIdx_; + const GGCache* ggCache_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/facecentered/diamond/fvgridgeometry.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/facecentered/diamond/fvgridgeometry.hh new file mode 100644 index 0000000..082428d --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/facecentered/diamond/fvgridgeometry.hh @@ -0,0 +1,400 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup DiamondDiscretization + * \copydoc Dumux::FaceCenteredDiamondFVGridGeometry + */ +#ifndef DUMUX_DISCRETIZATION_FACECENTERED_DIAMOND_FV_GRID_GEOMETRY +#define DUMUX_DISCRETIZATION_FACECENTERED_DIAMOND_FV_GRID_GEOMETRY + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace Dumux { + +namespace Detail { +template +using FaceCenteredDiamondGeometryHelper_t = Dune::Std::detected_or_t< + Dumux::DiamondGeometryHelper, + SpecifiesGeometryHelper, + T +>; +} // end namespace Detail + +/*! + * \ingroup DiamondDiscretization + * \brief The default traits for the face-centered diamond finite volume grid geometry + * Defines the scv and scvf types and the mapper types + * \tparam GridView the grid view type + */ +template +struct FaceCenteredDiamondDefaultGridGeometryTraits : public DefaultMapperTraits +{ + using SubControlVolume = FaceCenteredDiamondSubControlVolume; + using SubControlVolumeFace = FaceCenteredDiamondSubControlVolumeFace; + using DofMapper = Dune::MultipleCodimMultipleGeomTypeMapper; + + template + using LocalView = FaceCenteredDiamondFVElementGeometry; +}; + +/*! + * \ingroup DiamondDiscretization + * \brief Grid geometry for the diamond discretization + */ +template> +class FaceCenteredDiamondFVGridGeometry +: public BaseGridGeometry +{ + using ThisType = FaceCenteredDiamondFVGridGeometry; + using ParentType = BaseGridGeometry; + using GridIndexType = typename IndexTraits::GridIndex; + using LocalIndexType = typename IndexTraits::SmallLocalIndex; + using Element = typename GV::template Codim<0>::Entity; + + using Scalar = typename GV::ctype; + + static const int dim = GV::dimension; + static const int dimWorld = GV::dimensionworld; + + static_assert(dim > 1, "Only implemented for dim > 1"); + +public: + //! export discretization method + using DiscretizationMethod = DiscretizationMethods::FCDiamond; + static constexpr DiscretizationMethod discMethod = DiscretizationMethod{}; + static constexpr bool cachingEnabled = true; + + //! export the type of the fv element geometry (the local view type) + using LocalView = typename Traits::template LocalView; + //! export the type of sub control volume + using SubControlVolume = typename Traits::SubControlVolume; + //! export the type of sub control volume + using SubControlVolumeFace = typename Traits::SubControlVolumeFace; + //! export the grid view type + using GridView = GV; + //! export the dof mapper type + using DofMapper = typename Traits::DofMapper; + //! export the type of extrusion + using Extrusion = Extrusion_t; + //! export the finite element cache type + using FeCache = NonconformingFECache; + //! export whether the grid(geometry) supports periodicity + using SupportsPeriodicity = typename PeriodicGridTraits::SupportsPeriodicity; + + //! Constructor + FaceCenteredDiamondFVGridGeometry(const GridView& gridView, const std::string& paramGroup = "") + : ParentType(gridView) + , dofMapper_(gridView, Dune::mcmgLayout(Dune::Codim<1>{})) + , cache_(*this) + , periodicGridTraits_(this->gridView().grid()) + { + update_(); + } + + //! The total number of sub control volumes + std::size_t numScv() const + { return numScv_; } + + //! The total number of sub control volume faces + std::size_t numScvf() const + { return numScvf_; } + + //! The total number of boundary sub control volume faces + std::size_t numBoundaryScvf() const + { return numBoundaryScvf_; } + + //! the total number of dofs + std::size_t numDofs() const + { return this->gridView().size(1); } + + //! update all fvElementGeometries (call this after grid adaption) + void update(const GridView& gridView) + { + ParentType::update(gridView); + update_(); + } + + //! update all fvElementGeometries (call this after grid adaption) + void update(GridView&& gridView) + { + ParentType::update(std::move(gridView)); + update_(); + } + + //! The finite element cache for creating local FE bases + const FeCache& feCache() const + { return feCache_; } + + //! If a face / d.o.f. is on the boundary + bool dofOnBoundary(GridIndexType dofIdx) const + { return boundaryDofIndices_[dofIdx]; } + + //! Return a reference to the dof mapper + const DofMapper& dofMapper() const + { return dofMapper_; } + + //! If a d.o.f. is on a periodic boundary + bool dofOnPeriodicBoundary(GridIndexType dofIdx) const + { return periodicFaceMap_.count(dofIdx); } + + //! The index of the d.o.f. on the other side of the periodic boundary + GridIndexType periodicallyMappedDof(GridIndexType dofIdx) const + { return periodicFaceMap_.at(dofIdx); } + + //! Returns the map between dofs across periodic boundaries + const std::unordered_map& periodicDofMap() const + { return periodicFaceMap_; } + + //! local view of this object (constructed with the internal cache) + friend inline LocalView localView(const FaceCenteredDiamondFVGridGeometry& gg) + { return { gg.cache_ }; } + +private: + + class FCDiamondGridGeometryCache + { + friend class FaceCenteredDiamondFVGridGeometry; + public: + //! export the geometry helper type + using GeometryHelper = Detail::FaceCenteredDiamondGeometryHelper_t; + + explicit FCDiamondGridGeometryCache(const FaceCenteredDiamondFVGridGeometry& gg) + : gridGeometry_(&gg) + {} + + const FaceCenteredDiamondFVGridGeometry& gridGeometry() const + { return *gridGeometry_; } + + //! Get the global sub control volume indices of an element + const std::vector& scvs(GridIndexType eIdx) const + { return scvs_[eIdx]; } + + //! Get the global sub control volume face indices of an element + const std::vector& scvfs(GridIndexType eIdx) const + { return scvfs_[eIdx]; } + + //! Returns whether one of the geometry's scvfs lies on a boundary + bool hasBoundaryScvf(GridIndexType eIdx) const + { return hasBoundaryScvf_[eIdx]; } + + private: + void clear_() + { + scvs_.clear(); + scvfs_.clear(); + hasBoundaryScvf_.clear(); + } + + std::vector> scvs_; + std::vector> scvfs_; + std::vector hasBoundaryScvf_; + + const FaceCenteredDiamondFVGridGeometry* gridGeometry_; + }; + +public: + //! the cache type (only the caching implementation has this) + //! this alias should only be used by the local view implementation + using Cache = FCDiamondGridGeometryCache; +private: + using GeometryHelper = typename Cache::GeometryHelper; + + //! update all fvElementGeometries + void update_() + { + // clear containers (necessary after grid refinement) + cache_.clear_(); + dofMapper_.update(this->gridView()); + + // determine size of containers + const auto numElements = this->gridView().size(0); + cache_.scvs_.resize(numElements); + cache_.scvfs_.resize(numElements); + cache_.hasBoundaryScvf_.resize(numElements, false); + + boundaryDofIndices_.assign(numDofs(), false); + + numScv_ = 0; + numScvf_ = 0; + numBoundaryScvf_ = 0; + + // Build the scvs and scv faces + for (const auto& element : elements(this->gridView())) + { + const auto eIdx = this->elementMapper().index(element); + + const auto geometry = element.geometry(); + GeometryHelper geometryHelper(geometry); + + // build the scvs + cache_.scvs_[eIdx].reserve(geometryHelper.numScv()); + numScv_ += geometryHelper.numScv(); + for (LocalIndexType localScvIdx = 0; localScvIdx < geometryHelper.numScv(); ++localScvIdx) + { + const auto dofIndex = dofMapper().subIndex(element, localScvIdx, 1); + const auto& corners = geometryHelper.getScvCorners(localScvIdx); + const auto volume = Dumux::convexPolytopeVolume( + SubControlVolume::Traits::geometryType(geometry.type()), + [&](unsigned int i){ return corners[i]; } + ); + + cache_.scvs_[eIdx].emplace_back( + volume, + geometryHelper.facetCenter(localScvIdx), + Dumux::center(corners), + localScvIdx, + eIdx, + dofIndex + ); + } + + // build interior scvfs + LocalIndexType localScvfIdx = 0; + cache_.scvfs_[eIdx].reserve(geometryHelper.numInteriorScvf()); + numScvf_ += geometryHelper.numInteriorScvf(); + for (; localScvfIdx < geometryHelper.numInteriorScvf(); ++localScvfIdx) + { + const auto& corners = geometryHelper.getScvfCorners(localScvfIdx); + const auto& scvPair = geometryHelper.getInsideOutsideScvForScvf(localScvfIdx); + const auto area = Dumux::convexPolytopeVolume( + SubControlVolumeFace::Traits::interiorGeometryType(geometry.type()), + [&](unsigned int i){ return corners[i]; } + ); + + cache_.scvfs_[eIdx].emplace_back( + Dumux::center(corners), + area, + geometryHelper.normal(corners, scvPair), + scvPair, + localScvfIdx + ); + } + + // build boundary scvfs + for (const auto& intersection : intersections(this->gridView(), element)) + { + if (onDomainBoundary_(intersection)) + { + // store information that the face dof is on a boundary + const LocalIndexType localFacetIndex = intersection.indexInInside(); + const auto dofIndex = dofMapper().subIndex(element, localFacetIndex, 1); + boundaryDofIndices_[dofIndex] = true; + + // and that the element has a boundary face + cache_.hasBoundaryScvf_[eIdx] = true; + + // add boundary scvf + const auto geo = intersection.geometry(); + cache_.scvfs_[eIdx].emplace_back( + geo.center(), + geo.volume(), + intersection.centerUnitOuterNormal(), + std::array{{localFacetIndex, localFacetIndex}}, + localScvfIdx, + typename SubControlVolumeFace::Traits::BoundaryFlag{ intersection } + ); + + // increment local and global counters + ++localScvfIdx; + ++numBoundaryScvf_; + ++numScvf_; + } + + // handle periodic boundaries + if (onPeriodicBoundary_(intersection)) + { + const LocalIndexType localFacetIndex = intersection.indexInInside(); + const auto dofIndex = dofMapper().subIndex(element, localFacetIndex, 1); + + this->setPeriodic(); + + const auto& otherElement = intersection.outside(); + + LocalIndexType otherIntersectionLocalIdx = 0; + bool periodicFaceFound = false; + + for (const auto& otherIntersection : intersections(this->gridView(), otherElement)) + { + if (periodicFaceFound) + continue; + + if (Dune::FloatCmp::eq(intersection.centerUnitOuterNormal()*otherIntersection.centerUnitOuterNormal(), -1.0, 1e-7)) + { + const auto periodicDofIdx = dofMapper().subIndex(otherElement, otherIntersectionLocalIdx, 1); + periodicFaceMap_[dofIndex] = periodicDofIdx; + periodicFaceFound = true; + } + + ++otherIntersectionLocalIdx; + } + } + } + } + } + + bool onDomainBoundary_(const typename GridView::Intersection& intersection) const + { + return !intersection.neighbor() && intersection.boundary(); + } + + bool onProcessorBoundary_(const typename GridView::Intersection& intersection) const + { + return !intersection.neighbor() && !intersection.boundary(); + } + + bool onPeriodicBoundary_(const typename GridView::Intersection& intersection) const + { + return periodicGridTraits_.isPeriodic(intersection); + } + + // faces on the boundary + std::vector boundaryDofIndices_; + + DofMapper dofMapper_; + + std::size_t numScv_; + std::size_t numScvf_; + std::size_t numBoundaryScvf_; + + // a map for periodic boundary vertices + std::unordered_map periodicFaceMap_; + + const FeCache feCache_; + + Cache cache_; + + PeriodicGridTraits periodicGridTraits_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/facecentered/diamond/geometryhelper.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/facecentered/diamond/geometryhelper.hh new file mode 100644 index 0000000..020639a --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/facecentered/diamond/geometryhelper.hh @@ -0,0 +1,425 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup DiamondDiscretization + * \copydoc Dumux::DiamondGeometryHelper + */ +#ifndef DUMUX_DISCRETIZATION_FACECENTERED_DIAMOND_GEOMETRY_HELPER_HH +#define DUMUX_DISCRETIZATION_FACECENTERED_DIAMOND_GEOMETRY_HELPER_HH + +#include + +#include +#include +#include +#include + +#include + +namespace Dumux { + +//! Traits for an efficient corner storage for fc diamond method +template +struct FCDiamondMLGeometryTraits : public Dune::MultiLinearGeometryTraits +{ + // we use static vectors to store the corners as we know + // the maximum number of corners in advance (2^dim) + template< int mydim, int cdim > + struct CornerStorage + { + using Type = Dune::ReservedVector< Dune::FieldVector< ct, cdim >, (1<; + }; +}; + +namespace Detail::FCDiamond { + +template +struct ScvCorners; + +template<> +struct ScvCorners +{ + static constexpr Dune::GeometryType type() + { return Dune::GeometryTypes::triangle; } + + using Key = std::pair; // (i, codim) + static constexpr std::array, 3> keys = {{ + { Key{0, 2}, Key{1, 2}, Key{0, 0} }, + { Key{2, 2}, Key{0, 2}, Key{0, 0} }, + { Key{1, 2}, Key{2, 2}, Key{0, 0} } + }}; +}; + +template<> +struct ScvCorners +{ + static constexpr Dune::GeometryType type() + { return Dune::GeometryTypes::triangle; } + + using Key = std::pair; // (i, codim) + static constexpr std::array, 4> keys = {{ + { Key{2, 2}, Key{0, 2}, Key{0, 0} }, + { Key{1, 2}, Key{3, 2}, Key{0, 0} }, + { Key{0, 2}, Key{1, 2}, Key{0, 0} }, + { Key{3, 2}, Key{2, 2}, Key{0, 0} } + }}; +}; + +template<> +struct ScvCorners +{ + static constexpr Dune::GeometryType type() + { return Dune::GeometryTypes::tetrahedron; } + + using Key = std::pair; // (i, codim) + static constexpr std::array, 4> keys = {{ + { Key{0, 3}, Key{1, 3}, Key{2, 3}, Key{0, 0} }, + { Key{1, 3}, Key{0, 3}, Key{3, 3}, Key{0, 0} }, + { Key{0, 3}, Key{2, 3}, Key{3, 3}, Key{0, 0} }, + { Key{2, 3}, Key{1, 3}, Key{3, 3}, Key{0, 0} } + }}; +}; + +template<> +struct ScvCorners +{ + static constexpr Dune::GeometryType type() + { return Dune::GeometryTypes::pyramid; } + + using Key = std::pair; // (i, codim) + static constexpr std::array, 6> keys = {{ + { Key{4, 3}, Key{0, 3}, Key{6, 3}, Key{2, 3}, Key{0, 0} }, + { Key{1, 3}, Key{5, 3}, Key{3, 3}, Key{7, 3}, Key{0, 0} }, + { Key{4, 3}, Key{5, 3}, Key{0, 3}, Key{1, 3}, Key{0, 0} }, + { Key{2, 3}, Key{3, 3}, Key{6, 3}, Key{7, 3}, Key{0, 0} }, + { Key{0, 3}, Key{1, 3}, Key{2, 3}, Key{3, 3}, Key{0, 0} }, + { Key{6, 3}, Key{7, 3}, Key{4, 3}, Key{5, 3}, Key{0, 0} } + }}; +}; + +template +struct ScvfCorners; + +template<> +struct ScvfCorners +{ + static constexpr Dune::GeometryType type() + { return Dune::GeometryTypes::line; } + + using Key = std::pair; // (i, codim) + static constexpr std::array, 3> keys = {{ + { Key{0, 2}, Key{0, 0} }, + { Key{1, 2}, Key{0, 0} }, + { Key{2, 2}, Key{0, 0} } + }}; +}; + +template<> +struct ScvfCorners +{ + static constexpr Dune::GeometryType type() + { return Dune::GeometryTypes::line; } + + using Key = std::pair; // (i, codim) + static constexpr std::array, 4> keys = {{ + { Key{0, 2}, Key{0, 0} }, + { Key{1, 2}, Key{0, 0} }, + { Key{2, 2}, Key{0, 0} }, + { Key{3, 2}, Key{0, 0} } + }}; +}; + +template<> +struct ScvfCorners +{ + static constexpr Dune::GeometryType type() + { return Dune::GeometryTypes::triangle; } + + using Key = std::pair; // (i, codim) + static constexpr std::array, 6> keys = {{ + { Key{0, 3}, Key{1, 3}, Key{0, 0} }, + { Key{2, 3}, Key{0, 3}, Key{0, 0} }, + { Key{1, 3}, Key{2, 3}, Key{0, 0} }, + { Key{0, 3}, Key{3, 3}, Key{0, 0} }, + { Key{1, 3}, Key{3, 3}, Key{0, 0} }, + { Key{2, 3}, Key{3, 3}, Key{0, 0} } + }}; +}; + +template<> +struct ScvfCorners +{ + static constexpr Dune::GeometryType type() + { return Dune::GeometryTypes::triangle; } + + using Key = std::pair; // (i, codim) + static constexpr std::array, 12> keys = {{ + { Key{0, 3}, Key{4, 3}, Key{0, 0} }, + { Key{1, 3}, Key{5, 3}, Key{0, 0} }, + { Key{2, 3}, Key{6, 3}, Key{0, 0} }, + { Key{3, 3}, Key{7, 3}, Key{0, 0} }, + { Key{0, 3}, Key{2, 3}, Key{0, 0} }, + { Key{1, 3}, Key{3, 3}, Key{0, 0} }, + { Key{0, 3}, Key{1, 3}, Key{0, 0} }, + { Key{2, 3}, Key{3, 3}, Key{0, 0} }, + { Key{4, 3}, Key{6, 3}, Key{0, 0} }, + { Key{5, 3}, Key{7, 3}, Key{0, 0} }, + { Key{4, 3}, Key{5, 3}, Key{0, 0} }, + { Key{6, 3}, Key{7, 3}, Key{0, 0} } + }}; +}; + +// convert key array to global corner storage +template +S keyToCornerStorageImpl(const Geo& geo, const KeyArray& key, std::index_sequence) +{ + using Dune::referenceElement; + const auto ref = referenceElement(geo); + // key is a pair of a local sub-entity index (first) and the sub-entity's codim (second) + return { geo.global(ref.position(key[I].first, key[I].second))... }; +} + +// convert key array to global corner storage +template> +S keyToCornerStorage(const Geo& geo, const std::array& key) +{ + return keyToCornerStorageImpl(geo, key, Indices{}); +} + +// boundary corners for the i-th facet +template +S boundaryCornerStorageImpl(const Geo& geo, unsigned int i, std::index_sequence) +{ + using Dune::referenceElement; + const auto ref = referenceElement(geo); + // simply the vertices of the facet i + return { geo.global(ref.position(ref.subEntity(i, 1, ii, Geo::mydimension), Geo::mydimension))... }; +} + +// boundary corners for the i-th facet +template +S boundaryCornerStorage(const Geo& geo, unsigned int i) +{ + return boundaryCornerStorageImpl(geo, i, std::make_index_sequence{}); +} + +template +struct InsideOutsideScv; + +template +struct InsideOutsideScv +{ + static constexpr std::array, 3> pairs = {{ + {0, 1}, {0, 2}, {1, 2} + }}; +}; + +template +struct InsideOutsideScv +{ + static constexpr std::array, 4> pairs = {{ + {0, 2}, {1, 2}, {0, 3}, {1, 3} + }}; +}; + +template +struct InsideOutsideScv +{ + static constexpr std::array, 6> pairs = {{ + {0, 1}, {0, 2}, {0, 3}, {1, 2}, {1, 3}, {2, 3}, + }}; +}; + +template +struct InsideOutsideScv +{ + static constexpr std::array, 12> pairs = {{ + {0, 2}, {1, 2}, {0, 3}, {1, 3}, {0, 4}, {1, 4}, + {2, 4}, {3, 4}, {0, 5}, {1, 5}, {2, 5}, {3, 5} + }}; +}; + +} // end namespace Detail::FCDiamond + +/*! + * \ingroup DiamondDiscretization + * \brief Helper class to construct SCVs and SCVFs for the diamond scheme + */ +template +class DiamondGeometryHelper +{ + using Element = typename GridView::template Codim<0>::Entity; + + static constexpr auto dim = GridView::dimension; + static constexpr auto dimWorld = GridView::dimensionworld; + + using ScvCornerStorage = typename ScvType::Traits::CornerStorage; + using LocalIndexType = typename ScvType::Traits::LocalIndexType; + using ScvfCornerStorage = typename ScvfType::Traits::CornerStorage; + + // for the normal + using ctype = typename GridView::ctype; + using GlobalPosition = typename Dune::FieldVector; + +public: + explicit DiamondGeometryHelper(const typename Element::Geometry& geo) + : geo_(geo) + {} + + //! Create a corner storage with the scv corners for a given face (codim-1) index + ScvCornerStorage getScvCorners(unsigned int localFacetIndex) const + { + const auto type = geo_.type(); + if (type == Dune::GeometryTypes::triangle) + { + using Corners = Detail::FCDiamond::ScvCorners; + return Detail::FCDiamond::keyToCornerStorage(geo_, Corners::keys[localFacetIndex]); + } + else if (type == Dune::GeometryTypes::quadrilateral) + { + using Corners = Detail::FCDiamond::ScvCorners; + return Detail::FCDiamond::keyToCornerStorage(geo_, Corners::keys[localFacetIndex]); + } + else if (type == Dune::GeometryTypes::tetrahedron) + { + using Corners = Detail::FCDiamond::ScvCorners; + return Detail::FCDiamond::keyToCornerStorage(geo_, Corners::keys[localFacetIndex]); + } + else if (type == Dune::GeometryTypes::hexahedron) + { + using Corners = Detail::FCDiamond::ScvCorners; + return Detail::FCDiamond::keyToCornerStorage(geo_, Corners::keys[localFacetIndex]); + } + else + DUNE_THROW(Dune::NotImplemented, "Scv geometries for type " << type); + } + + //! Create a corner storage with the scvf corners for a given edge (codim-2) index + ScvfCornerStorage getScvfCorners(unsigned int localEdgeIndex) const + { + const auto type = geo_.type(); + if (type == Dune::GeometryTypes::triangle) + { + using Corners = Detail::FCDiamond::ScvfCorners; + return Detail::FCDiamond::keyToCornerStorage(geo_, Corners::keys[localEdgeIndex]); + } + else if (type == Dune::GeometryTypes::quadrilateral) + { + using Corners = Detail::FCDiamond::ScvfCorners; + return Detail::FCDiamond::keyToCornerStorage(geo_, Corners::keys[localEdgeIndex]); + } + else if (type == Dune::GeometryTypes::tetrahedron) + { + using Corners = Detail::FCDiamond::ScvfCorners; + return Detail::FCDiamond::keyToCornerStorage(geo_, Corners::keys[localEdgeIndex]); + } + else if (type == Dune::GeometryTypes::hexahedron) + { + using Corners = Detail::FCDiamond::ScvfCorners; + return Detail::FCDiamond::keyToCornerStorage(geo_, Corners::keys[localEdgeIndex]); + } + else + DUNE_THROW(Dune::NotImplemented, "Scvf geometries for type " << type); + } + + //! Create the sub control volume face geometries on the boundary for a given face index + ScvfCornerStorage getBoundaryScvfCorners(unsigned int localFacetIndex) const + { + const auto type = geo_.type(); + if (type == Dune::GeometryTypes::triangle || type == Dune::GeometryTypes::quadrilateral) + return Detail::FCDiamond::boundaryCornerStorage(geo_, localFacetIndex); + else if (type == Dune::GeometryTypes::tetrahedron) + return Detail::FCDiamond::boundaryCornerStorage(geo_, localFacetIndex); + else if (type == Dune::GeometryTypes::hexahedron) + return Detail::FCDiamond::boundaryCornerStorage(geo_, localFacetIndex); + else + DUNE_THROW(Dune::NotImplemented, "Boundary scvf geometries for type " << type); + } + + GlobalPosition facetCenter(unsigned int localFacetIndex) const + { + return geo_.global(referenceElement(geo_).position(localFacetIndex, 1)); + } + + std::array getInsideOutsideScvForScvf(unsigned int localEdgeIndex) + { + const auto type = geo_.type(); + if (type == Dune::GeometryTypes::triangle) + return Detail::FCDiamond::InsideOutsideScv::pairs[localEdgeIndex]; + else if (type == Dune::GeometryTypes::quadrilateral) + return Detail::FCDiamond::InsideOutsideScv::pairs[localEdgeIndex]; + else if (type == Dune::GeometryTypes::tetrahedron) + return Detail::FCDiamond::InsideOutsideScv::pairs[localEdgeIndex]; + else if (type == Dune::GeometryTypes::hexahedron) + return Detail::FCDiamond::InsideOutsideScv::pairs[localEdgeIndex]; + else + DUNE_THROW(Dune::NotImplemented, "Inside outside scv pairs for type " << type); + } + + //! number of interior sub control volume faces (number of codim-2 entities) + std::size_t numInteriorScvf() + { + return referenceElement(geo_).size(2); + } + + //! number of sub control volumes (number of codim-1 entities) + std::size_t numScv() + { + return referenceElement(geo_).size(1); + } + + template = 0> + GlobalPosition normal(const ScvfCornerStorage& p, const std::array& scvPair) + { + auto normal = Dumux::crossProduct(p[1]-p[0], p[2]-p[0]); + normal /= normal.two_norm(); + + const auto ref = referenceElement(geo_); + const auto v = facetCenter_(scvPair[1], ref) - facetCenter_(scvPair[0], ref); + + const auto s = v*normal; + if (std::signbit(s)) + normal *= -1; + + return normal; + } + + template = 0> + GlobalPosition normal(const ScvfCornerStorage& p, const std::array& scvPair) + { + //! obtain normal vector by 90° counter-clockwise rotation of t + const auto t = p[1] - p[0]; + GlobalPosition normal({-t[1], t[0]}); + normal /= normal.two_norm(); + + const auto ref = referenceElement(geo_); + const auto v = facetCenter_(scvPair[1], ref) - facetCenter_(scvPair[0], ref); + + const auto s = v*normal; + if (std::signbit(s)) + normal *= -1; + + return normal; + } + + const typename Element::Geometry& elementGeometry() const + { return geo_; } + +private: + template + GlobalPosition facetCenter_(unsigned int localFacetIndex, const RefElement& ref) const + { + return geo_.global(ref.position(localFacetIndex, 1)); + } + + const typename Element::Geometry& geo_; //!< Reference to the element geometry +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/facecentered/diamond/subcontrolvolume.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/facecentered/diamond/subcontrolvolume.hh new file mode 100644 index 0000000..6b09def --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/facecentered/diamond/subcontrolvolume.hh @@ -0,0 +1,122 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup DiamondDiscretization + * \copydoc Dumux::FaceCenteredDiamondSubControlVolume + */ +#ifndef DUMUX_DISCRETIZATION_FACECENTERED_DIAMOND_SUBCONTROLVOLUME_HH +#define DUMUX_DISCRETIZATION_FACECENTERED_DIAMOND_SUBCONTROLVOLUME_HH + +#include +#include +#include + +#include + +#include +#include "geometryhelper.hh" + +namespace Dumux { + +/*! + * \ingroup DiamondDiscretization + * \brief Default traits class to be used for the sub-control volumes + * \tparam GV the type of the grid view + */ +template +struct FaceCenteredDiamondScvGeometryTraits +{ + using Grid = typename GridView::Grid; + + static const int dim = Grid::dimension; + static const int dimWorld = Grid::dimensionworld; + + using GridIndexType = typename IndexTraits::GridIndex; + using LocalIndexType = typename IndexTraits::SmallLocalIndex; + using Scalar = typename Grid::ctype; + using Geometry = Dune::MultiLinearGeometry>; + + using CornerStorage = typename FCDiamondMLGeometryTraits::template CornerStorage::Type; + using GlobalPosition = typename CornerStorage::value_type; + + static constexpr Dune::GeometryType geometryType(Dune::GeometryType elementType) + { + if (elementType == Dune::GeometryTypes::hexahedron) + return Dune::GeometryTypes::pyramid; + else + return Dune::GeometryTypes::simplex(dim); + } +}; + +/*! + * \ingroup DiamondDiscretization + * \brief Face centered diamond subcontrolvolume face + */ +template> +class FaceCenteredDiamondSubControlVolume +{ + using Scalar = typename T::Scalar; + using GridIndexType = typename T::GridIndexType; + using LocalIndexType = typename T::LocalIndexType; + +public: + using GlobalPosition = typename T::GlobalPosition; + //! state the traits public and thus export all types + using Traits = T; + + FaceCenteredDiamondSubControlVolume() = default; + + FaceCenteredDiamondSubControlVolume(const Scalar& volume, + const GlobalPosition& dofPosition, + const GlobalPosition& center, + const LocalIndexType indexInElement, + const GridIndexType eIdx, + const GridIndexType dofIdx) + : center_(center) + , dofPosition_(dofPosition) + , volume_(volume) + , indexInElement_(indexInElement) + , eIdx_(eIdx) + , dofIdx_(dofIdx) + {} + + //! The center of the sub control volume + const GlobalPosition& center() const + { return center_; } + + //! The position of the degree of freedom + const GlobalPosition& dofPosition() const + { return dofPosition_; } + + Scalar volume() const + { return volume_; } + + GridIndexType dofIndex() const + { return dofIdx_; } + + LocalIndexType indexInElement() const + { return indexInElement_; } + + GridIndexType elementIndex() const + { return eIdx_; } + + LocalIndexType localDofIndex() const + { return indexInElement_; } + +private: + GlobalPosition center_; + GlobalPosition dofPosition_; + Scalar volume_; + LocalIndexType indexInElement_; + GridIndexType eIdx_; + GridIndexType dofIdx_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/facecentered/diamond/subcontrolvolumeface.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/facecentered/diamond/subcontrolvolumeface.hh new file mode 100644 index 0000000..7c8d23d --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/facecentered/diamond/subcontrolvolumeface.hh @@ -0,0 +1,160 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup DiamondDiscretization + * \copydoc Dumux::FaceCenteredDiamondSubControlVolumeFace + */ +#ifndef DUMUX_DISCRETIZATION_FACECENTERED_DIAMOND_SUBCONTROLVOLUMEFACE_HH +#define DUMUX_DISCRETIZATION_FACECENTERED_DIAMOND_SUBCONTROLVOLUMEFACE_HH + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +#include + +namespace Dumux { + +/*! + * \ingroup DiamondDiscretization + * \brief Default traits class to be used for the sub-control volumes + * for the cell-centered finite volume scheme using TPFA + * \tparam GV the type of the grid view + */ +template +struct FaceCenteredDiamondScvfGeometryTraits +{ + using Grid = typename GridView::Grid; + static constexpr int dim = Grid::dimension; + static constexpr int dimWorld = Grid::dimensionworld; + + using GridIndexType = typename IndexTraits::GridIndex; + using LocalIndexType = typename IndexTraits::SmallLocalIndex; + using Scalar = typename Grid::ctype; + using Geometry = Dune::MultiLinearGeometry>; + using CornerStorage = typename FCDiamondMLGeometryTraits::template CornerStorage::Type; + using GlobalPosition = typename CornerStorage::value_type; + using BoundaryFlag = Dumux::BoundaryFlag; + + static constexpr Dune::GeometryType interiorGeometryType(Dune::GeometryType) + { return Dune::GeometryTypes::simplex(dim-1); } +}; + +/*! + * \ingroup DiamondDiscretization + * \brief The SCVF implementation for diamond + */ +template> +class FaceCenteredDiamondSubControlVolumeFace +{ + using Scalar = typename T::Scalar; + using GridIndexType = typename T::GridIndexType; + using LocalIndexType = typename T::LocalIndexType; + using Geometry = typename T::Geometry; + using BoundaryFlag = typename T::BoundaryFlag; + +public: + //! state the traits public and thus export all types + using Traits = T; + + using GlobalPosition = typename T::GlobalPosition; + + FaceCenteredDiamondSubControlVolumeFace() = default; + + // interior scvf + FaceCenteredDiamondSubControlVolumeFace(const GlobalPosition& center, + const Scalar area, + const GlobalPosition& normal, + const std::array& scvIndices, + const LocalIndexType localScvfIdx) + : center_(center) + , unitOuterNormal_(normal) + , area_(area) + , localScvfIdx_(localScvfIdx) + , scvIndices_(scvIndices) + , boundary_(false) + , boundaryFlag_{} + {} + + // boundary scvf + FaceCenteredDiamondSubControlVolumeFace(const GlobalPosition& center, + const Scalar area, + const GlobalPosition& normal, + const std::array& scvIndices, + const LocalIndexType localScvfIdx, + const BoundaryFlag& bFlag) + : center_(center) + , unitOuterNormal_(normal) + , area_(area) + , localScvfIdx_(localScvfIdx) + , scvIndices_(scvIndices) + , boundary_(true) + , boundaryFlag_(bFlag) + {} + + //! The center of the sub control volume face + const GlobalPosition& center() const + { return center_; } + + //! The integration point of the sub control volume face + const GlobalPosition& ipGlobal() const + { return center_; } + + //! The unit outer normal + const GlobalPosition unitOuterNormal() const + { return unitOuterNormal_; } + + //! Index of the inside sub control volume + GridIndexType insideScvIdx() const + { return scvIndices_[0]; } + + //! index of the outside sub control volume + GridIndexType outsideScvIdx() const + { return scvIndices_[1]; } + + std::size_t numOutsideScvs() const + { return static_cast(!boundary()); } + + GridIndexType index() const + { return localScvfIdx_; } + + bool boundary() const + { return boundary_; } + + Scalar area() const + { return area_; } + + //! Return the boundary flag + typename BoundaryFlag::value_type boundaryFlag() const + { + return boundaryFlag_.get(); + } + +private: + GlobalPosition center_; + GlobalPosition unitOuterNormal_; + Scalar area_; + LocalIndexType localScvfIdx_; + std::array scvIndices_; + bool boundary_; + BoundaryFlag boundaryFlag_; +}; + + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/facecentered/staggered/consistentlyorientedgrid.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/facecentered/staggered/consistentlyorientedgrid.hh new file mode 100644 index 0000000..a99bab7 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/facecentered/staggered/consistentlyorientedgrid.hh @@ -0,0 +1,46 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup FaceCenteredStaggeredDiscretization + * \copydoc Dumux::ConsistentlyOrientedGrid + */ +#ifndef DUMUX_DISCRETIZATION_FACECENTERED_STAGGERED_CONSISTENTLY_ORIENTED_GRID_HH +#define DUMUX_DISCRETIZATION_FACECENTERED_STAGGERED_CONSISTENTLY_ORIENTED_GRID_HH + +#include + +// forward declare +namespace Dune { +template +class YaspGrid; + +template +class SubGrid; + +} + +namespace Dumux { + +/*! + * \brief Helper type to determine whether a grid is guaranteed to be oriented consistently. + * This means that the intersection indices always correspond to the ones of a reference element + * or, in other words, the elements are never rotated. + */ +template +struct ConsistentlyOrientedGrid : public std::false_type {}; + +template +struct ConsistentlyOrientedGrid> : public std::true_type {}; + +template +struct ConsistentlyOrientedGrid, mapIndexStorage>> : public std::true_type {}; + + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/facecentered/staggered/elementsolution.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/facecentered/staggered/elementsolution.hh new file mode 100644 index 0000000..6c90bae --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/facecentered/staggered/elementsolution.hh @@ -0,0 +1,161 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup FaceCenteredStaggeredDiscretization + * \copydoc Dumux::FaceCenteredStaggeredElementSolution + */ +#ifndef DUMUX_DISCRETIZATION_FACECENTERED_STAGGERED_ELEMENT_SOLUTION_HH +#define DUMUX_DISCRETIZATION_FACECENTERED_STAGGERED_ELEMENT_SOLUTION_HH + +#include +#include +#include + +#include +#include + +namespace Dumux { + +/*! + * \ingroup FaceCenteredStaggeredDiscretization + * \brief The global face variables class for staggered models + */ +template +class FaceCenteredStaggeredElementSolution +{ + using GridGeometry = typename FVElementGeometry::GridGeometry; + using GridView = typename GridGeometry::GridView; + using Element = typename GridView::template Codim<0>::Entity; + using SmallLocalIndexType = typename IndexTraits::SmallLocalIndex; + + static constexpr auto numScvsPerElement = GridView::Grid::dimension * 2; + +public: + //! export the primary variables type + using PrimaryVariables = PV; + + FaceCenteredStaggeredElementSolution() = default; + + //! Constructor with element, solution vector and grid geometry + template + FaceCenteredStaggeredElementSolution(const Element& element, + const SolutionVector& sol, + const GridGeometry& gridGeometry) + { + const auto fvGeometry = localView(gridGeometry).bindElement(element); + + for (const auto& scv : scvs(fvGeometry)) + priVars_[scv.indexInElement()] = sol[scv.dofIndex()]; + } + + //! Constructor with element, element volume variables and fv element geometry + template + FaceCenteredStaggeredElementSolution(const Element& element, + const ElementVolumeVariables& elemVolVars, + const FVElementGeometry& fvGeometry) + { + for (const auto& scv : scvs(fvGeometry)) + priVars_ = elemVolVars[scv].priVars(); + } + + //! Constructor with a primary variable object + FaceCenteredStaggeredElementSolution(PrimaryVariables&& priVars) + { + priVars_[0] = std::move(priVars); + for (int i = 1; i < priVars_.size(); ++i) + priVars_[i] = priVars_[0]; + } + + //! Constructor with a primary variable object + FaceCenteredStaggeredElementSolution(const PrimaryVariables& priVars) + { + priVars_[0] = priVars; + for (int i = 1; i < priVars_.size(); ++i) + priVars_[i] = priVars_[0]; + } + + //! extract the element solution from the solution vector using a mapper + template + void update(const Element& element, const SolutionVector& sol, + const GridGeometry& gridGeometry) + { + const auto fvGeometry = localView(gridGeometry).bindElement(element); + + for (const auto& scv : scvs(fvGeometry)) + priVars_[scv.indexInElement()] = sol[scv.dofIndex()]; + } + + //! bracket operator const access + const PrimaryVariables& operator [](SmallLocalIndexType localScvIdx) const + { + return priVars_[localScvIdx]; + } + + //! bracket operator + PrimaryVariables& operator [](SmallLocalIndexType localScvIdx) + { + return priVars_[localScvIdx]; + } + + //! return the size of the element solution + static constexpr std::size_t size() + { return numScvsPerElement; } + +private: + std::array priVars_; +}; + +/*! + * \ingroup Discretization + * \brief Make an element solution for face-centered staggered schemes + */ +template +auto elementSolution(const Element& element, const SolutionVector& sol, const GridGeometry& gg) +-> std::enable_if_t< + GridGeometry::discMethod == DiscretizationMethods::fcstaggered, + FaceCenteredStaggeredElementSolution< + typename GridGeometry::LocalView, + std::decay_t()[0])> + > +> +{ return { element, sol, gg }; } + +/*! + * \ingroup Discretization + * \brief Make an element solution for face-centered staggered schemes + */ +template +auto elementSolution(const Element& element, const ElementVolumeVariables& elemVolVars, const FVElementGeometry& gg) +-> std::enable_if_t< + FVElementGeometry::GridGeometry::discMethod == DiscretizationMethods::fcstaggered, + FaceCenteredStaggeredElementSolution< + FVElementGeometry, + typename ElementVolumeVariables::VolumeVariables::PrimaryVariables + > +> +{ return { element, elemVolVars, gg }; } + +/*! + * \ingroup Discretization + * \brief Make an element solution for face-centered staggered schemes + * \note This is e.g. used to construct an element solution at Dirichlet boundaries + */ +template +auto elementSolution(PrimaryVariables&& priVars) +-> std::enable_if_t< + FVElementGeometry::GridGeometry::discMethod == DiscretizationMethods::fcstaggered, + FaceCenteredStaggeredElementSolution< + FVElementGeometry, + std::decay_t + > +> +{ return { std::forward(priVars) }; } + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/fcdiamond.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/fcdiamond.hh new file mode 100644 index 0000000..df1d572 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/fcdiamond.hh @@ -0,0 +1,158 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Discretization + * \brief Defines a type tag and some properties for models using the diamond scheme. + This scheme features degrees of freedom at the elements' centers and intersections (faces). + */ + +#ifndef DUMUX_DISCRETIZATION_FACECENTERED_DIAMOND_HH +#define DUMUX_DISCRETIZATION_FACECENTERED_DIAMOND_HH + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace Dumux::Properties { + +//! Type tag for the staggered scheme. +// Create new type tags +namespace TTag { +struct FaceCenteredDiamondModel { using InheritsFrom = std::tuple; }; +} // end namespace TTag + +//! Set the default for the grid geometry +template +struct GridGeometry +{ +private: + static constexpr bool enableCache = getPropValue(); + using GridView = typename GetPropType::LeafGridView; +public: + using type = FaceCenteredDiamondFVGridGeometry; +}; + +//! The grid volume variables vector class +template +struct GridVolumeVariables +{ +private: + static constexpr bool enableCache = getPropValue(); + using Problem = GetPropType; + using VolumeVariables = GetPropType; + using Traits = CVFEDefaultGridVolumeVariablesTraits; +public: + using type = CVFEGridVolumeVariables; +}; + +//! Set the global flux variables cache vector class +template +struct GridFluxVariablesCache +{ +private: + static constexpr bool enableCache = getPropValue(); + using Problem = GetPropType; + + using Scalar = GetPropType; + using FluxVariablesCache = GetPropTypeOr + >; +public: + using type = CVFEGridFluxVariablesCache; +}; + +//! Set the grid variables (volume, flux and face variables) +template +struct GridVariables +{ +private: + using GG = GetPropType; + using GVV = GetPropType; + using GFVC = GetPropType; +public: + using type = FVGridVariables; +}; + +//! The flux variables cache type +template +struct FluxVariablesCache +{ +private: + using S = GetPropType; + using GG = GetPropType; +public: + using type = CVFEFluxVariablesCache; +}; + +//! Set the default for the ElementBoundaryTypes +template +struct ElementBoundaryTypes +{ +private: + using Problem = GetPropType; + using BoundaryTypes = typename ProblemTraits::BoundaryTypes; +public: + using type = CVFEElementBoundaryTypes; +}; + +} // namespace Dumux::Properties + +namespace Dumux::Detail { + +template +struct ProblemTraits +{ +private: + using GG = std::decay_t().gridGeometry())>; + using Element = typename GG::GridView::template Codim<0>::Entity; + using SubControlVolumeFace = typename GG::SubControlVolumeFace; +public: + using GridGeometry = GG; + // BoundaryTypes is whatever the problem returns from boundaryTypes(element, scv) + using BoundaryTypes = std::decay_t().boundaryTypes(std::declval(), std::declval()))>; +}; + +template +struct LocalDofTraits +{ + static constexpr int dim = GridView::dimension; + // Dofs are located at the facets + static constexpr int numCubeElementDofs = 2*dim; +}; + +template +concept FaceCenteredDiamondModel = std::is_same_v< + typename GetPropType::DiscretizationMethod, + DiscretizationMethods::FCDiamond +>; + +template +struct DiscretizationDefaultLocalOperator +{ + using type = CVFELocalResidual; +}; + +} // end namespace Dumux::Detail + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/fluxstencil.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/fluxstencil.hh new file mode 100644 index 0000000..863ada4 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/fluxstencil.hh @@ -0,0 +1,116 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Discretization + * \brief The flux stencil specialized for different discretization schemes + */ +#ifndef DUMUX_DISCRETIZATION_FLUXSTENCIL_HH +#define DUMUX_DISCRETIZATION_FLUXSTENCIL_HH + +#include + +#include +#include +#include + +namespace Dumux { + +/*! + * \ingroup Discretization + * \brief The flux stencil specialized for different discretization schemes + * \note There might be different stencils used for e.g. advection and diffusion for schemes + * where the stencil depends on variables. Also schemes might even have solution dependent + * stencil. However, we always reserve the stencil or all DOFs that are possibly involved + * since we use the flux stencil for matrix and assembly. This might lead to some zeros stored + * in the matrix. + */ +template +class FluxStencil; + +/* + * \ingroup Discretization + * \brief Flux stencil specialization for the cell-centered tpfa scheme + * \tparam FVElementGeometry The local view on the finite volume grid geometry + */ +template +class FluxStencil +{ + using GridGeometry = typename FVElementGeometry::GridGeometry; + using SubControlVolumeFace = typename GridGeometry::SubControlVolumeFace; + using GridView = typename GridGeometry::GridView; + using Element = typename GridView::template Codim<0>::Entity; + using GridIndexType = typename IndexTraits::GridIndex; + +public: + //! Each cell I couples to a cell J always only via one face + using ScvfStencilIForJ = Dune::ReservedVector; + + //! The flux stencil type + using Stencil = typename SubControlVolumeFace::Traits::GridIndexStorage; + + //! Returns the flux stencil + static Stencil stencil(const Element& element, + const FVElementGeometry& fvGeometry, + const SubControlVolumeFace& scvf) + { + if (scvf.boundary()) + return Stencil({scvf.insideScvIdx()}); + else if (scvf.numOutsideScvs() > 1) + { + Stencil stencil({scvf.insideScvIdx()}); + for (unsigned int i = 0; i < scvf.numOutsideScvs(); ++i) + stencil.push_back(scvf.outsideScvIdx(i)); + return stencil; + } + else + return Stencil({scvf.insideScvIdx(), scvf.outsideScvIdx()}); + } +}; + +/* + * \ingroup Discretization + * \brief Flux stencil specialization for the cell-centered mpfa scheme + * \tparam FVElementGeometry The local view on the finite volume grid geometry + */ +template +class FluxStencil +{ + using GridGeometry = typename FVElementGeometry::GridGeometry; + using SubControlVolumeFace = typename GridGeometry::SubControlVolumeFace; + using GridView = typename GridGeometry::GridView; + using Element = typename GridView::template Codim<0>::Entity; + using GridIndexType = typename IndexTraits::GridIndex; + + // Use the stencil type of the primary interaction volume + using NodalIndexSet = typename GridGeometry::GridIVIndexSets::DualGridIndexSet::NodalIndexSet; + +public: + //! We don't know yet how many faces couple to a neighboring element + using ScvfStencilIForJ = std::vector; + + //! The flux stencil type + using Stencil = typename NodalIndexSet::NodalGridStencilType; + + //! Returns the indices of the elements required for flux calculation on an scvf. + static const Stencil& stencil(const Element& element, + const FVElementGeometry& fvGeometry, + const SubControlVolumeFace& scvf) + { + const auto& gridGeometry = fvGeometry.gridGeometry(); + + // return the scv (element) indices in the interaction region + if (gridGeometry.vertexUsesSecondaryInteractionVolume(scvf.vertexIndex())) + return gridGeometry.gridInteractionVolumeIndexSets().secondaryIndexSet(scvf).gridScvIndices(); + else + return gridGeometry.gridInteractionVolumeIndexSets().primaryIndexSet(scvf).gridScvIndices(); + } +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/fvgridvariables.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/fvgridvariables.hh new file mode 100644 index 0000000..4cd0948 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/fvgridvariables.hh @@ -0,0 +1,160 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Discretization + * \brief The grid variable class for finite volume schemes, + * storing variables on scv and scvf (volume and flux variables) + */ +#ifndef DUMUX_FV_GRID_VARIABLES_HH +#define DUMUX_FV_GRID_VARIABLES_HH + +#include +#include + +namespace Dumux { + +/*! + * \ingroup Discretization + * \brief The grid variable class for finite volume schemes storing variables on scv and scvf (volume and flux variables) + * \tparam the type of the grid geometry + * \tparam the type of the grid volume variables + * \tparam the type of the grid flux variables cache + */ +template +class FVGridVariables +{ +public: + //! export type of the finite volume grid geometry + using GridGeometry = GG; + + //! export type of the finite volume grid geometry + using GridVolumeVariables = GVV; + + //! export type of the volume variables + using VolumeVariables = typename GridVolumeVariables::VolumeVariables; + + //! export primary variable type + using PrimaryVariables = typename VolumeVariables::PrimaryVariables; + + //! export scalar type (TODO get it directly from the volvars) + using Scalar = std::decay_t()[0])>; + + //! export type of the finite volume grid geometry + using GridFluxVariablesCache = GFVC; + + template + FVGridVariables(std::shared_ptr problem, + std::shared_ptr gridGeometry) + : gridGeometry_(gridGeometry) + , curGridVolVars_(*problem) + , prevGridVolVars_(*problem) + , gridFluxVarsCache_(*problem) + {} + + //! initialize all variables (stationary case) + template + void init(const SolutionVector& curSol) + { + // resize and update the volVars with the initial solution + curGridVolVars_.update(*gridGeometry_, curSol); + + // update the flux variables caches (always force flux cache update on initialization) + gridFluxVarsCache_.update(*gridGeometry_, curGridVolVars_, curSol, true); + + // set the volvars of the previous time step in case we have an instationary problem + // note that this means some memory overhead in the case of enabled caching, however + // this it outweighted by the advantage of having a single grid variables object for + // stationary and instationary problems + prevGridVolVars_ = curGridVolVars_; + } + + //! update all variables + template + void update(const SolutionVector& curSol, bool forceFluxCacheUpdate = false) + { + // resize and update the volVars with the initial solution + curGridVolVars_.update(*gridGeometry_, curSol); + + // update the flux variables caches + gridFluxVarsCache_.update(*gridGeometry_, curGridVolVars_, curSol, forceFluxCacheUpdate); + } + + //! update all variables after grid adaption + template + void updateAfterGridAdaption(const SolutionVector& curSol) + { + // update (always force flux cache update as the grid changed) + update(curSol, true); + + // for instationary problems also update the variables + // for the previous time step to the new grid + prevGridVolVars_ = curGridVolVars_; + } + + /*! + * \brief Sets the current state as the previous for next time step + * \note this has to be called at the end of each time step + */ + void advanceTimeStep() + { + prevGridVolVars_ = curGridVolVars_; + } + + //! resets state to the one before time integration + template + void resetTimeStep(const SolutionVector& solution) + { + // set the new time step vol vars to old vol vars + curGridVolVars_ = prevGridVolVars_; + + // update the flux variables caches + gridFluxVarsCache_.update(*gridGeometry_, curGridVolVars_, solution); + } + + //! return the flux variables cache + const GridFluxVariablesCache& gridFluxVarsCache() const + { return gridFluxVarsCache_; } + + //! return the flux variables cache + GridFluxVariablesCache& gridFluxVarsCache() + { return gridFluxVarsCache_; } + + //! return the current volume variables + const GridVolumeVariables& curGridVolVars() const + { return curGridVolVars_; } + + //! return the current volume variables + GridVolumeVariables& curGridVolVars() + { return curGridVolVars_; } + + //! return the volume variables of the previous time step (for instationary problems) + const GridVolumeVariables& prevGridVolVars() const + { return prevGridVolVars_; } + + //! return the volume variables of the previous time step (for instationary problems) + GridVolumeVariables& prevGridVolVars() + { return prevGridVolVars_; } + + //! return the finite volume grid geometry + const GridGeometry& gridGeometry() const + { return *gridGeometry_; } + +protected: + + std::shared_ptr gridGeometry_; //!< pointer to the constant grid geometry + +private: + GridVolumeVariables curGridVolVars_; //!< the current volume variables (primary and secondary variables) + GridVolumeVariables prevGridVolVars_; //!< the previous time step's volume variables (primary and secondary variables) + + GridFluxVariablesCache gridFluxVarsCache_; //!< the flux variables cache +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/fvproperties.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/fvproperties.hh new file mode 100644 index 0000000..81312b0 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/fvproperties.hh @@ -0,0 +1,80 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Discretization + * \brief Declares properties required for finite-volume models models. + */ + +#ifndef DUMUX_FV_PROPERTIES_HH +#define DUMUX_FV_PROPERTIES_HH + +#include +#include + +#include +#include +#include + +#include + +namespace Dumux { +namespace Properties { + +//! Type tag for finite-volume schemes. +// Create new type tags +namespace TTag { +struct FiniteVolumeModel { using InheritsFrom = std::tuple; }; +} // end namespace TTag + +//! The grid variables +template +struct GridVariables +{ +private: + using GG = GetPropType; + using GVV = GetPropType; + using GFVC = GetPropType; +public: + using type = FVGridVariables; +}; + +//! We do not store the FVGeometry by default +template +struct EnableGridGeometryCache { static constexpr bool value = false; }; + +//! We do not store the volume variables by default +template +struct EnableGridVolumeVariablesCache { static constexpr bool value = false; }; + +//! disable flux variables data caching by default +template +struct EnableGridFluxVariablesCache { static constexpr bool value = false; }; + +// TODO: bundle SolutionVector, JacobianMatrix +// in LinearAlgebra traits + +//! The type of a solution for the whole grid at a fixed time TODO: move to LinearAlgebra traits +template +struct SolutionVector { using type = Dune::BlockVector>; }; + +//! Set the type of a global jacobian matrix from the solution types TODO: move to LinearAlgebra traits +template +struct JacobianMatrix +{ +private: + using Scalar = GetPropType; + enum { numEq = GetPropType::numEq() }; + using MatrixBlock = typename Dune::FieldMatrix; +public: + using type = typename Dune::BCRSMatrix; +}; + +} // namespace Properties +} // namespace Dumux + + #endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/localdoftraits.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/localdoftraits.hh new file mode 100644 index 0000000..b9556f9 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/localdoftraits.hh @@ -0,0 +1,22 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Discretization + * \brief Element-specific traits of grid geometries / discretization schemes + */ +#ifndef DUMUX_DISCRETIZATION_LOCAL_DOF_TRAITS_HH +#define DUMUX_DISCRETIZATION_LOCAL_DOF_TRAITS_HH + +namespace Dumux::Detail { + +template +struct LocalDofTraits; + +} // end namespace Dumux::Detail + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/localview.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/localview.hh new file mode 100644 index 0000000..07adf09 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/localview.hh @@ -0,0 +1,31 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Discretization + * \brief Free function to get the local view of a grid cache object + */ + +#ifndef DUMUX_LOCAL_VIEW_HH +#define DUMUX_LOCAL_VIEW_HH + +namespace Dumux { + +/*! + * \ingroup Discretization + * \brief Free function to get the local view of a grid cache object + * \note A local object is only functional after calling its bind/bindElement method. + * \tparam GridCache the grid caching type (such as GridGeometry) + * \param gridCache the grid caching object we want to localView from + */ +template +inline typename GridCache::LocalView localView(const GridCache& gridCache) +{ return typename GridCache::LocalView(gridCache); } + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/method.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/method.hh new file mode 100644 index 0000000..1db64be --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/method.hh @@ -0,0 +1,157 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Discretization + * \brief The available discretization methods in Dumux + */ +#ifndef DUMUX_DISCRETIZATION_METHOD_HH +#define DUMUX_DISCRETIZATION_METHOD_HH + +#include +#include + +#include + +namespace Dumux::DiscretizationMethods { + +/* + * \brief Cell-centered finite volume scheme with two-point flux approximation + */ +struct CCTpfa : public Utility::Tag { + static std::string name() { return "cctpfa"; } +}; + + +/* + * \brief Cell-centered finite volume scheme with multi-point flux approximation + */ +struct CCMpfa : public Utility::Tag { + static std::string name() { return "ccmpfa"; } +}; + + +/* + * \brief Control-volume finite element methods + * This is a group of discretization methods that share certain properties. + * Therefore there is a single meta-tag parametrized in terms of the actual + * discretization method in the group. Having a common tag allows to specialize + * template agnostic of the underlying discretization type + */ +template +struct CVFE : public Utility::Tag> { + static std::string name() { return DM::name(); } +}; + + +#ifndef DOXYGEN +namespace Detail { + +template +struct IsCVFE : public std::false_type {}; + +template +struct IsCVFE> : public std::true_type {}; + +} // end namespace Detail +#endif + +/* + * \brief Template variable that is true when the discretization method DM is a CVFE schemes + */ +template +inline constexpr bool isCVFE = Detail::IsCVFE::value; + + +/* + * \brief Various control volume finite element discretization methods + */ +namespace CVFEMethods { + +struct PQ1 { + static std::string name() { return "box"; } +}; + +struct CR_RT { + static std::string name() { return "fcdiamond"; } +}; + +struct PQ1Bubble { + static std::string name() { return "pq1bubble"; } +}; + +} // end namespace CVFEMethods + + +/* + * \brief Vertex-centered finite volume scheme + * or control-volume finite element scheme based on a P1 (simplices) or Q1 (quads) basis + */ +using Box = CVFE; + +/* + * \brief Face-centered finite volume scheme + * or control-volume finite element scheme based on + * Crouzeix-Raviart (simplices) or Rannacher-Turek (quads) basis + */ +using FCDiamond = CVFE; + +/* + * \brief Vertex- and cell-centered finite volume scheme + * or control-volume finite element scheme based on + * linear Lagrangian elements with bubble function + */ +using PQ1Bubble = CVFE; + + +/* + * \brief Staggered-grid finite volume scheme (old) + */ +struct Staggered : public Utility::Tag { + static std::string name() { return "staggered"; } +}; + + +/* + * \brief Finite element method + */ +struct FEM : public Utility::Tag { + static std::string name() { return "fem"; } +}; + + +/* + * \brief Staggered-grid finite volume scheme + */ +struct FCStaggered : public Utility::Tag { + static std::string name() { return "fcstaggered"; } +}; + + +/* + * \brief Tag used for defaults not depending on the discretization + * or in situations where a discretization tag is needed but none + * can be provided (the implementation has to support this of course) + */ +struct None : public Utility::Tag { + static std::string name() { return "none"; } +}; + + +inline constexpr CCTpfa cctpfa{}; +inline constexpr CCMpfa ccmpfa{}; +inline constexpr Box box{}; +inline constexpr PQ1Bubble pq1bubble{}; +inline constexpr Staggered staggered{}; +inline constexpr FEM fem{}; +inline constexpr FCStaggered fcstaggered{}; +inline constexpr FCDiamond fcdiamond{}; +inline constexpr None none{}; + +} // end namespace Dumux::DiscretizationMethods + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/nonconformingfecache.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/nonconformingfecache.hh new file mode 100644 index 0000000..d88e795 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/nonconformingfecache.hh @@ -0,0 +1,65 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Discretization + * \brief A finite element cache for the non-conforming FE spaces RT and CR + */ +#ifndef DUMUX_DISCRETIZATION_NONCONFORMING_FECACHE_HH +#define DUMUX_DISCRETIZATION_NONCONFORMING_FECACHE_HH + +#include + +#include +#include + +#include +#include +#include +#include + +namespace Dumux { + +template< class CoordScalar, class Scalar, unsigned int dim> +class NonconformingFECache +{ + static_assert(dim == 2 || dim == 3, "Non-conforming FE spaces only implemented for 2D and 3D grids"); + + // These are so-called non-conforming finite element spaces + // the local basis is only continuous at given points on the faces + using RT = Dune::RannacherTurekLocalFiniteElement; + using CR = Dune::CrouzeixRaviartLocalFiniteElement; + +public: + using FiniteElementType = Dune::LocalFiniteElementVirtualInterface; + + NonconformingFECache() + : rtBasis_(std::make_unique>(RT{})) + , crBasis_(std::make_unique>(CR{})) + {} + + //! Get local finite element for given GeometryType + const FiniteElementType& get(const Dune::GeometryType& gt) const + { + if (gt.isSimplex()) + return *crBasis_; + else if (gt.isCube()) + return *rtBasis_; + else + DUNE_THROW(Dune::NotImplemented, + "Non-conforming local finite element for geometry type " << gt + ); + } + +private: + std::unique_ptr rtBasis_; + std::unique_ptr crBasis_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/pq1bubble.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/pq1bubble.hh new file mode 100644 index 0000000..42c74ab --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/pq1bubble.hh @@ -0,0 +1,152 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Discretization + * \brief Defines a type tag and some properties for models using the pq1bubble scheme. + */ + +#ifndef DUMUX_DISCRETIZTAION_PQ1BUBBLE_HH +#define DUMUX_DISCRETIZTAION_PQ1BUBBLE_HH + +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +namespace Dumux::Properties { + +//! Type tag for the pq1bubble scheme. +// Create new type tags +namespace TTag { +struct PQ1BubbleModel { using InheritsFrom = std::tuple; }; +} // end namespace TTag + +//! Set the default for the grid geometry +template +struct GridGeometry +{ +private: + static constexpr bool enableCache = getPropValue(); + using GridView = typename GetPropType::LeafGridView; + using Scalar = GetPropType; +public: + using type = PQ1BubbleFVGridGeometry; +}; + +//! The grid volume variables vector class +template +struct GridVolumeVariables +{ +private: + static constexpr bool enableCache = getPropValue(); + using Problem = GetPropType; + using VolumeVariables = GetPropType; + using Traits = CVFEDefaultGridVolumeVariablesTraits; +public: + using type = CVFEGridVolumeVariables; +}; + +//! The flux variables cache class +template +struct FluxVariablesCache +{ +private: + using GridGeometry = GetPropType; + using Scalar = GetPropType; +public: + using type = CVFEFluxVariablesCache; +}; + +//! The grid flux variables cache vector class +template +struct GridFluxVariablesCache +{ +private: + static constexpr bool enableCache = getPropValue(); + using Problem = GetPropType; + + using Scalar = GetPropType; + using FluxVariablesCache = GetPropTypeOr + >; +public: + using type = CVFEGridFluxVariablesCache; +}; + +//! Set the default for the ElementBoundaryTypes +template +struct ElementBoundaryTypes +{ +private: + using Problem = GetPropType; + using BoundaryTypes = typename ProblemTraits::BoundaryTypes; +public: + using type = CVFEElementBoundaryTypes; +}; + +} // namespace Dumux::Properties + +namespace Dumux::Detail { + +template +struct ProblemTraits +{ +private: + using GG = std::decay_t().gridGeometry())>; + using Element = typename GG::GridView::template Codim<0>::Entity; + using SubControlVolume = typename GG::SubControlVolume; +public: + using GridGeometry = GG; + // BoundaryTypes is whatever the problem returns from boundaryTypes(element, scv) + using BoundaryTypes = std::decay_t().boundaryTypes(std::declval(), std::declval()))>; +}; + +template +struct LocalDofTraits +{ + static constexpr int dim = GridView::dimension; + // Dofs are located at the vertices and element + static constexpr int numCubeElementDofs = (1< +concept PQ1BubbleModel = std::is_same_v< + typename GetPropType::DiscretizationMethod, + DiscretizationMethods::PQ1Bubble +>; + +template +struct DiscretizationDefaultLocalOperator +{ + using type = CVFELocalResidual; +}; + +} // end namespace Dumux::Detail + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/pq1bubble/fvelementgeometry.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/pq1bubble/fvelementgeometry.hh new file mode 100644 index 0000000..1f62c0d --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/pq1bubble/fvelementgeometry.hh @@ -0,0 +1,237 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup PQ1BubbleDiscretization + * \brief Base class for the local finite volume geometry for the pq1bubble method + * This builds up the sub control volumes and sub control volume faces + * for an element. + */ +#ifndef DUMUX_DISCRETIZATION_PQ1BUBBLE_FV_ELEMENT_GEOMETRY_HH +#define DUMUX_DISCRETIZATION_PQ1BUBBLE_FV_ELEMENT_GEOMETRY_HH + +#include +#include + +#include +#include +#include + +#include +#include + +#include + +namespace Dumux { + +/*! + * \ingroup PQ1BubbleDiscretization + * \brief Base class for the finite volume geometry vector for pq1bubble models + * This builds up the sub control volumes and sub control volume faces + * for each element. + * \tparam GG the finite volume grid geometry type + * \tparam enableGridGeometryCache if the grid geometry is cached or not + */ +template +class PQ1BubbleFVElementGeometry; + +//! specialization in case the FVElementGeometries are stored +template +class PQ1BubbleFVElementGeometry +{ + using GridView = typename GG::GridView; + static constexpr int dim = GridView::dimension; + static constexpr int dimWorld = GridView::dimensionworld; + using GridIndexType = typename IndexTraits::GridIndex; + using LocalIndexType = typename IndexTraits::LocalIndex; + using CoordScalar = typename GridView::ctype; + using FeLocalBasis = typename GG::FeCache::FiniteElementType::Traits::LocalBasisType; + using GGCache = typename GG::Cache; + using GeometryHelper = typename GGCache::GeometryHelper; +public: + //! export the element type + using Element = typename GridView::template Codim<0>::Entity; + //! export type of subcontrol volume + using SubControlVolume = typename GG::SubControlVolume; + //! export type of subcontrol volume face + using SubControlVolumeFace = typename GG::SubControlVolumeFace; + //! export type of finite volume grid geometry + using GridGeometry = GG; + //! the maximum number of scvs per element (2^dim for cubes) + // ToDo get this from GG + static constexpr std::size_t maxNumElementScvs = (1<scvs(eIdx_)[scvIdx]; + } + + //! Get a sub control volume face with a local scvf index + const SubControlVolumeFace& scvf(LocalIndexType scvfIdx) const + { + return ggCache_->scvfs(eIdx_)[scvfIdx]; + } + + //! iterator range for sub control volumes. Iterates over + //! all scvs of the bound element. + //! This is a free function found by means of ADL + //! To iterate over all sub control volumes of this FVElementGeometry use + //! for (auto&& scv : scvs(fvGeometry)) + friend inline Dune::IteratorRange::const_iterator> + scvs(const PQ1BubbleFVElementGeometry& fvGeometry) + { + using Iter = typename std::vector::const_iterator; + const auto& s = fvGeometry.ggCache_->scvs(fvGeometry.eIdx_); + return Dune::IteratorRange(s.begin(), s.end()); + } + + //! iterator range for sub control volumes faces. Iterates over + //! all scvfs of the bound element. + //! This is a free function found by means of ADL + //! To iterate over all sub control volume faces of this FVElementGeometry use + //! for (auto&& scvf : scvfs(fvGeometry)) + friend inline Dune::IteratorRange::const_iterator> + scvfs(const PQ1BubbleFVElementGeometry& fvGeometry) + { + using Iter = typename std::vector::const_iterator; + const auto& s = fvGeometry.ggCache_->scvfs(fvGeometry.eIdx_); + return Dune::IteratorRange(s.begin(), s.end()); + } + + //! Get a local finite element basis + const FeLocalBasis& feLocalBasis() const + { + return gridGeometry().feCache().get(element_->type()).localBasis(); + } + + //! The total number of sub control volumes + std::size_t numScv() const + { + return ggCache_->scvs(eIdx_).size(); + } + + //! The total number of sub control volume faces + std::size_t numScvf() const + { + return ggCache_->scvfs(eIdx_).size(); + } + + /*! + * \brief bind the local view (r-value overload) + * This overload is called when an instance of this class is a temporary in the usage context + * This allows a usage like this: `const auto view = localView(...).bind(element);` + */ + PQ1BubbleFVElementGeometry bind(const Element& element) && + { + this->bindElement(element); + return std::move(*this); + } + + //! this function is for compatibility reasons with cc methods + //! The pq1bubble stencil is always element-local so bind and bindElement + //! are identical. + void bind(const Element& element) & + { this->bindElement(element); } + + /*! + * \brief bind the local view (r-value overload) + * This overload is called when an instance of this class is a temporary in the usage context + * This allows a usage like this: `const auto view = localView(...).bindElement(element);` + */ + PQ1BubbleFVElementGeometry bindElement(const Element& element) && + { + this->bindElement(element); + return std::move(*this); + } + + //! Binding of an element, has to be called before using the fvgeometries + //! Prepares all the volume variables within the element + //! For compatibility reasons with the FVGeometry cache being disabled + void bindElement(const Element& element) & + { + element_ = element; + // cache element index + eIdx_ = gridGeometry().elementMapper().index(element); + } + + //! Returns true if bind/bindElement has already been called + bool isBound() const + { return static_cast(element_); } + + //! The bound element + const Element& element() const + { return *element_; } + + //! The grid geometry we are a restriction of + const GridGeometry& gridGeometry() const + { return ggCache_->gridGeometry(); } + + //! Returns whether one of the geometry's scvfs lies on a boundary + bool hasBoundaryScvf() const + { return ggCache_->hasBoundaryScvf(eIdx_); } + + //! The bound element index + std::size_t elementIndex() const + { return eIdx_; } + + //! Geometry of a sub control volume + typename SubControlVolume::Traits::Geometry geometry(const SubControlVolume& scv) const + { + if (scv.isOverlapping()) + DUNE_THROW(Dune::NotImplemented, "Geometry of overlapping scv"); + + assert(isBound()); + const auto geo = element().geometry(); + const GeometryHelper helper(geo); + return { + helper.getScvGeometryType(scv.indexInElement()), + helper.getScvCorners(scv.indexInElement()) + }; + } + + //! Geometry of a sub control volume face + typename SubControlVolumeFace::Traits::Geometry geometry(const SubControlVolumeFace& scvf) const + { + assert(isBound()); + const auto geo = element().geometry(); + if (scvf.boundary()) + { + GeometryHelper helper(geo); + const auto localScvfIdx = scvf.index() - helper.numInteriorScvf(); + const auto [localFacetIndex, isScvfLocalIdx] + = ggCache_->scvfBoundaryGeometryKeys(eIdx_)[localScvfIdx]; + return { + helper.getBoundaryScvfGeometryType(isScvfLocalIdx), + helper.getBoundaryScvfCorners(localFacetIndex, isScvfLocalIdx) + }; + } + else + { + GeometryHelper helper(geo); + return { + helper.getInteriorScvfGeometryType(scvf.index()), + helper.getScvfCorners(scvf.index()) + }; + } + } + +private: + const GGCache* ggCache_; + GridIndexType eIdx_; + + std::optional element_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/pq1bubble/fvgridgeometry.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/pq1bubble/fvgridgeometry.hh new file mode 100644 index 0000000..c4e98fa --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/pq1bubble/fvgridgeometry.hh @@ -0,0 +1,442 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup PQ1BubbleDiscretization + * \brief Base class for the finite volume geometry vector for the pq1bubble method + * This builds up the sub control volumes and sub control volume faces + * for each element of the grid partition. + */ +#ifndef DUMUX_DISCRETIZATION_PQ1BUBBLE_GRID_GEOMETRY_HH +#define DUMUX_DISCRETIZATION_PQ1BUBBLE_GRID_GEOMETRY_HH + +#include +#include + +#include + +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace Dumux { + +namespace Detail { +template +using PQ1BubbleGeometryHelper_t = Dune::Std::detected_or_t< + Dumux::PQ1BubbleGeometryHelper, + SpecifiesGeometryHelper, + T +>; +} // end namespace Detail + +template +struct PQ1BubbleMapperTraits :public DefaultMapperTraits +{ + using DofMapper = Dune::MultipleCodimMultipleGeomTypeMapper; + + /** + * \brief layout for elements and vertices + * + */ + static Dune::MCMGLayout layout() + { + return [](Dune::GeometryType gt, int dimgrid) { + return (gt.dim() == dimgrid) || (gt.dim() == 0); + }; + } +}; + +/*! + * \ingroup PQ1BubbleDiscretization + * \brief The default traits for the pq1bubble finite volume grid geometry + * Defines the scv and scvf types and the mapper types + * \tparam the grid view type + */ +template> +struct PQ1BubbleDefaultGridGeometryTraits +: public MapperTraits +{ + using SubControlVolume = PQ1BubbleSubControlVolume; + using SubControlVolumeFace = PQ1BubbleSubControlVolumeFace; + + template + using LocalView = PQ1BubbleFVElementGeometry; +}; + +/*! + * \ingroup PQ1BubbleDiscretization + * \brief Base class for the finite volume geometry vector for pq1bubble schemes + * This builds up the sub control volumes and sub control volume faces + * \note For caching enabled we store the fv geometries for the whole grid view which is memory intensive but faster + */ +template> +class PQ1BubbleFVGridGeometry +: public BaseGridGeometry +{ + using ThisType = PQ1BubbleFVGridGeometry; + using ParentType = BaseGridGeometry; + using GridIndexType = typename IndexTraits::GridIndex; + using LocalIndexType = typename IndexTraits::LocalIndex; + + using Element = typename GV::template Codim<0>::Entity; + using CoordScalar = typename GV::ctype; + static const int dim = GV::dimension; + static const int dimWorld = GV::dimensionworld; + + static_assert(dim > 1, "Only implemented for dim > 1"); + +public: + //! export the discretization method this geometry belongs to + using DiscretizationMethod = DiscretizationMethods::PQ1Bubble; + static constexpr DiscretizationMethod discMethod{}; + + //! export the type of the fv element geometry (the local view type) + using LocalView = typename Traits::template LocalView; + //! export the type of sub control volume + using SubControlVolume = typename Traits::SubControlVolume; + //! export the type of sub control volume + using SubControlVolumeFace = typename Traits::SubControlVolumeFace; + //! export the type of extrusion + using Extrusion = Extrusion_t; + //! export dof mapper type + using DofMapper = typename Traits::DofMapper; + //! export the finite element cache type + using FeCache = Dumux::PQ1BubbleFECache; + //! export the grid view type + using GridView = GV; + //! export whether the grid(geometry) supports periodicity + using SupportsPeriodicity = typename PeriodicGridTraits::SupportsPeriodicity; + + //! Constructor + PQ1BubbleFVGridGeometry(const GridView gridView) + : ParentType(gridView) + , dofMapper_(gridView, Traits::layout()) + , cache_(*this) + , periodicGridTraits_(this->gridView().grid()) + { + update_(); + } + + //! The dofMapper + const DofMapper& dofMapper() const + { return dofMapper_; } + + //! The total number of sub control volumes + std::size_t numScv() const + { return numScv_; } + + //! The total number of sub control volume faces + std::size_t numScvf() const + { return numScvf_; } + + //! The total number of boundary sub control volume faces + std::size_t numBoundaryScvf() const + { return numBoundaryScvf_; } + + //! The total number of degrees of freedom + std::size_t numDofs() const + { return this->dofMapper().size(); } + + //! update all geometries (call this after grid adaption) + void update(const GridView& gridView) + { + ParentType::update(gridView); + update_(); + } + + //! update all geometries (call this after grid adaption) + void update(GridView&& gridView) + { + ParentType::update(std::move(gridView)); + update_(); + } + + //! The finite element cache for creating local FE bases + const FeCache& feCache() const + { return feCache_; } + + //! If a vertex / d.o.f. is on the boundary + bool dofOnBoundary(GridIndexType dofIdx) const + { return boundaryDofIndices_[dofIdx]; } + + //! If a vertex / d.o.f. is on a periodic boundary + bool dofOnPeriodicBoundary(GridIndexType dofIdx) const + { return periodicDofMap_.count(dofIdx); } + + //! The index of the vertex / d.o.f. on the other side of the periodic boundary + GridIndexType periodicallyMappedDof(GridIndexType dofIdx) const + { return periodicDofMap_.at(dofIdx); } + + //! Returns the map between dofs across periodic boundaries + const std::unordered_map& periodicDofMap() const + { return periodicDofMap_; } + + //! local view of this object (constructed with the internal cache) + friend inline LocalView localView(const PQ1BubbleFVGridGeometry& gg) + { return { gg.cache_ }; } + +private: + + class PQ1BubbleGridGeometryCache + { + friend class PQ1BubbleFVGridGeometry; + public: + //! export the geometry helper type + using GeometryHelper = Detail::PQ1BubbleGeometryHelper_t; + + explicit PQ1BubbleGridGeometryCache(const PQ1BubbleFVGridGeometry& gg) + : gridGeometry_(&gg) + {} + + const PQ1BubbleFVGridGeometry& gridGeometry() const + { return *gridGeometry_; } + + //! Get the global sub control volume indices of an element + const std::vector& scvs(GridIndexType eIdx) const + { return scvs_[eIdx]; } + + //! Get the global sub control volume face indices of an element + const std::vector& scvfs(GridIndexType eIdx) const + { return scvfs_[eIdx]; } + + //! Returns whether one of the geometry's scvfs lies on a boundary + bool hasBoundaryScvf(GridIndexType eIdx) const + { return hasBoundaryScvf_[eIdx]; } + + //! Local mappings necessary to construct geometries of scvfs + const std::vector>& scvfBoundaryGeometryKeys(GridIndexType eIdx) const + { return scvfBoundaryGeometryKeys_.at(eIdx); } + + private: + void clear_() + { + scvs_.clear(); + scvfs_.clear(); + hasBoundaryScvf_.clear(); + scvfBoundaryGeometryKeys_.clear(); + } + + std::vector> scvs_; + std::vector> scvfs_; + std::vector hasBoundaryScvf_; + std::unordered_map>> scvfBoundaryGeometryKeys_; + + const PQ1BubbleFVGridGeometry* gridGeometry_; + }; + +public: + //! the cache type (only the caching implementation has this) + //! this alias should only be used by the local view implementation + using Cache = PQ1BubbleGridGeometryCache; + +private: + using GeometryHelper = typename Cache::GeometryHelper; + + void update_() + { + cache_.clear_(); + dofMapper_.update(this->gridView()); + + auto numElements = this->gridView().size(0); + cache_.scvs_.resize(numElements); + cache_.scvfs_.resize(numElements); + cache_.hasBoundaryScvf_.resize(numElements, false); + + boundaryDofIndices_.assign(numDofs(), false); + + numScv_ = 0; + numScvf_ = 0; + numBoundaryScvf_ = 0; + + // Build the scvs and scv faces + for (const auto& element : elements(this->gridView())) + { + auto eIdx = this->elementMapper().index(element); + + // get the element geometry + auto elementGeometry = element.geometry(); + const auto refElement = referenceElement(elementGeometry); + + // instantiate the geometry helper + GeometryHelper geometryHelper(elementGeometry); + + numScv_ += geometryHelper.numScv(); + // construct the sub control volumes + cache_.scvs_[eIdx].resize(geometryHelper.numScv()); + for (LocalIndexType scvLocalIdx = 0; scvLocalIdx < geometryHelper.numScv(); ++scvLocalIdx) + { + auto corners = geometryHelper.getScvCorners(scvLocalIdx); + cache_.scvs_[eIdx][scvLocalIdx] = SubControlVolume( + geometryHelper.scvVolume(scvLocalIdx, corners), + geometryHelper.dofPosition(scvLocalIdx), + Dumux::center(corners), + scvLocalIdx, + eIdx, + geometryHelper.dofIndex(this->dofMapper(), element, scvLocalIdx), + geometryHelper.isOverlappingScv(scvLocalIdx) + ); + } + + // construct the sub control volume faces + numScvf_ += geometryHelper.numInteriorScvf(); + cache_.scvfs_[eIdx].resize(geometryHelper.numInteriorScvf()); + LocalIndexType scvfLocalIdx = 0; + for (; scvfLocalIdx < geometryHelper.numInteriorScvf(); ++scvfLocalIdx) + { + const auto scvPair = geometryHelper.getScvPairForScvf(scvfLocalIdx); + const auto corners = geometryHelper.getScvfCorners(scvfLocalIdx); + const auto area = Dumux::convexPolytopeVolume( + geometryHelper.getInteriorScvfGeometryType(scvfLocalIdx), + [&](unsigned int i){ return corners[i]; } + ); + + cache_.scvfs_[eIdx][scvfLocalIdx] = SubControlVolumeFace( + Dumux::center(corners), + area, + geometryHelper.normal(corners, scvPair), + std::move(scvPair), + scvfLocalIdx, + geometryHelper.isOverlappingScvf(scvfLocalIdx) + ); + } + + // construct the sub control volume faces on the domain boundary + for (const auto& intersection : intersections(this->gridView(), element)) + { + if (intersection.boundary() && !intersection.neighbor()) + { + cache_.hasBoundaryScvf_[eIdx] = true; + + const auto localFacetIndex = intersection.indexInInside(); + const auto numBoundaryScvf = geometryHelper.numBoundaryScvf(localFacetIndex); + numScvf_ += numBoundaryScvf; + numBoundaryScvf_ += numBoundaryScvf; + + for (unsigned int isScvfLocalIdx = 0; isScvfLocalIdx < numBoundaryScvf; ++isScvfLocalIdx) + { + // find the scvs this scvf is belonging to + const auto scvPair = geometryHelper.getScvPairForBoundaryScvf(localFacetIndex, isScvfLocalIdx); + const auto corners = geometryHelper.getBoundaryScvfCorners(localFacetIndex, isScvfLocalIdx); + const auto area = Dumux::convexPolytopeVolume( + geometryHelper.getBoundaryScvfGeometryType(isScvfLocalIdx), + [&](unsigned int i){ return corners[i]; } + ); + cache_.scvfs_[eIdx].emplace_back( + Dumux::center(corners), + area, + intersection.centerUnitOuterNormal(), + std::move(scvPair), + scvfLocalIdx, + typename SubControlVolumeFace::Traits::BoundaryFlag{ intersection }, + geometryHelper.isOverlappingBoundaryScvf(localFacetIndex) + ); + + // store look-up map to construct boundary scvf geometries + cache_.scvfBoundaryGeometryKeys_[eIdx].emplace_back(std::array{{ + static_cast(localFacetIndex), + static_cast(isScvfLocalIdx) + }}); + + // increment local counter + scvfLocalIdx++; + } + + // TODO also move this to helper class + + // add all vertices on the intersection to the set of boundary vertices + for (int localVIdx = 0; localVIdx < numBoundaryScvf; ++localVIdx) + { + const auto vIdx = refElement.subEntity(localFacetIndex, 1, localVIdx, dim); + const auto vIdxGlobal = this->dofMapper().subIndex(element, vIdx, dim); + boundaryDofIndices_[vIdxGlobal] = true; + } + } + + // inform the grid geometry if we have periodic boundaries + else if (periodicGridTraits_.isPeriodic(intersection)) + { + this->setPeriodic(); + + // find the mapped periodic vertex of all vertices on periodic boundaries + const auto fIdx = intersection.indexInInside(); + const auto numFaceVerts = refElement.size(fIdx, 1, dim); + const auto eps = 1e-7*(elementGeometry.corner(1) - elementGeometry.corner(0)).two_norm(); + for (int localVIdx = 0; localVIdx < numFaceVerts; ++localVIdx) + { + const auto vIdx = refElement.subEntity(fIdx, 1, localVIdx, dim); + const auto vIdxGlobal = this->dofMapper().subIndex(element, vIdx, dim); + const auto vPos = elementGeometry.corner(vIdx); + + const auto& outside = intersection.outside(); + const auto outsideGeometry = outside.geometry(); + for (const auto& isOutside : intersections(this->gridView(), outside)) + { + // only check periodic vertices of the periodic neighbor + if (periodicGridTraits_.isPeriodic(isOutside)) + { + const auto fIdxOutside = isOutside.indexInInside(); + const auto numFaceVertsOutside = refElement.size(fIdxOutside, 1, dim); + for (int localVIdxOutside = 0; localVIdxOutside < numFaceVertsOutside; ++localVIdxOutside) + { + const auto vIdxOutside = refElement.subEntity(fIdxOutside, 1, localVIdxOutside, dim); + const auto vPosOutside = outsideGeometry.corner(vIdxOutside); + const auto shift = std::abs((this->bBoxMax()-this->bBoxMin())*intersection.centerUnitOuterNormal()); + if (std::abs((vPosOutside-vPos).two_norm() - shift) < eps) + periodicDofMap_[vIdxGlobal] = this->dofMapper().subIndex(outside, vIdxOutside, dim); + } + } + } + } + } + } + } + + // error check: periodic boundaries currently don't work for pq1bubble in parallel + if (this->isPeriodic() && this->gridView().comm().size() > 1) + DUNE_THROW(Dune::NotImplemented, "Periodic boundaries for pq1bubble method for parallel simulations!"); + } + + DofMapper dofMapper_; + + const FeCache feCache_; + + std::size_t numScv_; + std::size_t numScvf_; + std::size_t numBoundaryScvf_; + + // vertices on the boundary + std::vector boundaryDofIndices_; + + // a map for periodic boundary vertices + std::unordered_map periodicDofMap_; + + Cache cache_; + + PeriodicGridTraits periodicGridTraits_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/pq1bubble/geometryhelper.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/pq1bubble/geometryhelper.hh new file mode 100644 index 0000000..cc4ac92 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/pq1bubble/geometryhelper.hh @@ -0,0 +1,443 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup PQ1BubbleDiscretization + * \brief Helper class constructing the dual grid finite volume geometries + * for the cvfe discretizazion method + */ +#ifndef DUMUX_DISCRETIZATION_PQ1BUBBLE_GEOMETRY_HELPER_HH +#define DUMUX_DISCRETIZATION_PQ1BUBBLE_GEOMETRY_HELPER_HH + +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include + +namespace Dumux { + +//! Traits for an efficient corner storage for the PQ1Bubble method +template +struct PQ1BubbleMLGeometryTraits : public Dune::MultiLinearGeometryTraits +{ + // we use static vectors to store the corners as we know + // the maximum number of corners in advance (2^dim) + template< int mydim, int cdim > + struct CornerStorage + { + using Type = Dune::ReservedVector< Dune::FieldVector< ct, cdim >, (1<; + }; +}; + +namespace Detail::PQ1Bubble { + +template +struct OverlappingScvCorners; + +template<> +struct OverlappingScvCorners +{ + using Key = std::pair; // (i, codim) + static constexpr std::array, 1> keys = {{ + { Key{0, 1}, Key{1, 1} } + }}; +}; + +template<> +struct OverlappingScvCorners +{ + using Key = std::pair; // (i, codim) + static constexpr std::array, 1> keys = {{ + { Key{0, 1}, Key{1, 1}, Key{2, 1} } + }}; +}; + +template<> +struct OverlappingScvCorners +{ + using Key = std::pair; // (i, codim) + static constexpr std::array, 1> keys = {{ + { Key{2, 1}, Key{1, 1}, Key{0, 1}, Key{3, 1} } + }}; +}; + +template<> +struct OverlappingScvCorners +{ + using Key = std::pair; // (i, codim) + static constexpr std::array, 1> keys = {{ + { Key{0, 1}, Key{1, 1}, Key{2, 1}, Key{3, 1} } + }}; +}; + +template<> +struct OverlappingScvCorners +{ + using Key = std::pair; // (i, codim) + static constexpr std::array, 1> keys = {{ + { Key{0, 1}, Key{2, 1}, Key{3, 1}, Key{1, 1}, Key{4, 1}, Key{5, 1} } + }}; +}; + + +template +struct OverlappingScvfCorners; + +template<> +struct OverlappingScvfCorners +{ + using Key = std::pair; // (i, codim) + static constexpr std::array, 1> keys = {{ + { Key{0, 0} } + }}; +}; + +template<> +struct OverlappingScvfCorners +{ + using Key = std::pair; // (i, codim) + static constexpr std::array, 3> keys = {{ + { Key{0, 1}, Key{1, 1} }, + { Key{0, 1}, Key{2, 1} }, + { Key{1, 1}, Key{2, 1} } + }}; +}; + +template<> +struct OverlappingScvfCorners +{ + using Key = std::pair; // (i, codim) + static constexpr std::array, 4> keys = {{ + { Key{0, 1}, Key{2, 1} }, + { Key{2, 1}, Key{1, 1} }, + { Key{0, 1}, Key{3, 1} }, + { Key{1, 1}, Key{3, 1} } + }}; +}; + +template<> +struct OverlappingScvfCorners +{ + using Key = std::pair; // (i, codim) + static constexpr std::array, 10> keys = {{ + { Key{0, 1}, Key{1, 1}, Key{2, 1} }, + { Key{0, 1}, Key{1, 1}, Key{3, 1} }, + { Key{0, 1}, Key{2, 1}, Key{3, 1} }, + { Key{1, 1}, Key{2, 1}, Key{3, 1} } + }}; +}; + +template<> +struct OverlappingScvfCorners +{ + using Key = std::pair; // (i, codim) + static constexpr std::array, 8> keys = {{ + { Key{4, 1}, Key{0, 1}, Key{2, 1} }, + { Key{4, 1}, Key{2, 1}, Key{1, 1} }, + { Key{4, 1}, Key{0, 1}, Key{3, 1} }, + { Key{4, 1}, Key{1, 1}, Key{3, 1} }, + { Key{5, 1}, Key{0, 1}, Key{2, 1} }, + { Key{5, 1}, Key{2, 1}, Key{1, 1} }, + { Key{5, 1}, Key{0, 1}, Key{3, 1} }, + { Key{5, 1}, Key{1, 1}, Key{3, 1} } + }}; +}; + +} // end namespace Detail::PQ1Bubble + +/*! + * \ingroup PQ1BubbleDiscretization + * \brief A class to create sub control volume and sub control volume face geometries per element + */ +template +class PQ1BubbleGeometryHelper +{ + using Scalar = typename GridView::ctype; + using GlobalPosition = typename Dune::FieldVector; + using ScvCornerStorage = typename ScvType::Traits::CornerStorage; + using ScvfCornerStorage = typename ScvfType::Traits::CornerStorage; + using LocalIndexType = typename ScvType::Traits::LocalIndexType; + + using Element = typename GridView::template Codim<0>::Entity; + using Intersection = typename GridView::Intersection; + + static constexpr auto dim = GridView::dimension; + static constexpr auto dimWorld = GridView::dimensionworld; +public: + + PQ1BubbleGeometryHelper(const typename Element::Geometry& geometry) + : geo_(geometry) + , boxHelper_(geometry) + {} + + //! Create a vector with the scv corners + ScvCornerStorage getScvCorners(unsigned int localScvIdx) const + { + // proceed according to number of corners of the element + const auto type = geo_.type(); + const auto numBoxScv = boxHelper_.numScv(); + // reuse box geometry helper for the corner scvs + if (localScvIdx < numBoxScv) + return boxHelper_.getScvCorners(localScvIdx); + + const auto localOverlappingScvIdx = localScvIdx-numBoxScv; + if (type == Dune::GeometryTypes::triangle) + { + using Corners = Detail::PQ1Bubble::OverlappingScvCorners; + return Detail::Box::keyToCornerStorage(geo_, Corners::keys[localOverlappingScvIdx]); + } + else if (type == Dune::GeometryTypes::quadrilateral) + { + using Corners = Detail::PQ1Bubble::OverlappingScvCorners; + return Detail::Box::keyToCornerStorage(geo_, Corners::keys[localOverlappingScvIdx]); + } + else if (type == Dune::GeometryTypes::tetrahedron) + { + using Corners = Detail::PQ1Bubble::OverlappingScvCorners; + return Detail::Box::keyToCornerStorage(geo_, Corners::keys[localOverlappingScvIdx]); + } + else if (type == Dune::GeometryTypes::hexahedron) + { + using Corners = Detail::PQ1Bubble::OverlappingScvCorners; + return Detail::Box::keyToCornerStorage(geo_, Corners::keys[localOverlappingScvIdx]); + } + else + DUNE_THROW(Dune::NotImplemented, "PQ1Bubble scv geometries for dim=" << dim + << " dimWorld=" << dimWorld + << " type=" << type); + } + + Dune::GeometryType getScvGeometryType(unsigned int localScvIdx) const + { + // proceed according to number of corners of the element + const auto type = geo_.type(); + const auto numBoxScv = boxHelper_.numScv(); + if (localScvIdx < numBoxScv) + return Dune::GeometryTypes::cube(dim); + else if (type == Dune::GeometryTypes::simplex(dim)) + return Dune::GeometryTypes::simplex(dim); + else if (type == Dune::GeometryTypes::quadrilateral) + return Dune::GeometryTypes::quadrilateral; + else if (type == Dune::GeometryTypes::hexahedron) + return Dune::GeometryTypes::none(dim); // octahedron + else + DUNE_THROW(Dune::NotImplemented, "PQ1Bubble scv geometries for dim=" << dim + << " dimWorld=" << dimWorld + << " type=" << type); + } + + //! Create a vector with the corners of sub control volume faces + ScvfCornerStorage getScvfCorners(unsigned int localScvfIdx) const + { + // proceed according to number of corners + const auto type = geo_.type(); + const auto numBoxScvf = boxHelper_.numInteriorScvf(); + // reuse box geometry helper for the corner scvs + if (localScvfIdx < numBoxScvf) + return boxHelper_.getScvfCorners(localScvfIdx); + + const auto localOverlappingScvfIdx = localScvfIdx-numBoxScvf; + if (type == Dune::GeometryTypes::triangle) + { + using Corners = Detail::PQ1Bubble::OverlappingScvfCorners; + return Detail::Box::keyToCornerStorage(geo_, Corners::keys[localOverlappingScvfIdx]); + } + else if (type == Dune::GeometryTypes::quadrilateral) + { + using Corners = Detail::PQ1Bubble::OverlappingScvfCorners; + return Detail::Box::keyToCornerStorage(geo_, Corners::keys[localOverlappingScvfIdx]); + } + else if (type == Dune::GeometryTypes::tetrahedron) + { + using Corners = Detail::PQ1Bubble::OverlappingScvfCorners; + return Detail::Box::keyToCornerStorage(geo_, Corners::keys[localOverlappingScvfIdx]); + } + else if (type == Dune::GeometryTypes::hexahedron) + { + using Corners = Detail::PQ1Bubble::OverlappingScvfCorners; + return Detail::Box::keyToCornerStorage(geo_, Corners::keys[localOverlappingScvfIdx]); + } + else + DUNE_THROW(Dune::NotImplemented, "PQ1Bubble scvf geometries for dim=" << dim + << " dimWorld=" << dimWorld + << " type=" << type); + } + + Dune::GeometryType getInteriorScvfGeometryType(unsigned int localScvfIdx) const + { + const auto numBoxScvf = boxHelper_.numInteriorScvf(); + if (localScvfIdx < numBoxScvf) + return Dune::GeometryTypes::cube(dim-1); + else + return Dune::GeometryTypes::simplex(dim-1); + } + + //! Create the sub control volume face geometries on the boundary + ScvfCornerStorage getBoundaryScvfCorners(unsigned int localFacetIndex, + unsigned int indexInFacet) const + { + return boxHelper_.getBoundaryScvfCorners(localFacetIndex, indexInFacet); + } + + Dune::GeometryType getBoundaryScvfGeometryType(unsigned int localScvfIdx) const + { + return Dune::GeometryTypes::cube(dim-1); + } + + template = 0> + GlobalPosition normal(const ScvfCornerStorage& p, const std::array& scvPair) + { + auto normal = Dumux::crossProduct(p[1]-p[0], p[2]-p[0]); + normal /= normal.two_norm(); + + GlobalPosition v = dofPosition(scvPair[1]) - dofPosition(scvPair[0]); + + const auto s = v*normal; + if (std::signbit(s)) + normal *= -1; + + return normal; + } + + template = 0> + GlobalPosition normal(const ScvfCornerStorage& p, const std::array& scvPair) + { + //! obtain normal vector by 90° counter-clockwise rotation of t + const auto t = p[1] - p[0]; + GlobalPosition normal({-t[1], t[0]}); + normal /= normal.two_norm(); + + GlobalPosition v = dofPosition(scvPair[1]) - dofPosition(scvPair[0]); + + const auto s = v*normal; + if (std::signbit(s)) + normal *= -1; + + return normal; + } + + //! the wrapped element geometry + const typename Element::Geometry& elementGeometry() const + { return geo_; } + + //! number of interior sub control volume faces + std::size_t numInteriorScvf() const + { + return boxHelper_.numInteriorScvf() + referenceElement(geo_).size(dim); + } + + //! number of boundary sub control volume faces for face localFacetIndex + std::size_t numBoundaryScvf(unsigned int localFacetIndex) const + { + return referenceElement(geo_).size(localFacetIndex, 1, dim); + } + + //! number of sub control volumes (number of codim-1 entities) + std::size_t numScv() const + { + return boxHelper_.numScv() + 1; + } + + //! get scv volume + Scalar scvVolume(unsigned int localScvIdx, const ScvCornerStorage& p) const + { + const auto scvType = getScvGeometryType(localScvIdx); + if constexpr (dim == 3) + if (scvType == Dune::GeometryTypes::none(dim)) + return octahedronVolume_(p); + + return Dumux::convexPolytopeVolume( + scvType, + [&](unsigned int i){ return p[i]; } + ); + } + + template + auto dofIndex(const DofMapper& dofMapper, const Element& element, unsigned int localScvIdx) const + { + if (localScvIdx < numScv()-1) + return dofMapper.subIndex(element, localScvIdx, dim); + else + return dofMapper.index(element); + } + + GlobalPosition dofPosition(unsigned int localScvIdx) const + { + if (localScvIdx < numScv()-1) + return geo_.corner(localScvIdx); + else + return geo_.center(); + } + + std::array getScvPairForScvf(unsigned int localScvfIndex) const + { + const auto numEdges = referenceElement(geo_).size(dim-1); + if (localScvfIndex < numEdges) + return { + static_cast(referenceElement(geo_).subEntity(localScvfIndex, dim-1, 0, dim)), + static_cast(referenceElement(geo_).subEntity(localScvfIndex, dim-1, 1, dim)) + }; + else + return { + static_cast(numScv()-1), + static_cast(localScvfIndex-numEdges) + }; + } + + std::array getScvPairForBoundaryScvf(unsigned int localFacetIndex, unsigned int localIsScvfIndex) const + { + const LocalIndexType insideScvIdx + = static_cast(referenceElement(geo_).subEntity(localFacetIndex, 1, localIsScvfIndex, dim)); + return { insideScvIdx, insideScvIdx }; + } + + bool isOverlappingScvf(unsigned int localScvfIndex) const + { + if (localScvfIndex < boxHelper_.numInteriorScvf()) + return false; + else + return true; + } + + bool isOverlappingBoundaryScvf(unsigned int localFacetIndex) const + { + return false; + } + + bool isOverlappingScv(unsigned int localScvIndex) const + { + if (localScvIndex < boxHelper_.numScv()) + return false; + else + return true; + } + +private: + Scalar octahedronVolume_(const ScvCornerStorage& p) const + { + using std::abs; + return 1.0/6.0 * ( + abs(Dumux::tripleProduct(p[4]-p[0], p[1]-p[0], p[2]-p[0])) + + abs(Dumux::tripleProduct(p[5]-p[0], p[1]-p[0], p[2]-p[0])) + ); + } + + const typename Element::Geometry& geo_; //!< Reference to the element geometry + Dumux::BoxGeometryHelper boxHelper_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/pq1bubble/pq1bubblefecache.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/pq1bubble/pq1bubblefecache.hh new file mode 100644 index 0000000..823205d --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/pq1bubble/pq1bubblefecache.hh @@ -0,0 +1,65 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup PQ1BubbleDiscretization + * \brief A finite element cache for P1/Q1 function with bubble + */ +#ifndef DUMUX_DISCRETIZATION_PQ1BUBBLE_FECACHE_HH +#define DUMUX_DISCRETIZATION_PQ1BUBBLE_FECACHE_HH + +#include + +#include +#include + +#include +#include + +#include + +namespace Dumux { + +template< class CoordScalar, class Scalar, unsigned int dim> +class PQ1BubbleFECache +{ + static_assert(dim == 2 || dim == 3, "P1/Q1 bubble FE spaces only implemented for 2D and 3D grids"); + + // These are so-called non-conforming finite element spaces + // the local basis is only continuous at given points on the faces + using P1Bubble = Dumux::PQ1BubbleLocalFiniteElement; + using Q1Bubble = Dumux::PQ1BubbleLocalFiniteElement; + +public: + using FiniteElementType = Dune::LocalFiniteElementVirtualInterface; + + PQ1BubbleFECache() + : p1BubbleBasis_(std::make_unique>(P1Bubble{})) + , q1BubbleBasis_(std::make_unique>(Q1Bubble{})) + {} + + //! Get local finite element for given GeometryType + const FiniteElementType& get(const Dune::GeometryType& gt) const + { + if (gt.isSimplex()) + return *p1BubbleBasis_; + if (gt.isCube()) + return *q1BubbleBasis_; + else + DUNE_THROW(Dune::NotImplemented, + "Lagrange bubble local finite element for geometry type " << gt + ); + } + +private: + std::unique_ptr p1BubbleBasis_; + std::unique_ptr q1BubbleBasis_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/pq1bubble/pq1bubblelocalfiniteelement.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/pq1bubble/pq1bubblelocalfiniteelement.hh new file mode 100644 index 0000000..b18bdde --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/pq1bubble/pq1bubblelocalfiniteelement.hh @@ -0,0 +1,346 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup PQ1BubbleDiscretization + * \brief Evaluate P1/Q1 basis with bubble function + */ +#ifndef DUMUX_DISCRETIZATION_PQ1BUBBLE_LOCAL_FINITE_ELEMENT_HH +#define DUMUX_DISCRETIZATION_PQ1BUBBLE_LOCAL_FINITE_ELEMENT_HH + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include + +namespace Dumux::Detail { + +/*! + * \brief P1/Q1 + Bubble on the reference element + * + * \tparam D Type to represent the field in the domain + * \tparam R Type to represent the field in the range + * \tparam dim Dimension of the domain element + * \tparam typeId The geometry type + */ +template +class PQ1BubbleLocalBasis +{ + using PQ1FiniteElement = std::conditional_t< + Dune::GeometryType{ typeId } == Dune::GeometryTypes::cube(dim), + Dune::LagrangeCubeLocalFiniteElement, + Dune::LagrangeSimplexLocalFiniteElement + >; + static constexpr std::size_t numDofs + = Dune::GeometryType{ typeId } == Dune::GeometryTypes::cube(dim) ? (1<, + R, 1, Dune::FieldVector, + Dune::FieldMatrix + >; + + PQ1BubbleLocalBasis() + : pq1FiniteElement_{} + { + // precompute center values for normalization + const auto& p1Basis = pq1FiniteElement_.localBasis(); + const auto refElement = Dune::referenceElement(type()); + const auto& center = refElement.position(0, 0); + p1Basis.evaluateFunction(center, pq1AtCenter_); + } + + /*! + * \brief Number of shape functions (one for each vertex and one in the element) + */ + static constexpr unsigned int size() + { return numDofs; } + + /*! + * \brief Evaluate all shape functions + */ + void evaluateFunction(const typename Traits::DomainType& x, + std::vector& out) const + { + out.reserve(size()); + const auto& p1Basis = pq1FiniteElement_.localBasis(); + p1Basis.evaluateFunction(x, out); + const auto bubble = evaluateBubble_(x); + out.resize(size()); + out.back() = bubble; + for (int i = 0; i < numDofs-1; ++i) + out[i] -= pq1AtCenter_[i]*out.back(); + } + + /*! + * \brief Evaluate the Jacobians of all shape functions + */ + void evaluateJacobian(const typename Traits::DomainType& x, + std::vector& out) const + { + out.reserve(size()); + const auto& p1Basis = pq1FiniteElement_.localBasis(); + p1Basis.evaluateJacobian(x, out); + + std::vector shapeValues; + p1Basis.evaluateFunction(x, shapeValues); + + const auto bubbleJacobian = evaluateBubbleJacobian_(x); + + for (int i = 0; i < numDofs-1; ++i) + for (int k = 0; k < dim; ++k) + out[i][0][k] -= pq1AtCenter_[i]*bubbleJacobian[0][k]; + + out.resize(size()); + out.back() = bubbleJacobian; + } + + /** \brief Evaluate partial derivatives of any order of all shape functions + * + * \param order Order of the partial derivatives, in the classic multi-index notation + * \param in Position where to evaluate the derivatives + * \param[out] out The desired partial derivatives + */ + void partial(const std::array& order, + const typename Traits::DomainType& in, + std::vector& out) const + { + DUNE_THROW(Dune::NotImplemented, "Partial derivatives"); + } + + /*! + * \brief Evaluate the Jacobians of all shape functions + * we are actually cubic/quartic but cannot represent all cubic/quartic polynomials + */ + static constexpr unsigned int order() + { + return 1; + } + + /*! + * \brief The reference element type + */ + static constexpr Dune::GeometryType type() + { + return { typeId }; + } +private: + // evaluate bubble function at x + typename Traits::RangeType evaluateBubble_(const typename Traits::DomainType& x) const + { + if constexpr (type() == Dune::GeometryTypes::simplex(dim)) + { + if constexpr (dim == 2) + return 27*x[0]*x[1]*(1-x[0]-x[1]); + else if constexpr (dim == 3) + return 256*x[0]*x[1]*x[2]*(1-x[0]-x[1]-x[2]); + } + else if constexpr (type() == Dune::GeometryTypes::cube(dim)) + { + if constexpr (dim == 2) + return 16*x[0]*x[1]*(1-x[0])*(1-x[1]); + else if constexpr (dim == 3) + return 64*x[0]*x[1]*x[2]*(1-x[0])*(1-x[1])*(1-x[2]); + } + else + DUNE_THROW(Dune::NotImplemented, "Bubble function for " << type()); + } + + // evaluate bubble function at x + typename Traits::JacobianType evaluateBubbleJacobian_(const typename Traits::DomainType& x) const + { + if constexpr (type() == Dune::GeometryTypes::simplex(dim)) + { + if constexpr (dim == 2) + return {{27*(x[1]*(1-x[0]-x[1]) - x[0]*x[1]), + 27*(x[0]*(1-x[0]-x[1]) - x[0]*x[1])}}; + else if constexpr (dim == 3) + return {{256*(x[1]*x[2]*(1-x[0]-x[1]-x[2]) - x[0]*x[1]*x[2]), + 256*(x[0]*x[2]*(1-x[0]-x[1]-x[2]) - x[0]*x[1]*x[2]), + 256*(x[0]*x[1]*(1-x[0]-x[1]-x[2]) - x[0]*x[1]*x[2])}}; + } + else if constexpr (type() == Dune::GeometryTypes::cube(dim)) + { + if constexpr (dim == 2) + return {{16*(x[1]*(1-x[0])*(1-x[1]) - x[0]*x[1]*(1-x[1])), + 16*(x[0]*(1-x[0])*(1-x[1]) - x[0]*x[1]*(1-x[0]))}}; + else if constexpr (dim == 3) + return {{64*(x[1]*x[2]*(1-x[0])*(1-x[1])*(1-x[2]) - x[0]*x[1]*x[2]*(1-x[1]))*(1-x[2]), + 64*(x[0]*x[2]*(1-x[0])*(1-x[1])*(1-x[2]) - x[0]*x[1]*x[2]*(1-x[0]))*(1-x[2]), + 64*(x[0]*x[1]*(1-x[0])*(1-x[1])*(1-x[2]) - x[0]*x[1]*x[2]*(1-x[0]))*(1-x[1])}}; + } + else + DUNE_THROW(Dune::NotImplemented, "Bubble function for " << type() << " dim = " << dim); + } + + PQ1FiniteElement pq1FiniteElement_; + std::vector pq1AtCenter_; +}; + +/*! + * \brief Associations of the P1/Q1 + Bubble degrees of freedom to the facets of the reference element + * \tparam dim Dimension of the reference element + * \tparam typeId The geometry type + */ +template +class PQ1BubbleLocalCoefficients +{ + static constexpr std::size_t numDofs + = Dune::GeometryType{ typeId } == Dune::GeometryTypes::cube(dim) ? (1< localKeys_; +}; + +/*! + * \brief Evaluate the degrees of freedom of a P1 + Bubble basis + * + * \tparam LocalBasis The corresponding set of shape functions + */ +template +class PQ1BubbleLocalInterpolation +{ +public: + /*! + * \brief Evaluate a given function at the vertices and the cell midpoint + * + * \tparam F Type of function to evaluate + * \tparam C Type used for the values of the function + * \param[in] f Function to evaluate (call operator that gets a local position) + * \param[out] out Array of function values + */ + template + void interpolate (const F& f, std::vector& out) const + { + constexpr auto dim = LocalBasis::Traits::dimDomain; + + out.resize(LocalBasis::size()); + + const auto refElement = Dune::referenceElement(LocalBasis::type()); + + // Evaluate at the vertices and at the center + for (int i = 0; i < refElement.size(dim); ++i) + out[i] = f(refElement.position(i, dim)); + out.back() = f(refElement.position(0, 0)); + } +}; + +} // end namespace Dumux::Detail + +namespace Dumux { + +/*! + * \brief P1/Q1 + Bubble finite element + * + * \tparam D type used for domain coordinates + * \tparam R type used for function values + * \tparam dim dimension of the reference element + * \tparam typeId The geometry type + */ +template +class PQ1BubbleLocalFiniteElement +{ + using Basis = Detail::PQ1BubbleLocalBasis; + using Coefficients = Detail::PQ1BubbleLocalCoefficients; + using Interpolation = Detail::PQ1BubbleLocalInterpolation; + + static constexpr Dune::GeometryType gt = Dune::GeometryType{ typeId }; + static_assert( + gt == Dune::GeometryTypes::cube(dim) || gt == Dune::GeometryTypes::simplex(dim), + "Only implemented for cubes and simplices" + ); + +public: + using Traits = Dune::LocalFiniteElementTraits; + + /*! + * \brief Returns the local basis, i.e., the set of shape functions + */ + const typename Traits::LocalBasisType& localBasis() const + { + return basis_; + } + + /*! + * \brief Returns the assignment of the degrees of freedom to the element subentities + */ + const typename Traits::LocalCoefficientsType& localCoefficients() const + { + return coefficients_; + } + + /*! + * \brief Returns object that evaluates degrees of freedom + */ + const typename Traits::LocalInterpolationType& localInterpolation() const + { + return interpolation_; + } + + /*! + * \brief The number of coefficients in the basis + */ + static constexpr std::size_t size() + { + return Basis::size(); + } + + /*! + * \brief The reference element type that the local finite element is defined on + */ + static constexpr Dune::GeometryType type() + { + return Traits::LocalBasisType::type(); + } + +private: + Basis basis_; + Coefficients coefficients_; + Interpolation interpolation_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/pq1bubble/subcontrolvolume.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/pq1bubble/subcontrolvolume.hh new file mode 100644 index 0000000..2ce5577 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/pq1bubble/subcontrolvolume.hh @@ -0,0 +1,124 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup PQ1BubbleDiscretization + * \brief the sub control volume for the cvfe scheme + */ +#ifndef DUMUX_DISCRETIZATION_PQ1BUBBLE_SUBCONTROLVOLUME_HH +#define DUMUX_DISCRETIZATION_PQ1BUBBLE_SUBCONTROLVOLUME_HH + +#include +#include + +#include +#include +#include +#include + +namespace Dumux { + +/*! + * \ingroup PQ1BubbleDiscretization + * \brief Default traits class to be used for the sub-control volumes + * for the pq1bubble scheme + * \tparam GV the type of the grid view + */ +template +struct PQ1BubbleDefaultScvGeometryTraits +{ + using Grid = typename GridView::Grid; + + static const int dim = Grid::dimension; + static const int dimWorld = Grid::dimensionworld; + + using GridIndexType = typename IndexTraits::GridIndex; + using LocalIndexType = typename IndexTraits::LocalIndex; + using Scalar = typename Grid::ctype; + using GeometryTraits = PQ1BubbleMLGeometryTraits; + using Geometry = Dune::MultiLinearGeometry; + using CornerStorage = typename GeometryTraits::template CornerStorage::Type; + using GlobalPosition = typename CornerStorage::value_type; +}; + +/*! + * \ingroup PQ1BubbleDiscretization + * \brief the sub control volume for the pq1bubble scheme + * \tparam GV the type of the grid view + * \tparam T the scvf geometry traits + */ +template> +class PQ1BubbleSubControlVolume +{ + using GlobalPosition = typename T::GlobalPosition; + using Scalar = typename T::Scalar; + using GridIndexType = typename T::GridIndexType; + using LocalIndexType = typename T::LocalIndexType; + +public: + //! state the traits public and thus export all types + using Traits = T; + + PQ1BubbleSubControlVolume() = default; + + PQ1BubbleSubControlVolume(const Scalar& volume, + const GlobalPosition& dofPosition, + const GlobalPosition& center, + const LocalIndexType indexInElement, + const GridIndexType eIdx, + const GridIndexType dofIdx, + bool overlapping = false) + + : center_(center) + , dofPosition_(dofPosition) + , volume_(volume) + , indexInElement_(indexInElement) + , eIdx_(eIdx) + , dofIdx_(dofIdx) + , overlapping_(overlapping) + {} + + //! The center of the sub control volume + const GlobalPosition& center() const + { return center_; } + + //! The position of the degree of freedom + const GlobalPosition& dofPosition() const + { return dofPosition_; } + + Scalar volume() const + { return volume_; } + + //! returns true if the sub control volume is overlapping with another scv + bool isOverlapping() const + { return overlapping_; } + + GridIndexType dofIndex() const + { return dofIdx_; } + + LocalIndexType indexInElement() const + { return indexInElement_; } + + GridIndexType elementIndex() const + { return eIdx_; } + + LocalIndexType localDofIndex() const + { return indexInElement_; } + +private: + GlobalPosition center_; + GlobalPosition dofPosition_; + Scalar volume_; + LocalIndexType indexInElement_; + GridIndexType eIdx_; + GridIndexType dofIdx_; + bool overlapping_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/pq1bubble/subcontrolvolumeface.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/pq1bubble/subcontrolvolumeface.hh new file mode 100644 index 0000000..ef92638 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/pq1bubble/subcontrolvolumeface.hh @@ -0,0 +1,170 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup PQ1BubbleDiscretization + * \brief Base class for a sub control volume face + */ +#ifndef DUMUX_DISCRETIZATION_PQ1BUBBLE_SUBCONTROLVOLUMEFACE_HH +#define DUMUX_DISCRETIZATION_PQ1BUBBLE_SUBCONTROLVOLUMEFACE_HH + +#include + +#include +#include + +#include +#include +#include +#include + +namespace Dumux { + +/*! + * \ingroup PQ1BubbleDiscretization + * \brief Default traits class to be used for the sub-control volume faces + * for the cvfe scheme + * \tparam GV the type of the grid view + */ +template +struct PQ1BubbleDefaultScvfGeometryTraits +{ + using Grid = typename GridView::Grid; + static constexpr int dim = Grid::dimension; + static constexpr int dimWorld = Grid::dimensionworld; + using GridIndexType = typename IndexTraits::GridIndex; + using LocalIndexType = typename IndexTraits::LocalIndex; + using Scalar = typename Grid::ctype; + using GeometryTraits = PQ1BubbleMLGeometryTraits; + using Geometry = Dune::MultiLinearGeometry; + using CornerStorage = typename GeometryTraits::template CornerStorage::Type; + using GlobalPosition = typename CornerStorage::value_type; + using BoundaryFlag = Dumux::BoundaryFlag; +}; + +/*! + * \ingroup PQ1BubbleDiscretization + * \brief Class for a sub control volume face in the cvfe method, i.e a part of the boundary + * of a sub control volume we compute fluxes on. We simply use the base class here. + * \tparam GV the type of the grid view + * \tparam T the scvf geometry traits + */ +template > +class PQ1BubbleSubControlVolumeFace +: public SubControlVolumeFaceBase, T> +{ + using ThisType = PQ1BubbleSubControlVolumeFace; + using ParentType = SubControlVolumeFaceBase; + using GridIndexType = typename T::GridIndexType; + using LocalIndexType = typename T::LocalIndexType; + using Scalar = typename T::Scalar; + using CornerStorage = typename T::CornerStorage; + using Geometry = typename T::Geometry; + using BoundaryFlag = typename T::BoundaryFlag; + +public: + //! export the type used for global coordinates + using GlobalPosition = typename T::GlobalPosition; + //! state the traits public and thus export all types + using Traits = T; + + //! The default constructor + PQ1BubbleSubControlVolumeFace() = default; + + //! Constructor for inner scvfs + PQ1BubbleSubControlVolumeFace(const GlobalPosition& center, + const Scalar area, + const GlobalPosition& normal, + const std::array& scvIndices, + const LocalIndexType localScvfIdx, + bool overlapping = false) + : center_(center) + , unitOuterNormal_(normal) + , area_(area) + , localScvfIdx_(localScvfIdx) + , scvIndices_(scvIndices) + , boundary_(false) + , overlapping_(overlapping) + , boundaryFlag_{} + { } + + //! Constructor for boundary scvfs + PQ1BubbleSubControlVolumeFace(const GlobalPosition& center, + const Scalar area, + const GlobalPosition& normal, + const std::array& scvIndices, + const LocalIndexType localScvfIdx, + const BoundaryFlag& bFlag, + bool overlapping = false) + : center_(center) + , unitOuterNormal_(normal) + , area_(area) + , localScvfIdx_(localScvfIdx) + , scvIndices_(scvIndices) + , boundary_(true) + , overlapping_(overlapping) + , boundaryFlag_(bFlag) + {} + + //! The center of the sub control volume face + const GlobalPosition& center() const + { return center_; } + + //! The integration point for flux evaluations in global coordinates + const GlobalPosition& ipGlobal() const + { return center_; } + + //! The area of the sub control volume face + Scalar area() const + { return area_; } + + //! returns true if the sub control volume face is overlapping with another scv + bool isOverlapping() const + { return overlapping_; } + + bool boundary() const + { return boundary_; } + + //! The unit outer normal + const GlobalPosition unitOuterNormal() const + { return unitOuterNormal_; } + + //! Index of the inside sub control volume + GridIndexType insideScvIdx() const + { return scvIndices_[0]; } + + //! index of the outside sub control volume + GridIndexType outsideScvIdx() const + { return scvIndices_[1]; } + + //! The number of scvs on the outside of this face + std::size_t numOutsideScvs() const + { return static_cast(!boundary()); } + + //! The local index of this sub control volume face + LocalIndexType index() const + { return localScvfIdx_; } + + //! Return the boundary flag + typename BoundaryFlag::value_type boundaryFlag() const + { return boundaryFlag_.get(); } + +private: + GlobalPosition center_; + GlobalPosition unitOuterNormal_; + Scalar area_; + LocalIndexType localScvfIdx_; + std::array scvIndices_; + bool boundary_; + bool overlapping_; + BoundaryFlag boundaryFlag_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/scvandscvfiterators.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/scvandscvfiterators.hh new file mode 100644 index 0000000..a61252d --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/scvandscvfiterators.hh @@ -0,0 +1,164 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Discretization + * \brief Class providing iterators over sub control volumes and sub control volume faces of an element. + */ +#ifndef DUMUX_SCV_AND_SCVF_ITERATORS_HH +#define DUMUX_SCV_AND_SCVF_ITERATORS_HH + +#include +#include + +namespace Dumux { + +/*! + * \ingroup Discretization + * \brief Iterators over sub control volumes + * \note usage: for(const auto& scv : scvs(fvGeometry)) + */ +template +class ScvIterator : public Dune::ForwardIteratorFacade, + const SubControlVolume> +{ + using ThisType = ScvIterator; + using Iterator = typename Vector::const_iterator; +public: + ScvIterator(const Iterator& it, const FVElementGeometry& fvGeometry) + : it_(it), fvGeometryPtr_(&fvGeometry) {} + + ScvIterator() : it_(Iterator()), fvGeometryPtr_(nullptr) {} + + //! dereferencing yields a subcontrol volume + const SubControlVolume& dereference() const + { + return fvGeometryPtr_->scv(*it_); + } + + bool equals(const ThisType& other) const + { + return it_ == other.it_; + } + + void increment() + { + ++it_; + } + +private: + Iterator it_; + const FVElementGeometry* fvGeometryPtr_; +}; + +/*! + * \ingroup Discretization + * \brief Iterators over sub control volume faces of an fv geometry + * \note usage: for(const auto& scvf : scvfs(fvGeometry)) + */ +template +class ScvfIterator : public Dune::ForwardIteratorFacade, + const SubControlVolumeFace> +{ + using ThisType = ScvfIterator; + using Iterator = typename Vector::const_iterator; +public: + ScvfIterator(const Iterator& it, const FVElementGeometry& fvGeometry) + : it_(it), fvGeometryPtr_(&fvGeometry) {} + + ScvfIterator() : it_(Iterator()), fvGeometryPtr_(nullptr) {} + + //! dereferencing yields a subcontrol volume face + const SubControlVolumeFace& dereference() const + { + return fvGeometryPtr_->scvf(*it_); + } + + bool equals(const ThisType& other) const + { + return it_ == other.it_; + } + + void increment() + { + it_++; + } + +private: + Iterator it_; + const FVElementGeometry* fvGeometryPtr_; +}; + +/*! + * \ingroup Discretization + * \brief Iterators over sub control volume faces of an fv geometry and a given sub control volume + * \note usage: for(const auto& scvf : scvfs(fvGeometry, scv)) + */ +template +class SkippingScvfIterator : public Dune::ForwardIteratorFacade, + const SubControlVolumeFace> +{ + using ThisType = SkippingScvfIterator; + using Iterator = typename Vector::const_iterator; +public: + + SkippingScvfIterator() : it_(Iterator()), fvGeometryPtr_(nullptr) {} + + static ThisType makeBegin(const Vector& vector, const FVElementGeometry& fvGeometry, const std::size_t scvIdx) + { + auto begin = vector.begin(); + const auto end = vector.end(); + + while (begin != end && fvGeometry.scvf(*begin).insideScvIdx() != scvIdx) + ++begin; + + return SkippingScvfIterator(begin, end, fvGeometry, scvIdx); + } + + static ThisType makeEnd(const Vector& vector, const FVElementGeometry& fvGeometry, const std::size_t scvIdx) + { + return SkippingScvfIterator(vector.end(), vector.end(), fvGeometry, scvIdx); + } + + //! dereferencing yields a subcontrol volume face + const SubControlVolumeFace& dereference() const + { + return fvGeometryPtr_->scvf(*it_); + } + + bool equals(const ThisType& other) const + { + return it_ == other.it_; + } + + void increment() + { + ++it_; + while (it_ != itEnd_ && dereference().insideScvIdx() != scvIdx_) + ++it_; + } + +private: + + SkippingScvfIterator(const Iterator& itBegin, const Iterator& itEnd, const FVElementGeometry& fvGeometry, const std::size_t scvIdx) + : it_(itBegin), fvGeometryPtr_(&fvGeometry), itEnd_(itEnd), scvIdx_(scvIdx) {} + + Iterator it_; + const FVElementGeometry* fvGeometryPtr_; + const Iterator itEnd_; + std::size_t scvIdx_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/staggered/elementsolution.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/staggered/elementsolution.hh new file mode 100644 index 0000000..0ddab11 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/staggered/elementsolution.hh @@ -0,0 +1,72 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup StaggeredDiscretization + * \brief The local element solution class for staggered methods + */ +#ifndef DUMUX_STAGGERED_ELEMENT_SOLUTION_HH +#define DUMUX_STAGGERED_ELEMENT_SOLUTION_HH + +#include +#include +#include + +namespace Dumux { + +/*! + * \ingroup StaggeredDiscretization + * \brief Helper function to create a PrimaryVariables object from CellCenterPrimaryVariables + * \tparam PrimaryVariables The type of the desired primary variables object + * \tparam CellCenterPrimaryVariables The type of the cell center (input) primary variables object + * \param cellCenterPriVars The cell center (input) primary variables object + */ +template +PrimaryVariables makePriVarsFromCellCenterPriVars(const CellCenterPrimaryVariables& cellCenterPriVars) +{ + static_assert(int(PrimaryVariables::dimension) > int(CellCenterPrimaryVariables::dimension), + "PrimaryVariables' size must be greater than the one of CellCenterPrimaryVariables"); + + PrimaryVariables priVars(0.0); + constexpr auto offset = PrimaryVariables::dimension - CellCenterPrimaryVariables::dimension; + for (std::size_t i = 0; i < cellCenterPriVars.size(); ++i) + priVars[i + offset] = cellCenterPriVars[i]; + return priVars; +} + +template +using StaggeredElementSolution = Dune::BlockVector; + +/*! + * \ingroup Discretization + * \brief Make an element solution for staggered schemes + * \note This is e.g. used to construct an element solution at Dirichlet boundaries + */ +template +auto elementSolution(PrimaryVariables&& priVars) +-> std::enable_if_t> +{ + return StaggeredElementSolution({std::move(priVars)}); +} + +/*! + * \ingroup Discretization + * \brief Helper function to create an elementSolution from cell center primary variables + * \tparam PrimaryVariables The type of the desired primary variables object + * \tparam CellCenterPrimaryVariables The type of the cell center (input) primary variables object + * \param cellCenterPriVars The cell center (input) primary variables object + */ +template +StaggeredElementSolution makeElementSolutionFromCellCenterPrivars(const CellCenterPrimaryVariables& cellCenterPriVars) +{ + return StaggeredElementSolution({makePriVarsFromCellCenterPriVars(cellCenterPriVars)}); +} + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/subcontrolvolumebase.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/subcontrolvolumebase.hh new file mode 100644 index 0000000..77bf99a --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/subcontrolvolumebase.hh @@ -0,0 +1,84 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Discretization + * \brief Base class for a sub control volume + */ +#ifndef DUMUX_SUBCONTROLVOLUME_HH +#define DUMUX_SUBCONTROLVOLUME_HH + +namespace Dumux { + +/*! + * \ingroup Discretization + * \brief Base class for a sub control volume, i.e a part of the control + * volume we are making the balance for. Defines the general interface. + * \tparam Imp the implementation + * \tparam ScvGeometryTraits traits of this class + */ +template +class SubControlVolumeBase +{ + using Implementation = Imp; + using GridIndexType = typename ScvGeometryTraits::GridIndexType; + using LocalIndexType = typename ScvGeometryTraits::LocalIndexType; + using Scalar = typename ScvGeometryTraits::Scalar; + +public: + //! export the type used for global coordinates + using GlobalPosition = typename ScvGeometryTraits::GlobalPosition; + //! state the traits public and thus export all types + using Traits = ScvGeometryTraits; + + //! The center of the sub control volume + GlobalPosition center() const + { + return asImp_().center(); + } + + //! The volume of the sub control volume + Scalar volume() const + { + return asImp_().volume(); + } + + //! The index of the dof this scv is embedded in (ccfv) + GridIndexType dofIndex() const + { + return asImp_().dofIndex(); + } + + //! The index of the dof this scv is embedded in (box) + LocalIndexType localDofIndex() const + { + return asImp_().localDofIndex(); + } + + // The position of the dof this scv is embedded in + GlobalPosition dofPosition() const + { + return asImp_().dofPosition(); + } + + //! The global index of the element this scv is embedded in + GridIndexType elementIndex() const + { + return asImp_().elementIndex(); + } + +private: + const Implementation& asImp_() const + { return *static_cast(this);} + + Implementation& asImp_() + { return *static_cast(this);} +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/subcontrolvolumefacebase.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/subcontrolvolumefacebase.hh new file mode 100644 index 0000000..0624f82 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/discretization/subcontrolvolumefacebase.hh @@ -0,0 +1,101 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Discretization + * \brief Base class for a sub control volume face + */ +#ifndef DUMUX_DISCRETIZATION_SUBCONTROLVOLUMEFACEBASE_HH +#define DUMUX_DISCRETIZATION_SUBCONTROLVOLUMEFACEBASE_HH + +#include +#include + +namespace Dumux { + +/*! + * \ingroup Discretization + * \brief Base class for a sub control volume face, i.e a part of the boundary + * of a sub control volume we computing a flux on. + * \tparam Imp the implementation + * \tparam ScvGeometryTraits traits of this class + */ +template +class SubControlVolumeFaceBase +{ + using Implementation = Imp; + using GridIndexType = typename ScvfGeometryTraits::GridIndexType; + using Scalar = typename ScvfGeometryTraits::Scalar; + +public: + //! export the type used for global coordinates + using GlobalPosition = typename ScvfGeometryTraits::GlobalPosition; + //! state the traits public and thus export all types + using Traits = ScvfGeometryTraits; + + //! The center of the sub control volume face + GlobalPosition center() const + { + return asImp_().center(); + } + + //! The integration point for flux evaluations in global coordinates + GlobalPosition ipGlobal() const + { + // Return center for now + return asImp_().ipGlobal(); + } + + //! The area of the sub control volume face + Scalar area() const + { + return asImp_().area(); + } + + //! returns boolean if the sub control volume face is on the boundary + bool boundary() const + { + return asImp_().boundary(); + } + + //! the unit outward pointing normal on the scv face + GlobalPosition unitOuterNormal() const + { + return asImp_().unitOuterNormal(); + } + + //! index of the inside sub control volume for spatial param evaluation + GridIndexType insideScvIdx() const + { + return asImp_().insideScvIdx(); + } + + //! index of the outside sub control volume for spatial param evaluation + //! This results in undefined behaviour if boundary is true + //! In case of multiple outside scv indices (network grids) an index can be supplied + GridIndexType outsideScvIdx(int i = 0) const + { + return asImp_().outsideScvIdx(i); + } + + //! The global index of this sub control volume face + GridIndexType index() const + { + return asImp_().index(); + } + +private: + const Implementation& asImp_() const + { return *static_cast(this); } + + Implementation& asImp_() + { return *static_cast(this); } +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/box/fourierslaw.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/box/fourierslaw.hh new file mode 100644 index 0000000..28db8a4 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/box/fourierslaw.hh @@ -0,0 +1,93 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup BoxFlux + * \brief This file contains the data which is required to calculate + * energy fluxes due to molecular diffusion with Fourier's law. + */ +#ifndef DUMUX_DISCRETIZATION_BOX_FOURIERS_LAW_HH +#define DUMUX_DISCRETIZATION_BOX_FOURIERS_LAW_HH + +#include +#include +#include +#include +#include + +namespace Dumux { + +// forward declaration +template +class FouriersLawImplementation; + +/*! + * \ingroup BoxFlux + * \brief Specialization of Fourier's Law for the box method. + */ +template +class FouriersLawImplementation +{ + using Scalar = GetPropType; + using Problem = GetPropType; + using GridGeometry = GetPropType; + using FVElementGeometry = typename GridGeometry::LocalView; + using SubControlVolumeFace = typename GridGeometry::SubControlVolumeFace; + using Extrusion = Extrusion_t; + using ElementVolumeVariables = typename GetPropType::LocalView; + using ElementFluxVariablesCache = typename GetPropType::LocalView; + using GridView = typename GetPropType::GridView; + using Element = typename GridView::template Codim<0>::Entity; + +public: + /*! + * \brief Returns the heat flux within the porous medium + * (in J/s) across the given sub-control volume face. + * \note This law assumes thermal equilibrium between the fluid + * and solid phases, and uses an effective thermal conductivity + * for the overall aggregate. + */ + static Scalar flux(const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const SubControlVolumeFace& scvf, + const ElementFluxVariablesCache& elemFluxVarsCache) + { + // get inside and outside diffusion tensors and calculate the harmonic mean + const auto& insideScv = fvGeometry.scv(scvf.insideScvIdx()); + const auto& outsideScv = fvGeometry.scv(scvf.outsideScvIdx()); + const auto& insideVolVars = elemVolVars[insideScv]; + const auto& outsideVolVars = elemVolVars[outsideScv]; + + // effective diffusion tensors + auto insideLambda = insideVolVars.effectiveThermalConductivity(); + auto outsideLambda = outsideVolVars.effectiveThermalConductivity(); + + // scale by extrusion factor + insideLambda *= insideVolVars.extrusionFactor(); + outsideLambda *= outsideVolVars.extrusionFactor(); + + // the resulting averaged diffusion tensor + const auto lambda = faceTensorAverage(insideLambda, outsideLambda, scvf.unitOuterNormal()); + + // evaluate gradTemp at integration point + const auto& fluxVarsCache = elemFluxVarsCache[scvf]; + + // compute the temperature gradient with the shape functions + Dune::FieldVector gradTemp(0.0); + for (auto&& scv : scvs(fvGeometry)) + gradTemp.axpy(elemVolVars[scv].temperature(), fluxVarsCache.gradN(scv.indexInElement())); + + // compute the heat conduction flux + return -1.0*vtmv(scvf.unitOuterNormal(), lambda, gradTemp)*Extrusion::area(fvGeometry, scvf); + } +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/ccmpfa/fourierslaw.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/ccmpfa/fourierslaw.hh new file mode 100644 index 0000000..757a10a --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/ccmpfa/fourierslaw.hh @@ -0,0 +1,196 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup CCMpfaFlux + * \brief Fourier's law for cell-centered finite volume schemes with multi-point flux approximation + */ +#ifndef DUMUX_DISCRETIZATION_CC_MPFA_FOURIERS_LAW_HH +#define DUMUX_DISCRETIZATION_CC_MPFA_FOURIERS_LAW_HH + +#include +#include + +#include +#include + +namespace Dumux { + +//! forward declaration of the method-specific implementation +template +class FouriersLawImplementation; + +/*! + * \ingroup CCMpfaFlux + * \brief Fourier's law for cell-centered finite volume schemes with multi-point flux approximation + */ +template +class FouriersLawImplementation +{ + using Scalar = GetPropType; + using Problem = GetPropType; + using GridView = typename GetPropType::GridView; + using Element = typename GridView::template Codim<0>::Entity; + + static constexpr int dim = GridView::dimension; + static constexpr int dimWorld = GridView::dimensionworld; + + using GridGeometry = GetPropType; + using FVElementGeometry = typename GridGeometry::LocalView; + using SubControlVolumeFace = typename FVElementGeometry::SubControlVolumeFace; + using ElementVolumeVariables = typename GetPropType::LocalView; + using GridFluxVariablesCache = GetPropType; + using ElementFluxVarsCache = typename GridFluxVariablesCache::LocalView; + using FluxVariablesCache = typename GridFluxVariablesCache::FluxVariablesCache; + + //! Class that fills the cache corresponding to mpfa Darcy's Law + class MpfaFouriersLawCacheFiller + { + public: + //! Function to fill an MpfaDarcysLawCache of a given scvf + //! This interface has to be met by any cache filler class for heat conduction quantities + template + static void fill(FluxVariablesCache& scvfFluxVarsCache, + const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const SubControlVolumeFace& scvf, + const FluxVariablesCacheFiller& fluxVarsCacheFiller) + { + // get interaction volume from the flux vars cache filler & update the cache + if (fvGeometry.gridGeometry().vertexUsesSecondaryInteractionVolume(scvf.vertexIndex())) + scvfFluxVarsCache.updateHeatConduction(fluxVarsCacheFiller.secondaryInteractionVolume(), + fluxVarsCacheFiller.secondaryIvLocalFaceData(), + fluxVarsCacheFiller.secondaryIvDataHandle()); + else + scvfFluxVarsCache.updateHeatConduction(fluxVarsCacheFiller.primaryInteractionVolume(), + fluxVarsCacheFiller.primaryIvLocalFaceData(), + fluxVarsCacheFiller.primaryIvDataHandle()); + } + }; + + //! The cache used in conjunction with the mpfa Fourier's Law + class MpfaFouriersLawCache + { + using DualGridNodalIndexSet = GetPropType; + using Stencil = typename DualGridNodalIndexSet::NodalGridStencilType; + + static constexpr bool considerSecondaryIVs = GridGeometry::MpfaHelper::considerSecondaryIVs(); + using PrimaryDataHandle = typename ElementFluxVarsCache::PrimaryIvDataHandle::HeatConductionHandle; + using SecondaryDataHandle = typename ElementFluxVarsCache::SecondaryIvDataHandle::HeatConductionHandle; + + //! sets the pointer to the data handle (overload for secondary data handles) + template< bool doSecondary = considerSecondaryIVs, std::enable_if_t = 0 > + void setHandlePointer_(const SecondaryDataHandle& dataHandle) + { secondaryHandlePtr_ = &dataHandle; } + + //! sets the pointer to the data handle (overload for primary data handles) + void setHandlePointer_(const PrimaryDataHandle& dataHandle) + { primaryHandlePtr_ = &dataHandle; } + + public: + // export filler type + using Filler = MpfaFouriersLawCacheFiller; + + /*! + * \brief Update cached objects (transmissibilities). + * This is used for updates with primary interaction volumes. + * + * \param iv The interaction volume this scvf is embedded in + * \param localFaceData iv-local info on this scvf + * \param dataHandle Transmissibility matrix & gravity data of this iv + */ + template + void updateHeatConduction(const IV& iv, + const LocalFaceData& localFaceData, + const DataHandle& dataHandle) + { + switchFluxSign_ = localFaceData.isOutsideFace(); + stencil_ = &iv.stencil(); + setHandlePointer_(dataHandle.heatConductionHandle()); + } + + //! The stencil corresponding to the transmissibilities (primary type) + const Stencil& heatConductionStencil() const { return *stencil_; } + + //! The corresponding data handles + const PrimaryDataHandle& heatConductionPrimaryDataHandle() const { return *primaryHandlePtr_; } + const SecondaryDataHandle& heatConductionSecondaryDataHandle() const { return *secondaryHandlePtr_; } + + //! Returns whether or not this scvf is an "outside" face in the scope of the iv. + bool heatConductionSwitchFluxSign() const { return switchFluxSign_; } + + private: + bool switchFluxSign_; + + //! pointers to the corresponding iv-data handles + const PrimaryDataHandle* primaryHandlePtr_; + const SecondaryDataHandle* secondaryHandlePtr_; + + //! The stencil, i.e. the grid indices j + const Stencil* stencil_; + }; + +public: + using DiscretizationMethod = DiscretizationMethods::CCMpfa; + // state the discretization method this implementation belongs to + static constexpr DiscretizationMethod discMethod{}; + + // state the type for the corresponding cache and its filler + using Cache = MpfaFouriersLawCache; + + /*! + * \brief Returns the heat flux within the porous medium + * (in J/s) across the given sub-control volume face. + * \note This law assumes thermal equilibrium between the fluid + * and solid phases, and uses an effective thermal conductivity + * for the overall aggregate. + */ + static Scalar flux(const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const SubControlVolumeFace& scvf, + const ElementFluxVarsCache& elemFluxVarsCache) + { + const auto& fluxVarsCache = elemFluxVarsCache[scvf]; + + // forward to the private function taking the iv data handle + if (fluxVarsCache.usesSecondaryIv()) + return flux_(problem, fluxVarsCache, fluxVarsCache.heatConductionSecondaryDataHandle()); + else + return flux_(problem, fluxVarsCache, fluxVarsCache.heatConductionPrimaryDataHandle()); + } + +private: + template< class Problem, class FluxVarsCache, class DataHandle > + static Scalar flux_(const Problem& problem, + const FluxVarsCache& cache, + const DataHandle& dataHandle) + { + const bool switchSign = cache.heatConductionSwitchFluxSign(); + + const auto localFaceIdx = cache.ivLocalFaceIndex(); + const auto idxInOutside = cache.indexInOutsideFaces(); + const auto& Tj = dataHandle.uj(); + const auto& tij = dim == dimWorld ? dataHandle.T()[localFaceIdx] + : (!switchSign ? dataHandle.T()[localFaceIdx] + : dataHandle.tijOutside()[localFaceIdx][idxInOutside]); + Scalar scvfFlux = tij*Tj; + + // switch the sign if necessary + if (switchSign) + scvfFlux *= -1.0; + + return scvfFlux; + } +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/cctpfa/fourierslaw.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/cctpfa/fourierslaw.hh new file mode 100644 index 0000000..58d6793 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/cctpfa/fourierslaw.hh @@ -0,0 +1,240 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! +* \file +* \ingroup CCTpfaFlux +* \brief Fourier's law for cell-centered finite volume schemes with two-point flux approximation +*/ +#ifndef DUMUX_DISCRETIZATION_CC_TPFA_FOURIERS_LAW_HH +#define DUMUX_DISCRETIZATION_CC_TPFA_FOURIERS_LAW_HH + +#include +#include + +#include +#include +#include + +namespace Dumux { + +// forward declaration +template +class FouriersLawImplementation; + +/*! + * \ingroup CCTpfaFlux + * \brief Fourier's law for cell-centered finite volume schemes with two-point flux approximation + */ +template +class FouriersLawImplementation +{ + using Implementation = FouriersLawImplementation; + using Scalar = GetPropType; + using Problem = GetPropType; + using GridGeometry = GetPropType; + using FVElementGeometry = typename GridGeometry::LocalView; + using SubControlVolumeFace = typename GridGeometry::SubControlVolumeFace; + using Extrusion = Extrusion_t; + using GridView = typename GridGeometry::GridView; + using ElementVolumeVariables = typename GetPropType::LocalView; + using VolumeVariables = typename ElementVolumeVariables::VolumeVariables; + using Element = typename GridView::template Codim<0>::Entity; + using GridFluxVariablesCache = GetPropType; + using ElementFluxVarsCache = typename GridFluxVariablesCache::LocalView; + using FluxVariablesCache = typename GridFluxVariablesCache::FluxVariablesCache; + + static const int dim = GridView::dimension; + static const int dimWorld = GridView::dimensionworld; + + //! Class that fills the cache corresponding to tpfa Fick's Law + class TpfaFouriersLawCacheFiller + { + public: + //! Function to fill a TpfaFicksLawCache of a given scvf + //! This interface has to be met by any diffusion-related cache filler class + template + static void fill(FluxVariablesCache& scvfFluxVarsCache, + const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const SubControlVolumeFace& scvf, + const FluxVariablesCacheFiller& fluxVarsCacheFiller) + { + scvfFluxVarsCache.updateHeatConduction(problem, element, fvGeometry, elemVolVars, scvf); + } + }; + + //! Class that caches the transmissibility + class TpfaFouriersLawCache + { + public: + using Filler = TpfaFouriersLawCacheFiller; + + void updateHeatConduction(const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const SubControlVolumeFace &scvf) + { + tij_ = Implementation::calculateTransmissibility(problem, element, fvGeometry, elemVolVars, scvf); + } + + const Scalar& heatConductionTij() const + { return tij_; } + + private: + Scalar tij_; + }; + +public: + using DiscretizationMethod = DiscretizationMethods::CCTpfa; + //! state the discretization method this implementation belongs to + static constexpr DiscretizationMethod discMethod{}; + + //! export the type for the corresponding cache + using Cache = TpfaFouriersLawCache; + + /*! + * \brief Returns the heat flux within the porous medium + * (in J/s) across the given sub-control volume face. + * \note This law assumes thermal equilibrium between the fluid + * and solid phases, and uses an effective thermal conductivity + * for the overall aggregate. + * This overload allows to explicitly specify the inside and outside volume variables + * which can be useful to evaluate conductive fluxes at boundaries with given outside values. + * This only works if scvf.numOutsideScv() == 1. + * + */ + static Scalar flux(const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const VolumeVariables& insideVolVars, + const VolumeVariables& outsideVolVars, + const SubControlVolumeFace& scvf, + const ElementFluxVarsCache& elemFluxVarsCache) + { + if constexpr (isMixedDimensional_) + if (scvf.numOutsideScv() != 1) + DUNE_THROW(Dune::Exception, "This flux overload requires scvf.numOutsideScv() == 1"); + + // heat conductivities are always solution dependent (?) + Scalar tij = elemFluxVarsCache[scvf].heatConductionTij(); + + // get the inside/outside temperatures + const auto tInside = insideVolVars.temperature(); + const auto tOutside = outsideVolVars.temperature(); + + return tij*(tInside - tOutside); + } + + /*! + * \brief Returns the heat flux within the porous medium + * (in J/s) across the given sub-control volume face. + * \note This law assumes thermal equilibrium between the fluid + * and solid phases, and uses an effective thermal conductivity + * for the overall aggregate. + */ + static Scalar flux(const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const SubControlVolumeFace& scvf, + const ElementFluxVarsCache& elemFluxVarsCache) + { + // heat conductivities are always solution dependent (?) + Scalar tij = elemFluxVarsCache[scvf].heatConductionTij(); + + // get the inside/outside temperatures + const auto tInside = elemVolVars[scvf.insideScvIdx()].temperature(); + const auto tOutside = scvf.numOutsideScvs() == 1 ? elemVolVars[scvf.outsideScvIdx()].temperature() + : branchingFacetTemperature_(problem, element, fvGeometry, elemVolVars, elemFluxVarsCache, scvf, tInside, tij); + + return tij*(tInside - tOutside); + } + + //! Compute transmissibilities + static Scalar calculateTransmissibility(const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const SubControlVolumeFace& scvf) + { + Scalar tij; + + const auto insideScvIdx = scvf.insideScvIdx(); + const auto& insideScv = fvGeometry.scv(insideScvIdx); + const auto& insideVolVars = elemVolVars[insideScvIdx]; + + const auto insideLambda = insideVolVars.effectiveThermalConductivity(); + const Scalar ti = computeTpfaTransmissibility(fvGeometry, scvf, insideScv, insideLambda, insideVolVars.extrusionFactor()); + + // for the boundary (dirichlet) or at branching points we only need ti + if (scvf.boundary() || scvf.numOutsideScvs() > 1) + { + tij = Extrusion::area(fvGeometry, scvf)*ti; + } + // otherwise we compute a tpfa harmonic mean + else + { + const auto outsideScvIdx = scvf.outsideScvIdx(); + const auto& outsideScv = fvGeometry.scv(outsideScvIdx); + const auto& outsideVolVars = elemVolVars[outsideScvIdx]; + + const auto outsideLambda = outsideVolVars.effectiveThermalConductivity(); + Scalar tj; + if constexpr (dim == dimWorld) + // assume the normal vector from outside is anti parallel so we save flipping a vector + tj = -1.0*computeTpfaTransmissibility(fvGeometry, scvf, outsideScv, outsideLambda, outsideVolVars.extrusionFactor()); + else + tj = computeTpfaTransmissibility(fvGeometry, fvGeometry.flipScvf(scvf.index()), outsideScv, outsideLambda, outsideVolVars.extrusionFactor()); + + // check for division by zero! + if (ti*tj <= 0.0) + tij = 0; + else + tij = Extrusion::area(fvGeometry, scvf)*(ti * tj)/(ti + tj); + } + + return tij; + } + +private: + + //! compute the temperature at branching facets for network grids + static Scalar branchingFacetTemperature_(const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const ElementFluxVarsCache& elemFluxVarsCache, + const SubControlVolumeFace& scvf, + Scalar insideTemperature, + Scalar insideTi) + { + Scalar sumTi(insideTi); + Scalar sumTempTi(insideTi*insideTemperature); + + for (unsigned int i = 0; i < scvf.numOutsideScvs(); ++i) + { + const auto outsideScvIdx = scvf.outsideScvIdx(i); + const auto& outsideVolVars = elemVolVars[outsideScvIdx]; + const auto& flippedScvf = fvGeometry.flipScvf(scvf.index(), i); + const auto& outsideFluxVarsCache = elemFluxVarsCache[flippedScvf]; + + auto outsideTi = outsideFluxVarsCache.heatConductionTij(); + sumTi += outsideTi; + sumTempTi += outsideTi*outsideVolVars.temperature(); + } + return sumTempTi/sumTi; + } + + static constexpr bool isMixedDimensional_ = static_cast(GridView::dimension) < static_cast(GridView::dimensionworld); +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/facetensoraverage.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/facetensoraverage.hh new file mode 100644 index 0000000..bc3cd3b --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/facetensoraverage.hh @@ -0,0 +1,75 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Flux + * \brief A free function to average a Tensor at an interface. + */ +#ifndef DUMUX_FACE_TENSOR_AVERAGE_HH +#define DUMUX_FACE_TENSOR_AVERAGE_HH + +#include +#include + +namespace Dumux { + +/*! + * \brief Average of a discontinuous scalar field at discontinuity interface + * (for compatibility reasons with the function below) + * \return the harmonic average of the scalars + * \param T1 first scalar parameter + * \param T2 second scalar parameter + * \param normal The unit normal vector of the interface + */ +template +Scalar faceTensorAverage(const Scalar T1, + const Scalar T2, + const Dune::FieldVector& normal) +{ return Dumux::harmonicMean(T1, T2); } + +/*! + * \brief Average of a discontinuous tensorial field at discontinuity interface + * \note We do a harmonic average of the part normal to the interface (alpha*I) and + * an arithmetic average of the tangential part (T - alpha*I). + * \return the averaged tensor + * \param T1 first tensor + * \param T2 second tensor + * \param normal The unit normal vector of the interface + */ +template +Dune::FieldMatrix faceTensorAverage(const Dune::FieldMatrix& T1, + const Dune::FieldMatrix& T2, + const Dune::FieldVector& normal) +{ + // determine nT*k*n + Dune::FieldVector tmp; + Dune::FieldVector tmp2; + T1.mv(normal, tmp); + T2.mv(normal, tmp2); + const Scalar alpha1 = tmp*normal; + const Scalar alpha2 = tmp2*normal; + + const Scalar alphaHarmonic = Dumux::harmonicMean(alpha1, alpha2); + const Scalar alphaAverage = 0.5*(alpha1 + alpha2); + + Dune::FieldMatrix T(0.0); + for (int i = 0; i < dim; ++i) + { + for (int j = 0; j < dim; ++j) + { + T[i][j] += 0.5*(T1[i][j] + T2[i][j]); + if (i == j) + T[i][j] += alphaHarmonic - alphaAverage; + } + } + + return T; +} + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/fluxvariablesbase.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/fluxvariablesbase.hh new file mode 100644 index 0000000..070213a --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/fluxvariablesbase.hh @@ -0,0 +1,86 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Flux + * \brief Base class for the flux variables living on a sub control volume face + */ +#ifndef DUMUX_DISCRETIZATION_FLUXVARIABLESBASE_HH +#define DUMUX_DISCRETIZATION_FLUXVARIABLESBASE_HH + +#include + +namespace Dumux { + +/*! + * \ingroup Flux + * \brief Base class for the flux variables living on a sub control volume face + * + * \tparam Problem the problem type to solve (for boundary conditions) + * \tparam FVElementGeometry the element geometry type + * \tparam ElementVolumeVariables the element volume variables type + * \tparam ElementFluxVariablesCache the element flux variables cache type + */ +template +class FluxVariablesBase +{ + using GridView = typename FVElementGeometry::GridGeometry::GridView; + using Element = typename GridView::template Codim<0>::Entity; + using Stencil = std::vector; + using SubControlVolumeFace = typename FVElementGeometry::SubControlVolumeFace; + +public: + + //! Initialize the flux variables storing some temporary pointers + void init(const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const SubControlVolumeFace &scvFace, + const ElementFluxVariablesCache& elemFluxVarsCache) + { + problemPtr_ = &problem; + elementPtr_ = &element; + scvFacePtr_ = &scvFace; + fvGeometryPtr_ = &fvGeometry; + elemVolVarsPtr_ = &elemVolVars; + elemFluxVarsCachePtr_ = &elemFluxVarsCache; + } + + const Problem& problem() const + { return *problemPtr_; } + + const Element& element() const + { return *elementPtr_; } + + const SubControlVolumeFace& scvFace() const + { return *scvFacePtr_; } + + const FVElementGeometry& fvGeometry() const + { return *fvGeometryPtr_; } + + const ElementVolumeVariables& elemVolVars() const + { return *elemVolVarsPtr_; } + + const ElementFluxVariablesCache& elemFluxVarsCache() const + { return *elemFluxVarsCachePtr_; } + +private: + const Problem* problemPtr_; //!< Pointer to the problem + const Element* elementPtr_; //!< Pointer to the element at hand + const FVElementGeometry* fvGeometryPtr_; //!< Pointer to the current FVElementGeometry + const SubControlVolumeFace* scvFacePtr_; //!< Pointer to the sub control volume face for which the flux variables are created + const ElementVolumeVariables* elemVolVarsPtr_; //!< Pointer to the current element volume variables + const ElementFluxVariablesCache* elemFluxVarsCachePtr_; //!< Pointer to the current element flux variables cache +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/fluxvariablescaching.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/fluxvariablescaching.hh new file mode 100644 index 0000000..32e22dd --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/fluxvariablescaching.hh @@ -0,0 +1,61 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Flux + * \brief Classes related to flux variables caching + */ +#ifndef DUMUX_DISCRETIZATION_FLUXVAR_CACHING_HH +#define DUMUX_DISCRETIZATION_FLUXVAR_CACHING_HH + +namespace Dumux { +namespace FluxVariablesCaching { + +//! The empty filler class corresponding to EmptyCache +struct EmptyCacheFiller +{ + EmptyCacheFiller() = default; + + template + EmptyCacheFiller(const Problem& p) {} + + static constexpr bool isSolDependent = false; // the cache is empty + + template + static void fill(Args&&... args) {} +}; + +//! An empty flux variables cache +template +struct EmptyCache +{ + //! export type used for scalar values + using Scalar = S; + + template + void update(Args&&... args) {} +}; + +#ifndef DOXYGEN // hide the empty caches from doxygen +// an empty cache filler +// \note Never use the _EmptyCache directly as it lead to ambiguous definitions +struct _EmptyCache +{ using Filler = EmptyCacheFiller; }; +#endif // DOXYGEN + +/*! + * \ingroup Flux + * \brief Empty caches to use in a constitutive flux law/process, e.g. Darcy's law + */ +struct EmptyAdvectionCache : public _EmptyCache {}; +struct EmptyDiffusionCache : public _EmptyCache {}; +struct EmptyHeatConductionCache : public _EmptyCache {}; + +} // end namespace FluxVariablesCaching +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/fourierslaw.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/fourierslaw.hh new file mode 100644 index 0000000..ddd3cfd --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/fourierslaw.hh @@ -0,0 +1,36 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Flux + * \brief Diffusive heat flux according to Fourier's law + * + * According to Fourier's law, the heat flux is proportional to the gradient of the temperature. \n + * The proportonality constant is the thermal conductivity \f$\lambda \f$. \n + * The flux is calculated as:\n + * \n + * \f[ + * \textbf{j}_{heat} = - \lambda \; \nabla T + * \f] + * \n + * \n + * In general, for porous medium local thermal equilibrium is assumed.\n + * To account for effects of porous media, an effective thermal conductivity \f$\lambda_{pm}\f$ is used.\n + * Models for the effective thermal conductivity are for example: Johansen \cite johansen1977, simple fluid lumping, Somerton \cite somerton1974. + * + */ +#ifndef DUMUX_FLUX_FOURIERS_LAW_HH +#define DUMUX_FLUX_FOURIERS_LAW_HH + +#include + +#include +#include +#include +#include + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/fourierslaw_fwd.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/fourierslaw_fwd.hh new file mode 100644 index 0000000..1cface4 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/fourierslaw_fwd.hh @@ -0,0 +1,35 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Flux + * \brief Fourier's law specialized for different discretization schemes + * This file contains the data which is required to calculate + * diffusive mass fluxes due to molecular diffusion with Fourier's law. + */ +#ifndef DUMUX_FLUX_FOURIERS_LAW_FWD_HH +#define DUMUX_FLUX_FOURIERS_LAW_FWD_HH + +#include +#include + +namespace Dumux { + +// declaration of primary template +template +class FouriersLawImplementation; + +/*! + * \ingroup Flux + * \brief Evaluates the heat conduction flux according to Fouriers's law + */ +template +using FouriersLaw = FouriersLawImplementation::DiscretizationMethod>; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/referencesystemformulation.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/referencesystemformulation.hh new file mode 100644 index 0000000..cca16c1 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/referencesystemformulation.hh @@ -0,0 +1,61 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Flux + * \brief The reference frameworks and formulations available for splitting total fluxes into a advective and diffusive part + */ + +#ifndef DUMUX_FLUX_REFERENCESYSTEMFORMULATION_HH +#define DUMUX_FLUX_REFERENCESYSTEMFORMULATION_HH + +namespace Dumux { + +/*! + * \brief The formulations available for Fick's law related to the reference system + * \ingroup Flux + * \note The total flux of a component can be split into an advective and a diffusive part. + * In our framework, the advective part is based on a momentum balance. Standard momentum balances, e.g., + * the Navier-Stokes equations or Darcy's law yield mass-averaged velocities (see Multicomponent Mass Transfer, Taylor & Krishna, 1993 \cite taylor1993a), + * therefore we use the appropriate formulation of Fick's law (mass averaged formulation) per default. + * + * This means that the diffusive fluxes are calculated with the mass fraction gradients and the unit of the fluxes is kg/s. + * It is also possible to use a molar-averaged reference system, which can be beneficial, e.g., + * when it is known that the molar-averaged advective velocity would be zero. When using a molar-averaged reference velocity, + * Fick's law is formulated with mole fraction gradients and the unit of the flux is moles/s. This means that depending on the reference system, + * the units of the fluxes need to be adapted to be used in mass or mole balances. +*/ +enum class ReferenceSystemFormulation +{ + massAveraged, molarAveraged +}; + +/*! + * \ingroup Flux + * \brief evaluates the density to be used in Fick's law based on the reference system + */ +template +typename VolumeVariables::PrimaryVariables::value_type +massOrMolarDensity(const VolumeVariables& volVars, ReferenceSystemFormulation referenceSys, const int phaseIdx) +{ + return (referenceSys == ReferenceSystemFormulation::massAveraged) ? volVars.density(phaseIdx) : volVars.molarDensity(phaseIdx); +} + +/*! + * \ingroup Flux + * \brief returns the mass or mole fraction to be used in Fick's law based on the reference system + */ +template +typename VolumeVariables::PrimaryVariables::value_type +massOrMoleFraction(const VolumeVariables& volVars, ReferenceSystemFormulation referenceSys, const int phaseIdx, const int compIdx) +{ + return (referenceSys == ReferenceSystemFormulation::massAveraged) ? volVars.massFraction(phaseIdx, compIdx) : volVars.moleFraction(phaseIdx, compIdx); +} + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/staggered/freeflow/fourierslaw.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/staggered/freeflow/fourierslaw.hh new file mode 100644 index 0000000..219b127 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/staggered/freeflow/fourierslaw.hh @@ -0,0 +1,104 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup StaggeredFlux + * \brief Specialization of Fourier's Law for the staggered free flow method. + */ +#ifndef DUMUX_DISCRETIZATION_STAGGERED_FOURIERS_LAW_HH +#define DUMUX_DISCRETIZATION_STAGGERED_FOURIERS_LAW_HH + +#include +#include + +#include +#include +#include + +namespace Dumux { + +// forward declaration +template +class FouriersLawImplementation; + +/*! + * \ingroup StaggeredFlux + * \brief Specialization of Fourier's Law for the staggered free flow method. + */ +template +class FouriersLawImplementation +{ + using Scalar = GetPropType; + using GridGeometry = GetPropType; + using FVElementGeometry = typename GridGeometry::LocalView; + using SubControlVolumeFace = typename GridGeometry::SubControlVolumeFace; + using Extrusion = Extrusion_t; + using ElementVolumeVariables = typename GetPropType::LocalView; + using Element = typename GridGeometry::GridView::template Codim<0>::Entity; + using Indices = typename GetPropType::Indices; + +public: + using DiscretizationMethod = DiscretizationMethods::Staggered; + // state the discretization method this implementation belongs to + static constexpr DiscretizationMethod discMethod{}; + + //! state the type for the corresponding cache + //! We don't cache anything for this law + using Cache = FluxVariablesCaching::EmptyDiffusionCache; + + /*! + * \brief Returns the heat flux within the porous medium + * (in J/s) across the given sub-control volume face. + * \note This law assumes thermal equilibrium between the fluid + * and solid phases, and uses an effective thermal conductivity + * for the overall aggregate. + */ + template + static Scalar flux(const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const SubControlVolumeFace &scvf) + { + Scalar flux(0.0); + + // conductive energy flux is zero for outflow boundary conditions + if (scvf.boundary() && problem.boundaryTypes(element, scvf).isOutflow(Indices::energyEqIdx)) + return flux; + + const auto& insideScv = fvGeometry.scv(scvf.insideScvIdx()); + const auto& insideVolVars = elemVolVars[scvf.insideScvIdx()]; + const auto& outsideVolVars = elemVolVars[scvf.outsideScvIdx()]; + + const Scalar insideTemperature = insideVolVars.temperature(); + const Scalar outsideTemperature = outsideVolVars.temperature(); + + const Scalar insideLambda = insideVolVars.effectiveThermalConductivity() * insideVolVars.extrusionFactor(); + const Scalar insideDistance = (insideScv.dofPosition() - scvf.ipGlobal()).two_norm(); + + if (scvf.boundary()) + { + flux = insideLambda * (insideTemperature - outsideTemperature) / insideDistance; + } + else + { + const auto& outsideScv = fvGeometry.scv(scvf.outsideScvIdx()); + const Scalar outsideLambda = outsideVolVars.effectiveThermalConductivity() * outsideVolVars.extrusionFactor(); + const Scalar outsideDistance = (outsideScv.dofPosition() - scvf.ipGlobal()).two_norm(); + const Scalar avgLambda = harmonicMean(insideLambda, outsideLambda, insideDistance, outsideDistance); + + flux = avgLambda * (insideTemperature - outsideTemperature) / (insideDistance + outsideDistance); + } + + flux *= Extrusion::area(fvGeometry, scvf); + return flux; + } +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/upwindscheme.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/upwindscheme.hh new file mode 100644 index 0000000..4dea69a --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/flux/upwindscheme.hh @@ -0,0 +1,201 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Flux + * \brief Base class for the upwind scheme + */ +#ifndef DUMUX_DISCRETIZATION_UPWINDSCHEME_HH +#define DUMUX_DISCRETIZATION_UPWINDSCHEME_HH + +#include +#include + +namespace Dumux { + +//! Forward declaration of the upwind scheme implementation +template +class UpwindSchemeImpl; + +/*! + * \ingroup Flux + * \brief The upwind scheme used for the advective fluxes. + * This depends on the chosen discretization method. + */ +template +using UpwindScheme = UpwindSchemeImpl; + +namespace Detail { + +//! returns the upwind factor which is multiplied to the advective flux across the given scvf +template +Scalar upwindSchemeMultiplier(const ElemVolVars& elemVolVars, + const SubControlVolumeFace& scvf, + const UpwindTermFunction& upwindTerm, + Scalar flux, int phaseIdx) +{ + // TODO: pass this from outside? + static const Scalar upwindWeight = getParamFromGroup(elemVolVars.gridVolVars().problem().paramGroup(), "Flux.UpwindWeight"); + + const auto& insideVolVars = elemVolVars[scvf.insideScvIdx()]; + const auto& outsideVolVars = elemVolVars[scvf.outsideScvIdx()]; + + using std::signbit; + if (signbit(flux)) // if sign of flux is negative + return (upwindWeight*upwindTerm(outsideVolVars) + + (1.0 - upwindWeight)*upwindTerm(insideVolVars)); + else + return (upwindWeight*upwindTerm(insideVolVars) + + (1.0 - upwindWeight)*upwindTerm(outsideVolVars)); +} + +} // end namespace Detail + +//! Upwind scheme for control-volume finite element methods (uses the standard scheme) +template +class UpwindSchemeImpl> +{ +public: + //! applies a simple upwind scheme to the precalculated advective flux + template + static Scalar apply(const FluxVariables& fluxVars, + const UpwindTermFunction& upwindTerm, + Scalar flux, int phaseIdx) + { + return apply(fluxVars.elemVolVars(), fluxVars.scvFace(), upwindTerm, flux, phaseIdx); + } + + //! applies a simple upwind scheme to the precalculated advective flux across the given scvf + template + static Scalar apply(const ElemVolVars& elemVolVars, + const SubControlVolumeFace& scvf, + const UpwindTermFunction& upwindTerm, + Scalar flux, int phaseIdx) + { + return flux*multiplier(elemVolVars, scvf, upwindTerm, flux, phaseIdx); + } + + //! returns the upwind factor which is multiplied to the advective flux across the given scvf + template + static Scalar multiplier(const ElemVolVars& elemVolVars, + const SubControlVolumeFace& scvf, + const UpwindTermFunction& upwindTerm, + Scalar flux, int phaseIdx) + { + return Detail::upwindSchemeMultiplier(elemVolVars, scvf, upwindTerm, flux, phaseIdx); + } +}; + +//! Upwind scheme for the cell-centered tpfa scheme +template +class UpwindSchemeImpl +{ + using GridView = typename GridGeometry::GridView; + static constexpr int dim = GridView::dimension; + static constexpr int dimWorld = GridView::dimensionworld; + +public: + //! returns the upwind factor which is multiplied to the advective flux across the given scvf + template + static Scalar multiplier(const ElemVolVars& elemVolVars, + const SubControlVolumeFace& scvf, + const UpwindTermFunction& upwindTerm, + Scalar flux, int phaseIdx) + { + static_assert(dim == dimWorld, "Multiplier cannot be computed on surface grids using cell-centered scheme!"); + return Detail::upwindSchemeMultiplier(elemVolVars, scvf, upwindTerm, flux, phaseIdx); + } + + //! applies a simple upwind scheme to the precalculated advective flux across the given scvf + template + static Scalar apply(const ElemVolVars& elemVolVars, + const SubControlVolumeFace& scvf, + const UpwindTermFunction& upwindTerm, + Scalar flux, int phaseIdx) + { + static_assert(dim == dimWorld, "This upwind scheme cannot be used on surface grids using cell-centered schemes!"); + return flux*multiplier(elemVolVars, scvf, upwindTerm, flux, phaseIdx); + } + + // For surface and network grids (dim < dimWorld) we have to do a special upwind scheme + template + static Scalar apply(const FluxVariables& fluxVars, + const UpwindTermFunction& upwindTerm, + Scalar flux, int phaseIdx) + { + const auto& scvf = fluxVars.scvFace(); + const auto& elemVolVars = fluxVars.elemVolVars(); + + // standard scheme + if constexpr (dim == dimWorld) + return apply(elemVolVars, scvf, upwindTerm, flux, phaseIdx); + + // on non-branching points the standard scheme works + if (scvf.numOutsideScvs() == 1) + return flux*Detail::upwindSchemeMultiplier(elemVolVars, scvf, upwindTerm, flux, phaseIdx); + + // handle branching points with a more complicated upwind scheme + // we compute a flux-weighted average of all inflowing branches + const auto& insideVolVars = elemVolVars[scvf.insideScvIdx()]; + + Scalar branchingPointUpwindTerm = 0.0; + Scalar sumUpwindFluxes = 0.0; + + // if the inside flux is positive (outflow) do fully upwind and return flux + using std::signbit; + if (!signbit(flux)) + return upwindTerm(insideVolVars)*flux; + else + sumUpwindFluxes += flux; + + for (unsigned int i = 0; i < scvf.numOutsideScvs(); ++i) + { + // compute the outside flux + const auto& fvGeometry = fluxVars.fvGeometry(); + const auto outsideScvIdx = scvf.outsideScvIdx(i); + const auto outsideElement = fvGeometry.gridGeometry().element(outsideScvIdx); + const auto& flippedScvf = fvGeometry.flipScvf(scvf.index(), i); + + using AdvectionType = typename FluxVariables::AdvectionType; + const auto outsideFlux = AdvectionType::flux(fluxVars.problem(), + outsideElement, + fvGeometry, + elemVolVars, + flippedScvf, + phaseIdx, + fluxVars.elemFluxVarsCache()); + + if (!signbit(outsideFlux)) + branchingPointUpwindTerm += upwindTerm(elemVolVars[outsideScvIdx])*outsideFlux; + else + sumUpwindFluxes += outsideFlux; + } + + // the flux might be zero + if (sumUpwindFluxes != 0.0) + branchingPointUpwindTerm /= -sumUpwindFluxes; + else + branchingPointUpwindTerm = 0.0; + + // upwind scheme (always do fully upwind at branching points) + // a weighting here would lead to an error since the derivation is based on a fully upwind scheme + // TODO How to implement a weight of e.g. 0.5 + if (signbit(flux)) + return flux*branchingPointUpwindTerm; + else + return flux*upwindTerm(insideVolVars); + } +}; + +//! Upwind scheme for cell-centered mpfa schemes +template +class UpwindSchemeImpl +: public UpwindSchemeImpl {}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/energy/indices.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/energy/indices.hh new file mode 100644 index 0000000..a92df85 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/energy/indices.hh @@ -0,0 +1,34 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup FreeflowNIModel + * \copydoc Dumux::NavierStokesEnergyIndices + */ +#ifndef DUMUX_FREEFLOW_NAVIER_STOKES_ENERGY_INDICES_HH +#define DUMUX_FREEFLOW_NAVIER_STOKES_ENERGY_INDICES_HH + +namespace Dumux { + +/*! + * \ingroup FreeflowNIModel + * \brief Indices for the non-isothermal Navier-Stokes model. + * + * \tparam IsothermalIndices The isothermal indices class + * \tparam numEq the number of equations of the non-isothermal model + */ +template +class NavierStokesEnergyIndices : public IsothermalIndices +{ +public: + static constexpr int energyEqIdx = numEq - 1; + static constexpr int temperatureIdx = numEq - 1; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/energy/iofields.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/energy/iofields.hh new file mode 100644 index 0000000..36ec1b0 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/energy/iofields.hh @@ -0,0 +1,50 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup FreeflowNIModel + * \copydoc Dumux::NavierStokesEnergyIOFields + */ +#ifndef DUMUX_FREEFLOW_NAVIERSTOKES_ENERGY_IO_FIELDS_HH +#define DUMUX_FREEFLOW_NAVIERSTOKES_ENERGY_IO_FIELDS_HH + +#include + +namespace Dumux { + +/*! + * \ingroup FreeflowNIModel + * \brief Adds I/O fields specific to non-isothermal free-flow models + */ +template +struct NavierStokesEnergyIOFields +{ + + //! Add the non-isothermal specific output fields. + template + static void initOutputModule(OutputModule& out) + { + IsothermalIOFields::initOutputModule(out); + + out.addVolumeVariable([](const auto& v){ return v.temperature(); }, IOName::temperature()); + out.addVolumeVariable([](const auto& v){ return v.fluidThermalConductivity(); }, "lambda"); + } + + //! return the names of the primary variables + template + static std::string primaryVariableName(int pvIdx, int state = 0) + { + if (pvIdx < ModelTraits::numEq() - 1) + return IsothermalIOFields::template primaryVariableName(pvIdx, state); + else + return IOName::temperature(); + } +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/energy/model.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/energy/model.hh new file mode 100644 index 0000000..7585ed4 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/energy/model.hh @@ -0,0 +1,63 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup FreeflowNIModel + * + * \brief A single-phase, non-isothermal free-flow model + * + * In addition to the momentum and mass/mole balance equations, this model also solves the energy balance equation : + * \f[ + * \frac{\partial (\varrho v)}{\partial t} + * + \nabla \cdot \left( \varrho h {\boldsymbol{v}} + * - \lambda_\text{eff} \nabla T \right) - q_T = 0 + * \f] + * + * + * For laminar Navier-Stokes flow the effective thermal conductivity is the fluid + * thermal conductivity: \f$ \lambda_\text{eff} = \lambda \f$. + * + * For turbulent Reynolds-averaged Navier-Stokes flow the eddy thermal conductivity is added: + * \f$ \lambda_\text{eff} = \lambda + \lambda_\text{t} \f$. + * The eddy thermal conductivity \f$ \lambda_\text{t} \f$ is related to the eddy viscosity \f$ \nu_\text{t} \f$ + * by the turbulent Prandtl number: + * \f[ \lambda_\text{t} = \frac{\nu_\text{t} \varrho c_\text{p}}{\mathrm{Pr}_\text{t}} \f] + */ + +#ifndef DUMUX_FREEFLOW_NAVIER_STOKES_ENERGY_MODEL_HH +#define DUMUX_FREEFLOW_NAVIER_STOKES_ENERGY_MODEL_HH + +#include "indices.hh" +#include "iofields.hh" + +namespace Dumux { + +/*! + * \ingroup FreeflowNIModel + * \brief Specifies a number properties of non-isothermal free-flow + * flow models based on the specifics of a given isothermal model. + * \tparam IsothermalT Model traits of the isothermal model + */ +template +struct NavierStokesEnergyModelTraits : public IsothermalT +{ + //! Export the isothermal model traits + using IsothermalTraits = IsothermalT; + + //! We solve for one more equation, i.e. the energy balance + static constexpr int numEq() { return IsothermalTraits::numEq()+1; } + + //! We additionally solve for the equation balance + static constexpr bool enableEnergyBalance() { return true; } + + //! the indices + using Indices = NavierStokesEnergyIndices; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/energy/volumevariables.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/energy/volumevariables.hh new file mode 100644 index 0000000..abb069e --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/energy/volumevariables.hh @@ -0,0 +1,157 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup FreeflowNIModel + * \brief Base class for the model specific class which provides + * access to all volume averaged quantities. + */ + +#ifndef DUMUX_FREEFLOW_NAVIER_STOKES_ENERGY_VOLUME_VARIABLES_HH +#define DUMUX_FREEFLOW_NAVIER_STOKES_ENERGY_VOLUME_VARIABLES_HH + +#include +#include + + +namespace Dumux { + +namespace Detail { + +struct EmptyFreeFlowHeatCondType {}; + +template +struct FreeFlowHeatCondType +{ + using type = EmptyFreeFlowHeatCondType; +}; + +template +struct FreeFlowHeatCondType +{ + using type = typename Traits::HeatConductionType; +}; + +} // end namespace Detail + +/*! + * \ingroup FreeflowNIModel + * \brief The isothermal base class + */ +template +class NavierStokesEnergyVolumeVariables +{ + using Scalar = typename Traits::PrimaryVariables::value_type; + static constexpr bool enableEnergyBalance = Traits::ModelTraits::enableEnergyBalance(); + +public: + using FluidState = typename Traits::FluidState; + using FluidSystem = typename Traits::FluidSystem; + using HeatConductionType = typename Detail::FreeFlowHeatCondType::type; + + /*! + * \brief Returns the temperature at a given sub-control volume + * + * \param elemSol A vector containing all primary variables connected to the element + * \param problem The object specifying the problem which ought to + * be simulated + * \param element An element which contains part of the control volume + * \param scv The sub-control volume + */ + template + Scalar getTemperature(const ElementSolution& elemSol, + const Problem& problem, + const Element& element, + const SubControlVolume& scv) const + { + if constexpr (enableEnergyBalance) + return elemSol[scv.localDofIndex()][Traits::ModelTraits::Indices::temperatureIdx]; + else + return problem.spatialParams().temperature(element, scv, elemSol); + } + + + //! The effective thermal conductivity is zero for isothermal models + void updateEffectiveThermalConductivity() + { + if constexpr (enableEnergyBalance) + lambdaEff_ = Traits::EffectiveThermalConductivityModel::effectiveThermalConductivity(asImp_()); + } + + /*! + * \brief Returns the total internal energy of a phase in the + * sub-control volume. + * + * \param phaseIdx The phase index + */ + Scalar internalEnergy(const int phaseIdx = 0) const + { + if constexpr (enableEnergyBalance) + return asImp_().fluidState().internalEnergy(0); + else + return 0.0; + } + + /*! + * \brief Returns the total enthalpy of a phase in the sub-control + * volume. + * + * \param phaseIdx The phase index + */ + Scalar enthalpy(const int phaseIdx = 0) const + { + if constexpr (enableEnergyBalance) + return asImp_().fluidState().enthalpy(0); + else + return 0.0; + } + + /*! + * \brief Returns the temperature of a fluid phase assuming thermal nonequilibrium + * the sub-control volume. + * \param phaseIdx The local index of the phases + */ + Scalar temperatureFluid(const int phaseIdx = 0) const + { return asImp_().fluidState().temperature(0); } + + + /*! + * \brief Returns the thermal conductivity \f$\mathrm{[W/(m*K)]}\f$ + * of a fluid phase in the sub-control volume. + */ + Scalar fluidThermalConductivity(const int phaseIdx = 0) const + { return FluidSystem::thermalConductivity(asImp_().fluidState(), 0); } + + /*! + * \brief Returns the effective thermal conductivity \f$\mathrm{[W/(m*K)]}\f$ in + * the sub-control volume. Specific to equilibirum models (case fullThermalEquilibrium). + */ + Scalar effectiveThermalConductivity(const int phaseIdx = 0) const + { return lambdaEff_; } + + + //! The phase enthalpy is zero for isothermal models + //! This is needed for completing the fluid state + template + static Scalar enthalpy(const FluidState& fluidState, + const ParameterCache& paramCache) + { + if constexpr (enableEnergyBalance) + return FluidSystem::enthalpy(fluidState, paramCache, 0); + else + return 0.0; + } + +protected: + Scalar lambdaEff_; + const Impl &asImp_() const { return *static_cast(this); } + Impl &asImp_() { return *static_cast(this); } +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/iofields.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/iofields.hh new file mode 100644 index 0000000..bfa0db5 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/iofields.hh @@ -0,0 +1,135 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup NavierStokesModel + * \copydoc Dumux::NavierStokesIOFields + */ +#ifndef DUMUX_NAVIER_STOKES_IO_FIELDS_HH +#define DUMUX_NAVIER_STOKES_IO_FIELDS_HH + +#include +#include + +namespace Dumux { + +/*! + * \ingroup NavierStokesModel + * \brief helper function to determine the names of cell-centered primary variables of a model with staggered grid discretization + * \note use this as input for the load solution function + */ +template +std::function createCellCenterPVNameFunction(const std::string& paramGroup = "") +{ + static constexpr auto offset = ModelTraits::numEq() - PrimaryVariables::dimension; + + if (hasParamInGroup(paramGroup, "LoadSolution.CellCenterPriVarNames")) + { + const auto pvName = getParamFromGroup>(paramGroup, "LoadSolution.CellCenterPriVarNames"); + return [n = std::move(pvName)](int pvIdx, int state = 0){ return n[pvIdx]; }; + } + else + // add an offset to the pvIdx so that the velocities are skipped + return [](int pvIdx, int state = 0){ return IOFields::template primaryVariableName(pvIdx + offset, state); }; +} + +/*! + * \ingroup NavierStokesModel + * \brief helper function to determine the names of primary variables on the cell faces of a model with staggered grid discretization + * \note use this as input for the load solution function + */ +template +std::function createFacePVNameFunction(const std::string& paramGroup = "") +{ + if (hasParamInGroup(paramGroup, "LoadSolution.FacePriVarNames")) + { + const auto pvName = getParamFromGroup>(paramGroup, "LoadSolution.FacePriVarNames"); + return [n = std::move(pvName)](int pvIdx, int state = 0){ return n[pvIdx]; }; + } + else + // there is only one scalar-type primary variable called "v" living on the faces + return [](int pvIdx, int state = 0){ return IOFields::template primaryVariableName(pvIdx , state); }; +} + +// forward declare +template +class StaggeredVtkOutputModule; + +/*! + * \ingroup NavierStokesModel + * \brief Adds I/O fields for the Navier-Stokes model + */ +class NavierStokesIOFields +{ + //! Helper strcuts to determine whether a staggered grid discretization is used + template + struct isStaggered : public std::false_type {}; + + template + struct isStaggered> + : public std::true_type {}; + +public: + //! Initialize the Navier-Stokes specific output fields. + template + static void initOutputModule(OutputModule& out) + { + out.addVolumeVariable([](const auto& v){ return v.pressure(); }, IOName::pressure()); + out.addVolumeVariable([](const auto& v){ return v.density(); }, IOName::density()); + + // add discretization-specific fields + additionalOutput_(out, isStaggered()); + } + + //! return the names of the primary variables + template + static std::string primaryVariableName(int pvIdx = 0, int state = 0) + { + if (pvIdx < ModelTraits::dim()) + return "v"; + else + return IOName::pressure(); + } + +private: + + //! Adds discretization-specific fields (nothing by default). + template + static void additionalOutput_(OutputModule& out, std::false_type) + { } + + //! Adds discretization-specific fields (velocity vectors on the faces for the staggered discretization). + template + static void additionalOutput_(OutputModule& out, std::true_type) + { + const bool writeFaceVars = getParamFromGroup(out.paramGroup(), "Vtk.WriteFaceData", false); + if(writeFaceVars) + { + auto faceVelocityVector = [](const auto& scvf, const auto& faceVars) + { + using VelocityVector = std::decay_t; + + VelocityVector velocity(0.0); + velocity[scvf.directionIndex()] = faceVars.velocitySelf(); + return velocity; + }; + + out.addFaceVariable(faceVelocityVector, "faceVelocity"); + + auto faceNormalVelocity = [](const auto& faceVars) + { + return faceVars.velocitySelf(); + }; + + out.addFaceVariable(faceNormalVelocity, "v"); + } + } +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/mass/1p/advectiveflux.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/mass/1p/advectiveflux.hh new file mode 100644 index 0000000..cf709be --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/mass/1p/advectiveflux.hh @@ -0,0 +1,57 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// + /*! + * \file + * \ingroup NavierStokesModel + * \brief Helper struct defining the advective fluxes of the single-phase flow + * Navier-Stokes mass model + */ +#ifndef DUMUX_FREEFLOW_NAVIERSTOKES_MASS_1P_ADVECTIVE_FLUX_HH +#define DUMUX_FREEFLOW_NAVIERSTOKES_MASS_1P_ADVECTIVE_FLUX_HH + +namespace Dumux { + +#ifndef DOXYGEN +// forward declare +struct NavierStokesMassOnePModelTraits; + +template +struct NavierStokesEnergyModelTraits; + +template +struct AdvectiveFlux; +#endif + +/*! + * \ingroup NavierStokesModel + * \brief Helper struct defining the advective fluxes of the single-phase flow + * Navier-Stokes mass model + */ +template +struct AdvectiveFlux +{ + template + static void addAdvectiveFlux(NumEqVector& flux, + const UpwindFunction& upwind) + { + using ModelTraits = T; + + // get equation index + const auto eqIdx = ModelTraits::Indices::conti0EqIdx; + flux[eqIdx] += upwind([](const auto& volVars) { return volVars.density(); }); + } +}; + +// use the same mass flux for the non-isothermal model (heat fluxes are added separately) +template<> +struct AdvectiveFlux> +: public AdvectiveFlux +{}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/mass/1p/fluxvariables.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/mass/1p/fluxvariables.hh new file mode 100644 index 0000000..e97c505 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/mass/1p/fluxvariables.hh @@ -0,0 +1,81 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup NavierStokesModel + * \copydoc Dumux::NavierStokesMassOnePFluxVariables + */ +#ifndef DUMUX_FREEFLOW_NAVIERSTOKES_MASS_1P_FLUXVARIABLES_HH +#define DUMUX_FREEFLOW_NAVIERSTOKES_MASS_1P_FLUXVARIABLES_HH + +#include +#include + +#include "advectiveflux.hh" + +namespace Dumux { + +/*! + * \ingroup NavierStokesModel + * \brief The flux variables class for the single-phase flow Navier-Stokes model. + */ +template::GridGeometry>> +class NavierStokesMassOnePFluxVariables +: public NavierStokesScalarConservationModelFluxVariables +{ + using ParentType = NavierStokesScalarConservationModelFluxVariables; + + using VolumeVariables = typename ElementVolumeVariables::VolumeVariables; + using NumEqVector = typename VolumeVariables::PrimaryVariables; + +public: + + /*! + * \brief Returns the advective mass flux in kg/s + * or the advective mole flux in mole/s. + */ + NumEqVector advectiveFlux(int phaseIdx = 0) const + { + NumEqVector result(0.0); + // g++ requires to capture 'this' by value + const auto upwinding = [this](const auto& term) { return this->getAdvectiveFlux(term); }; + AdvectiveFlux::addAdvectiveFlux(result, upwinding); + return result; + } + + /*! + * \brief Returns all fluxes for the single-phase flow, multi-component + * Navier-Stokes model: the advective mass flux in kg/s + * or the advective mole flux in mole/s and the energy flux + * in J/s (for nonisothermal models). + */ + NumEqVector flux(int phaseIdx = 0) const + { + NumEqVector flux = advectiveFlux(phaseIdx); + ParentType::addHeatFlux(flux); + return flux; + } +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/mass/1p/indices.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/mass/1p/indices.hh new file mode 100644 index 0000000..3966cc4 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/mass/1p/indices.hh @@ -0,0 +1,29 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup NavierStokesModel + * \copydoc Dumux::NavierStokesIndices + */ +#ifndef DUMUX_FREEFLOW_NAVIERSTOKES_MASS_1P_INDICES_HH +#define DUMUX_FREEFLOW_NAVIERSTOKES_MASS_1P_INDICES_HH + +namespace Dumux { + +/*! + * \ingroup NavierStokesModel + * \brief The common indices for the isothermal Navier-Stokes mass conservation model. + */ +struct NavierStokesMassOnePIndices +{ + static constexpr int conti0EqIdx = 0; //!< Index of the mass/mole balance equation + static constexpr int pressureIdx = conti0EqIdx; //!< Index of the pressure +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/mass/1p/localresidual.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/mass/1p/localresidual.hh new file mode 100644 index 0000000..5f42190 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/mass/1p/localresidual.hh @@ -0,0 +1,115 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup NavierStokesModel + * \copydoc Dumux::NavierStokesResidualImpl + */ +#ifndef DUMUX_FREEFLOW_NAVIERSTOKES_MASS_1P_LOCAL_RESIDUAL_HH +#define DUMUX_FREEFLOW_NAVIERSTOKES_MASS_1P_LOCAL_RESIDUAL_HH + +#include + +#include +#include +#include + +namespace Dumux { + +/*! + * \ingroup NavierStokesModel + * \brief Traits class to be specialized for problems to add auxiliary fluxes + * \note This can be used, for example, to implement flux stabilization terms + */ +template +struct ImplementsAuxiliaryFluxNavierStokesMassOneP +: public std::false_type +{}; + +/*! + * \ingroup NavierStokesModel + * \brief Element-wise calculation of the Navier-Stokes residual for single-phase flow. + */ +template +class NavierStokesMassOnePLocalResidual +: public DiscretizationDefaultLocalOperator +{ + using ParentType = DiscretizationDefaultLocalOperator; + using GridVariables = GetPropType; + using GridVolumeVariables = typename GridVariables::GridVolumeVariables; + using ElementVolumeVariables = typename GridVolumeVariables::LocalView; + using VolumeVariables = typename GridVolumeVariables::VolumeVariables; + + using GridFluxVariablesCache = typename GridVariables::GridFluxVariablesCache; + using ElementFluxVariablesCache = typename GridFluxVariablesCache::LocalView; + + using Scalar = GetPropType; + using Problem = GetPropType; + using GridGeometry = GetPropType; + using FVElementGeometry = typename GridGeometry::LocalView; + using SubControlVolume = typename FVElementGeometry::SubControlVolume; + using SubControlVolumeFace = typename FVElementGeometry::SubControlVolumeFace; + using GridView = typename GridGeometry::GridView; + using Element = typename GridView::template Codim<0>::Entity; + using FluxVariables = GetPropType; + using NumEqVector = Dumux::NumEqVector>; + using ModelTraits = GetPropType; + +public: + //! Use the parent type's constructor + using ParentType::ParentType; + + /*! + * \brief Calculate the storage term of the equation + */ + NumEqVector computeStorage(const Problem& problem, + const SubControlVolume& scv, + const VolumeVariables& volVars) const + { + NumEqVector storage(0.0); + storage[ModelTraits::Indices::conti0EqIdx] = volVars.density(); + + // consider energy storage for non-isothermal models + if constexpr (ModelTraits::enableEnergyBalance()) + storage[ModelTraits::Indices::energyEqIdx] = volVars.density() * volVars.internalEnergy(); + + return storage; + } + + /*! + * \brief Evaluate the mass flux over a face of a sub control volume. + * + * \param problem The problem + * \param element The element + * \param fvGeometry The finite volume geometry context + * \param elemVolVars The volume variables for all flux stencil elements + * \param scvf The sub control volume face to compute the flux on + * \param elemFluxVarsCache The cache related to flux computation + */ + NumEqVector computeFlux(const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const SubControlVolumeFace& scvf, + const ElementFluxVariablesCache& elemFluxVarsCache) const + { + FluxVariables fluxVars; + fluxVars.init(problem, element, fvGeometry, elemVolVars, scvf, elemFluxVarsCache); + auto flux = fluxVars.flux(0); + + // the auxiliary flux is enabled if the trait is specialized for the problem + // this can be used, for example, to implement flux stabilization terms + if constexpr (ImplementsAuxiliaryFluxNavierStokesMassOneP::value) + flux += problem.auxiliaryFlux(element, fvGeometry, elemVolVars, elemFluxVarsCache, scvf); + + return flux; + } +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/mass/1p/model.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/mass/1p/model.hh new file mode 100644 index 0000000..b01e665 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/mass/1p/model.hh @@ -0,0 +1,319 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup NavierStokesModel + * + * \brief A single-phase, isothermal Navier-Stokes model + * + * This model implements a single-phase, isothermal Navier-Stokes model, solving the momentum balance equation + * \f[ + \frac{\partial (\varrho \textbf{v})}{\partial t} + \nabla \cdot (\varrho \textbf{v} \textbf{v}^{\text{T}}) = \nabla \cdot (\mu (\nabla \textbf{v} + \nabla \textbf{v}^{\text{T}})) + - \nabla p + \varrho \textbf{g} - \textbf{f} + * \f] + * By setting the runtime parameter Problem.EnableInertiaTerms to false the Stokes + * equation can be solved. In this case the term + * \f[ + * \nabla \cdot (\varrho \textbf{v} \textbf{v}^{\text{T}}) + * \f] + * is neglected. + * + * The mass balance equation + * \f[ + \frac{\partial \varrho}{\partial t} + \nabla \cdot (\varrho \textbf{v}) - q = 0 + * \f] + * + * closes the system. + * + */ + +#ifndef DUMUX_FREEFLOW_NAVIERSTOKES_1P_MODEL_HH +#define DUMUX_FREEFLOW_NAVIERSTOKES_1P_MODEL_HH + +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include + +#include "localresidual.hh" +#include "volumevariables.hh" +#include "fluxvariables.hh" +#include "indices.hh" + +namespace Dumux { + +/*! + * \ingroup NavierStokesModel + * \brief Traits for the single-phase flow Navier-Stokes mass model + */ +struct NavierStokesMassOnePModelTraits +{ + //! There are as many momentum balance equations as dimensions + //! and one mass balance equation. + static constexpr int numEq() { return 1; } + + //! The number of phases is 1 + static constexpr int numFluidPhases() { return 1; } + + //! The number of components is 1 + static constexpr int numFluidComponents() { return 1; } + + //! Enable advection + static constexpr bool enableAdvection() { return true; } + + //! The one-phase one-component model has no molecular diffusion + static constexpr bool enableMolecularDiffusion() { return false; } + + //! The model is isothermal + static constexpr bool enableEnergyBalance() { return false; } + + //! The model does not include a turbulence model + static constexpr bool usesTurbulenceModel() { return false; } + + //! return the type of turbulence model used + static constexpr auto turbulenceModel() + { return TurbulenceModel::none; } + + //! the indices + using Indices = NavierStokesMassOnePIndices; +}; + +/*! + * \ingroup NavierStokesModel + * \brief Traits class for the volume variables of the Navier-Stokes model. + * + * \tparam PV The type used for primary variables + * \tparam FSY The fluid system type + * \tparam FST The fluid state type + * \tparam MT The model traits + */ +template +struct NavierStokesMassOnePVolumeVariablesTraits +{ + using PrimaryVariables = PV; + using FluidSystem = FSY; + using FluidState = FST; + using ModelTraits = MT; +}; + +// \{ +/////////////////////////////////////////////////////////////////////////// +// properties for the single-phase Navier-Stokes model +/////////////////////////////////////////////////////////////////////////// +namespace Properties { + +////////////////////////////////////////////////////////////////// +// Type tags +////////////////////////////////////////////////////////////////// + +// Create new type tags +namespace TTag { +//! The type tag for the single-phase, isothermal Navier-Stokes model +struct NavierStokesMassOneP{ using InheritsFrom = std::tuple; }; +struct NavierStokesMassOnePNI{ using InheritsFrom = std::tuple; }; +} // end namespace TTag + + +template +struct ModelTraits +{ using type = NavierStokesMassOnePModelTraits; }; + +/*! + * \brief The fluid state which is used by the volume variables to + * store the thermodynamic state. This should be chosen + * appropriately for the model ((non-)isothermal, equilibrium, ...). + * This can be done in the problem. + */ +template +struct FluidState +{ +private: + using Scalar = GetPropType; + using FluidSystem = GetPropType; +public: + using type = Dumux::ImmiscibleFluidState; +}; + +//! The local residual +template +struct LocalResidual +{ using type = NavierStokesMassOnePLocalResidual; }; + +//! Set the volume variables property +template +struct VolumeVariables +{ +private: + using PV = GetPropType; + using FSY = GetPropType; + using FST = GetPropType; + using MT = GetPropType; + + static_assert(FSY::numPhases == MT::numFluidPhases(), "Number of phases mismatch between model and fluid system"); + static_assert(FST::numPhases == MT::numFluidPhases(), "Number of phases mismatch between model and fluid state"); + static_assert(!FSY::isMiscible(), "The Navier-Stokes model only works with immiscible fluid systems."); + + using Traits = NavierStokesMassOnePVolumeVariablesTraits; +public: + using type = NavierStokesMassOnePVolumeVariables; +}; + +//! The flux variables +template +struct FluxVariables +{ +private: + using Problem = GetPropType; + using ModelTraits = GetPropType; + struct DiffusiveFluxTypes {}; // no diffusion + using ElementVolumeVariables = typename GetPropType::LocalView; + using ElementFluxVariablesCache = typename GetPropType::LocalView; +public: + using type = NavierStokesMassOnePFluxVariables< + Problem, ModelTraits, DiffusiveFluxTypes, ElementVolumeVariables, ElementFluxVariablesCache + >; +}; + +// ! The specific I/O fields +template +struct IOFields { using type = NavierStokesIOFields; }; + +template +struct CouplingManager +{ +public: + using type = struct EmptyCouplingManager {}; +}; + +// Set the default spatial parameters +template +struct SpatialParams +{ + using GridGeometry = GetPropType; + using Scalar = GetPropType; + using type = FreeFlowDefaultSpatialParams; +}; +/////////////////////////////////////////////////////////////////////////// +// Properties for the non-isothermal single phase model +/////////////////////////////////////////////////////////////////////////// + +//! Add temperature to the output +template +struct IOFields +{ using type = NavierStokesEnergyIOFields; }; + +//! The model traits of the non-isothermal model +template +struct ModelTraits +{ using type = NavierStokesEnergyModelTraits; }; + +//! Set the volume variables property +template +struct VolumeVariables +{ +private: + using PV = GetPropType; + using FSY = GetPropType; + using FST = GetPropType; + using MT = GetPropType; + + static_assert(FSY::numPhases == MT::numFluidPhases(), "Number of phases mismatch between model and fluid system"); + static_assert(FST::numPhases == MT::numFluidPhases(), "Number of phases mismatch between model and fluid state"); + static_assert(!FSY::isMiscible(), "The Navier-Stokes model only works with immiscible fluid systems."); + + using BaseTraits = NavierStokesMassOnePVolumeVariablesTraits; + using ETCM = GetPropType; + using HCT = GetPropType; + struct NITraits : public BaseTraits + { + using EffectiveThermalConductivityModel = ETCM; + using HeatConductionType = HCT; + }; +public: + using type = NavierStokesMassOnePVolumeVariables; +}; + +//! Use the average for effective conductivities +template +struct ThermalConductivityModel +{ + struct type + { + template + static auto effectiveThermalConductivity(const VolVars& volVars) + { + return volVars.fluidThermalConductivity(); + } + }; +}; + +template +struct HeatConductionType +{ using type = FouriersLaw; }; + +//! The flux variables +template +struct FluxVariables +{ +private: + using Problem = GetPropType; + using ModelTraits = GetPropType; + + struct DiffusiveFluxTypes + { + using HeatConductionType = GetPropType; + }; + + using ElementVolumeVariables = typename GetPropType::LocalView; + using ElementFluxVariablesCache = typename GetPropType::LocalView; + +public: + using type = NavierStokesMassOnePFluxVariables< + Problem, ModelTraits, DiffusiveFluxTypes, ElementVolumeVariables, ElementFluxVariablesCache + >; +}; + +template +struct FluxVariablesCache +{ + struct type : public GetPropType::Cache + {}; +}; + +template +struct FluxVariablesCacheFiller +{ + using Problem = GetPropType; + using ModelTraits = GetPropType; + static constexpr bool diffusionIsSolDependent = false; // no diffusion; + static constexpr bool heatConductionIsSolDependent + = getPropValue(); + + using type = FreeFlowScalarFluxVariablesCacheFiller< + Problem, ModelTraits, diffusionIsSolDependent, heatConductionIsSolDependent + >; +}; + +template +struct SolutionDependentHeatConduction +{ static constexpr bool value = true; }; + +} // end namespace Properties +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/mass/1p/volumevariables.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/mass/1p/volumevariables.hh new file mode 100644 index 0000000..5cbcacc --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/mass/1p/volumevariables.hh @@ -0,0 +1,150 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup NavierStokesModel + * + * \copydoc Dumux::NavierStokesVolumeVariables + */ +#ifndef DUMUX_FREEFLOW_NAVIERSTOKES_MASS_1P_VOLUME_VARIABLES_HH +#define DUMUX_FREEFLOW_NAVIERSTOKES_MASS_1P_VOLUME_VARIABLES_HH + +#include +#include + +namespace Dumux { + +/*! + * \ingroup NavierStokesModel + * \brief Volume variables for the single-phase Navier-Stokes model. + */ +template +class NavierStokesMassOnePVolumeVariables +: public NavierStokesScalarConservationModelVolumeVariables +, public NavierStokesEnergyVolumeVariables> +{ + using ParentType = NavierStokesScalarConservationModelVolumeVariables; + using EnergyVolumeVariables = NavierStokesEnergyVolumeVariables>; + using Scalar = typename Traits::PrimaryVariables::value_type; + +public: + //! export the type used for the primary variables + using PrimaryVariables = typename Traits::PrimaryVariables; + //! export the indices type + using Indices = typename Traits::ModelTraits::Indices; + //! Export the underlying fluid system + using FluidSystem = typename Traits::FluidSystem; + //! Export the fluid state type + using FluidState = typename Traits::FluidState; + + //! Return number of phases considered by the model + static constexpr int numFluidPhases() { return Traits::ModelTraits::numFluidPhases(); } + //! Return number of components considered by the model + static constexpr int numFluidComponents() { return Traits::ModelTraits::numFluidComponents(); } + + /*! + * \brief Update all quantities for a given control volume + * + * \param elemSol A vector containing all primary variables connected to the element + * \param problem The object specifying the problem which ought to + * be simulated + * \param element An element which contains part of the control volume + * \param scv The sub-control volume + */ + template + void update(const ElementSolution& elemSol, + const Problem& problem, + const Element& element, + const SubControlVolume& scv) + { + ParentType::update(elemSol, problem, element, scv); + completeFluidState(elemSol, problem, element,scv, fluidState_); + EnergyVolumeVariables::updateEffectiveThermalConductivity(); + } + + /*! + * \brief Sets complete fluid state + * + * \param elemSol A vector containing all primary variables connected to the element + * \param problem The object specifying the problem which ought to + * be simulated + * \param element An element which contains part of the control volume + * \param scv The sub-control volume + * \param fluidState A container with the current (physical) state of the fluid + */ + template + void completeFluidState(const ElemSol& elemSol, + const Problem& problem, + const Element& element, + const Scv& scv, + FluidState& fluidState) + { + fluidState.setTemperature(/*phaseIdx=*/0, EnergyVolumeVariables::getTemperature(elemSol, problem, element, scv)); + + const auto& priVars = elemSol[scv.localDofIndex()]; + fluidState.setPressure(/*phaseIdx=*/0, priVars[Indices::pressureIdx]); + + // saturation in a single phase is always 1 and thus redundant + // to set. But since we use the fluid state shared by the + // immiscible multi-phase models, so we have to set it here... + fluidState.setSaturation(/*phaseIdx=*/0, 1.0); + + typename FluidSystem::ParameterCache paramCache; + paramCache.updatePhase(fluidState, /*phaseIdx=*/0); + + Scalar value = FluidSystem::density(fluidState, paramCache, /*phaseIdx=*/0); + fluidState.setDensity(/*phaseIdx=*/0, value); + + value = FluidSystem::viscosity(fluidState, paramCache, /*phaseIdx=*/0); + fluidState.setViscosity(/*phaseIdx=*/0, value); + + // compute and set the enthalpy + value = EnergyVolumeVariables::enthalpy(fluidState, paramCache); + fluidState.setEnthalpy(/*phaseIdx=*/0, value); + } + + /*! + * \brief Returns the effective pressure \f$\mathrm{[Pa]}\f$ of a given phase within + * the control volume. + */ + Scalar pressure(int phaseIdx = 0) const + { return fluidState_.pressure(phaseIdx); } + + /*! + * \brief Returns the fluid state of the control volume. + */ + const FluidState& fluidState() const + { return fluidState_; } + + /*! + * \brief Returns the dynamic viscosity \f$\mathrm{[Pa s]}\f$ of the fluid within the + * control volume. + */ + Scalar viscosity(int phaseIdx = 0) const + { return fluidState_.viscosity(phaseIdx); } + + /*! + * \brief Returns the mass density \f$\mathrm{[kg/m^3]}\f$ of a given phase within the + * control volume. + */ + Scalar density(int phaseIdx = 0) const + { return fluidState_.density(phaseIdx); } + + /*! + * \brief Returns the temperature \f$\mathrm{[K]}\f$ inside the sub-control volume. + * + */ + Scalar temperature() const + { return fluidState_.temperature(); } + +protected: + FluidState fluidState_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/mass/problem.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/mass/problem.hh new file mode 100644 index 0000000..06da873 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/mass/problem.hh @@ -0,0 +1,151 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup NavierStokesModel + * \copydoc Dumux::NavierStokesMassProblem + */ +#ifndef DUMUX_NAVIERSTOKES_MASS_PROBLEM_HH +#define DUMUX_NAVIERSTOKES_MASS_PROBLEM_HH + +#include +#include +#include +#include +#include +#include + +namespace Dumux { + +// default implementation +template +class NavierStokesMassProblemImpl : public FVProblemWithSpatialParams +{ + using ParentType = FVProblemWithSpatialParams; + using Implementation = GetPropType; + + using GridGeometry = GetPropType; + using GridView = typename GridGeometry::GridView; + using FVElementGeometry = typename GridGeometry::LocalView; + using SubControlVolumeFace = typename FVElementGeometry::SubControlVolumeFace; + using Element = typename GridView::template Codim<0>::Entity; + using GlobalPosition = typename SubControlVolumeFace::GlobalPosition; + using VelocityVector = GlobalPosition; + using Scalar = GetPropType; + using CouplingManager = GetPropType; + using ModelTraits = GetPropType; + + static constexpr bool isCoupled_ = !std::is_empty_v; + +public: + + //! These types are used in place of the typical NumEqVector type. + //! In the momentum problem, there is a discrepancy between + //! the assembly NumEqVector type, and the type used in the residual. + //! These aliases are used in both problems to differentiate. + using InitialValues = Dune::FieldVector; + using Sources = Dune::FieldVector; + using DirichletValues = Dune::FieldVector; + using BoundaryFluxes = Dune::FieldVector; + + //! Export the boundary types. + using BoundaryTypes = Dumux::BoundaryTypes; + + //! this problem is used for the mass balance model + static constexpr bool isMomentumProblem() { return false; } + + /*! + * \brief The constructor + * \param gridGeometry The finite volume grid geometry + * \param couplingManager The coupling manager (couples mass and momentum equations) + * \param paramGroup The parameter group in which to look for runtime parameters first (default is "") + */ + NavierStokesMassProblemImpl(std::shared_ptr gridGeometry, + std::shared_ptr couplingManager, + const std::string& paramGroup = "") + : ParentType(gridGeometry, paramGroup) + , couplingManager_(couplingManager) + {} + + /*! + * \brief The constructor for usage without a coupling manager + * \param gridGeometry The finite volume grid geometry + * \param paramGroup The parameter group in which to look for runtime parameters first (default is "") + */ + NavierStokesMassProblemImpl(std::shared_ptr gridGeometry, + const std::string& paramGroup = "") + : NavierStokesMassProblemImpl(gridGeometry, {}, paramGroup) + {} + + /*! + * \brief Returns the normal velocity at a given sub control volume face. + */ + VelocityVector faceVelocity(const Element& element, + const FVElementGeometry& fvGeometry, + const SubControlVolumeFace& scvf) const + { + if constexpr (isCoupled_) + return couplingManager_->faceVelocity(element, scvf); + else + return asImp_().velocityAtPos(scvf.ipGlobal()); + } + + /*! + * \brief Returns the velocity at the element center. + */ + VelocityVector elementVelocity(const FVElementGeometry& fvGeometry) const + { + if constexpr (isCoupled_) + return couplingManager_->elementVelocity(fvGeometry); + else + return asImp_().velocityAtPos(fvGeometry.element().geometry().center()); + } + + /*! + * \brief Returns the velocity at a given position. + */ + VelocityVector velocityAtPos(const GlobalPosition&) const + { + DUNE_THROW(Dune::NotImplemented, "velocityAtPos not implemented"); + } + + const CouplingManager& couplingManager() const + { + if constexpr (isCoupled_) + return *couplingManager_; + else + DUNE_THROW(Dune::InvalidStateException, + "Accessing coupling manager of an uncoupled problem is not possible." + ); + } + +private: + //! Returns the implementation of the problem (i.e. static polymorphism) + Implementation &asImp_() + { return *static_cast(this); } + + //! \copydoc asImp_() + const Implementation &asImp_() const + { return *static_cast(this); } + + std::shared_ptr couplingManager_; +}; + +/*! + * \ingroup NavierStokesModel + * \brief Navier-Stokes mass problem class + * + * Inherit from this problem to implement Navier-Stokes mass problems + */ +template +using NavierStokesMassProblem = NavierStokesMassProblemImpl< + TypeTag, typename GetPropType::DiscretizationMethod +>; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/momentum/boundarytypes.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/momentum/boundarytypes.hh new file mode 100644 index 0000000..6cd7b35 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/momentum/boundarytypes.hh @@ -0,0 +1,108 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup NavierStokesModel + * \copydoc Dumux::NavierStokesBoundaryTypes + */ +#ifndef FREEFLOW_NAVIERSTOKES_MOMENTUM_BOUNDARY_TYPES_HH +#define FREEFLOW_NAVIERSTOKES_MOMENTUM_BOUNDARY_TYPES_HH + +#include + +namespace Dumux { + +/*! + * \ingroup NavierStokesModel + * \brief Class to specify the type of a boundary condition for the Navier-Stokes model. + */ +template +class NavierStokesMomentumBoundaryTypes : public BoundaryTypes +{ + using ParentType = BoundaryTypes; + +public: + NavierStokesMomentumBoundaryTypes() + { + for (int eqIdx=0; eqIdx < size; ++eqIdx) + resetEq(eqIdx); + } + + /*! + * \brief Reset the boundary types for one equation. + */ + void resetEq(const int eqIdx) + { + ParentType::resetEq(eqIdx); + + boundaryInfo_[eqIdx].isSymmetry = false; + boundaryInfo_[eqIdx].isBeaversJoseph = false; + } + + /*! + * \brief Sets a symmetry boundary condition for all equations + */ + void setAllSymmetry() + { + for (int eqIdx=0; eqIdx < size; ++eqIdx) + { + resetEq(eqIdx); + boundaryInfo_[eqIdx].isSymmetry = true; + } + } + + /*! + * \brief Returns true if the there is a symmetry boundary condition + */ + bool isSymmetry() const + { return boundaryInfo_[0].isSymmetry; } + + /*! + * \brief Set a boundary condition for a single equation to + * Beavers-Joseph(-Saffmann) (special case of Dirichlet b.c.). + */ + void setBeaversJoseph(const int eqIdx) + { + resetEq(eqIdx); + boundaryInfo_[eqIdx].isBeaversJoseph = true; + } + + /*! + * \brief Returns true if an equation is used to specify a + * Beavers-Joseph(-Saffman) boundary condition. + * + * \param eqIdx The index of the equation + */ + bool isBeaversJoseph(const int eqIdx) const + { return boundaryInfo_[eqIdx].isBeaversJoseph; } + + /*! + * \brief Returns true if some equation is used to specify a + * Beavers-Joseph(-Saffman) boundary condition. + */ + bool hasBeaversJoseph() const + { + for (int i = 0; i < size; ++i) + if (boundaryInfo_[i].isBeaversJoseph) + return true; + return false; + } + +protected: + //! use bitfields to minimize the size + struct NavierStokesBoundaryInfo + { + bool isSymmetry : 1; + bool isBeaversJoseph : 1; + }; + + std::array boundaryInfo_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/momentum/cvfe/flux.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/momentum/cvfe/flux.hh new file mode 100644 index 0000000..5d22514 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/momentum/cvfe/flux.hh @@ -0,0 +1,207 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup NavierStokesModel + * \copydoc Dumux::NavierStokesFluxVariablesImpl + */ +#ifndef DUMUX_NAVIERSTOKES_MOMENTUM_CVFE_FLUXVARIABLES_HH +#define DUMUX_NAVIERSTOKES_MOMENTUM_CVFE_FLUXVARIABLES_HH + +#include + +#include +#include +#include + +#include + +namespace Dumux { + +/*! + * \ingroup NavierStokesModel + * \brief Context for computing fluxes + * + * \tparam Problem the problem type to solve + * \tparam FVElementGeometry the element geometry type + * \tparam ElementVolumeVariables the element volume variables type + * \tparam ElementFluxVariablesCache the element flux variables cache type + */ +template +class NavierStokesMomentumFluxContext +{ + using Element = typename FVElementGeometry::Element; + using SubControlVolumeFace = typename FVElementGeometry::SubControlVolumeFace; +public: + + //! Initialize the flux variables storing some temporary pointers + NavierStokesMomentumFluxContext( + const Problem& problem, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const ElementFluxVariablesCache& elemFluxVarsCache, + const SubControlVolumeFace& scvf + ) + : problem_(problem) + , fvGeometry_(fvGeometry) + , elemVolVars_(elemVolVars) + , elemFluxVarsCache_(elemFluxVarsCache) + , scvf_(scvf) + {} + + const Problem& problem() const + { return problem_; } + + const Element& element() const + { return fvGeometry_.element(); } + + const SubControlVolumeFace& scvFace() const + { return scvf_; } + + const FVElementGeometry& fvGeometry() const + { return fvGeometry_; } + + const ElementVolumeVariables& elemVolVars() const + { return elemVolVars_; } + + const ElementFluxVariablesCache& elemFluxVarsCache() const + { return elemFluxVarsCache_; } + +private: + const Problem& problem_; + const FVElementGeometry& fvGeometry_; + const ElementVolumeVariables& elemVolVars_; + const ElementFluxVariablesCache& elemFluxVarsCache_; + const SubControlVolumeFace& scvf_; +}; + +/*! + * \ingroup NavierStokesModel + * \brief The flux variables class for the Navier-Stokes model using control-volume finite element schemes + */ +template +class NavierStokesMomentumFluxCVFE +{ + using GridView = typename GridGeometry::GridView; + using Element = typename GridView::template Codim<0>::Entity; + using Scalar = typename NumEqVector::value_type; + + using Extrusion = Extrusion_t; + + static constexpr int dim = GridView::dimension; + static constexpr int dimWorld = GridView::dimensionworld; + + using Tensor = Dune::FieldMatrix; + static_assert(NumEqVector::dimension == dimWorld, "Wrong dimension of velocity vector"); + +public: + /*! + * \brief Returns the diffusive momentum flux due to viscous forces + */ + template + NumEqVector advectiveMomentumFlux(const Context& context) const + { + if (!context.problem().enableInertiaTerms()) + return NumEqVector(0.0); + + const auto& fvGeometry = context.fvGeometry(); + const auto& elemVolVars = context.elemVolVars(); + const auto& scvf = context.scvFace(); + const auto& fluxVarCache = context.elemFluxVarsCache()[scvf]; + const auto& shapeValues = fluxVarCache.shapeValues(); + + // interpolate velocity at scvf + NumEqVector v(0.0); + for (const auto& scv : scvs(fvGeometry)) + v.axpy(shapeValues[scv.indexInElement()][0], elemVolVars[scv].velocity()); + + // get density from the problem + const Scalar density = context.problem().density(context.element(), context.fvGeometry(), scvf); + + const auto vn = v*scvf.unitOuterNormal(); + const auto& insideVolVars = elemVolVars[scvf.insideScvIdx()]; + const auto& outsideVolVars = elemVolVars[scvf.outsideScvIdx()]; + const auto upwindVelocity = vn > 0 ? insideVolVars.velocity() : outsideVolVars.velocity(); + const auto downwindVelocity = vn > 0 ? outsideVolVars.velocity() : insideVolVars.velocity(); + static const auto upwindWeight = getParamFromGroup(context.problem().paramGroup(), "Flux.UpwindWeight"); + const auto advectiveTermIntegrand = density*vn * (upwindWeight * upwindVelocity + (1.0-upwindWeight)*downwindVelocity); + + return advectiveTermIntegrand * Extrusion::area(fvGeometry, scvf) * insideVolVars.extrusionFactor(); + } + + /*! + * \brief Returns the diffusive momentum flux due to viscous forces + */ + template + NumEqVector diffusiveMomentumFlux(const Context& context) const + { + const auto& element = context.element(); + const auto& fvGeometry = context.fvGeometry(); + const auto& elemVolVars = context.elemVolVars(); + const auto& scvf = context.scvFace(); + const auto& fluxVarCache = context.elemFluxVarsCache()[scvf]; + + // interpolate velocity gradient at scvf + Tensor gradV(0.0); + for (const auto& scv : scvs(fvGeometry)) + { + const auto& volVars = elemVolVars[scv]; + for (int dir = 0; dir < dim; ++dir) + gradV[dir].axpy(volVars.velocity(dir), fluxVarCache.gradN(scv.indexInElement())); + } + + // get viscosity from the problem + const auto mu = context.problem().effectiveViscosity(element, fvGeometry, scvf); + + static const bool enableUnsymmetrizedVelocityGradient + = getParamFromGroup(context.problem().paramGroup(), "FreeFlow.EnableUnsymmetrizedVelocityGradient", false); + + // compute -mu*gradV*n*dA + NumEqVector diffusiveFlux = enableUnsymmetrizedVelocityGradient ? + mv(gradV, scvf.unitOuterNormal()) + : mv(gradV + getTransposed(gradV),scvf.unitOuterNormal()); + + diffusiveFlux *= -mu; + + static const bool enableDilatationTerm = getParamFromGroup(context.problem().paramGroup(), "FreeFlow.EnableDilatationTerm", false); + if (enableDilatationTerm) + diffusiveFlux += 2.0/3.0 * mu * trace(gradV) * scvf.unitOuterNormal(); + + diffusiveFlux *= Extrusion::area(fvGeometry, scvf) * elemVolVars[scvf.insideScvIdx()].extrusionFactor(); + return diffusiveFlux; + } + + template + NumEqVector pressureContribution(const Context& context) const + { + const auto& element = context.element(); + const auto& fvGeometry = context.fvGeometry(); + const auto& elemVolVars = context.elemVolVars(); + const auto& scvf = context.scvFace(); + + // The pressure force needs to take the extruded scvf area into account + const auto pressure = context.problem().pressure(element, fvGeometry, scvf); + + // The pressure contribution calculated above might have a much larger numerical value compared to the viscous or inertial forces. + // This may lead to numerical inaccuracies due to loss of significance (cancellation) for the final residual value. + // In the end, we are only interested in a pressure gradient between the two relevant faces so we can + // subtract a constant reference value from the actual pressure contribution. + const auto referencePressure = context.problem().referencePressure(); + + NumEqVector pn(scvf.unitOuterNormal()); + pn *= (pressure-referencePressure)*Extrusion::area(fvGeometry, scvf)*elemVolVars[scvf.insideScvIdx()].extrusionFactor(); + + return pn; + } +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/momentum/cvfe/indices.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/momentum/cvfe/indices.hh new file mode 100644 index 0000000..6eeda13 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/momentum/cvfe/indices.hh @@ -0,0 +1,61 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup NavierStokesModel + * \copydoc Dumux::NavierStokesIndices + */ +#ifndef DUMUX_NAVIERSTOKES_MOMENTUM_CVFE_INDICES_HH +#define DUMUX_NAVIERSTOKES_MOMENTUM_CVFE_INDICES_HH + +namespace Dumux { + +/*! + * \ingroup NavierStokesModel + * \brief The common indices for the isothermal Navier-Stokes model. + * + * \tparam dimension The dimension of the problem + */ +template +struct NavierStokesMomentumCVFEIndices +{ + static constexpr int dimXIdx = 0; //!< Index of the x-component of a vector of size dim + static constexpr int dimYIdx = 1; //!< Index of the y-component of a vector of size dim + static constexpr int dimZIdx = 2; //!< Index of the z-component of a vector of size dim + + static constexpr auto dim = dimension; + + static constexpr int momentumXBalanceIdx = 0; //!< Index of the momentum balance equation x-component + static constexpr int momentumYBalanceIdx = 1; //!< Index of the momentum balance equation y-component + static constexpr int momentumZBalanceIdx = 2; //!< Index of the momentum balance equation z-component + + static constexpr int velocityXIdx = 0; //!< Index of the velocity in a solution vector x-component + static constexpr int velocityYIdx = 1; //!< Index of the velocity in a solution vector y-component + static constexpr int velocityZIdx = 2; //!< Index of the velocity in a solution vector z-component + + /*! + * \brief Index of the velocity in a solution vector given a certain direction. + * \param dirIdx The index of the direction. + */ + static constexpr int velocity(int dirIdx) + { + return dirIdx; + } + + /*! + * \brief Index of the momentum balance equation given the direction + * \param dirIdx The index of the direction. + */ + static constexpr int momentumBalanceIdx(int dirIdx) + { + return dirIdx; + } +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/momentum/cvfe/localresidual.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/momentum/cvfe/localresidual.hh new file mode 100644 index 0000000..a92ec5b --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/momentum/cvfe/localresidual.hh @@ -0,0 +1,167 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup NavierStokesModel + * \copydoc Dumux::NavierStokesResidualImpl + */ +#ifndef DUMUX_NAVIERSTOKES_MOMENTUM_CVFE_LOCAL_RESIDUAL_HH +#define DUMUX_NAVIERSTOKES_MOMENTUM_CVFE_LOCAL_RESIDUAL_HH + +#include + +#include +#include + +#include +#include +#include + +#include + +namespace Dumux { + +/*! + * \ingroup NavierStokesModel + * \brief Element-wise calculation of the Navier-Stokes residual for models using CVFE discretizations + */ +template +class NavierStokesMomentumCVFELocalResidual +: public CVFELocalResidual +{ + using ParentType = CVFELocalResidual; + + using GridVariables = GetPropType; + + using GridVolumeVariables = typename GridVariables::GridVolumeVariables; + using ElementVolumeVariables = typename GridVolumeVariables::LocalView; + using VolumeVariables = typename GridVolumeVariables::VolumeVariables; + + using GridFluxVariablesCache = typename GridVariables::GridFluxVariablesCache; + using ElementFluxVariablesCache = typename GridFluxVariablesCache::LocalView; + + using Scalar = GetPropType; + using Problem = GetPropType; + using GridGeometry = GetPropType; + using FVElementGeometry = typename GridGeometry::LocalView; + using SubControlVolume = typename FVElementGeometry::SubControlVolume; + using SubControlVolumeFace = typename FVElementGeometry::SubControlVolumeFace; + using GridView = typename GridGeometry::GridView; + using Element = typename GridView::template Codim<0>::Entity; + using ElementBoundaryTypes = GetPropType; + using Indices = typename GetPropType::Indices; + using PrimaryVariables = GetPropType; + using NumEqVector = Dumux::NumEqVector; + + using Extrusion = Extrusion_t; + + using ModelTraits = GetPropType; + + static constexpr auto dim = GridView::dimension; + + using FluxContext = NavierStokesMomentumFluxContext; + using FluxHelper = NavierStokesMomentumFluxCVFE; + +public: + //! Use the parent type's constructor + using ParentType::ParentType; + + /*! + * \brief Calculate the storage term of the equation + * + * \param problem The problem to solve + * \param fvGeometry The finite-volume geometry of the element + * \param scv The sub-control volume over which we integrate the storage term + * \param volVars The volume variables associated with the scv + * \param isPreviousStorage If set to true, the storage term is evaluated on the previous time level. + * + */ + NumEqVector computeStorage(const Problem& problem, + const FVElementGeometry& fvGeometry, + const SubControlVolume& scv, + const VolumeVariables& volVars, + const bool isPreviousStorage) const + { + return problem.density(fvGeometry.element(), fvGeometry, scv, isPreviousStorage) * volVars.velocity(); + } + + /*! + * \brief Calculate the source term of the equation + * + * \param problem The problem to solve + * \param element The DUNE Codim<0> entity for which the residual + * ought to be calculated + * \param fvGeometry The finite-volume geometry of the element + * \param elemVolVars The volume variables associated with the element stencil + * \param scv The sub-control volume over which we integrate the source term + * + */ + NumEqVector computeSource(const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const SubControlVolume& scv) const + { + NumEqVector source = ParentType::computeSource(problem, element, fvGeometry, elemVolVars, scv); + + // add rho*g (note that gravity might be zero in case it's disabled in the problem) + source += problem.density(element, fvGeometry, scv) * problem.gravity(); + + // Axisymmetric problems in 2D feature an extra source term arising from the transformation to cylindrical coordinates. + // See Ferziger/Peric: Computational methods for Fluid Dynamics (2020) + // https://doi.org/10.1007/978-3-319-99693-6 + // Chapter 9.9 and Eq. (9.81) and comment on finite volume methods + if constexpr (dim == 2 && isRotationalExtrusion) + { + // the radius with respect to the rotation axis + const auto r = scv.center()[Extrusion::radialAxis] - fvGeometry.gridGeometry().bBoxMin()[Extrusion::radialAxis]; + + // The velocity term is new with respect to Cartesian coordinates and handled below as a source term + // It only enters the balance of the momentum balance in radial direction + source[Extrusion::radialAxis] += -2.0*problem.effectiveViscosity(element, fvGeometry, scv) + * elemVolVars[scv].velocity(Extrusion::radialAxis) / (r*r); + + // Pressure term (needed because we incorporate pressure in terms of a surface integral). + // grad(p) becomes div(pI) + (p/r)*n_r in cylindrical coordinates. The second term + // is new with respect to Cartesian coordinates and handled below as a source term. + source[Extrusion::radialAxis] += problem.pressure(element, fvGeometry, scv)/r; + } + + return source; + } + + /*! + * \brief Evaluates the mass flux over a face of a sub control volume. + * + * \param problem The problem + * \param element The element + * \param fvGeometry The finite volume geometry context + * \param elemVolVars The volume variables for all flux stencil elements + * \param scvf The sub control volume face to compute the flux on + * \param elemFluxVarsCache The cache related to flux computation + */ + NumEqVector computeFlux(const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const SubControlVolumeFace& scvf, + const ElementFluxVariablesCache& elemFluxVarsCache) const + { + FluxContext context(problem, fvGeometry, elemVolVars, elemFluxVarsCache, scvf); + FluxHelper fluxHelper; + + NumEqVector flux(0.0); + flux += fluxHelper.advectiveMomentumFlux(context); + flux += fluxHelper.diffusiveMomentumFlux(context); + flux += fluxHelper.pressureContribution(context); + return flux; + } +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/momentum/cvfe/model.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/momentum/cvfe/model.hh new file mode 100644 index 0000000..aa97cda --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/momentum/cvfe/model.hh @@ -0,0 +1,196 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup NavierStokesModel + * + * \brief A single-phase, isothermal Navier-Stokes model + * + * This model implements a single-phase, isothermal Navier-Stokes model, solving the momentum balance equation + * \f[ + \frac{\partial (\varrho \textbf{v})}{\partial t} + \nabla \cdot (\varrho \textbf{v} \textbf{v}^{\text{T}}) = \nabla \cdot (\mu (\nabla \textbf{v} + \nabla \textbf{v}^{\text{T}})) + - \nabla p + \varrho \textbf{g} - \textbf{f} + * \f] + * By setting the runtime parameter Problem.EnableInertiaTerms to false the Stokes + * equation can be solved. In this case the term + * \f[ + * \nabla \cdot (\varrho \textbf{v} \textbf{v}^{\text{T}}) + * \f] + * is neglected. + * + * The mass balance equation + * \f[ + \frac{\partial \varrho}{\partial t} + \nabla \cdot (\varrho \textbf{v}) - q = 0 + * \f] + * + * closes the system. + */ + +#ifndef DUMUX_NAVIERSTOKES_MOMENTUM_CVFE_MODEL_HH +#define DUMUX_NAVIERSTOKES_MOMENTUM_CVFE_MODEL_HH + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +namespace Dumux { + +/*! + * \ingroup NavierStokesModel + * \brief Traits for the Navier-Stokes model + * + * \tparam dimension The dimension of the problem + */ +template +struct NavierStokesMomentumCVFEModelTraits +{ + //! The dimension of the model + static constexpr int dim() { return dimension; } + + //! There are as many momentum balance equations as dimensions + //! and one mass balance equation. + static constexpr int numEq() { return dim(); } + + //! The number of phases is 1 + static constexpr int numFluidPhases() { return 1; } + + //! The number of components is 1 + static constexpr int numFluidComponents() { return 1; } + + //! Enable advection + static constexpr bool enableAdvection() { return true; } + + //! The one-phase model has no molecular diffusion + static constexpr bool enableMolecularDiffusion() { return false; } + + //! The model is isothermal + static constexpr bool enableEnergyBalance() { return false; } + + //! The model does not include a turbulence model + static constexpr bool usesTurbulenceModel() { return false; } + + //! return the type of turbulence model used + static constexpr auto turbulenceModel() + { return TurbulenceModel::none; } + + //! the indices + using Indices = NavierStokesMomentumCVFEIndices; +}; + +/*! + * \ingroup NavierStokesModel + * \brief Traits class for the volume variables of the Navier-Stokes model. + * + * \tparam PV The type used for primary variables + * \tparam FSY The fluid system type + * \tparam FST The fluid state type + * \tparam MT The model traits + */ +template +struct NavierStokesMomentumCVFEVolumeVariablesTraits +{ + using PrimaryVariables = PV; + using FluidSystem = FSY; + using FluidState = FST; + using ModelTraits = MT; +}; + +} // end namespace Dumux + +// \{ +/////////////////////////////////////////////////////////////////////////// +// properties for the single-phase Navier-Stokes model +/////////////////////////////////////////////////////////////////////////// +namespace Dumux::Properties { + +////////////////////////////////////////////////////////////////// +// Type tags +////////////////////////////////////////////////////////////////// + +// Create new type tags +namespace TTag { +//! The type tag for the single-phase, isothermal Navier-Stokes model +struct NavierStokesMomentumCVFE { using InheritsFrom = std::tuple; }; +} // end namespace TTag + +/////////////////////////////////////////////////////////////////////////// +// default property values for the isothermal single phase model +/////////////////////////////////////////////////////////////////////////// +//!< states some specifics of the Navier-Stokes model +template +struct ModelTraits +{ +private: + using GridView = typename GetPropType::GridView; + static constexpr auto dim = GridView::dimension; +public: + using type = NavierStokesMomentumCVFEModelTraits; +}; + +/*! + * \brief The fluid state which is used by the volume variables to + * store the thermodynamic state. This should be chosen + * appropriately for the model ((non-)isothermal, equilibrium, ...). + * This can be done in the problem. + */ +template +struct FluidState{ +private: + using Scalar = GetPropType; + using FluidSystem = GetPropType; +public: + using type = ImmiscibleFluidState; +}; + +//! The local residual +template +struct LocalResidual +{ using type = NavierStokesMomentumCVFELocalResidual; }; + +//! Set the volume variables property +template +struct VolumeVariables +{ +private: + using PV = GetPropType; + using FSY = GetPropType; + using FST = GetPropType; + using MT = GetPropType; + + static_assert(FSY::numPhases == MT::numFluidPhases(), "Number of phases mismatch between model and fluid system"); + static_assert(FST::numPhases == MT::numFluidPhases(), "Number of phases mismatch between model and fluid state"); + static_assert(!FSY::isMiscible(), "The Navier-Stokes model only works with immiscible fluid systems."); + + using Traits = NavierStokesMomentumCVFEVolumeVariablesTraits; +public: + using type = NavierStokesMomentumCVFEVolumeVariables; +}; + +// This is the default (model not coupled with a mass (pressure) discretization) +// i.e. the pressure is supplied via the problem as an analytical solution +// or from a separate computation +template +struct CouplingManager +{ + struct EmptyCouplingManager {}; + using type = EmptyCouplingManager; +}; + +} // end namespace Dumux::Properties + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/momentum/cvfe/volumevariables.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/momentum/cvfe/volumevariables.hh new file mode 100644 index 0000000..ab9688a --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/momentum/cvfe/volumevariables.hh @@ -0,0 +1,94 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup NavierStokesModel + * + * \copydoc Dumux::NavierStokesVolumeVariables + */ +#ifndef DUMUX_NAVIERSTOKES_MOMENTUM_CVFE_VOLUME_VARIABLES_HH +#define DUMUX_NAVIERSTOKES_MOMENTUM_CVFE_VOLUME_VARIABLES_HH + + +namespace Dumux { + +/*! + * \ingroup NavierStokesModel + * \brief Volume variables for the single-phase Navier-Stokes model. + */ +template +class NavierStokesMomentumCVFEVolumeVariables +{ + using Scalar = typename Traits::PrimaryVariables::value_type; + + static_assert(Traits::PrimaryVariables::dimension == Traits::ModelTraits::dim()); + +public: + //! export the type used for the primary variables + using PrimaryVariables = typename Traits::PrimaryVariables; + + //! export the indices type + using Indices = typename Traits::ModelTraits::Indices; + + /*! + * \brief Update all quantities for a given control volume + * + * \param elemSol A vector containing all primary variables connected to the element + * \param problem The object specifying the problem which ought to + * be simulated + * \param element An element which contains part of the control volume + * \param scv The sub-control volume + */ + template + void update(const ElementSolution& elemSol, + const Problem& problem, + const Element& element, + const SubControlVolume& scv) + { + priVars_ = elemSol[scv.indexInElement()]; + extrusionFactor_ = problem.spatialParams().extrusionFactor(element, scv, elemSol); + } + + /*! + * \brief Return how much the sub-control volume is extruded. + * + * This means the factor by which a lower-dimensional (1D or 2D) + * entity needs to be expanded to get a full dimensional cell. The + * default is 1.0 which means that 1D problems are actually + * thought as pipes with a cross section of 1 m^2 and 2D problems + * are assumed to extend 1 m to the back. + */ + Scalar extrusionFactor() const + { return extrusionFactor_; } + + PrimaryVariables velocity() const + { return priVars_; } + + Scalar velocity(const int dirIdx) const + { return priVars_[dirIdx]; } + + /*! + * \brief Return a component of primary variable vector + * \param pvIdx The index of the primary variable of interest + */ + Scalar priVar(const int pvIdx) const + { return priVars_[pvIdx]; } + + /*! + * \brief Return the primary variable vector + */ + const PrimaryVariables& priVars() const + { return priVars_; } + +private: + PrimaryVariables priVars_; + Scalar extrusionFactor_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/momentum/problem.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/momentum/problem.hh new file mode 100644 index 0000000..d2c680d --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/momentum/problem.hh @@ -0,0 +1,843 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup NavierStokesModel + * \copydoc Dumux::NavierStokesMomentumProblem + */ +#ifndef DUMUX_NAVIERSTOKES_MOMENTUM_PROBLEM_HH +#define DUMUX_NAVIERSTOKES_MOMENTUM_PROBLEM_HH + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Dumux { + +template +class NavierStokesMomentumProblemImpl; + +template +class NavierStokesMomentumProblemImpl +: public FVProblemWithSpatialParams +{ + using ParentType = FVProblemWithSpatialParams; + using Implementation = GetPropType; + + using GridGeometry = GetPropType; + using GridView = typename GridGeometry::GridView; + using Element = typename GridView::template Codim<0>::Entity; + + using GridVariables = GetPropType; + using GridVolumeVariables = typename GridVariables::GridVolumeVariables; + using ElementVolumeVariables = typename GridVolumeVariables::LocalView; + using Scalar = GetPropType; + + using FVElementGeometry = typename GridGeometry::LocalView; + using SubControlVolume = typename FVElementGeometry::SubControlVolume; + using SubControlVolumeFace = typename FVElementGeometry::SubControlVolumeFace; + using Indices = typename GetPropType::Indices; + + enum { + dim = GridView::dimension, + dimWorld = GridView::dimensionworld + }; + + using GlobalPosition = typename SubControlVolumeFace::GlobalPosition; + using VelocityVector = Dune::FieldVector; + using GravityVector = Dune::FieldVector; + using CouplingManager = GetPropType; + using ModelTraits = GetPropType; + + static constexpr bool isCoupled_ = !std::is_empty_v; + + +public: + //! These types are used in place of the typical NumEqVector type. + //! In the numeqvector assembly type, only one equation per DOF (face) is considered + //! while the type here provides one entry for each world dimension. + using InitialValues = Dune::FieldVector; + using Sources = Dune::FieldVector; + using DirichletValues = Dune::FieldVector; + using BoundaryFluxes = Dune::FieldVector; + + using MomentumFluxType = Dune::FieldVector; + + //! Export the boundary types. + using BoundaryTypes = NavierStokesMomentumBoundaryTypes; + + //! This problem is used for the momentum balance model. + static constexpr bool isMomentumProblem() { return true; } + + /*! + * \brief The constructor + * \param gridGeometry The finite volume grid geometry + * \param couplingManager The coupling manager (couples mass and momentum equations) + * \param paramGroup The parameter group in which to look for runtime parameters first (default is "") + */ + NavierStokesMomentumProblemImpl(std::shared_ptr gridGeometry, + std::shared_ptr couplingManager, + const std::string& paramGroup = "") + : ParentType(gridGeometry, paramGroup) + , gravity_(0.0) + , couplingManager_(couplingManager) + { + if (getParamFromGroup(paramGroup, "Problem.EnableGravity")) + gravity_[dim-1] = -9.81; + + enableInertiaTerms_ = getParamFromGroup(paramGroup, "Problem.EnableInertiaTerms"); + } + + /*! + * \brief The constructor for usage without a coupling manager + * \param gridGeometry The finite volume grid geometry + * \param paramGroup The parameter group in which to look for runtime parameters first (default is "") + */ + NavierStokesMomentumProblemImpl(std::shared_ptr gridGeometry, + const std::string& paramGroup = "") + : NavierStokesMomentumProblemImpl(gridGeometry, {}, paramGroup) + {} + + /*! + * \brief Evaluate the source term for all phases within a given + * sub-control-volume. + * + * This is the method for the case where the source term is + * potentially solution dependent and requires some quantities that + * are specific to the fully-implicit method. + * + * \param element The finite element + * \param fvGeometry The finite-volume geometry + * \param elemVolVars All volume variables for the element + * \param scv The sub control volume + * + * For this method, the return parameter stores the conserved quantity rate + * generated or annihilate per volume unit. Positive values mean + * that the conserved quantity is created, negative ones mean that it vanishes. + * E.g. for the mass balance that would be a mass rate in \f$ [ kg / (m^3 \cdot s)] \f$. + */ + Sources source(const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const SubControlVolume& scv) const + { + // forward to solution independent, fully-implicit specific interface + return asImp_().sourceAtPos(scv.dofPosition()); + } + + /*! + * \brief Evaluate the source term for all phases within a given + * sub-control-volume. + * + * \param globalPos The position of the center of the finite volume + * for which the source term ought to be + * specified in global coordinates + * + * For this method, the values parameter stores the conserved quantity rate + * generated or annihilate per volume unit. Positive values mean + * that the conserved quantity is created, negative ones mean that it vanishes. + * E.g. for the mass balance that would be a mass rate in \f$ [ kg / (m^3 \cdot s)] \f$. + */ + Sources sourceAtPos(const GlobalPosition& globalPos) const + { + //! As a default, i.e. if the user's problem does not overload any source method + //! return 0.0 (no source terms) + return Sources(0.0); + } + + /*! + * \brief Adds contribution of point sources for a specific sub control volume + * to the values. + * Caution: Only overload this method in the implementation if you know + * what you are doing. + */ + Sources scvPointSources(const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const SubControlVolume& scv) const + { + if (!this->pointSourceMap().empty()) + DUNE_THROW(Dune::NotImplemented, "scvPointSources not implemented"); + + return Sources(0.0); + } + + /*! + * \brief Specifies which kind of boundary condition should be + * used for which equation on a given boundary segment. + * + * \param element The finite element + * \param scvf The sub control volume face + */ + auto boundaryTypes(const Element& element, + const SubControlVolumeFace& scvf) const + { + // Forward it to the method which only takes the global coordinate. + // We evaluate the boundary type at the center of the sub control volume face + // in order to avoid ambiguities at domain corners. + return asImp_().boundaryTypesAtPos(scvf.center()); + } + + /*! + * \brief Evaluate the boundary conditions for a dirichlet + * control volume face. + * + * \param element The finite element + * \param scvf the sub control volume face + * \note used for cell-centered discretization schemes + */ + DirichletValues dirichlet(const Element& element, const SubControlVolumeFace& scvf) const + { + return asImp_().dirichletAtPos(scvf.ipGlobal()); + } + + /*! + * \brief Evaluates the boundary conditions for a Neumann control volume. + * + * \param element The element for which the Neumann boundary condition is set + * \param fvGeometry The fvGeometry + * \param elemVolVars The element volume variables + * \param elemFluxVarsCache The element flux variables cache + * \param scvf The boundary sub control volume face + */ + template + BoundaryFluxes neumann(const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const ElementFluxVariablesCache& elemFluxVarsCache, + const SubControlVolumeFace& scvf) const + { return asImp_().neumannAtPos(scvf.ipGlobal()); } + + /*! + * \brief Returns the neumann flux at a given position. + */ + BoundaryFluxes neumannAtPos(const GlobalPosition& globalPos) const + { return BoundaryFluxes(0.0); } //! A default, i.e. if the user's does not overload any neumann method + + /*! + * \brief Returns the acceleration due to gravity. + * + * If the Problem.EnableGravity parameter is true, this means + * \f$\boldsymbol{g} = ( 0,\dots,\ -9.81)^T \f$, else \f$\boldsymbol{g} = ( 0,\dots, 0)^T \f$ + */ + const GravityVector& gravity() const + { return gravity_; } + + /*! + * \brief Returns whether inertia terms should be considered. + */ + bool enableInertiaTerms() const + { return enableInertiaTerms_; } + + /*! + * \brief Returns the pressure at a given sub control volume face. + * \note Overload this if a fixed pressure shall be prescribed (e.g., given by an analytical solution). + */ + Scalar pressure(const Element& element, + const FVElementGeometry& fvGeometry, + const SubControlVolumeFace& scvf) const + { + if constexpr (isCoupled_) + return couplingManager_->pressure(element, fvGeometry, scvf); + else + return asImp_().pressureAtPos(scvf.ipGlobal()); + } + + /*! + * \brief Returns the pressure at a given position. + */ + Scalar pressureAtPos(const GlobalPosition&) const + { + DUNE_THROW(Dune::NotImplemented, "pressureAtPos not implemented"); + } + + /*! + * \brief Returns a reference pressure at a given sub control volume face. + * This pressure is subtracted from the actual pressure for the momentum balance + * which potentially helps to improve numerical accuracy by avoiding issues related do floating point arithmetic. + * \note Overload this for reference pressures other than zero. + */ + Scalar referencePressure(const Element& element, + const FVElementGeometry& fvGeometry, + const SubControlVolumeFace& scvf) const + { return 0.0; } + + /*! + * \brief Returns the density at a given sub control volume face. + * \note Overload this if a fixed density shall be prescribed. + */ + Scalar density(const Element& element, + const FVElementGeometry& fvGeometry, + const SubControlVolumeFace& scvf) const + { + if constexpr (isCoupled_) + return couplingManager_->density(element, fvGeometry, scvf); + else + return asImp_().densityAtPos(scvf.ipGlobal()); + } + + /*! + * \brief Returns the density at a given sub control volume. + * \note Overload this if a fixed density shall be prescribed. + */ + Scalar density(const Element& element, + const SubControlVolume& scv, + const bool isPreviousTimeStep = false) const + { + if constexpr (isCoupled_) + return couplingManager_->density(element, scv, isPreviousTimeStep); + else + return asImp_().densityAtPos(scv.dofPosition()); + } + + auto insideAndOutsideDensity(const Element& element, + const FVElementGeometry& fvGeometry, + const SubControlVolumeFace& scvf, + const bool isPreviousTimeStep = false) const + { + if constexpr (isCoupled_) + return couplingManager_->insideAndOutsideDensity(element, fvGeometry, scvf, isPreviousTimeStep); + else + { + const auto rho = asImp_().densityAtPos(scvf.ipGlobal()); + return std::make_pair(rho, rho); + } + } + + /*! + * \brief Returns the density at a given position. + */ + Scalar densityAtPos(const GlobalPosition&) const + { + DUNE_THROW(Dune::NotImplemented, "densityAtPos not implemented"); + } + + /*! + * \brief Returns the effective dynamic viscosity at a given sub control volume face. + * \note Overload this if a fixed viscosity shall be prescribed. + */ + Scalar effectiveViscosity(const Element& element, + const FVElementGeometry& fvGeometry, + const SubControlVolumeFace& scvf) const + { + if constexpr (isCoupled_) + return couplingManager_->effectiveViscosity(element, fvGeometry, scvf); + else + return asImp_().effectiveViscosityAtPos(scvf.ipGlobal()); + } + + /*! + * \brief Returns the effective dynamic viscosity at a given sub control volume. + * \note Overload this if a fixed viscosity shall be prescribed. + */ + Scalar effectiveViscosity(const Element& element, + const FVElementGeometry& fvGeometry, + const SubControlVolume& scv) const + { + if constexpr (isCoupled_) + return couplingManager_->effectiveViscosity(element, fvGeometry, scv); + else + return asImp_().effectiveViscosityAtPos(scv.dofPosition()); + } + + /*! + * \brief Returns the effective dynamic viscosity at a given position. + */ + Scalar effectiveViscosityAtPos(const GlobalPosition&) const + { + DUNE_THROW(Dune::NotImplemented, "effectiveViscosityAtPos not implemented"); + } + + /*! + * \brief Applies the initial solution for all degrees of freedom of the grid. + * \param sol the initial solution vector + */ + template + void applyInitialSolution(SolutionVector& sol) const + { + sol.resize(this->gridGeometry().numDofs()); + std::vector dofHandled(this->gridGeometry().numDofs(), false); + auto fvGeometry = localView(this->gridGeometry()); + for (const auto& element : elements(this->gridGeometry().gridView())) + { + fvGeometry.bindElement(element); + for (const auto& scv : scvs(fvGeometry)) + { + const auto dofIdx = scv.dofIndex(); + if (!dofHandled[dofIdx]) + { + dofHandled[dofIdx] = true; + sol[dofIdx] = asImp_().initial(scv)[scv.dofAxis()]; + } + } + } + } + + /*! + * \brief Evaluate the initial value at a sub control volume + */ + InitialValues initial(const SubControlVolume& scv) const + { + return asImp_().initialAtPos(scv.dofPosition()); + } + + //! Convenience function for staggered grid implementation. + Scalar pseudo3DWallFriction(const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const SubControlVolume& scv, + const Scalar height, + const Scalar factor = 8.0) const + { + const Scalar velocity = elemVolVars[scv].velocity(); + const auto scvf = scvfs(fvGeometry, scv).begin(); + const Scalar viscosity = effectiveViscosity(element, fvGeometry, *scvf); + return pseudo3DWallFriction(velocity, viscosity, height, factor); + } + + /*! + * \brief An additional drag term can be included as source term for the momentum balance + * to mimic 3D flow behavior in 2D: + * \f[ + * f_{drag} = -(8 \mu / h^2)v + * \f] + * Here, \f$h\f$ corresponds to the extruded height that is + * bounded by the imaginary walls. See Flekkoy et al. (1995) \cite flekkoy1995a
+ * A value of 8.0 is used as a default factor, corresponding + * to the velocity profile at the center plane + * of the virtual height (maximum velocity). Setting this value to 12.0 corresponds + * to an depth-averaged velocity (Venturoli and Boek, 2006) \cite venturoli2006a. + */ + Scalar pseudo3DWallFriction(const Scalar velocity, + const Scalar viscosity, + const Scalar height, + const Scalar factor = 8.0) const + { + static_assert(dim == 2, "Pseudo 3D wall friction may only be used in 2D"); + return -factor * velocity * viscosity / (height*height); + } + + /*! + * \brief Returns true if the scvf is located on a boundary with a slip condition. + * \note This member function must be overloaded in the problem implementation. + * Make sure to use scvf.center() for querying the spatial location. + */ + bool onSlipBoundary(const FVElementGeometry& fvGeometry, const SubControlVolumeFace& scvf) const + { return asImp_().onSlipBoundaryAtPos(scvf.center()); } + + /*! + * \brief Returns true if the scvf is located on a boundary with a slip condition. + * \note This member function must be overloaded in the problem implementation. + */ + bool onSlipBoundaryAtPos(const GlobalPosition& pos) const + { return false; } + + const CouplingManager& couplingManager() const + { + if constexpr (isCoupled_) + return *couplingManager_; + else + DUNE_THROW(Dune::InvalidStateException, + "Accessing coupling manager of an uncoupled problem is not possible." + ); + } + +private: + //! Returns the implementation of the problem (i.e. static polymorphism) + Implementation& asImp_() + { return *static_cast(this); } + + //! \copydoc asImp_() + const Implementation& asImp_() const + { return *static_cast(this); } + + GravityVector gravity_; + bool enableInertiaTerms_; + std::shared_ptr couplingManager_; +}; + +/*! + * \brief Navier-Stokes default problem implementation for CVFE discretizations + */ +template +class NavierStokesMomentumProblemImpl> +: public FVProblemWithSpatialParams +{ + using ParentType = FVProblemWithSpatialParams; + using Implementation = GetPropType; + + using GridGeometry = GetPropType; + using GridView = typename GridGeometry::GridView; + using Element = typename GridView::template Codim<0>::Entity; + + using GridVariables = GetPropType; + using GridVolumeVariables = typename GridVariables::GridVolumeVariables; + using ElementVolumeVariables = typename GridVolumeVariables::LocalView; + using Scalar = GetPropType; + + using FVElementGeometry = typename GridGeometry::LocalView; + using SubControlVolume = typename FVElementGeometry::SubControlVolume; + using SubControlVolumeFace = typename FVElementGeometry::SubControlVolumeFace; + using Indices = typename GetPropType::Indices; + + static constexpr int dim = GridView::dimension; + static constexpr int dimWorld = GridView::dimensionworld; + + using GlobalPosition = typename SubControlVolumeFace::GlobalPosition; + using VelocityVector = Dune::FieldVector; + using GravityVector = Dune::FieldVector; + using CouplingManager = GetPropType; + using ModelTraits = GetPropType; + +public: + //! These types are used in place of the typical NumEqVector type. + //! In the numeqvector assembly type, only one equation per DOF (face) is considered + //! while the type here provides one entry for each world dimension. + using InitialValues = Dune::FieldVector; + using Sources = Dune::FieldVector; + using DirichletValues = Dune::FieldVector; + using BoundaryFluxes = Dune::FieldVector; + + //! Export the boundary types. + using BoundaryTypes = NavierStokesMomentumBoundaryTypes; + + //! This problem is used for the momentum balance model. + static constexpr bool isMomentumProblem() { return true; } + + /*! + * \brief The constructor + * \param gridGeometry The finite volume grid geometry + * \param couplingManager A coupling manager (in case this problem is a coupled "mass-momentum"/full Navier-Stokes problem) + * \param paramGroup The parameter group in which to look for runtime parameters first (default is "") + */ + NavierStokesMomentumProblemImpl(std::shared_ptr gridGeometry, + std::shared_ptr couplingManager, + const std::string& paramGroup = "") + : ParentType(gridGeometry, paramGroup) + , gravity_(0.0) + , couplingManager_(couplingManager) + { + if (getParamFromGroup(paramGroup, "Problem.EnableGravity")) + gravity_[dim-1] = -9.81; + + enableInertiaTerms_ = getParamFromGroup(paramGroup, "Problem.EnableInertiaTerms"); + } + + /*! + * \brief The constructor for usage without a coupling manager + * \param gridGeometry The finite volume grid geometry + * \param paramGroup The parameter group in which to look for runtime parameters first (default is "") + */ + NavierStokesMomentumProblemImpl(std::shared_ptr gridGeometry, + const std::string& paramGroup = "") + : NavierStokesMomentumProblemImpl(gridGeometry, {}, paramGroup) + {} + + /*! + * \brief Evaluate the source term for all phases within a given + * sub-control-volume. + * + * This is the method for the case where the source term is + * potentially solution dependent and requires some quantities that + * are specific to the fully-implicit method. + * + * \param element The finite element + * \param fvGeometry The finite-volume geometry + * \param elemVolVars All volume variables for the element + * \param scv The sub control volume + * + * For this method, the return parameter stores the conserved quantity rate + * generated or annihilate per volume unit. Positive values mean + * that the conserved quantity is created, negative ones mean that it vanishes. + * E.g. for the mass balance that would be a mass rate in \f$ [ kg / (m^3 \cdot s)] \f$. + */ + template + Sources source(const Element &element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const SubControlVolume &scv) const + { + // forward to solution independent, fully-implicit specific interface + return asImp_().sourceAtPos(scv.center()); + } + + /*! + * \brief Evaluate the source term for all phases at a given position + * + * \param globalPos The position of the center of the finite volume + * for which the source term ought to be + * specified in global coordinates + * + * For this method, the values parameter stores the conserved quantity rate + * generated or annihilate per volume unit. Positive values mean + * that the conserved quantity is created, negative ones mean that it vanishes. + * E.g. for the mass balance that would be a mass rate in \f$ [ kg / (m^3 \cdot s)] \f$. + */ + Sources sourceAtPos(const GlobalPosition &globalPos) const + { + //! As a default, i.e. if the user's problem does not overload any source method + //! return 0.0 (no source terms) + return Sources(0.0); + } + + /*! + * \brief Specifies which kind of boundary condition should be + * used for which equation on a given boundary segment. + * + * \param element The finite element + * \param scv The sub control volume + */ + BoundaryTypes boundaryTypes(const Element& element, + const SubControlVolume& scv) const + { + // forward it to the method which only takes the global coordinate + return asImp_().boundaryTypesAtPos(scv.dofPosition()); + } + + /*! + * \brief Specifies which kind of boundary condition should be + * used for which equation on a given boundary segment. + * + * \param element The finite element + * \param scvf The sub control volume face + */ + BoundaryTypes boundaryTypes(const Element& element, + const SubControlVolumeFace& scvf) const + { + DUNE_THROW(Dune::InvalidStateException, "boundaryTypes(..., scvf) called for a CVFE method."); + } + + /*! + * \brief Evaluate the boundary conditions for a Dirichlet + * control volume. + * + * \param element The finite element + * \param scv the sub control volume + */ + DirichletValues dirichlet(const Element& element, const SubControlVolume& scv) const + { + // forward it to the method which only takes the global coordinate + return asImp_().dirichletAtPos(scv.dofPosition()); + } + + /*! + * \brief Evaluate the boundary conditions for a Dirichlet + * control volume face. + * + * \param element The finite element + * \param scvf the sub control volume face + */ + DirichletValues dirichlet(const Element& element, const SubControlVolumeFace& scvf) const + { + // forward it to the method which only takes the global coordinate + DUNE_THROW(Dune::InvalidStateException, "dirichlet(scvf) called for CVFE method."); + } + + /*! + * \brief Returns the acceleration due to gravity. + * + * If the Problem.EnableGravity parameter is true, this means + * \f$\boldsymbol{g} = ( 0,\dots,\ -9.81)^T \f$, else \f$\boldsymbol{g} = ( 0,\dots, 0)^T \f$ + */ + const GravityVector& gravity() const + { return gravity_; } + + /*! + * \brief Returns whether inertia terms should be considered. + */ + bool enableInertiaTerms() const + { return enableInertiaTerms_; } + + /*! + * \brief Returns a reference pressure + * This pressure is subtracted from the actual pressure for the momentum balance + * which potentially helps to improve numerical accuracy by avoiding issues related do floating point arithmetic. + * \note Overload this for reference pressures other than zero. + */ + Scalar referencePressure() const + { return 0.0; } + + /*! + * \brief Returns the pressure at a given sub control volume face. + * \note Overload this if a fixed pressure shall be prescribed (e.g., given by an analytical solution). + */ + Scalar pressure(const Element& element, + const FVElementGeometry& fvGeometry, + const SubControlVolumeFace& scvf) const + { + if constexpr (std::is_empty_v) + return asImp_().pressureAtPos(scvf.ipGlobal()); + else + return couplingManager_->pressure(element, fvGeometry, scvf); + } + + /*! + * \brief Returns the pressure at a given sub control volume. + * \note Overload this if a fixed pressure shall be prescribed (e.g., given by an analytical solution). + */ + Scalar pressure(const Element& element, + const FVElementGeometry& fvGeometry, + const SubControlVolume& scv, + const bool isPreviousTimeStep = false) const + { + if constexpr (std::is_empty_v) + return asImp_().pressureAtPos(scv.dofPosition()); + else + return couplingManager_->pressure(element, fvGeometry, scv, isPreviousTimeStep); + } + + /*! + * \brief Returns the pressure at a given position. + */ + Scalar pressureAtPos(const GlobalPosition&) const + { + DUNE_THROW(Dune::NotImplemented, "pressureAtPos not implemented"); + } + + /*! + * \brief Returns the density at a given sub control volume face. + * \note Overload this if a fixed density shall be prescribed. + */ + Scalar density(const Element& element, + const FVElementGeometry& fvGeometry, + const SubControlVolumeFace& scvf) const + { + if constexpr (std::is_empty_v) + return asImp_().densityAtPos(scvf.ipGlobal()); + else + return couplingManager_->density(element, fvGeometry, scvf); + } + + /*! + * \brief Returns the density at a given sub control volume. + * \note Overload this if a fixed density shall be prescribed. + */ + Scalar density(const Element& element, + const FVElementGeometry& fvGeometry, + const SubControlVolume& scv, + const bool isPreviousTimeStep = false) const + { + if constexpr (std::is_empty_v) + return asImp_().densityAtPos(scv.dofPosition()); + else + return couplingManager_->density(element, fvGeometry, scv, isPreviousTimeStep); + } + + + /*! + * \brief Returns the density at a given position. + */ + Scalar densityAtPos(const GlobalPosition&) const + { + DUNE_THROW(Dune::NotImplemented, "densityAtPos not implemented"); + } + + /*! + * \brief Returns the effective dynamic viscosity at a given sub control volume face. + * \note Overload this if a fixed viscosity shall be prescribed. + */ + Scalar effectiveViscosity(const Element& element, + const FVElementGeometry& fvGeometry, + const SubControlVolumeFace& scvf) const + { + if constexpr (std::is_empty_v) + return asImp_().effectiveViscosityAtPos(scvf.ipGlobal()); + else + return couplingManager_->effectiveViscosity(element, fvGeometry, scvf); + } + + /*! + * \brief Returns the effective dynamic viscosity at a given sub control volume. + * \note Overload this if a fixed viscosity shall be prescribed. + */ + Scalar effectiveViscosity(const Element& element, + const FVElementGeometry& fvGeometry, + const SubControlVolume& scv, + const bool isPreviousTimeStep = false) const + { + if constexpr (std::is_empty_v) + return asImp_().effectiveViscosityAtPos(scv.dofPosition()); + else + return couplingManager_->effectiveViscosity(element, fvGeometry, scv, isPreviousTimeStep); + } + + /*! + * \brief Returns the effective dynamic viscosity at a given position. + */ + Scalar effectiveViscosityAtPos(const GlobalPosition&) const + { + DUNE_THROW(Dune::NotImplemented, "effectiveViscosityAtPos not implemented"); + } + + /*! + * \brief Applies the initial solution for all degrees of freedom of the grid. + * \param sol the initial solution vector + */ + template + void applyInitialSolution(SolutionVector& sol) const + { + static_assert(GridGeometry::discMethod == DiscretizationMethods::CVFE{}); + sol.resize(this->gridGeometry().numDofs()); + std::vector dofHandled(this->gridGeometry().numDofs(), false); + auto fvGeometry = localView(this->gridGeometry()); + for (const auto& element : elements(this->gridGeometry().gridView())) + { + fvGeometry.bindElement(element); + for (const auto& scv : scvs(fvGeometry)) + { + const auto dofIdx = scv.dofIndex(); + if (!dofHandled[dofIdx]) + { + dofHandled[dofIdx] = true; + sol[dofIdx] = asImp_().initial(scv); + } + } + } + } + + /*! + * \brief Evaluate the initial value at an sub control volume + */ + InitialValues initial(const SubControlVolume& scv) const + { + static_assert(GridGeometry::discMethod == DiscretizationMethods::CVFE{}); + return asImp_().initialAtPos(scv.dofPosition()); + } + +private: + //! Returns the implementation of the problem (i.e. static polymorphism) + Implementation &asImp_() + { return *static_cast(this); } + + //! \copydoc asImp_() + const Implementation &asImp_() const + { return *static_cast(this); } + + GravityVector gravity_; + bool enableInertiaTerms_; + std::shared_ptr couplingManager_ = {}; +}; + +/*! + * \ingroup NavierStokesModel + * \brief Navier-Stokes momentum problem class + * + * Inherit from this problem to implement Navier-Stokes momentum problems + */ +template +using NavierStokesMomentumProblem = NavierStokesMomentumProblemImpl< + TypeTag, typename GetPropType::DiscretizationMethod +>; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/momentum/velocityreconstruction.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/momentum/velocityreconstruction.hh new file mode 100644 index 0000000..6340ccc --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/momentum/velocityreconstruction.hh @@ -0,0 +1,132 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup NavierStokesModel + * \copydoc Dumux::StaggeredVelocityReconstruction + */ +#ifndef DUMUX_NAVIERSTOKES_STAGGERED_VELOCITYRECONSTRUCTION_HH +#define DUMUX_NAVIERSTOKES_STAGGERED_VELOCITYRECONSTRUCTION_HH + +#include +#include +#include +#include + +namespace Dumux { + +/*! + * \ingroup NavierStokesModel + * \brief Helper class for reconstructing the velocity. + */ +struct StaggeredVelocityReconstruction +{ + //! Return the velocity vector at the center of the primal grid. + template + static auto cellCenterVelocity(const VelocityHelper& getFaceVelocity, + const FVElementGeometry& fvGeometry) + { + static_assert(FVElementGeometry::GridGeometry::discMethod == DiscretizationMethods::cctpfa); + using VelocityVector = typename FVElementGeometry::GridGeometry::GlobalCoordinate; + VelocityVector result(0.0); + + const auto directionIndex = [&](const auto& vector) + { + return std::find_if(vector.begin(), vector.end(), [](const auto& x) { return std::abs(x) > 1e-8; } ) - vector.begin(); + }; + + for (const auto& scvf : scvfs(fvGeometry)) + { + const auto dirIdx = directionIndex(scvf.unitOuterNormal()); + result[dirIdx] += 0.5*getFaceVelocity(fvGeometry, scvf)[dirIdx]; + } + return result; + } + + //! Return the velocity vector at dof position given an scv + template + static auto faceVelocity(const SubControlVolume& scv, + const FVElementGeometry& fvGeometry, + const VelocityHelper& getNormalVelocityDofValue) + { + int axis = scv.dofAxis(); + const int dim = FVElementGeometry::GridGeometry::GridView::dimension; + using Scalar = typename SubControlVolume::Traits::Scalar; + using GlobalPosition = typename FVElementGeometry::GridGeometry::GlobalCoordinate; + using VelocityVector = typename FVElementGeometry::GridGeometry::GlobalCoordinate; + VelocityVector faceVelocityVector(0.0); + + // per dimension, we have at max two velocities from which we'll compute an average + std::array, dim> normalVelocities; + if (scv.boundary() && !fvGeometry.gridGeometry().dofOnPeriodicBoundary(scv.dofIndex())) + { + // iterate through the inner lateral velocities, + for (const auto& scvf : scvfs(fvGeometry, scv)) + { + if (scvf.isFrontal()) + continue; + + // at a lateral velocity, find the inner and outer normal velocities + const auto& orthogonalScvf = fvGeometry.lateralOrthogonalScvf(scvf); + const auto& lateralScv = fvGeometry.scv(orthogonalScvf.insideScvIdx()); + auto lateralAxis = lateralScv.dofAxis(); + normalVelocities[lateralAxis].push_back( getNormalVelocityDofValue(lateralScv) ) ; + } + } + else + { + // Find the location of interpolation, if periodic, from both sides + const GlobalPosition selfPosition = scv.dofPosition(); + GlobalPosition outsideSelfPosition = selfPosition; + if (fvGeometry.gridGeometry().dofOnPeriodicBoundary(scv.dofIndex())) + outsideSelfPosition = fvGeometry.outsidePeriodicScv(scv).dofPosition(); + + // iterate through the lateral velocities to get to the normal locations + for (const auto& scvf : scvfs(fvGeometry, scv)) + { + if (scvf.isFrontal()) + continue; + + // at a lateral velocity, find the inner and outer normal velocities + const auto& orthogonalScvf = fvGeometry.lateralOrthogonalScvf(scvf); + const auto& insideLateralScv = fvGeometry.scv(orthogonalScvf.insideScvIdx()); + const auto& outsideLateralScv = fvGeometry.scv(orthogonalScvf.outsideScvIdx()); + const auto& lateralAxis = insideLateralScv.dofAxis(); + + // Find the inside normal velocities + const auto& insideNormalVelocity = getNormalVelocityDofValue(insideLateralScv); + const auto& insideNormalPosition = insideLateralScv.dofPosition()[axis]; + + // Find the outside normal velocities + const auto& outsideNormalVelocity = getNormalVelocityDofValue(outsideLateralScv); + const auto& outsideNormalPosition = outsideLateralScv.dofPosition()[axis]; + + // Linear interpolation at the face plane and add to normal velocity collection + const auto& innerDistance = std::abs(insideNormalPosition - selfPosition[axis]); + const auto& totalDistance = std::abs(outsideNormalPosition - outsideSelfPosition[axis]) + innerDistance; + const auto& velDiff = outsideNormalVelocity - insideNormalVelocity; + + normalVelocities[lateralAxis].push_back(insideNormalVelocity + (velDiff * innerDistance / totalDistance)); + } + } + + for (int i = 0; i < faceVelocityVector.size(); i++) + { + if (i == axis) + faceVelocityVector[i] = getNormalVelocityDofValue(scv); + else + faceVelocityVector[i] = std::accumulate(normalVelocities[i].begin(), normalVelocities[i].end(), 0.0) / normalVelocities[i].size(); + } + + return faceVelocityVector; + } + +}; + +} // end namespace Dumux + +#endif // DUMUX_NAVIERSTOKES_STAGGERED_VELOCITYRECONSTRUCTION_HH diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/scalarfluxvariables.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/scalarfluxvariables.hh new file mode 100644 index 0000000..c5d6389 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/scalarfluxvariables.hh @@ -0,0 +1,145 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup NavierStokesModel + * \copydoc Dumux::NavierStokesFluxVariablesImpl + */ +#ifndef DUMUX_NAVIERSTOKES_SCALAR_CONSERVATION_MODEL_FLUXVARIABLES_HH +#define DUMUX_NAVIERSTOKES_SCALAR_CONSERVATION_MODEL_FLUXVARIABLES_HH + +#include +#include +#include +#include +#include +#include + +namespace Dumux { + +/*! + * \ingroup NavierStokesModel + * \brief The flux variables base class for scalar quantities balanced in the Navier-Stokes model. + */ +template::GridGeometry>> +class NavierStokesScalarConservationModelFluxVariables +: public FluxVariablesBase::GridGeometry::LocalView, + ElementVolumeVariables, + ElementFluxVariablesCache> +{ + using Scalar = typename ElementVolumeVariables::VolumeVariables::PrimaryVariables::value_type; + using GridGeometry = typename ProblemTraits::GridGeometry; + using SubControlVolumeFace = typename GridGeometry::SubControlVolumeFace; + using Element = typename GridGeometry::GridView::template Codim<0>::Entity; + using FVElementGeometry = typename GridGeometry::LocalView; + using Extrusion = Extrusion_t::GridGeometry>; + +public: + + struct AdvectionType + { + static Scalar flux(const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const SubControlVolumeFace& scvf, + const int phaseIdx, + const ElementFluxVariablesCache& elemFluxVarsCache) + { + const auto velocity = problem.faceVelocity(element, fvGeometry, scvf); + const Scalar volumeFlux = velocity*scvf.unitOuterNormal()*Extrusion::area(fvGeometry, scvf)*extrusionFactor_(elemVolVars, scvf); + return volumeFlux; + } + }; + + /*! + * \brief Returns the advective flux computed by the respective law. + */ + template + Scalar getAdvectiveFlux(const FunctionType& upwindTerm) const + { + if constexpr (ModelTraits::enableAdvection()) + { + const auto& scvf = this->scvFace(); + const auto velocity = this->problem().faceVelocity(this->element(), this->fvGeometry(), scvf); + const Scalar volumeFlux = velocity*scvf.unitOuterNormal()*Extrusion::area(this->fvGeometry(), scvf)*extrusionFactor_(this->elemVolVars(), scvf); + return UpwindScheme::apply(*this, upwindTerm, volumeFlux, 0/*phaseIdx*/); + } + else + return 0.0; + } + + /*! + * \brief Returns the conductive energy flux computed by the respective law. + */ + Scalar heatConductionFlux() const + { + if constexpr (ModelTraits::enableEnergyBalance()) + { + using HeatConductionType = typename FluxTypes::HeatConductionType; + return HeatConductionType::flux(this->problem(), + this->element(), + this->fvGeometry(), + this->elemVolVars(), + this->scvFace(), + this->elemFluxVarsCache()); + } + else + return 0.0; + } + + /*! + * \brief Returns the advective energy flux. + */ + Scalar heatAdvectionFlux() const + { + if constexpr (ModelTraits::enableEnergyBalance()) + { + const auto upwindTerm = [](const auto& volVars) { return volVars.density() * volVars.enthalpy(); }; + return getAdvectiveFlux(upwindTerm); + } + else + return 0.0; + } + + /*! + * \brief Returns the total energy flux. + */ + Scalar heatFlux() const + { + return heatConductionFlux() + heatAdvectionFlux(); + } + + /*! + * \brief Adds the energy flux to a given flux vector. + */ + template + void addHeatFlux(NumEqVector& flux) const + { + if constexpr (ModelTraits::enableEnergyBalance()) + flux[ModelTraits::Indices::energyEqIdx] = heatFlux(); + } + +private: + + static Scalar extrusionFactor_(const ElementVolumeVariables& elemVolVars, const SubControlVolumeFace& scvf) + { + const auto& insideVolVars = elemVolVars[scvf.insideScvIdx()]; + const auto& outsideVolVars = elemVolVars[scvf.outsideScvIdx()]; + return harmonicMean(insideVolVars.extrusionFactor(), outsideVolVars.extrusionFactor()); + } +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/scalarfluxvariablescachefiller.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/scalarfluxvariablescachefiller.hh new file mode 100644 index 0000000..6b7662e --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/scalarfluxvariablescachefiller.hh @@ -0,0 +1,151 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup NavierStokesModel + * \brief A helper class to fill the flux variables cache + */ +#ifndef DUMUX_FREEFLOW_SCALAR_FLUXVARIABLESCACHE_FILLER_HH +#define DUMUX_FREEFLOW_SCALAR_FLUXVARIABLESCACHE_FILLER_HH + +#include +#include + +#include +#include +#include +#include + +namespace Dumux { + +// forward declaration +template +class FreeFlowScalarFluxVariablesCacheFillerImplementation; + +/*! + * \ingroup NavierStokesModel + * \brief The flux variables cache filler class for free flow + * + * Helps filling the flux variables cache depending on several policies + */ +template +using FreeFlowScalarFluxVariablesCacheFiller = FreeFlowScalarFluxVariablesCacheFillerImplementation::GridGeometry::DiscretizationMethod>; + +//! Specialization of the flux variables cache filler for the cell centered tpfa method +template +class FreeFlowScalarFluxVariablesCacheFillerImplementation +{ + using GridGeometry = typename ProblemTraits::GridGeometry; + using GridView = typename GridGeometry::GridView; + using FVElementGeometry = typename GridGeometry::LocalView; + using SubControlVolume = typename GridGeometry::SubControlVolume; + using SubControlVolumeFace = typename GridGeometry::SubControlVolumeFace; + + using Element = typename GridView::template Codim<0>::Entity; + + static constexpr bool diffusionEnabled = ModelTraits::enableMolecularDiffusion(); + static constexpr bool heatConductionEnabled = ModelTraits::enableEnergyBalance(); + +public: + static constexpr bool isSolDependent = (diffusionEnabled && diffusionIsSolDependent) || + (heatConductionEnabled && heatConductionIsSolDependent); + + //! The constructor. Sets the problem pointer + FreeFlowScalarFluxVariablesCacheFillerImplementation(const Problem& problem) + : problemPtr_(&problem) {} + + /*! + * \brief function to fill the flux variables caches + * + * \param fluxVarsCacheContainer Either the element or global flux variables cache + * \param scvfFluxVarsCache The flux var cache to be updated corresponding to the given scvf + * \param element The finite element + * \param fvGeometry The finite volume geometry + * \param elemVolVars The element volume variables + * \param scvf The corresponding sub-control volume face + * \param forceUpdateAll if true, forces all caches to be updated (even the solution-independent ones) + */ + template + void fill(FluxVariablesCacheContainer& fluxVarsCacheContainer, + FluxVariablesCache& scvfFluxVarsCache, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const SubControlVolumeFace& scvf, + const bool forceUpdateAll = false) + { + // fill the physics-related quantities of the caches + if (forceUpdateAll) + { + if constexpr (diffusionEnabled) + fillDiffusion_(scvfFluxVarsCache, element, fvGeometry, elemVolVars, scvf); + if constexpr (heatConductionEnabled) + fillHeatConduction_(scvfFluxVarsCache, element, fvGeometry, elemVolVars, scvf); + } + else + { + if constexpr (diffusionEnabled && diffusionIsSolDependent) + fillDiffusion_(scvfFluxVarsCache, element, fvGeometry, elemVolVars, scvf); + if constexpr (heatConductionEnabled && heatConductionIsSolDependent) + fillHeatConduction_(scvfFluxVarsCache, element, fvGeometry, elemVolVars, scvf); + } + } + +private: + + const Problem& problem() const + { return *problemPtr_; } + + + //! method to fill the diffusive quantities + template + void fillDiffusion_(FluxVariablesCache& scvfFluxVarsCache, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const SubControlVolumeFace& scvf) + { + using DiffusionType = typename ElementVolumeVariables::VolumeVariables::MolecularDiffusionType; + using DiffusionFiller = typename DiffusionType::Cache::Filler; + using FluidSystem = typename ElementVolumeVariables::VolumeVariables::FluidSystem; + + static constexpr int numPhases = ModelTraits::numFluidPhases(); + static constexpr int numComponents = ModelTraits::numFluidComponents(); + + // forward to the filler of the diffusive quantities + if constexpr (FluidSystem::isTracerFluidSystem()) + for (unsigned int phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) + for (unsigned int compIdx = 0; compIdx < numComponents; ++compIdx) + DiffusionFiller::fill(scvfFluxVarsCache, phaseIdx, compIdx, problem(), element, fvGeometry, elemVolVars, scvf, *this); + else + for (unsigned int phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) + for (unsigned int compIdx = 0; compIdx < numComponents; ++compIdx) + if (compIdx != FluidSystem::getMainComponent(phaseIdx)) + DiffusionFiller::fill(scvfFluxVarsCache, phaseIdx, compIdx, problem(), element, fvGeometry, elemVolVars, scvf, *this); + } + + //! method to fill the quantities related to heat conduction + template + void fillHeatConduction_(FluxVariablesCache& scvfFluxVarsCache, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const SubControlVolumeFace& scvf) + { + using HeatConductionType = typename ElementVolumeVariables::VolumeVariables::HeatConductionType; + using HeatConductionFiller = typename HeatConductionType::Cache::Filler; + + // forward to the filler of the diffusive quantities + HeatConductionFiller::fill(scvfFluxVarsCache, problem(), element, fvGeometry, elemVolVars, scvf, *this); + } + + const Problem* problemPtr_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/scalarvolumevariables.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/scalarvolumevariables.hh new file mode 100644 index 0000000..7f89ea0 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/scalarvolumevariables.hh @@ -0,0 +1,94 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup NavierStokesModel + * \copydoc Dumux::NavierStokesVolumeVariables + */ +#ifndef DUMUX_NAVIERSTOKES_SCALAR_CONSERVATION_MODEL_VOLUME_VARIABLES_HH +#define DUMUX_NAVIERSTOKES_SCALAR_CONSERVATION_MODEL_VOLUME_VARIABLES_HH + + +namespace Dumux { + +/*! + * \ingroup NavierStokesModel + * \brief Volume variables for the single-phase Navier-Stokes model. + */ +template +class NavierStokesScalarConservationModelVolumeVariables +{ + using Scalar = typename Traits::PrimaryVariables::value_type; + +public: + //! export the type used for the primary variables + using PrimaryVariables = typename Traits::PrimaryVariables; + //! export the indices type + using Indices = typename Traits::ModelTraits::Indices; + //! Export the underlying fluid system + using FluidSystem = typename Traits::FluidSystem; + //! Export the fluid state type + using FluidState = typename Traits::FluidState; + + //! Return number of phases considered by the model + static constexpr int numFluidPhases() { return Traits::ModelTraits::numFluidPhases(); } + //! Return number of components considered by the model + static constexpr int numFluidComponents() { return Traits::ModelTraits::numFluidComponents(); } + + /*! + * \brief Update all quantities for a given control volume + * + * \param elemSol A vector containing all primary variables connected to the element + * \param problem The object specifying the problem which ought to + * be simulated + * \param element An element which contains part of the control volume + * \param scv The sub-control volume + */ + template + void update(const ElementSolution& elemSol, + const Problem& problem, + const Element& element, + const SubControlVolume& scv) + { + priVars_ = elemSol[scv.indexInElement()]; + extrusionFactor_ = problem.spatialParams().extrusionFactor(element, scv, elemSol); + } + + /*! + * \brief Return how much the sub-control volume is extruded. + * + * This means the factor by which a lower-dimensional (1D or 2D) + * entity needs to be expanded to get a full dimensional cell. The + * default is 1.0 which means that 1D problems are actually + * thought as pipes with a cross section of 1 m^2 and 2D problems + * are assumed to extend 1 m to the back. + */ + Scalar extrusionFactor() const + { return extrusionFactor_; } + + /*! + * \brief Return a component of primary variable vector + * + * \param pvIdx The index of the primary variable of interest + */ + Scalar priVar(const int pvIdx) const + { return priVars_[pvIdx]; } + + /*! + * \brief Return the primary variable vector + */ + const PrimaryVariables& priVars() const + { return priVars_; } + +protected: + PrimaryVariables priVars_; + Scalar extrusionFactor_; +}; + +} // end namespace Dumux + +#endif // DUMUX_NAVIERSTOKES_MOMENTUM_VOLUME_VARIABLES_HH diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/velocityoutput.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/velocityoutput.hh new file mode 100644 index 0000000..f833b28 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/navierstokes/velocityoutput.hh @@ -0,0 +1,112 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup NavierStokesModel + * \copydoc Dumux::StaggeredFreeFlowVelocityOutput + */ +#ifndef DUMUX_FREEFLOW_NAVIERSTOKES_VELOCITYOUTPUT_HH +#define DUMUX_FREEFLOW_NAVIERSTOKES_VELOCITYOUTPUT_HH + +#include +#include +#include +#include +#include +#include + +namespace Dumux { + +/*! + * \ingroup NavierStokesModel + * \brief Velocity output for staggered free-flow models + */ +template +class NavierStokesVelocityOutput : public VelocityOutput +{ + using ParentType = VelocityOutput; + using GridGeometry = typename GridVariables::GridGeometry; + using FVElementGeometry = typename GridGeometry::LocalView; + using GridVolumeVariables = typename GridVariables::GridVolumeVariables; + using ElementVolumeVariables = typename GridVolumeVariables::LocalView; + using ElementFluxVarsCache = typename GridVariables::GridFluxVariablesCache::LocalView; + using VolumeVariables = typename GridVariables::VolumeVariables; + using FluidSystem = typename VolumeVariables::FluidSystem; + using GridView = typename GridGeometry::GridView; + using Element = typename GridView::template Codim<0>::Entity; + using FieldType = typename ParentType::FieldType; + +public: + using VelocityVector = typename ParentType::VelocityVector; + + NavierStokesVelocityOutput(const std::string& paramGroup = "") + { + enableOutput_ = getParamFromGroup(paramGroup, "Vtk.AddVelocity", true); + } + + //! Returns whether to enable the velocity output or not + bool enableOutput() const override { return enableOutput_; } + + //! returns the phase name of a given phase index + std::string phaseName(int phaseIdx) const override { return FluidSystem::phaseName(phaseIdx); } + + //! returns the number of phases + int numFluidPhases() const override { return VolumeVariables::numFluidPhases(); } + + //! returns the field type + FieldType fieldType() const override { return FieldType::element; } + + //! Calculate the velocities for the scvs in the element + //! We assume the local containers to be bound to the complete stencil + void calculateVelocity(VelocityVector& velocity, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const ElementFluxVarsCache& elemFluxVarsCache, + int phaseIdx) const override + { + using CouplingManager = std::decay_t; + using MomGG = std::decay_t().problem(CouplingManager::freeFlowMomentumIndex).gridGeometry())>; + if constexpr (MomGG::discMethod == DiscretizationMethods::fcstaggered) + calculateVelocityForStaggeredGrid_(velocity, element, fvGeometry, elemVolVars); + else if constexpr (DiscretizationMethods::isCVFE) + calculateVelocityForCVFESchemes_(velocity, element, fvGeometry, elemVolVars); + else + DUNE_THROW(Dune::NotImplemented, "Navier-Stokes velocity output for scheme " << MomGG::discMethod); + } + +private: + void calculateVelocityForStaggeredGrid_(VelocityVector& velocity, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars) const + { + const auto eIdx = fvGeometry.gridGeometry().elementMapper().index(element); + const auto getFaceVelocity = [&](const FVElementGeometry& fvG, const auto& scvf) + { + return elemVolVars.gridVolVars().problem().faceVelocity(element, fvGeometry, scvf); + }; + + velocity[eIdx] = StaggeredVelocityReconstruction::cellCenterVelocity(getFaceVelocity, fvGeometry); + } + + void calculateVelocityForCVFESchemes_(VelocityVector& velocity, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars) const + { + const auto eIdx = fvGeometry.gridGeometry().elementMapper().index(element); + velocity[eIdx] = elemVolVars.gridVolVars().problem().elementVelocity(fvGeometry); + } + + + bool enableOutput_; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/nonisothermal/indices.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/nonisothermal/indices.hh new file mode 100644 index 0000000..97c6ca6 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/nonisothermal/indices.hh @@ -0,0 +1,34 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup FreeflowNIModel + * \copydoc Dumux::FreeflowNonIsothermalIndices + */ +#ifndef DUMUX_FREEFLOW_NI_INDICES_HH +#define DUMUX_FREEFLOW_NI_INDICES_HH + +namespace Dumux { + +/*! + * \ingroup FreeflowNIModel + * \brief Indices for the non-isothermal Navier-Stokes model. + * + * \tparam IsothermalIndices The isothermal indices class + * \tparam numEq the number of equations of the non-isothermal model + */ +template +class FreeflowNonIsothermalIndices : public IsothermalIndices +{ +public: + static constexpr int energyEqIdx = numEq - 1; + static constexpr int temperatureIdx = numEq - 1; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/nonisothermal/iofields.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/nonisothermal/iofields.hh new file mode 100644 index 0000000..46ceddd --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/nonisothermal/iofields.hh @@ -0,0 +1,53 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup FreeflowNIModel + * \copydoc Dumux::FreeflowNonIsothermalIOFields + */ +#ifndef DUMUX_FREEFLOW_NI_IO_FIELDS_HH +#define DUMUX_FREEFLOW_NI_IO_FIELDS_HH + + +#include + +namespace Dumux { + +/*! + * \ingroup FreeflowNIModel + * \brief Adds I/O fields specific to non-isothermal free-flow models + */ +template +struct FreeflowNonIsothermalIOFields +{ + + //! Add the non-isothermal specific output fields. + template + static void initOutputModule(OutputModule& out) + { + IsothermalIOFields::initOutputModule(out); + + out.addVolumeVariable([](const auto& v){ return v.temperature(); }, IOName::temperature()); + out.addVolumeVariable([](const auto& v){ return v.thermalConductivity(); }, "lambda"); + if (turbulenceModel) + out.addVolumeVariable([](const auto& v){ return v.effectiveThermalConductivity() - v.thermalConductivity(); }, "lambda_t"); + } + + //! return the names of the primary variables + template + static std::string primaryVariableName(int pvIdx, int state = 0) + { + if (pvIdx < ModelTraits::numEq() - 1) + return IsothermalIOFields::template primaryVariableName(pvIdx, state); + else + return IOName::temperature(); + } +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/nonisothermal/model.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/nonisothermal/model.hh new file mode 100644 index 0000000..ed8a29a --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/nonisothermal/model.hh @@ -0,0 +1,60 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup FreeflowNIModel + * + * \brief A single-phase, non-isothermal free-flow model + * + * In addition to the momentum and mass/mole balance equations, this model also solves the energy balance equation : + * \f[ + * \frac{\partial (\varrho u)}{\partial t} + * + \nabla \cdot \left( \varrho h {\boldsymbol{v}} + * - \lambda_\text{eff} \nabla T \right) - q_T = 0 + * \f] + * + * + * For laminar Navier-Stokes flow the effective thermal conductivity is the fluid + * thermal conductivity: \f$ \lambda_\text{eff} = \lambda \f$. + * + * For turbulent Reynolds-averaged Navier-Stokes flow the eddy thermal conductivity is added: + * \f$ \lambda_\text{eff} = \lambda + \lambda_\text{t} \f$. + * The eddy thermal conductivity \f$ \lambda_\text{t} \f$ is related to the eddy viscosity \f$ \nu_\text{t} \f$ + * by the turbulent Prandtl number: + * \f[ \lambda_\text{t} = \frac{\nu_\text{t} \varrho c_\text{p}}{\mathrm{Pr}_\text{t}} \f] + */ + +#ifndef DUMUX_FREEFLOW_NI_MODEL_HH +#define DUMUX_FREEFLOW_NI_MODEL_HH + +#include +#include "indices.hh" + +namespace Dumux { + +/*! + * \ingroup FreeflowNIModel + * \brief Specifies a number properties of non-isothermal free-flow + * flow models based on the specifics of a given isothermal model. + * \tparam IsothermalTraits Model traits of the isothermal model + */ +template +struct FreeflowNIModelTraits : public IsothermalTraits +{ + //! We solve for one more equation, i.e. the energy balance + static constexpr int numEq() { return IsothermalTraits::numEq()+1; } + + //! We additionally solve for the equation balance + static constexpr bool enableEnergyBalance() { return true; } + + //! the indices + using Indices = FreeflowNonIsothermalIndices; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/properties.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/properties.hh new file mode 100644 index 0000000..036a38b --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/properties.hh @@ -0,0 +1,48 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup FreeflowModels + * \brief Defines a type tag and some properties for free flow models. + */ + +#ifndef DUMUX_FREE_FLOW_PROPERTIES_HH +#define DUMUX_FREE_FLOW_PROPERTIES_HH + +#include +#include +#include + +#include "turbulencemodel.hh" +#include "spatialparams.hh" + +namespace Dumux { +namespace Properties { + +//! Type tag for free-flow models +// Create new type tags +namespace TTag { +struct FreeFlow { using InheritsFrom = std::tuple; }; +} // end namespace TTag + +//! Use Fourier's Law as default heat conduction type +template +struct HeatConductionType { using type = FouriersLaw; }; + +// Set the default spatial parameters +template +struct SpatialParams +{ + using GridGeometry = GetPropType; + using Scalar = GetPropType; + using type = FreeFlowDefaultSpatialParams; +}; + +} // namespace Properties +} // namespace Dumux + + #endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/spatialparams.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/spatialparams.hh new file mode 100644 index 0000000..c119a46 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/spatialparams.hh @@ -0,0 +1,125 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup FreeflowModels + * \brief Definition of the spatial parameters for the freeflow problems. + */ +#ifndef DUMUX_FREEFLOW_SPATIAL_PARAMS_HH +#define DUMUX_FREEFLOW_SPATIAL_PARAMS_HH + +#include + +namespace Dumux { + +#ifndef DOXYGEN +namespace Detail::BrinkmanSpatialParams { + +template +struct hasInversePermeabilityAtPos +{ + template + auto operator()(const SpatialParams& a) + -> decltype(a.inversePermeabilityAtPos(std::declval())) + {} +}; + +template +struct hasBrinkmanEpsilonAtPos +{ + template + auto operator()(const SpatialParams& a) + -> decltype(a.brinkmanEpsilonAtPos(std::declval())) + {} +}; + +} // end namespace Detail +#endif + +/*! + * \ingroup FreeflowModels + * \brief Definition of the spatial parameters for the freeflow problems. + */ +template +class FreeFlowSpatialParams +: public FVSpatialParams +{ + using ParentType = FVSpatialParams; + +public: + FreeFlowSpatialParams(std::shared_ptr gridGeometry) + : ParentType(gridGeometry) + {} +}; + +/*! + * \ingroup FreeflowModels + * \brief Definition of the spatial parameters for the darcy-brinkman problems. + */ +template +class BrinkmanSpatialParams +: public FreeFlowSpatialParams +{ + using ParentType = FreeFlowSpatialParams; + using GridView = typename GridGeometry::GridView; + using Element = typename GridView::template Codim<0>::Entity; + using GlobalPosition = typename Element::Geometry::GlobalCoordinate; + using FVElementGeometry = typename GridGeometry::LocalView; + using SubControlVolume = typename GridGeometry::SubControlVolume; + +public: + BrinkmanSpatialParams(std::shared_ptr gridGeometry) + : ParentType(gridGeometry) + {} + + decltype(auto) inversePermeability(const Element& element, + const FVElementGeometry& fvGeometry, + const SubControlVolume& scv) const + { + static_assert(decltype(isValid(Detail::BrinkmanSpatialParams::hasInversePermeabilityAtPos())(this->asImp_()))::value," \n\n" + " Your spatial params class has to either implement\n\n" + " const PermeabilityType& inversePermeabilityAtPos(const GlobalPosition& globalPos) const\n\n" + " or overload this function\n\n" + " const PermeabilityType& inversePermeability(const Element& element,\n" + " const FVElementGeometry& fvGeometry,\n" + " const SubControlVolume& scv) const\n" + " This can be done simply with the invert() function of the DimMatrix type (e.g. permeability.invert()). \n\n"); + return this->asImp_().inversePermeabilityAtPos(scv.dofPosition()); + } + + Scalar brinkmanEpsilon(const Element& element, + const FVElementGeometry& fvGeometry, + const SubControlVolume& scv) const + { + static_assert(decltype(isValid(Detail::BrinkmanSpatialParams::hasBrinkmanEpsilonAtPos())(this->asImp_()))::value," \n\n" + " Your spatial params class has to either implement\n\n" + " const Scalar& brinkmanEpsilonAtPos(const GlobalPosition& globalPos) const\n\n" + " or overload this function\n\n" + " const Scalar& brinkmanEpsilon(const Element& element,\n" + " const FVElementGeometry& fvGeometry,\n" + " const SubControlVolume& scv) const\n\n"); + return this->asImp_().brinkmanEpsilonAtPos(scv.dofPosition()); + } + +}; + +/*! + * \ingroup FreeflowModels + * \brief Definition of the spatial parameters for the freeflow problems. + */ +template +class FreeFlowDefaultSpatialParams +: public FreeFlowSpatialParams> +{ + using ParentType = FreeFlowSpatialParams>; +public: + using ParentType::ParentType; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/turbulencemodel.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/turbulencemodel.hh new file mode 100644 index 0000000..ee9c04d --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/freeflow/turbulencemodel.hh @@ -0,0 +1,94 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup FreeflowModels + * \brief The available free flow turbulence models in Dumux + */ +#ifndef DUMUX_FREEFLOW_TURBLENCEMODEL_HH +#define DUMUX_FREEFLOW_TURBLENCEMODEL_HH + +#include +#include + +namespace Dumux { + + /*! + * \brief The available free flow turbulence models in Dumux + * \ingroup FreeflowModels + * \note Use none for plain (Navier-) Stokes models (DNS) + */ + enum class TurbulenceModel + { + none, zeroeq, oneeq, kepsilon, lowrekepsilon, komega, sst + }; + + constexpr unsigned int numTurbulenceEq(TurbulenceModel model) + { + if (model == TurbulenceModel::none || model == TurbulenceModel::zeroeq) + return 0; + else if (model == TurbulenceModel::oneeq) + return 1; + else + return 2; + } + + /** + * \brief return the name of the Turbulence Model + */ + std::string turbulenceModelToString(TurbulenceModel turbulenceModel) + { + switch (turbulenceModel) + { + case TurbulenceModel::none: return "No_TurbModel"; + case TurbulenceModel::zeroeq: return "ZeroEq"; + case TurbulenceModel::oneeq: return "OneEq"; + case TurbulenceModel::kepsilon: return "KEpsilon"; + case TurbulenceModel::lowrekepsilon: return "LowReKEpsilon"; + case TurbulenceModel::komega: return "KOmega"; + case TurbulenceModel::sst: return "KOmegaSST"; + default: return "Invalid"; // should never be reached + } + } + + /*! + * \brief The available variations of the SST Turbulence Model + * \ingroup SSTModel + */ + enum class SSTModel + { BSL, SST }; + + /** + * \brief return the name of the sst Model as a string + */ + std::string sstModelToString(SSTModel sstModel) + { + switch (sstModel) + { + case SSTModel::BSL: return "BSL"; + case SSTModel::SST: return "SST"; + default: return "Invalid"; + } + } + + /** + * \brief Convenience function to convert user input given as std::string + * to the corresponding enum class used for choosing the SST Model + */ + SSTModel sstModelFromString(const std::string& sstModel) + { + if (sstModel == "BSL") return SSTModel::BSL; + if (sstModel == "SST") return SSTModel::SST; + DUNE_THROW(ParameterException, "\nThis SST Model approach : \"" << sstModel << "\" is not implemented.\n" + << "The available SST models are as follows: \n" + << sstModelToString(SSTModel::BSL) << ": The Baseline SST Model n\n" + << sstModelToString(SSTModel::SST) << ": The full standard SST Model"); + } + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/boundingboxtree.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/boundingboxtree.hh new file mode 100644 index 0000000..05eca22 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/boundingboxtree.hh @@ -0,0 +1,446 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Geometry + * \brief An axis-aligned bounding box volume hierarchy for dune grids + * + * Dumux implementation of an AABB tree + * Inspired by the AABB tree implementation in DOLFIN by Anders Logg which has the + * following license info: DOLFIN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ +#ifndef DUMUX_GEOMETRY_BOUNDINGBOXTREE_HH +#define DUMUX_GEOMETRY_BOUNDINGBOXTREE_HH + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace Dumux { + + +#ifndef DOXYGEN +namespace Detail { + +template +inline constexpr ctype minimumBaseEpsilon = 10.0*std::numeric_limits::epsilon(); + +} // namespace Detail +#endif // DOXYGEN + +/*! + * \ingroup Geometry + * \brief An axis-aligned bounding box volume tree implementation + * + * The class constructs a hierarchical structure of bounding box volumes around + * grid entities. This class can be used to efficiently compute intersections + * between a grid and other geometrical object. It only implements the intersection + * of two of such bounding box trees, so that two independent grids can be intersected. + * \tparam GeometricEntitySet has the following requirements + * * export dimensionworld, ctype + * * a size() member function returning the number of entities + * * begin() and end() member function returning at least forward iterators to entities + * * an index() method returning a consecutive index given an entity + * * an entity() method returning an entity given the consecutive index + * * entities have the following requirements: + * * a member function geometry() returning a geometry with the member functions + * * corner() and corners() returning global coordinates and number of corners + */ +template +class BoundingBoxTree +{ + enum { dimworld = GeometricEntitySet::dimensionworld }; + using ctype = typename GeometricEntitySet::ctype; + + /*! + * \brief Bounding box node data structure + * leaf nodes are indicated by setting child0 to + * the node itself and child1 to the index of the entity in the bounding box. + */ + struct BoundingBoxNode + { + std::size_t child0; + std::size_t child1; + }; + +public: + //! the type of entity set this tree was built with + using EntitySet = GeometricEntitySet; + + //! Default Constructor + BoundingBoxTree() = default; + + //! Constructor with gridView + explicit BoundingBoxTree(std::shared_ptr set) + { build(set); } + + //! Build up bounding box tree for a grid with leafGridView + void build(std::shared_ptr set) + { + // set the pointer to the entity set + entitySet_ = set; + + // clear all internal data + boundingBoxNodes_.clear(); + boundingBoxCoordinates_.clear(); + + // start the timer + Dune::Timer timer; + + // Create bounding boxes for all elements + const auto numLeaves = set->size(); + + // reserve enough space for the nodes and the coordinates + const auto numNodes = 2*numLeaves - 1; + boundingBoxNodes_.reserve(numNodes); + boundingBoxCoordinates_.reserve(numNodes*2*dimworld); + + // create a vector for leaf boxes (min and max for all dims) + std::vector leafBoxes(2*dimworld*numLeaves); + + for (const auto& geometricEntity : *set) + computeEntityBoundingBox_(leafBoxes.data() + 2*dimworld*set->index(geometricEntity), geometricEntity); + + // create the leaf partition, the set of available indices (to be sorted) + std::vector leafPartition(numLeaves); + std::iota(leafPartition.begin(), leafPartition.end(), 0); + + // Recursively build the bounding box tree + build_(leafBoxes, leafPartition.begin(), leafPartition.end()); + + // We are done, log output + std::cout << "Computed bounding box tree with " << numBoundingBoxes() + << " nodes for " << numLeaves << " grid entities in " + << timer.stop() << " seconds." << std::endl; + } + + //! the entity set this tree was built with + const EntitySet& entitySet() const + { return *entitySet_; } + + ///////////////////////////////////////////////////// + //! Interface to be used by other bounding box trees + ///////////////////////////////////////////////////// + + //! Get an existing bounding box for a given node + const BoundingBoxNode& getBoundingBoxNode(std::size_t nodeIdx) const + { return boundingBoxNodes_[nodeIdx]; } + + //! Get an existing bounding box for a given node + const ctype* getBoundingBoxCoordinates(std::size_t nodeIdx) const + { return boundingBoxCoordinates_.data() + 2*dimworld*nodeIdx; } + + //! Get the number of bounding boxes currently in the tree + std::size_t numBoundingBoxes() const + { return boundingBoxNodes_.size(); } + + //! Check whether a bounding box node is a leaf node + //! Leaf nodes have itself as child0 + bool isLeaf(const BoundingBoxNode& node, std::size_t nodeIdx) const + { return node.child0 == nodeIdx; } + +private: + + //! vector of bounding box nodes + std::vector boundingBoxNodes_; + + //! vector of bounding box coordinates + std::vector boundingBoxCoordinates_; + + //! a pointer to the entity set + std::shared_ptr entitySet_; + + //! Compute the bounding box of a grid entity + template + void computeEntityBoundingBox_(ctype* b, const Entity& entity) const + { + // get the bounding box coordinates + ctype* xMin = b; + ctype* xMax = b + dimworld; + + // get mesh entity data + auto geometry = entity.geometry(); + + // Get coordinates of first vertex + auto corner = geometry.corner(0); + for (std::size_t dimIdx = 0; dimIdx < dimworld; ++dimIdx) + xMin[dimIdx] = xMax[dimIdx] = corner[dimIdx]; + + // Compute the min and max over the remaining vertices + for (std::size_t cornerIdx = 1; cornerIdx < geometry.corners(); ++cornerIdx) + { + corner = geometry.corner(cornerIdx); + for (std::size_t dimIdx = 0; dimIdx < dimworld; ++dimIdx) + { + using std::max; + using std::min; + xMin[dimIdx] = min(xMin[dimIdx], corner[dimIdx]); + xMax[dimIdx] = max(xMax[dimIdx], corner[dimIdx]); + } + } + } + + //! Build bounding box tree for all entities recursively + std::size_t build_(const std::vector& leafBoxes, + const std::vector::iterator& begin, + const std::vector::iterator& end) + { + assert(begin < end); + + // If we reached the end of the recursion, i.e. only a leaf box is left + if (end - begin == 1) + { + // Get the bounding box coordinates for the leaf + const std::size_t leafNodeIdx = *begin; + const auto beginCoords = leafBoxes.begin() + 2*dimworld*leafNodeIdx; + const auto endCoords = beginCoords + 2*dimworld; + + // Store the data in the bounding box + // leaf nodes are indicated by setting child0 to + // the node itself and child1 to the index of the entity in the bounding box. + return addBoundingBox_(BoundingBoxNode{numBoundingBoxes(), leafNodeIdx}, beginCoords, endCoords); + } + + // Compute the bounding box of all bounding boxes in the range [begin, end] + const auto bCoords = computeBBoxOfBBoxes_(leafBoxes, begin, end); + + // sort bounding boxes along the longest axis + const auto axis = computeLongestAxis_(bCoords); + + // nth_element sorts the range to make sure that middle points to the coordinate median in axis direction + // this is the most expensive part of the algorithm + auto middle = begin + (end - begin)/2; + std::nth_element(begin, middle, end, [&leafBoxes, axis](std::size_t i, std::size_t j) + { + const ctype* bi = leafBoxes.data() + 2*dimworld*i; + const ctype* bj = leafBoxes.data() + 2*dimworld*j; + return bi[axis] + bi[axis + dimworld] < bj[axis] + bj[axis + dimworld]; + }); + + // split the bounding boxes into two at the middle iterator and call build recursively, each + // call resulting in a new node of this bounding box, i.e. the root will be added at the end of the process. + return addBoundingBox_(BoundingBoxNode{build_(leafBoxes, begin, middle), build_(leafBoxes, middle, end)}, + bCoords.begin(), bCoords.end()); + } + + //! Add a new bounding box to the tree + template + std::size_t addBoundingBox_(BoundingBoxNode&& node, + const Iterator& coordBegin, + const Iterator& coordEnd) + { + // Add the bounding box + boundingBoxNodes_.emplace_back(node); + + // Add the bounding box's coordinates + boundingBoxCoordinates_.insert(boundingBoxCoordinates_.end(), coordBegin, coordEnd); + + // return the index of the new node + return boundingBoxNodes_.size() - 1; + } + + //! Compute the bounding box of a vector of bounding boxes + std::array + computeBBoxOfBBoxes_(const std::vector& leafBoxes, + const std::vector::iterator& begin, + const std::vector::iterator& end) + { + std::array bBoxCoords; + + // copy the iterator and get coordinates of first box + auto it = begin; + const auto* bFirst = leafBoxes.data() + 2*dimworld*(*it); + + for (int coordIdx = 0; coordIdx < 2*dimworld; ++coordIdx) + bBoxCoords[coordIdx] = bFirst[coordIdx]; + + // Compute min and max with the remaining boxes + for (; it != end; ++it) + { + const auto* b = leafBoxes.data() + 2*dimworld*(*it); + for (int coordIdx = 0; coordIdx < dimworld; ++coordIdx) + if (b[coordIdx] < bBoxCoords[coordIdx]) + bBoxCoords[coordIdx] = b[coordIdx]; + for (int coordIdx = dimworld; coordIdx < 2*dimworld; ++coordIdx) + if (b[coordIdx] > bBoxCoords[coordIdx]) + bBoxCoords[coordIdx] = b[coordIdx]; + } + + return bBoxCoords; + } + + //! Compute the bounding box of a vector of bounding boxes + std::size_t computeLongestAxis_(const std::array& bCoords) + { + std::array axisLength; + for (int coordIdx = 0; coordIdx < dimworld; ++coordIdx) + axisLength[coordIdx] = bCoords[dimworld + coordIdx] - bCoords[coordIdx]; + + return std::distance(axisLength.begin(), std::max_element(axisLength.begin(), axisLength.end())); + } +}; + +/*! + * \brief Check whether a point is intersectin a bounding box (dimworld == 3) + * \param point The point + * \param b Pointer to bounding box coordinates + */ +template = 0> +inline bool intersectsPointBoundingBox(const Dune::FieldVector& point, const ctype* b) +{ + static constexpr ctype eps_ = 1.0e-7; + + using std::max; + const auto dx = b[3] - b[0]; + const auto dy = b[4] - b[1]; + const auto dz = b[5] - b[2]; + const ctype eps = max({dx, dy, dz})*eps_; + return (b[0] - eps <= point[0] && point[0] <= b[3] + eps && + b[1] - eps <= point[1] && point[1] <= b[4] + eps && + b[2] - eps <= point[2] && point[2] <= b[5] + eps); +} + +/*! + * \brief Check whether a point is intersectin a bounding box (dimworld == 2) + * \param point The point + * \param b Pointer to bounding box coordinates + */ +template = 0> +inline bool intersectsPointBoundingBox(const Dune::FieldVector& point, const ctype* b) +{ + static constexpr ctype eps_ = 1.0e-7; + + using std::max; + const auto dx = b[2] - b[0]; + const auto dy = b[3] - b[1]; + const ctype eps = max(dx, dy)*eps_; + return (b[0] - eps <= point[0] && point[0] <= b[2] + eps && + b[1] - eps <= point[1] && point[1] <= b[3] + eps); +} + +/*! + * \brief Check whether a point is intersectin a bounding box (dimworld == 1) + * \param point The point + * \param b Pointer to bounding box coordinates + */ +template = 0> +inline bool intersectsPointBoundingBox(const Dune::FieldVector& point, const ctype* b) +{ + static constexpr ctype eps_ = 1.0e-7; + const ctype eps0 = eps_*(b[1] - b[0]); + return b[0] - eps0 <= point[0] && point[0] <= b[1] + eps0; +} + +/*! + * \ingroup Geometry + * \brief Determine if a point intersects an axis-aligned bounding box + * The bounding box is given by the lower left corner (min) and the upper right corner (max) + */ +template +inline bool intersectsPointBoundingBox(const Dune::FieldVector& point, + const Dune::FieldVector& min, + const Dune::FieldVector& max) +{ + std::array bBox; + std::copy(min.begin(), min.end(), bBox.begin()); + std::copy(max.begin(), max.end(), bBox.begin()+dimworld); + return intersectsPointBoundingBox(point, bBox.data()); +} + +/*! + * \brief Check whether a bounding box is intersecting another bounding box (dimworld == 3) + * \param a Pointer to first bounding box coordinates + * \param b Pointer to second bounding box coordinates + */ +template = 0> +inline bool intersectsBoundingBoxBoundingBox(const ctypea* a, const ctypeb* b) +{ + using ctype = typename Dune::PromotionTraits::PromotedType; + static constexpr ctype eps_ = 1.0e-7; + const ctype scale0 = std::max(b[3]-b[0], a[3]-a[0]); + const ctype scale1 = std::max(b[4]-b[1], a[4]-a[1]); + const ctype scale2 = std::max(b[5]-b[2], a[5]-a[2]); + const ctype maxScale = std::max(scale0, std::max(scale1, scale2)); + const ctype minEps = maxScale*Detail::minimumBaseEpsilon; + const ctype eps0 = std::max(eps_*scale0, minEps); + const ctype eps1 = std::max(eps_*scale1, minEps); + const ctype eps2 = std::max(eps_*scale2, minEps); + return (b[0] - eps0 <= a[3] && a[0] <= b[3] + eps0 && + b[1] - eps1 <= a[4] && a[1] <= b[4] + eps1 && + b[2] - eps2 <= a[5] && a[2] <= b[5] + eps2); + +} + +/*! + * \brief Check whether a bounding box is intersecting another bounding box (dimworld == 2) + * \param a Pointer to first bounding box coordinates + * \param b Pointer to second bounding box coordinates + */ +template = 0> +inline bool intersectsBoundingBoxBoundingBox(const ctypea* a, const ctypeb* b) +{ + using ctype = typename Dune::PromotionTraits::PromotedType; + static constexpr ctype eps_ = 1.0e-7; + const ctype scale0 = std::max(b[2]-b[0], a[2]-a[0]); + const ctype scale1 = std::max(b[3]-b[1], a[3]-a[1]); + const ctype maxScale = std::max(scale0, scale1); + const ctype minEps = maxScale*Detail::minimumBaseEpsilon; + const ctype eps0 = std::max(eps_*scale0, minEps); + const ctype eps1 = std::max(eps_*scale1, minEps); + return (b[0] - eps0 <= a[2] && a[0] <= b[2] + eps0 && + b[1] - eps1 <= a[3] && a[1] <= b[3] + eps1); +} + +/*! + * \brief Check whether a bounding box is intersecting another bounding box (dimworld == 1) + * \param a Pointer to first bounding box coordinates + * \param b Pointer to second bounding box coordinates + */ +template = 0> +inline bool intersectsBoundingBoxBoundingBox(const ctypea* a, const ctypeb* b) +{ + using ctype = typename Dune::PromotionTraits::PromotedType; + static constexpr ctype eps_ = 1.0e-7; + const ctype scale0 = std::max(b[1]-b[0], a[1]-a[0]); + const ctype eps0 = std::max(eps_*scale0, Detail::minimumBaseEpsilon*scale0); + return b[0] - eps0 <= a[1] && a[0] <= b[1] + eps0; +} + +/*! + * \brief Compute squared distance between point and bounding box + * \param point The point + * \param b Pointer to bounding box coordinates + */ +template +inline ctype squaredDistancePointBoundingBox(const Dune::FieldVector& point, const ctype* b) +{ + ctype squaredDistance = 0.0; + for (int d = 0; d < dimworld; ++d) + { + if (point[d] < b[d]) + squaredDistance += (point[d] - b[d])*(point[d] - b[d]); + if (point[d] > b[d+dimworld]) + squaredDistance += (point[d] - b[d+dimworld])*(point[d] - b[d+dimworld]); + } + return squaredDistance; +} + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/center.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/center.hh new file mode 100644 index 0000000..7cce326 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/center.hh @@ -0,0 +1,34 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Geometry + * \brief Compute the center point of a convex polytope geometry or a random-access container of corner points + */ +#ifndef DUMUX_GEOMETRY_CENTER_HH +#define DUMUX_GEOMETRY_CENTER_HH + +#include + +namespace Dumux { + +/*! + * \ingroup Geometry + * \brief The center of a given list of corners + */ +template +typename Corners::value_type center(const Corners& corners) +{ + using Pos = typename Corners::value_type; + auto center = std::accumulate(corners.begin(), corners.end(), Pos(0.0)); + center /= corners.size(); + return center; +} + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/geometricentityset.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/geometricentityset.hh new file mode 100644 index 0000000..172fe6c --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/geometricentityset.hh @@ -0,0 +1,328 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Geometry + * \brief An interface for a set of geometric entities + * \note This can be used e.g. to construct a bounding box volume hierarchy of a grid + * It defines the minimum requirement for such a set + */ +#ifndef DUMUX_GEOMETRY_GEOMETRIC_ENTITY_SET_HH +#define DUMUX_GEOMETRY_GEOMETRIC_ENTITY_SET_HH + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace Dumux { + +/*! + * \ingroup Geometry + * \brief An interface for a set of geometric entities based on a GridView + * \note This can be used e.g. to construct a bounding box volume hierarchy of a grid + * It defines the minimum requirement for such a set + */ +template > +class GridViewGeometricEntitySet +{ + using EntityMap = Dumux::EntityMap; +public: + using Entity = typename GridView::template Codim::Entity; + + explicit GridViewGeometricEntitySet(const GridView& gridView) + : GridViewGeometricEntitySet(gridView, Mapper(gridView, Dune::mcmgLayout(Dune::Codim()))) + {} + + GridViewGeometricEntitySet(const GridView& gridView, const Mapper& mapper) + : gridView_(gridView) + , mapper_(mapper) + , entityMap_(std::make_shared(gridView.grid(), mapper_)) + {} + + GridViewGeometricEntitySet(const GridView& gridView, + const Mapper& mapper, + std::shared_ptr entityMap) + : gridView_(gridView) + , mapper_(mapper) + , entityMap_(entityMap) + {} + + /*! + * \brief The world dimension of the entity set + */ + enum { dimensionworld = GridView::dimensionworld }; + + /*! + * \brief the coordinate type + */ + using ctype = typename GridView::ctype; + + /*! + * \brief the number of entities in this set + */ + decltype(auto) size() const + { return gridView_.size(codim); } + + /*! + * \brief begin iterator to enable range-based for iteration + */ + decltype(auto) begin() const + { return entities(gridView_, Dune::Codim()).begin(); } + + /*! + * \brief end iterator to enable range-based for iteration + */ + decltype(auto) end() const + { return entities(gridView_, Dune::Codim()).end(); } + + /*! + * \brief get an entities index + */ + std::size_t index(const Entity& e) const + { return mapper_.index(e); } + + /*! + * \brief get an entity from an index + */ + Entity entity(std::size_t index) const + { assert(index < entityMap_->size()); return (*entityMap_)[index]; } + +private: + GridView gridView_; + Mapper mapper_; + std::shared_ptr entityMap_; +}; + +} // end namespace Dumux + +#ifndef DOXYGEN +namespace Dumux::Detail::GeometricEntity { + +/*! +* \brief Wrapper to turn a geometry into a geometric entity +*/ +template +class EntityWrapper +{ +public: + using Geometry = GeoType; + + /*! + * \brief Constructor + */ + EntityWrapper(const Geometry& geo, const std::size_t index) : geo_(geo), index_(index) {} + + /*! + * \brief Constructor + */ + EntityWrapper(Geometry&& geo, const std::size_t index) : geo_(std::move(geo)), index_(index) {} + + /*! + * \brief Returns the geometry + */ + const Geometry& geometry() const + { return geo_; } + + /*! + * \brief Returns the index of the geometry + */ + std::size_t index() const + { return index_; } + +private: + Geometry geo_; + std::size_t index_; +}; + +} // end namespace Dumux::Detail::GeometricEntity +#endif // DOXYGEN + +namespace Dumux { + +/*! + * \ingroup Geometry + * \brief An interface for a set of geometric entities + * \note This can be used e.g. to construct a bounding box volume hierarchy of a grid + * It defines the minimum requirement for such a set + */ +template +class GeometriesEntitySet +{ +public: + using Entity = Detail::GeometricEntity::EntityWrapper; + + /*! + * \brief Constructor for initializer_list + */ + GeometriesEntitySet(std::initializer_list&& geometries) + { + std::size_t index = 0; + // note: std::initializer_list::begin() returns const T*, + // thus no moving will be performed and only the copying ctor of + // EntityWrapper can be called + for (auto&& g : geometries) + entities_.emplace_back(g, index++); + } + + /*! + * \brief Constructor for a vector of geometries + */ + explicit GeometriesEntitySet(const std::vector& geometries) + { + std::size_t index = 0; + for (auto&& g : geometries) + entities_.emplace_back(g, index++); + } + + /*! + * \brief Constructor for a vector of geometries + */ + explicit GeometriesEntitySet(std::vector&& geometries) + { + std::size_t index = 0; + for (auto&& g : geometries) + entities_.emplace_back(std::move(g), index++); + } + + /*! + * \brief The world dimension of the entity set + */ + enum { dimensionworld = Entity::Geometry::coorddimension }; + + /*! + * \brief the coordinate type + */ + using ctype = typename Entity::Geometry::ctype; + + /*! + * \brief the number of entities in this set + */ + decltype(auto) size() const + { return entities_.size(); } + + /*! + * \brief begin iterator to enable range-based for iteration + */ + decltype(auto) begin() const + { return entities_.begin(); } + + /*! + * \brief end iterator to enable range-based for iteration + */ + decltype(auto) end() const + { return entities_.end(); } + + /*! + * \brief get an entities index + */ + template + std::size_t index(const Entity& e) const + { return e.index(); } + + /*! + * \brief get an entity from an index + */ + const Entity& entity(std::size_t index) const + { assert(index < entities_.size()); return entities_[index]; } + +private: + std::vector entities_; +}; + +/*! + * \ingroup Geometry + * \brief An interface for a fixed-size set of geometric entities + * \note This can be used e.g. to construct a bounding box volume hierarchy of a grid + * It defines the minimum requirement for such a set + */ +template +class FixedSizeGeometriesEntitySet +{ + template + FixedSizeGeometriesEntitySet(GT&& gt, std::index_sequence) + : entities_{{ Entity(std::get(gt), I)... }} + { static_assert(sizeof...(I) == N, "Number of geometries must match the size of the entity set"); } + +public: + using Entity = Detail::GeometricEntity::EntityWrapper; + + /*! + * \brief Constructor with one or more geometries as arguments + * \note The number of geometries must match the size of the entity set + */ + template + FixedSizeGeometriesEntitySet(G&&... g) + : FixedSizeGeometriesEntitySet(std::forward_as_tuple(std::forward(g)...), std::make_index_sequence{}) + {} + + /*! + * \brief The world dimension of the entity set + */ + static constexpr int dimensionworld = Entity::Geometry::coorddimension; + + /*! + * \brief the coordinate type + */ + using ctype = typename Entity::Geometry::ctype; + + /*! + * \brief the number of entities in this set + */ + constexpr auto size() const + { return entities_.size(); } + + /*! + * \brief begin iterator to enable range-based for iteration + */ + decltype(auto) begin() const + { return entities_.begin(); } + + /*! + * \brief end iterator to enable range-based for iteration + */ + decltype(auto) end() const + { return entities_.end(); } + + /*! + * \brief get an entities index + */ + template + std::size_t index(const Entity& e) const + { return e.index(); } + + /*! + * \brief get an entity from an index + */ + const Entity& entity(std::size_t index) const + { assert(index < entities_.size()); return entities_[index]; } + +private: + std::array entities_; +}; + +/*! + * \ingroup Geometry + * \brief An interface for a geometric entity set with a single geometry + */ +template +class SingleGeometryEntitySet +: public FixedSizeGeometriesEntitySet +{ + using ParentType = FixedSizeGeometriesEntitySet; +public: + using ParentType::ParentType; +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/geometryintersection.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/geometryintersection.hh new file mode 100644 index 0000000..13bf982 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/geometryintersection.hh @@ -0,0 +1,1808 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Geometry + * \brief A class for collision detection of two geometries + * and computation of intersection corners + */ +#ifndef DUMUX_GEOMETRY_INTERSECTION_HH +#define DUMUX_GEOMETRY_INTERSECTION_HH + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace Dumux { +namespace IntersectionPolicy { + +//! Policy structure for point-like intersections +template +struct PointPolicy +{ + using ctype = ct; + using Point = Dune::FieldVector; + + static constexpr int dimWorld = dw; + static constexpr int dimIntersection = 0; + using Intersection = Point; +}; + +//! Policy structure for segment-like intersections +template +struct SegmentPolicy +{ + using ctype = ct; + using Point = Dune::FieldVector; + using Segment = std::array; + + static constexpr int dimWorld = dw; + static constexpr int dimIntersection = 1; + using Intersection = Segment; +}; + +//! Policy structure for polygon-like intersections +template +struct PolygonPolicy +{ + using ctype = ct; + using Point = Dune::FieldVector; + using Polygon = std::vector; + + static constexpr int dimWorld = dw; + static constexpr int dimIntersection = 2; + using Intersection = Polygon; +}; + +//! Policy structure for polyhedron-like intersections +template +struct PolyhedronPolicy +{ + using ctype = ct; + using Point = Dune::FieldVector; + using Polyhedron = std::vector; + + static constexpr int dimWorld = dw; + static constexpr int dimIntersection = 3; + using Intersection = Polyhedron; +}; + +//! default policy chooser class +template +class DefaultPolicyChooser +{ + static constexpr int dimworld = Geometry1::coorddimension; + static constexpr int isDim = std::min( int(Geometry1::mydimension), int(Geometry2::mydimension) ); + static_assert(dimworld == int(Geometry2::coorddimension), + "Geometries must have the same coordinate dimension!"); + static_assert(int(Geometry1::mydimension) <= 3 && int(Geometry2::mydimension) <= 3, + "Geometries must have dimension 3 or less."); + using ctype = typename Dune::PromotionTraits::PromotedType; + + using DefaultPolicies = std::tuple, + SegmentPolicy, + PolygonPolicy, + PolyhedronPolicy>; +public: + using type = std::tuple_element_t; +}; + +//! Helper alias to define the default intersection policy +template +using DefaultPolicy = typename DefaultPolicyChooser::type; + +} // end namespace IntersectionPolicy + +namespace Detail { + +/*! + * \ingroup Geometry + * \brief Algorithm to find segment-like intersections of a polygon/polyhedron with a + * segment. The result is stored in the form of the local coordinates tfirst + * and tlast on the segment geo1. + * \param geo1 the first geometry + * \param geo2 the second geometry (dimGeo2 < dimGeo1) + * \param baseEps the base epsilon used for floating point comparisons + * \param tfirst stores the local coordinate of beginning of intersection segment + * \param tlast stores the local coordinate of the end of intersection segment + * \param getFacetCornerIndices Function to obtain the facet corner indices on geo1 + * \param computeNormal Function to obtain the normal vector on a facet on geo1 + */ +template< class Geo1, class Geo2, class ctype, + class GetFacetCornerIndices, class ComputeNormalFunction > +bool computeSegmentIntersection(const Geo1& geo1, const Geo2& geo2, ctype baseEps, + ctype& tfirst, ctype& tlast, + const GetFacetCornerIndices& getFacetCornerIndices, + const ComputeNormalFunction& computeNormal) +{ + static_assert(int(Geo2::mydimension) == 1, "Geometry2 must be a segment"); + static_assert(int(Geo1::mydimension) > int(Geo2::mydimension), + "Dimension of Geometry1 must be higher than that of Geometry2"); + + const auto a = geo2.corner(0); + const auto b = geo2.corner(1); + const auto d = b - a; + + // The initial interval is the whole segment. + // Afterwards we start clipping the interval + // at the intersections with the facets of geo1 + tfirst = 0.0; + tlast = 1.0; + + const auto facets = getFacetCornerIndices(geo1); + for (const auto& f : facets) + { + const auto n = computeNormal(f); + + const auto c0 = geo1.corner(f[0]); + const ctype denom = n*d; + const ctype dist = n*(a-c0); + + // use first edge of the facet to scale eps + const auto edge1 = geo1.corner(f[1]) - geo1.corner(f[0]); + const ctype eps = baseEps*edge1.two_norm(); + + // if denominator is zero the segment in parallel to the edge. + // If the distance is positive there is no intersection + using std::abs; + if (abs(denom) < eps) + { + if (dist > eps) + return false; + } + else // not parallel: compute line-line intersection + { + const ctype t = -dist / denom; + // if entering half space cut tfirst if t is larger + using std::signbit; + if (signbit(denom)) + { + if (t > tfirst) + tfirst = t; + } + // if exiting half space cut tlast if t is smaller + else + { + if (t < tlast) + tlast = t; + } + // there is no intersection of the interval is empty + // use unscaled epsilon since t is in local coordinates + if (tfirst > tlast - baseEps) + return false; + } + } + + // If we made it until here an intersection exists. + return true; +} + +} // end namespace Detail + +/*! + * \ingroup Geometry + * \brief A class for geometry collision detection and intersection calculation + * The class can be specialized for combinations of dimworld, dim1, dim2, where + * dimworld is the world dimension embedding a grid of dim1 and a grid of dim2. + */ +template +, + int dimworld = Geometry1::coorddimension, + int dim1 = Geometry1::mydimension, + int dim2 = Geometry2::mydimension> +class GeometryIntersection +{ + static constexpr int dimWorld = Policy::dimWorld; + +public: + using ctype = typename Policy::ctype; + using Point = typename Policy::Point; + using Intersection = typename Policy::Intersection; + + //! Determine if the two geometries intersect and compute the intersection geometry + static bool intersection(const Geometry1& geo1, const Geometry2& geo2, Intersection& intersection) + { + static_assert(dimworld == Geometry2::coorddimension, "Can only intersect geometries of same coordinate dimension"); + DUNE_THROW(Dune::NotImplemented, "Geometry intersection detection with intersection computation not implemented for dimworld = " + << dimworld << ", dim1 = " << dim1 << ", dim2 = " << dim2); + } +}; + +/*! + * \ingroup Geometry + * \brief A class for segment--segment intersection in 2d space + */ +template +class GeometryIntersection +{ + enum { dimworld = 2 }; + enum { dim1 = 1 }; + enum { dim2 = 1 }; + + // base epsilon for floating point comparisons + static constexpr typename Policy::ctype eps_ = 1.5e-7; + +public: + using ctype = typename Policy::ctype; + using Point = typename Policy::Point; + using Intersection = typename Policy::Intersection; + + /*! + * \brief Colliding two segments + * \param geo1/geo2 The geometries to intersect + * \param intersection The intersection point + * \note This overload is used when point-like intersections are seeked + */ + template = 0> + static bool intersection(const Geometry1& geo1, const Geometry2& geo2, Intersection& intersection) + { + static_assert(int(dimworld) == int(Geometry2::coorddimension), "Can only collide geometries of same coordinate dimension"); + + const auto v1 = geo1.corner(1) - geo1.corner(0); + const auto v2 = geo2.corner(1) - geo2.corner(0); + const auto ac = geo2.corner(0) - geo1.corner(0); + + auto n2 = Point({-1.0*v2[1], v2[0]}); + n2 /= n2.two_norm(); + + // compute distance of first corner on geo2 to line1 + const auto dist12 = n2*ac; + + // first check parallel segments + using std::abs; + using std::sqrt; + + const auto v1Norm2 = v1.two_norm2(); + const auto eps = eps_*sqrt(v1Norm2); + const auto eps2 = eps_*v1Norm2; + + const auto sp = n2*v1; + if (abs(sp) < eps) + { + if (abs(dist12) > eps) + return false; + + // point intersection can only be one of the corners + if ( (v1*v2) < 0.0 ) + { + if ( ac.two_norm2() < eps2 ) + { intersection = geo2.corner(0); return true; } + + if ( (geo2.corner(1) - geo1.corner(1)).two_norm2() < eps2 ) + { intersection = geo2.corner(1); return true; } + } + else + { + if ( (geo2.corner(1) - geo1.corner(0)).two_norm2() < eps2 ) + { intersection = geo2.corner(1); return true; } + + if ( (geo2.corner(0) - geo1.corner(1)).two_norm2() < eps2 ) + { intersection = geo2.corner(0); return true; } + } + + // no intersection + return false; + } + + // intersection point on v1 in local coords + const auto t1 = dist12 / sp; + + // check if the local coords are valid + if (t1 < -1.0*eps_ || t1 > 1.0 + eps_) + return false; + + // compute global coordinates + auto isPoint = geo1.global(t1); + + // check if point is in bounding box of v2 + const auto c = geo2.corner(0); + const auto d = geo2.corner(1); + + using std::min; using std::max; + std::array bBox({ min(c[0], d[0]), min(c[1], d[1]), max(c[0], d[0]), max(c[1], d[1]) }); + + if ( intersectsPointBoundingBox(isPoint, bBox.data()) ) + { + intersection = std::move(isPoint); + return true; + } + + return false; + } + + /*! + * \brief Colliding two segments + * \param geo1/geo2 The geometries to intersect + * \param intersection Container to store corners of intersection segment + * \note this overload is used when segment-like intersections are seeked + */ + template = 0> + static bool intersection(const Geometry1& geo1, const Geometry2& geo2, Intersection& intersection) + { + static_assert(int(dimworld) == int(Geometry2::coorddimension), "Can only collide geometries of same coordinate dimension"); + + const auto& a = geo1.corner(0); + const auto& b = geo1.corner(1); + const auto ab = b - a; + + const auto& p = geo2.corner(0); + const auto& q = geo2.corner(1); + const auto pq = q - p; + Dune::FieldVector n2{-pq[1], pq[0]}; + + using std::max; + const auto abNorm2 = ab.two_norm2(); + const auto pqNorm2 = pq.two_norm2(); + const auto eps2 = eps_*max(abNorm2, pqNorm2); + + // non-parallel segments do not intersect in a segment. + using std::abs; + if (abs(n2*ab) > eps2) + return false; + + // check if the segments lie on the same line + const auto ap = p - a; + if (abs(ap*n2) > eps2) + return false; + + // compute scaled local coordinates of corner 1/2 of segment2 on segment1 + auto t1 = ab*ap; + auto t2 = ab*(q - a); + + using std::swap; + if (t1 > t2) + swap(t1, t2); + + using std::clamp; + t1 = clamp(t1, 0.0, abNorm2); + t2 = clamp(t2, 0.0, abNorm2); + + if (abs(t2-t1) < eps2) + return false; + + intersection = Intersection({geo1.global(t1/abNorm2), geo1.global(t2/abNorm2)}); + return true; + } +}; + +/*! + * \ingroup Geometry + * \brief A class for polygon--segment intersection in 2d space + */ +template +class GeometryIntersection +{ + enum { dimworld = 2 }; + enum { dim1 = 2 }; + enum { dim2 = 1 }; + +public: + using ctype = typename Policy::ctype; + using Point = typename Policy::Point; + using Intersection = typename Policy::Intersection; + +private: + static constexpr ctype eps_ = 1.5e-7; // base epsilon for floating point comparisons + +public: + /*! + * \brief Colliding segment and convex polygon + * \param geo1/geo2 The geometries to intersect + * \param intersection If the geometries collide intersection holds the + * corner points of the intersection object in global coordinates. + * \note This overload is used when segment-like intersections are seeked. + */ + template = 0> + static bool intersection(const Geometry1& geo1, const Geometry2& geo2, Intersection& intersection) + { + static_assert(int(dimworld) == int(Geometry2::coorddimension), "Can only collide geometries of same coordinate dimension"); + + ctype tfirst, tlast; + if (intersect_(geo1, geo2, tfirst, tlast)) + { + // the intersection exists. Export the intersections geometry now: + // s(t) = a + t(b-a) in [tfirst, tlast] + intersection = Intersection({geo2.global(tfirst), geo2.global(tlast)}); + return true; + } + + return false; + } + + /*! + * \brief Colliding segment and convex polygon + * \param geo1/geo2 The geometries to intersect + * \param intersection If the geometries collide intersection holds the + * corner points of the intersection object in global coordinates. + * \note This overload is used when point-like intersections are seeked. + */ + template = 0> + static bool intersection(const Geometry1& geo1, const Geometry2& geo2, typename P::Intersection& intersection) + { + static_assert(int(dimworld) == int(Geometry2::coorddimension), "Can only collide geometries of same coordinate dimension"); + + ctype tfirst, tlast; + if (!intersect_(geo1, geo2, tfirst, tlast)) + { + // if start & end point are same, the intersection is a point + using std::abs; + if ( tfirst > tlast - eps_ ) + { + intersection = Intersection(geo2.global(tfirst)); + return true; + } + } + + return false; + } + +private: + /*! + * \brief Obtain local coordinates of start/end point of the intersecting segment. + */ + static bool intersect_(const Geometry1& geo1, const Geometry2& geo2, ctype& tfirst, ctype& tlast) + { + // lambda to obtain the facet corners on geo1 + auto getFacetCorners = [] (const Geometry1& geo1) + { + std::vector< std::array > facetCorners; + switch (geo1.corners()) + { + case 4: // quadrilateral + facetCorners = {{0, 2}, {3, 1}, {1, 0}, {2, 3}}; + break; + case 3: // triangle + facetCorners = {{1, 0}, {0, 2}, {2, 1}}; + break; + default: + DUNE_THROW(Dune::NotImplemented, "Collision of segment and geometry of type " + << geo1.type() << " with "<< geo1.corners() << " corners."); + } + + return facetCorners; + }; + + // lambda to obtain the normal on a facet + const auto center1 = geo1.center(); + auto computeNormal = [&geo1, ¢er1] (const std::array& facetCorners) + { + const auto c0 = geo1.corner(facetCorners[0]); + const auto c1 = geo1.corner(facetCorners[1]); + const auto edge = c1 - c0; + + Dune::FieldVector n({-1.0*edge[1], edge[0]}); + n /= n.two_norm(); + + // orientation of the element is not known + // make sure normal points outwards of element + if ( n*(center1-c0) > 0.0 ) + n *= -1.0; + + return n; + }; + + return Detail::computeSegmentIntersection(geo1, geo2, eps_, tfirst, tlast, getFacetCorners, computeNormal); + } +}; + +/*! + * \ingroup Geometry + * \brief A class for segment--polygon intersection in 2d space + */ +template +class GeometryIntersection +: public GeometryIntersection +{ + using Base = GeometryIntersection; + +public: + /*! + * \brief Colliding segment and convex polygon + * \param geo1/geo2 The geometries to intersect + * \param intersection If the geometries collide intersection holds the + * corner points of the intersection object in global coordinates. + * \note This forwards to the polygon-segment specialization with swapped arguments. + */ + template + static bool intersection(const Geometry1& geo1, const Geometry2& geo2, typename Base::Intersection& intersection) + { + return Base::intersection(geo2, geo1, intersection); + } +}; + +/*! + * \ingroup Geometry + * \brief A class for polygon--polygon intersection in 2d space + */ +template +class GeometryIntersection +{ + enum { dimworld = 2 }; + enum { dim1 = 2 }; + enum { dim2 = 2 }; + +public: + using ctype = typename Policy::ctype; + using Point = typename Policy::Point; + using Intersection = typename Policy::Intersection; + +private: + static constexpr ctype eps_ = 1.5e-7; // base epsilon for floating point comparisons + +public: + /*! + * \brief Colliding two polygons + * \note First we find the vertex candidates for the intersection region as follows: + * Add polygon vertices that are inside the other polygon + * Add intersections of polygon edges + * Remove duplicate points from the list + * Compute the convex hull polygon + * Return a triangulation of that polygon as intersection + * \param geo1/geo2 The geometries to intersect + * \param intersection Container to store the corner points of the polygon (as convex hull) + * \note This overload is used when polygon like intersections are seeked + */ + template = 0> + static bool intersection(const Geometry1& geo1, const Geometry2& geo2, Intersection& intersection) + { + static_assert(int(dimworld) == int(Geometry2::coorddimension), "Can only collide geometries of same coordinate dimension"); + + // the candidate intersection points + std::vector points; points.reserve(6); + + // add polygon1 corners that are inside polygon2 + for (int i = 0; i < geo1.corners(); ++i) + if (intersectsPointGeometry(geo1.corner(i), geo2)) + points.emplace_back(geo1.corner(i)); + + const auto numPoints1 = points.size(); + if (numPoints1 != geo1.corners()) + { + // add polygon2 corners that are inside polygon1 + for (int i = 0; i < geo2.corners(); ++i) + if (intersectsPointGeometry(geo2.corner(i), geo1)) + points.emplace_back(geo2.corner(i)); + + if (points.empty()) + return false; + + if (points.size() - numPoints1 != geo2.corners()) + { + const auto refElement1 = referenceElement(geo1); + const auto refElement2 = referenceElement(geo2); + + // add intersections of edges + using SegGeometry = Dune::MultiLinearGeometry; + using PointPolicy = IntersectionPolicy::PointPolicy; + for (int i = 0; i < refElement1.size(dim1-1); ++i) + { + const auto localEdgeGeom1 = refElement1.template geometry(i); + const auto edge1 = SegGeometry( Dune::GeometryTypes::line, + std::vector( {geo1.global(localEdgeGeom1.corner(0)), + geo1.global(localEdgeGeom1.corner(1))} )); + + for (int j = 0; j < refElement2.size(dim2-1); ++j) + { + const auto localEdgeGeom2 = refElement2.template geometry(j); + const auto edge2 = SegGeometry( Dune::GeometryTypes::line, + std::vector( {geo2.global(localEdgeGeom2.corner(0)), + geo2.global(localEdgeGeom2.corner(1))} )); + + using EdgeTest = GeometryIntersection; + typename EdgeTest::Intersection edgeIntersection; + if (EdgeTest::intersection(edge1, edge2, edgeIntersection)) + points.emplace_back(edgeIntersection); + } + } + } + } + + if (points.empty()) + return false; + + // remove duplicates + const auto eps = (geo1.corner(0) - geo1.corner(1)).two_norm()*eps_; + std::sort(points.begin(), points.end(), [&eps](const auto& a, const auto& b) -> bool + { + using std::abs; + return (abs(a[0]-b[0]) > eps ? a[0] < b[0] : a[1] < b[1]); + }); + + auto removeIt = std::unique(points.begin(), points.end(), [&eps](const auto& a, const auto&b) + { + return (b-a).two_norm() < eps; + }); + + points.erase(removeIt, points.end()); + + // return false if we don't have at least three unique points + if (points.size() < 3) + return false; + + // intersection polygon is convex hull of above points + intersection = grahamConvexHull<2>(points); + assert(!intersection.empty()); + return true; + } + + /*! + * \brief Colliding two polygons + * \param geo1/geo2 The geometries to intersect + * \param intersection Container to store the corners of intersection segment + * \note this overload is used when segment-like intersections are seeked + * \todo implement this query + */ + template = 0> + static bool intersection(const Geometry1& geo1, const Geometry2& geo2, Intersection& intersection) + { + static_assert(int(dimworld) == int(Geometry2::coorddimension), "Can only collide geometries of same coordinate dimension"); + DUNE_THROW(Dune::NotImplemented, "Polygon-polygon intersection detection for segment-like intersections"); + } + + /*! + * \brief Colliding two polygons + * \param geo1/geo2 The geometries to intersect + * \param intersection The intersection point + * \note this overload is used when point-like intersections are seeked + * \todo implement this query + */ + template = 0> + static bool intersection(const Geometry1& geo1, const Geometry2& geo2, Intersection& intersection) + { + static_assert(int(dimworld) == int(Geometry2::coorddimension), "Can only collide geometries of same coordinate dimension"); + DUNE_THROW(Dune::NotImplemented, "Polygon-polygon intersection detection for touching points"); + } +}; + +/*! + * \ingroup Geometry + * \brief A class for polyhedron--segment intersection in 3d space + */ +template +class GeometryIntersection +{ + enum { dimworld = 3 }; + enum { dim1 = 3 }; + enum { dim2 = 1 }; + +public: + using ctype = typename Policy::ctype; + using Point = typename Policy::Point; + using Intersection = typename Policy::Intersection; + +private: + static constexpr ctype eps_ = 1.5e-7; // base epsilon for floating point comparisons + +public: + /*! + * \brief Colliding segment and convex polyhedron + * \note Algorithm based on the one from "Real-Time Collision Detection" by Christer Ericson, + * published by Morgan Kaufmann Publishers, (c) 2005 Elsevier Inc. + * Basis is the theorem that for any two non-intersecting convex polyhedrons + * a separating plane exists. + * \param geo1/geo2 The geometries to intersect + * \param intersection If the geometries collide intersection holds the corner points of + * the intersection object in global coordinates. + * \note This overload is used when segment-like intersections are seeked. + */ + template = 0> + static bool intersection(const Geometry1& geo1, const Geometry2& geo2, Intersection& intersection) + { + static_assert(int(dimworld) == int(Geometry2::coorddimension), "Can only collide geometries of same coordinate dimension"); + + ctype tfirst, tlast; + if (intersect_(geo1, geo2, tfirst, tlast)) + { + // Intersection exists. Export the intersections geometry now: + // s(t) = a + t(b-a) in [tfirst, tlast] + intersection = Intersection({geo2.global(tfirst), geo2.global(tlast)}); + return true; + } + + return false; + } + + /*! + * \brief Colliding segment and convex polyhedron + * \note Algorithm based on the one from "Real-Time Collision Detection" by Christer Ericson, + * published by Morgan Kaufmann Publishers, (c) 2005 Elsevier Inc. + * Basis is the theorem that for any two non-intersecting convex polyhedrons + * a separating plane exists. + * \param geo1/geo2 The geometries to intersect + * \param intersection If the geometries collide intersection holds the corner points of + * the intersection object in global coordinates. + * \note This overload is used when point-like intersections are seeked. + */ + template = 0> + static bool intersection(const Geometry1& geo1, const Geometry2& geo2, Intersection& intersection) + { + static_assert(int(dimworld) == int(Geometry2::coorddimension), "Can only collide geometries of same coordinate dimension"); + + ctype tfirst, tlast; + if (intersect_(geo1, geo2, tfirst, tlast)) + { + // if start & end point are same, the intersection is a point + using std::abs; + if ( abs(tfirst - tlast) < eps_ ) + { + intersection = Intersection(geo2.global(tfirst)); + return true; + } + } + + return false; + } + +private: + /*! + * \brief Obtain local coordinates of start/end point of the intersecting segment. + */ + static bool intersect_(const Geometry1& geo1, const Geometry2& geo2, ctype& tfirst, ctype& tlast) + { + // lambda to obtain facet corners on geo1 + auto getFacetCorners = [] (const Geometry1& geo1) + { + std::vector< std::vector > facetCorners; + // sort facet corner so that normal n = (p1-p0)x(p2-p0) always points outwards + switch (geo1.corners()) + { + // todo compute intersection geometries + case 8: // hexahedron + facetCorners = {{2, 0, 6, 4}, {1, 3, 5, 7}, {0, 1, 4, 5}, + {3, 2, 7, 6}, {1, 0, 3, 2}, {4, 5, 6, 7}}; + break; + case 6: // prism + facetCorners = {{0, 2, 1}, {3, 4, 5}, {0, 1, 3, 4}, {0, 3, 2, 5}, {1, 2, 4, 5}}; + break; + case 5: // pyramid + facetCorners = {{0, 2, 1, 3}, {0, 1, 4}, {1, 3, 4}, {2, 4, 3}, {0, 4, 2}}; + break; + case 4: // tetrahedron + facetCorners = {{1, 0, 2}, {0, 1, 3}, {0, 3, 2}, {1, 2, 3}}; + break; + default: + DUNE_THROW(Dune::NotImplemented, "Collision of segment and geometry of type " + << geo1.type() << ", "<< geo1.corners() << " corners."); + } + + return facetCorners; + }; + + // lambda to obtain the normal on a facet + auto computeNormal = [&geo1] (const std::vector& facetCorners) + { + const auto v0 = geo1.corner(facetCorners[1]) - geo1.corner(facetCorners[0]); + const auto v1 = geo1.corner(facetCorners[2]) - geo1.corner(facetCorners[0]); + + auto n = crossProduct(v0, v1); + n /= n.two_norm(); + + return n; + }; + + return Detail::computeSegmentIntersection(geo1, geo2, eps_, tfirst, tlast, getFacetCorners, computeNormal); + } +}; + +/*! + * \ingroup Geometry + * \brief A class for segment--polyhedron intersection in 3d space + */ +template +class GeometryIntersection +: public GeometryIntersection +{ + using Base = GeometryIntersection; +public: + /*! + * \brief Colliding segment and convex polyhedron + * \param geo1/geo2 The geometries to intersect + * \param intersection If the geometries collide intersection holds the + * corner points of the intersection object in global coordinates. + * \note This forwards to the polyhedron-segment specialization with swapped arguments. + */ + template + static bool intersection(const Geometry1& geo1, const Geometry2& geo2, typename Base::Intersection& intersection) + { + return Base::intersection(geo2, geo1, intersection); + } +}; + +/*! + * \ingroup Geometry + * \brief A class for polygon--polygon intersections in 3d space + */ +template +class GeometryIntersection +{ + enum { dimworld = 3 }; + enum { dim1 = 2 }; + enum { dim2 = 2 }; + +public: + using ctype = typename Policy::ctype; + using Point = typename Policy::Point; + using Intersection = typename Policy::Intersection; + +private: + static constexpr ctype eps_ = 1.5e-7; // base epsilon for floating point comparisons + +public: + /*! + * \brief Colliding two polygons + * \note First we find the vertex candidates for the intersection region as follows: + * Add vertices of first polygon that are inside the second polygon + * Add intersections of polygon edges + * Remove duplicate points from the list + * Compute the convex hull polygon + * \param geo1/geo2 The geometries to intersect + * \param intersection Container to store the corner points of the polygon (as convex hull) + * \note This overload is used when polygon like intersections are seeked + */ + template = 0> + static bool intersection(const Geometry1& geo1, const Geometry2& geo2, Intersection& intersection) + { + static_assert(int(dimworld) == int(Geometry2::coorddimension), "Can only collide geometries of same coordinate dimension"); + + // if the geometries are not parallel, there cannot be a polygon intersection + const auto a1 = geo1.corner(1) - geo1.corner(0); + const auto b1 = geo1.corner(2) - geo1.corner(0); + const auto n1 = crossProduct(a1, b1); + + const auto a2 = geo2.corner(1) - geo2.corner(0); + const auto b2 = geo2.corner(2) - geo2.corner(0); + const auto n2 = crossProduct(a2, b2); + + // compute an epsilon scaled with larger edge length + const auto a1Norm2 = a1.two_norm2(); + const auto b1Norm2 = b1.two_norm2(); + + using std::max; + const auto maxNorm2 = max(a1Norm2, b1Norm2); + const auto eps2 = maxNorm2*eps_; + + // early exit if polygons are not parallel + using std::abs; + if (crossProduct(n1, n2).two_norm2() > eps2*maxNorm2) + return false; + + // they are parallel, verify that they are on the same plane + auto d1 = geo2.corner(0) - geo1.corner(0); + if (d1.two_norm2() < eps2) + d1 = geo2.corner(1) - geo1.corner(0); + + using std::sqrt; + const auto maxNorm = sqrt(maxNorm2); + const auto eps3 = maxNorm*eps2; + + if (abs(d1*n2) > eps3) + return false; + + // the candidate intersection points + std::vector points; points.reserve(8); + + // add polygon1 corners that are inside polygon2 + for (int i = 0; i < geo1.corners(); ++i) + if (intersectsPointGeometry(geo1.corner(i), geo2)) + points.emplace_back(geo1.corner(i)); + + const auto numPoints1 = points.size(); + const bool resultIsGeo1 = numPoints1 == geo1.corners(); + if (!resultIsGeo1) + { + // add polygon2 corners that are inside polygon1 + for (int i = 0; i < geo2.corners(); ++i) + if (intersectsPointGeometry(geo2.corner(i), geo1)) + points.emplace_back(geo2.corner(i)); + + const bool resultIsGeo2 = (points.size() - numPoints1) == geo2.corners(); + if (!resultIsGeo2) + { + const auto referenceElement1 = referenceElement(geo1); + const auto referenceElement2 = referenceElement(geo2); + + // add intersections of edges + using SegGeometry = Dune::MultiLinearGeometry; + using PointPolicy = IntersectionPolicy::PointPolicy; + for (int i = 0; i < referenceElement1.size(dim1-1); ++i) + { + const auto localEdgeGeom1 = referenceElement1.template geometry(i); + const auto edge1 = SegGeometry( + Dune::GeometryTypes::line, + std::vector({ + geo1.global(localEdgeGeom1.corner(0)), + geo1.global(localEdgeGeom1.corner(1)) + }) + ); + + for (int j = 0; j < referenceElement2.size(dim2-1); ++j) + { + const auto localEdgeGeom2 = referenceElement2.template geometry(j); + const auto edge2 = SegGeometry( + Dune::GeometryTypes::line, + std::vector({ + geo2.global(localEdgeGeom2.corner(0)), + geo2.global(localEdgeGeom2.corner(1)) + }) + ); + + using EdgeTest = GeometryIntersection; + typename EdgeTest::Intersection edgeIntersection; + if (EdgeTest::intersection(edge1, edge2, edgeIntersection)) + points.emplace_back(edgeIntersection); + } + } + } + } + + if (points.empty()) + return false; + + // remove duplicates + const auto eps = maxNorm*eps_; + std::sort(points.begin(), points.end(), [eps] (const auto& a, const auto& b) -> bool + { + using std::abs; + return (abs(a[0]-b[0]) > eps ? a[0] < b[0] + : (abs(a[1]-b[1]) > eps ? a[1] < b[1] + : (a[2] < b[2]))); + }); + + const auto squaredEps = eps*eps; + points.erase(std::unique( + points.begin(), points.end(), + [squaredEps] (const auto& a, const auto&b) { return (b-a).two_norm2() < squaredEps; }), + points.end() + ); + + // return false if we don't have at least three unique points + if (points.size() < 3) + return false; + + // intersection polygon is convex hull of above points + intersection = grahamConvexHull<2>(points); + assert(!intersection.empty()); + return true; + } + + /*! + * \brief Colliding two polygons + * \param geo1/geo2 The geometries to intersect + * \param intersection Container to store the corners of intersection segment + * \note this overload is used when segment-like intersections are seeked + * \todo implement this query + */ + template = 0> + static bool intersection(const Geometry1& geo1, const Geometry2& geo2, Intersection& intersection) + { + static_assert(int(dimworld) == int(Geometry2::coorddimension), "Can only collide geometries of same coordinate dimension"); + DUNE_THROW(Dune::NotImplemented, "Polygon-polygon intersection detection for segment-like intersections"); + } + + /*! + * \brief Colliding two polygons + * \param geo1/geo2 The geometries to intersect + * \param intersection The intersection point + * \note this overload is used when point-like intersections are seeked + * \todo implement this query + */ + template = 0> + static bool intersection(const Geometry1& geo1, const Geometry2& geo2, Intersection& intersection) + { + static_assert(int(dimworld) == int(Geometry2::coorddimension), "Can only collide geometries of same coordinate dimension"); + DUNE_THROW(Dune::NotImplemented, "Polygon-polygon intersection detection for touching points"); + } +}; + +/*! + * \ingroup Geometry + * \brief A class for polyhedron--polygon intersection in 3d space + */ +template +class GeometryIntersection +{ + enum { dimworld = 3 }; + enum { dim1 = 3 }; + enum { dim2 = 2 }; + +public: + using ctype = typename Policy::ctype; + using Point = typename Policy::Point; + using Intersection = typename Policy::Intersection; + +private: + static constexpr ctype eps_ = 1.5e-7; // base epsilon for floating point comparisons + +public: + /*! + * \brief Colliding polygon and convex polyhedron + * \note First we find the vertex candidates for the intersection region as follows: + * Add triangle vertices that are inside the tetrahedron + * Add tetrahedron vertices that are inside the triangle + * Add all intersection points of tetrahedron edges (codim 2) with the triangle (codim 0) (6*1 tests) + * Add all intersection points of triangle edges (codim 1) with tetrahedron faces (codim 1) (3*4 tests) + * Remove duplicate points from the list + * Compute the convex hull polygon + * Return a triangulation of that polygon as intersection + * \param geo1/geo2 The geometries to intersect + * \param intersection Container to store the corner points of the polygon (as convex hull) + * \note This overload is used when polygon like intersections are seeked + */ + template = 0> + static bool intersection(const Geometry1& geo1, const Geometry2& geo2, Intersection& intersection) + { + static_assert(int(dimworld) == int(Geometry2::coorddimension), "Can only collide geometries of same coordinate dimension"); + + // the candidate intersection points + std::vector points; points.reserve(10); + + // add 3d geometry corners that are inside the 2d geometry + for (int i = 0; i < geo1.corners(); ++i) + if (intersectsPointGeometry(geo1.corner(i), geo2)) + points.emplace_back(geo1.corner(i)); + + // add 2d geometry corners that are inside the 3d geometry + for (int i = 0; i < geo2.corners(); ++i) + if (intersectsPointGeometry(geo2.corner(i), geo1)) + points.emplace_back(geo2.corner(i)); + + // get some geometry types + using PolyhedronFaceGeometry = Dune::MultiLinearGeometry; + using SegGeometry = Dune::MultiLinearGeometry; + + const auto refElement1 = referenceElement(geo1); + const auto refElement2 = referenceElement(geo2); + + // intersection policy for point-like intersections (used later) + using PointPolicy = IntersectionPolicy::PointPolicy; + + // add intersection points of all polyhedron edges (codim dim-1) with the polygon + for (int i = 0; i < refElement1.size(dim1-1); ++i) + { + const auto localEdgeGeom = refElement1.template geometry(i); + const auto p = geo1.global(localEdgeGeom.corner(0)); + const auto q = geo1.global(localEdgeGeom.corner(1)); + const auto segGeo = SegGeometry(Dune::GeometryTypes::line, std::vector{p, q}); + + using PolySegTest = GeometryIntersection; + typename PolySegTest::Intersection polySegIntersection; + if (PolySegTest::intersection(geo2, segGeo, polySegIntersection)) + points.emplace_back(std::move(polySegIntersection)); + } + + // add intersection points of all polygon faces (codim 1) with the polyhedron faces + for (int i = 0; i < refElement1.size(1); ++i) + { + const auto faceGeo = [&]() + { + const auto localFaceGeo = refElement1.template geometry<1>(i); + if (localFaceGeo.corners() == 4) + { + const auto a = geo1.global(localFaceGeo.corner(0)); + const auto b = geo1.global(localFaceGeo.corner(1)); + const auto c = geo1.global(localFaceGeo.corner(2)); + const auto d = geo1.global(localFaceGeo.corner(3)); + + return PolyhedronFaceGeometry(Dune::GeometryTypes::cube(2), std::vector{a, b, c, d}); + } + else + { + const auto a = geo1.global(localFaceGeo.corner(0)); + const auto b = geo1.global(localFaceGeo.corner(1)); + const auto c = geo1.global(localFaceGeo.corner(2)); + + return PolyhedronFaceGeometry(Dune::GeometryTypes::simplex(2), std::vector{a, b, c}); + } + }(); + + for (int j = 0; j < refElement2.size(1); ++j) + { + const auto localEdgeGeom = refElement2.template geometry<1>(j); + const auto p = geo2.global(localEdgeGeom.corner(0)); + const auto q = geo2.global(localEdgeGeom.corner(1)); + + const auto segGeo = SegGeometry(Dune::GeometryTypes::line, std::vector{p, q}); + + using PolySegTest = GeometryIntersection; + typename PolySegTest::Intersection polySegIntersection; + if (PolySegTest::intersection(faceGeo, segGeo, polySegIntersection)) + points.emplace_back(std::move(polySegIntersection)); + } + } + + // return if no intersection points were found + if (points.empty()) return false; + + // remove duplicates + const auto eps = (geo1.corner(0) - geo1.corner(1)).two_norm()*eps_; + const auto notEqual = [eps] (auto a, auto b) { using std::abs; return abs(b-a) > eps; }; + std::sort(points.begin(), points.end(), [notEqual](const auto& a, const auto& b) -> bool + { + return (notEqual(a[0], b[0]) ? a[0] < b[0] + : (notEqual(a[1], b[1]) ? a[1] < b[1] + : (a[2] < b[2]))); + }); + + const auto squaredEps = eps*eps; + points.erase(std::unique( + points.begin(), points.end(), + [squaredEps] (const auto& a, const auto&b) { return (b-a).two_norm2() < squaredEps; }), + points.end() + ); + + // return false if we don't have more than three unique points + if (points.size() < 3) return false; + + // intersection polygon is convex hull of above points + intersection = grahamConvexHull<2>(points); + assert(!intersection.empty()); + + return true; + } + + /*! + * \brief Colliding segment and convex polyhedron + * \note First we find the vertex candidates for the intersection region as follows: + * Add triangle vertices that are inside the tetrahedron + * Add tetrahedron vertices that are inside the triangle + * Add all intersection points of tetrahedron edges (codim 2) with the triangle (codim 0) (6*1 tests) + * Add all intersection points of triangle edges (codim 1) with tetrahedron faces (codim 1) (3*4 tests) + * Remove duplicate points from the list + * Compute the convex hull polygon + * Return a triangulation of that polygon as intersection + * \param geo1/geo2 The geometries to intersect + * \param intersection Container to store the intersection result + * \todo implement overloads for segment or point-like intersections + */ + template = 0> + static bool intersection(const Geometry1& geo1, const Geometry2& geo2, Intersection& intersection) + { + static_assert(int(dimworld) == int(Geometry2::coorddimension), "Can only collide geometries of same coordinate dimension"); + DUNE_THROW(Dune::NotImplemented, "Polyhedron-polygon intersection detection only implemented for polygon-like intersections"); + } +}; + +/*! + * \ingroup Geometry + * \brief A class for polygon--polyhedron intersection in 3d space + */ +template +class GeometryIntersection +: public GeometryIntersection +{ + using Base = GeometryIntersection; +public: + /*! + * \brief Colliding polygon and convex polyhedron + * \param geo1/geo2 The geometries to intersect + * \param intersection If the geometries collide intersection holds the + * corner points of the intersection object in global coordinates. + * \note This forwards to the polyhedron-polygon specialization with swapped arguments. + */ + template + static bool intersection(const Geometry1& geo1, const Geometry2& geo2, typename Base::Intersection& intersection) + { + return Base::intersection(geo2, geo1, intersection); + } +}; + +/*! + * \ingroup Geometry + * \brief A class for polyhedron--polyhedron intersection in 3d space + */ +template +class GeometryIntersection +{ + enum { dimworld = 3 }; + enum { dim1 = 3 }; + enum { dim2 = 3 }; + +public: + using ctype = typename Policy::ctype; + using Point = typename Policy::Point; + using Intersection = typename Policy::Intersection; + +private: + static constexpr ctype eps_ = 1.5e-7; // base epsilon for floating point comparisons + +public: + /*! + * \brief Colliding two convex polyhedra + * \note First we find the vertex candidates for the intersection region as follows: + * Add vertices that are inside the other geometry for both geometries + * Add all intersection points of edges (codim 2) with the other tetrahedron's faces triangle + * Remove duplicate points from the list + * Return a triangulation of the polyhedron formed by the convex hull of the point cloud + * \param geo1/geo2 The geometries to intersect + * \param intersection Container to store the corner points of the polygon (as convex hull) + * \note This overload is used when polyhedron-like intersections are seeked + */ + template = 0> + static bool intersection(const Geometry1& geo1, const Geometry2& geo2, Intersection& intersection) + { + static_assert( + int(dimworld) == int(Geometry2::coorddimension), + "Can only collide geometries of same coordinate dimension" + ); + + const auto refElement1 = referenceElement(geo1); + const auto refElement2 = referenceElement(geo2); + + // the candidate intersection points + std::vector points; + points.reserve(refElement1.size(2) + refElement2.size(2)); + + // add corners inside the other geometry + const auto addPointIntersections = [&](const auto& g1, const auto& g2) + { + for (int i = 0; i < g1.corners(); ++i) + if (const auto& c = g1.corner(i); intersectsPointGeometry(c, g2)) + points.emplace_back(c); + }; + + addPointIntersections(geo1, geo2); + addPointIntersections(geo2, geo1); + + // get geometry types for the facets + using PolyhedronFaceGeometry = Dune::MultiLinearGeometry; + using SegGeometry = Dune::MultiLinearGeometry; + + // intersection policy for point-like intersections (used later) + using PointPolicy = IntersectionPolicy::PointPolicy; + + // add intersection points of all edges with the faces of the other polyhedron + const auto addEdgeIntersections = [&](const auto& g1, const auto& g2, const auto& ref1, const auto& ref2) + { + for (int i = 0; i < ref1.size(1); ++i) + { + const auto faceGeo = [&]() + { + const auto localFaceGeo = ref1.template geometry<1>(i); + if (localFaceGeo.corners() == 4) + { + const auto a = g1.global(localFaceGeo.corner(0)); + const auto b = g1.global(localFaceGeo.corner(1)); + const auto c = g1.global(localFaceGeo.corner(2)); + const auto d = g1.global(localFaceGeo.corner(3)); + + return PolyhedronFaceGeometry( + Dune::GeometryTypes::cube(2), std::vector{a, b, c, d} + ); + } + else + { + const auto a = g1.global(localFaceGeo.corner(0)); + const auto b = g1.global(localFaceGeo.corner(1)); + const auto c = g1.global(localFaceGeo.corner(2)); + + return PolyhedronFaceGeometry( + Dune::GeometryTypes::simplex(2), std::vector{a, b, c} + ); + } + }(); + + for (int j = 0; j < ref2.size(2); ++j) + { + const auto localEdgeGeom = ref2.template geometry<2>(j); + const auto p = g2.global(localEdgeGeom.corner(0)); + const auto q = g2.global(localEdgeGeom.corner(1)); + + const auto segGeo = SegGeometry(Dune::GeometryTypes::line, std::vector{p, q}); + + using PolySegTest = GeometryIntersection; + typename PolySegTest::Intersection polySegIntersection; + if (PolySegTest::intersection(faceGeo, segGeo, polySegIntersection)) + points.emplace_back(std::move(polySegIntersection)); + } + } + }; + + addEdgeIntersections(geo1, geo2, refElement1, refElement2); + addEdgeIntersections(geo2, geo1, refElement2, refElement1); + + // return if no intersection points were found + if (points.empty()) + return false; + + // remove duplicates + const auto norm = (geo1.corner(0) - geo1.corner(1)).two_norm(); + const auto eps = norm*eps_; + const auto notEqual = [eps] (auto a, auto b) { using std::abs; return abs(b-a) > eps; }; + std::sort(points.begin(), points.end(), [notEqual](const auto& a, const auto& b) -> bool + { + return (notEqual(a[0], b[0]) ? a[0] < b[0] + : (notEqual(a[1], b[1]) ? a[1] < b[1] + : (a[2] < b[2]))); + }); + + const auto squaredEps = eps*eps; + points.erase(std::unique( + points.begin(), points.end(), + [squaredEps] (const auto& a, const auto&b) { return (b-a).two_norm2() < squaredEps; }), + points.end() + ); + + // return false if we don't have more than four unique points (dim+1) + if (points.size() < 4) + return false; + + // check if the points are coplanar (then we don't have a 3-dimensional intersection) + const bool coplanar = [&] + { + const auto p0 = points[0]; + const auto normal = crossProduct(points[1] - p0, points[2] - p0); + // include the normal in eps (instead of norm*norm) since the normal can become very small + // if the first three points are very close together + const auto epsCoplanar = normal.two_norm()*norm*eps_; + for (int i = 3; i < points.size(); ++i) + { + const auto ad = points[i] - p0; + using std::abs; + if (abs(normal*ad) > epsCoplanar) + return false; + } + + return true; + }(); + + if (coplanar) + return false; + + // we return the intersection as point cloud (that can be triangulated later) + intersection = points; + + return true; + } + + /*! + * \brief Colliding segment and convex polyhedron + * \param geo1/geo2 The geometries to intersect + * \param intersection Container to store the intersection result + * \todo implement overloads for polygon-, segment- or point-like intersections + */ + template = 0> + static bool intersection(const Geometry1& geo1, const Geometry2& geo2, Intersection& intersection) + { + static_assert(int(dimworld) == int(Geometry2::coorddimension), "Can only collide geometries of same coordinate dimension"); + DUNE_THROW(Dune::NotImplemented, "Polyhedron-polygon intersection detection only implemented for polygon-like intersections"); + } +}; + +/*! + * \ingroup Geometry + * \brief A class for polygon--segment intersection in 3d space + */ +template +class GeometryIntersection +{ + enum { dimworld = 3 }; + enum { dim1 = 2 }; + enum { dim2 = 1 }; + +public: + using ctype = typename Policy::ctype; + using Point = typename Policy::Point; + using Intersection = typename Policy::Intersection; + +private: + static constexpr ctype eps_ = 1.5e-7; // base epsilon for floating point comparisons + +public: + /*! + * \brief Colliding segment and convex polyhedron + * \note Algorithm based on the one from "Real-Time Collision Detection" by Christer Ericson, + * published by Morgan Kaufmann Publishers, (c) 2005 Elsevier Inc. (Chapter 5.3.6) + * \param geo1/geo2 The geometries to intersect + * \param intersection If the geometries collide, is holds the corner points of + * the intersection object in global coordinates. + * \note This overload is used when point-like intersections are seeked + */ + template = 0> + static bool intersection(const Geometry1& geo1, const Geometry2& geo2, Intersection& intersection) + { + static_assert(int(dimworld) == int(Geometry2::coorddimension), "Can only collide geometries of same coordinate dimension"); + + ctype tfirst, tlast; + if (intersect_(geo1, geo2, tfirst, tlast)) + { + // the intersection exists. Export the intersections geometry now: + // s(t) = a + t(b-a) in [tfirst, tlast] + intersection = Intersection({geo2.global(tfirst), geo2.global(tlast)}); + return true; + } + + return false; + } + + /*! + * \brief Colliding segment and convex polyhedron + * \note Algorithm based on the one from "Real-Time Collision Detection" by Christer Ericson, + * published by Morgan Kaufmann Publishers, (c) 2005 Elsevier Inc. (Chapter 5.3.6) + * \param geo1/geo2 The geometries to intersect + * \param is If the geometries collide, is holds the corner points of + * the intersection object in global coordinates. + * \note This overload is used when point-like intersections are seeked + */ + template = 0> + static bool intersection(const Geometry1& geo1, const Geometry2& geo2, Intersection& is) + { + static_assert(int(dimworld) == int(Geometry2::coorddimension), "Can only collide geometries of same coordinate dimension"); + + const auto p = geo2.corner(0); + const auto q = geo2.corner(1); + + const auto a = geo1.corner(0); + const auto b = geo1.corner(1); + const auto c = geo1.corner(2); + + if (geo1.corners() == 3) + return intersect_(a, b, c, p, q, is); + + else if (geo1.corners() == 4) + { + Intersection is1, is2; + bool hasSegment1, hasSegment2; + + const auto d = geo1.corner(3); + const bool intersects1 = intersect_(a, b, d, p, q, is1, hasSegment1); + const bool intersects2 = intersect_(a, d, c, p, q, is2, hasSegment2); + + if (hasSegment1 || hasSegment2) + return false; + + if (intersects1) { is = is1; return true; } + if (intersects2) { is = is2; return true; } + + return false; + } + + else + DUNE_THROW(Dune::NotImplemented, "Collision of segment and geometry of type " + << geo1.type() << ", "<< geo1.corners() << " corners."); + } + +private: + /*! + * \brief Obtain local coordinates of start/end point of the intersecting segment. + */ + template = 0> + static bool intersect_(const Geometry1& geo1, const Geometry2& geo2, ctype& tfirst, ctype& tlast) + { + // lambda to obtain the facet corners on geo1 + auto getFacetCorners = [] (const Geometry1& geo1) + { + std::vector< std::array > facetCorners; + switch (geo1.corners()) + { + case 4: // quadrilateral + facetCorners = {{0, 2}, {3, 1}, {1, 0}, {2, 3}}; + break; + case 3: // triangle + facetCorners = {{1, 0}, {0, 2}, {2, 1}}; + break; + default: + DUNE_THROW(Dune::NotImplemented, "Collision of segment and geometry of type " + << geo1.type() << " with "<< geo1.corners() << " corners."); + } + + return facetCorners; + }; + + const auto center1 = geo1.center(); + const auto normal1 = crossProduct(geo1.corner(1) - geo1.corner(0), + geo1.corner(2) - geo1.corner(0)); + + // lambda to obtain the normal on a facet + auto computeNormal = [¢er1, &normal1, &geo1] (const std::array& facetCorners) + { + const auto c0 = geo1.corner(facetCorners[0]); + const auto c1 = geo1.corner(facetCorners[1]); + const auto edge = c1 - c0; + + Dune::FieldVector n = crossProduct(edge, normal1); + n /= n.two_norm(); + + // orientation of the element is not known + // make sure normal points outwards of element + if ( n*(center1-c0) > 0.0 ) + n *= -1.0; + + return n; + }; + + return Detail::computeSegmentIntersection(geo1, geo2, eps_, tfirst, tlast, getFacetCorners, computeNormal); + } + + /*! + * \brief triangle--segment point-like intersection with points as input. + */ + template = 0> + static bool intersect_(const Point& a, const Point& b, const Point& c, + const Point& p, const Point& q, + Intersection& is) + { + bool hasSegment; + return intersect_(a, b, c, p, q, is, hasSegment); + } + + /*! + * \brief triangle--segment point-like intersection with points as input. Also + * stores if a segment-like intersection was found in the provided boolean. + */ + template = 0> + static bool intersect_(const Point& a, const Point& b, const Point& c, + const Point& p, const Point& q, + Intersection& is, bool& hasSegmentIs) + { + hasSegmentIs = false; + + const auto ab = b - a; + const auto ac = c - a; + const auto qp = p - q; + + // compute the triangle normal that defines the triangle plane + const auto normal = crossProduct(ab, ac); + + // compute the denominator + // if denom is 0 the segment is parallel and we can return + const auto denom = normal*qp; + const auto abnorm2 = ab.two_norm2(); + const auto eps = eps_*abnorm2*qp.two_norm(); + using std::abs; + if (abs(denom) < eps) + { + const auto pa = a - p; + const auto denom2 = normal*pa; + if (abs(denom2) > eps_*pa.two_norm()*abnorm2) + return false; + + // if we get here, we are in-plane. Check if a + // segment-like intersection with geometry 1 exists. + using SegmentPolicy = typename IntersectionPolicy::SegmentPolicy; + using Triangle = Dune::AffineGeometry; + using Segment = Dune::AffineGeometry; + using SegmentIntersectionAlgorithm = GeometryIntersection; + using SegmentIntersectionType = typename SegmentIntersectionAlgorithm::Intersection; + SegmentIntersectionType segmentIs; + + Triangle triangle(Dune::GeometryTypes::simplex(2), std::array({a, b, c})); + Segment segment(Dune::GeometryTypes::simplex(1), std::array({p, q})); + if (SegmentIntersectionAlgorithm::intersection(triangle, segment, segmentIs)) + { + hasSegmentIs = true; + return false; + } + + // We are in-plane. A point-like + // intersection can only be on the edges + for (const auto& ip : {p, q}) + { + if ( intersectsPointSimplex(ip, a, b) + || intersectsPointSimplex(ip, b, c) + || intersectsPointSimplex(ip, c, a) ) + { + is = ip; + return true; + } + } + + return false; + } + + // compute intersection t value of pq with plane of triangle. + // a segment intersects if and only if 0 <= t <= 1. + const auto ap = p - a; + const auto t = (ap*normal)/denom; + if (t < 0.0 - eps_) return false; + if (t > 1.0 + eps_) return false; + + // compute the barycentric coordinates and check if the intersection point + // is inside the bounds of the triangle + const auto e = crossProduct(qp, ap); + const auto v = (ac*e)/denom; + if (v < -eps_ || v > 1.0 + eps_) return false; + const auto w = -(ab*e)/denom; + if (w < -eps_ || v + w > 1.0 + eps_) return false; + + // Now we are sure there is an intersection points + // Perform delayed division compute the last barycentric coordinate component + const auto u = 1.0 - v - w; + + Point ip(0.0); + ip.axpy(u, a); + ip.axpy(v, b); + ip.axpy(w, c); + is = ip; + return true; + } +}; + +/*! + * \ingroup Geometry + * \brief A class for segment--polygon intersection in 3d space + */ +template +class GeometryIntersection +: public GeometryIntersection +{ + using Base = GeometryIntersection; +public: + /*! + * \brief Colliding segment and convex polygon + * \param geo1/geo2 The geometries to intersect + * \param intersection If the geometries collide intersection holds the + * corner points of the intersection object in global coordinates. + * \note This forwards to the polyhedron-polygon specialization with swapped arguments. + */ + template + static bool intersection(const Geometry1& geo1, const Geometry2& geo2, typename Base::Intersection& intersection) + { + return Base::intersection(geo2, geo1, intersection); + } +}; + +/*! + * \ingroup Geometry + * \brief A class for segment--segment intersection in 3d space + */ +template +class GeometryIntersection +{ + enum { dimworld = 3 }; + enum { dim1 = 1 }; + enum { dim2 = 1 }; + +public: + using ctype = typename Policy::ctype; + using Point = typename Policy::Point; + using Intersection = typename Policy::Intersection; + +private: + static constexpr ctype eps_ = 1.5e-7; // base epsilon for floating point comparisons + +public: + /*! + * \brief Colliding two segments + * \param geo1/geo2 The geometries to intersect + * \param intersection The intersection point + * \note This overload is used when point-like intersections are seeked + */ + template = 0> + static bool intersection(const Geometry1& geo1, const Geometry2& geo2, Intersection& intersection) + { + static_assert(int(dimworld) == int(Geometry2::coorddimension), "Can only collide geometries of same coordinate dimension"); + + const auto v1 = geo1.corner(1) - geo1.corner(0); + const auto v2 = geo2.corner(1) - geo2.corner(0); + const auto ac = geo2.corner(0) - geo1.corner(0); + + const auto v1Norm2 = v1.two_norm2(); + const auto eps2 = eps_*v1Norm2; + + const auto n = crossProduct(v1, v2); + + // first check if segments are parallel + using std::abs; + if ( n.two_norm2() < eps2*v1Norm2 ) + { + // check if they lie on the same line + if (crossProduct(v1, ac).two_norm2() > eps2) + return false; + + // they lie on the same line, + // if so, point intersection can only be one of the corners + const auto sp = v1*v2; + if ( sp < 0.0 ) + { + if ( ac.two_norm2() < eps2 ) + { intersection = geo2.corner(0); return true; } + + if ( (geo2.corner(1) - geo1.corner(1)).two_norm2() < eps2 ) + { intersection = geo2.corner(1); return true; } + } + else + { + if ( (geo2.corner(1) - geo1.corner(0)).two_norm2() < eps2 ) + { intersection = geo2.corner(1); return true; } + + if ( (geo2.corner(0) - geo1.corner(1)).two_norm2() < eps2 ) + { intersection = geo2.corner(0); return true; } + } + + // no intersection + return false; + } + + // in-plane normal vector on v1 + const auto v1Normal = crossProduct(v1, n); + + // intersection point on v2 in local coords + const auto t2 = - 1.0*(ac*v1Normal) / (v2*v1Normal); + + // check if the local coords are valid + if (t2 < -1.0*eps_ || t2 > 1.0 + eps_) + return false; + + if (auto ip = geo2.global(t2); intersectsPointGeometry(ip, geo1)) + { intersection = std::move(ip); return true; } + + return false; + } + + /*! + * \brief Colliding two segments in 3D + * \param geo1/geo2 The geometries to intersect + * \param intersection If the geometries collide, is holds the corner points of + * the intersection object in global coordinates. + * \note This overload is used when segment-like intersections are seeked + */ + template = 0> + static bool intersection(const Geometry1& geo1, const Geometry2& geo2, Intersection& intersection) + { + static_assert(int(dimworld) == int(Geometry2::coorddimension), "Can only collide geometries of same coordinate dimension"); + + const auto& a = geo1.corner(0); + const auto& b = geo1.corner(1); + const auto ab = b-a; + + const auto& p = geo2.corner(0); + const auto& q = geo2.corner(1); + const auto pq = q-p; + + const auto abNorm2 = ab.two_norm2(); + const auto pqNorm2 = pq.two_norm2(); + + using std::max; + const auto eps2 = eps_*max(abNorm2, pqNorm2); + + // if the segment are not parallel there is no segment intersection + using std::abs; + if (crossProduct(ab, pq).two_norm2() > eps2*eps2) + return false; + + const auto ap = (p-a); + const auto aq = (q-a); + + // points have to be colinear + if (crossProduct(ap, aq).two_norm2() > eps2*eps2) + return false; + + // scaled local coordinates + // we save the division for the normalization for now + // and do it in the very end if we find an intersection + auto tp = ap*ab; + auto tq = aq*ab; + + // make sure they are sorted + using std::swap; + if (tp > tq) + swap(tp, tq); + + using std::clamp; + tp = clamp(tp, 0.0, abNorm2); + tq = clamp(tq, 0.0, abNorm2); + + if (abs(tp-tq) < eps2) + return false; + + intersection = Intersection({geo1.global(tp/abNorm2), geo1.global(tq/abNorm2)}); + return true; + } +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/grahamconvexhull.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/grahamconvexhull.hh new file mode 100644 index 0000000..2a9f645 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/grahamconvexhull.hh @@ -0,0 +1,206 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Geometry + * \brief A function to compute the convex hull of a point cloud + */ +#ifndef DUMUX_GEOMETRY_GRAHAM_CONVEX_HULL_HH +#define DUMUX_GEOMETRY_GRAHAM_CONVEX_HULL_HH + +#include +#include +#include +#include + +#include +#include + +#include + +namespace Dumux { + +/*! + * \ingroup Geometry + * \brief Returns the orientation of a sequence a-->b-->c in one plane (defined by normal vector) + * \return -1 if a-->b-->c forms a counter-clockwise turn (given the normal vector) + * +1 for a clockwise turn, + * 0 if they are on one line (colinear) + */ +template +int getOrientation(const Dune::FieldVector& a, + const Dune::FieldVector& b, + const Dune::FieldVector& c, + const Dune::FieldVector& normal) +{ + const auto d = b-a; + const auto e = c-b; + const auto f = Dumux::crossProduct(d, e); + const auto area = f*normal; + return Dumux::sign(-area); +} + +/*! + * \ingroup Geometry + * \brief Compute the points making up the convex hull around the given set of unordered points + * \note We assume that all points are coplanar and there are no identical points in the list + * \note This algorithm changes the order of the given points a bit + * as they are unordered anyway this shouldn't matter too much + */ +template = 0> +std::vector> +grahamConvexHullImpl(std::vector>& points) +{ + using Point = Dune::FieldVector; + std::vector convexHull; + + // return empty convex hull + if (points.size() < 3) + return convexHull; + + // return the points (already just one triangle) + if (points.size() == 3) + return points; + + // try to compute the normal vector of the plane + const auto a = points[1] - points[0]; + auto b = points[2] - points[0]; + auto normal = Dumux::crossProduct(a, b); + + // make sure the normal vector has non-zero length + std::size_t k = 2; + auto norm = normal.two_norm(); + while (norm == 0.0 && k < points.size()-1) + { + b = points[++k]; + normal = Dumux::crossProduct(a, b); + norm = normal.two_norm(); + } + + // if all given points are colinear -> return empty convex hull + if (norm == 0.0) + return convexHull; + + using std::sqrt; + const auto eps = 1e-7*sqrt(norm); + normal /= norm; + + // find the element with the smallest x coordinate (if x is the same, smallest y coordinate, and so on...) + auto minIt = std::min_element(points.begin(), points.end(), [&eps](const auto& a, const auto& b) + { + using std::abs; + return (abs(a[0]-b[0]) > eps ? a[0] < b[0] : (abs(a[1]-b[1]) > eps ? a[1] < b[1] : (a[2] < b[2]))); + }); + + // swap the smallest element to the front + std::iter_swap(minIt, points.begin()); + + // choose the first (min element) as the pivot point + // sort in counter-clockwise order around pivot point + const auto pivot = points[0]; + std::sort(points.begin()+1, points.end(), [&](const auto& a, const auto& b) + { + const int order = getOrientation(pivot, a, b, normal); + if (order == 0) + return (a-pivot).two_norm() < (b-pivot).two_norm(); + else + return (order == -1); + }); + + // push the first three points + convexHull.reserve(50); + convexHull.push_back(points[0]); + convexHull.push_back(points[1]); + convexHull.push_back(points[2]); + + // This is the heart of the algorithm + // pop_back until the last point in the queue forms a counter-clockwise oriented line + // with the next two vertices. Then add these points to the queue. + for (std::size_t i = 3; i < points.size(); ++i) + { + Point p = convexHull.back(); + convexHull.pop_back(); + // keep popping until the orientation a->b->currentp is counter-clockwise + while (getOrientation(convexHull.back(), p, points[i], normal) != -1) + { + // make sure the queue doesn't get empty + if (convexHull.size() == 1) + { + // before we reach i=size-1 there has to be a good candidate + // as not all points are colinear (a non-zero plane normal exists) + assert(i < points.size()-1); + p = points[i++]; + } + else + { + p = convexHull.back(); + convexHull.pop_back(); + } + } + + // add back the last popped point and this point + convexHull.emplace_back(std::move(p)); + convexHull.push_back(points[i]); + } + + return convexHull; +} + +/*! + * \ingroup Geometry + * \brief Compute the points making up the convex hull around the given set of unordered points + * \note This is the specialization for 2d space. Here, we make use of the generic implementation + * for the case of coplanar points in 3d space (a more efficient implementation could be provided). + */ +template = 0> +std::vector> +grahamConvexHullImpl(const std::vector>& points) +{ + std::vector> points3D; + points3D.reserve(points.size()); + std::transform(points.begin(), points.end(), std::back_inserter(points3D), + [](const auto& p) { return Dune::FieldVector({p[0], p[1], 0.0}); }); + + const auto result3D = grahamConvexHullImpl<2>(points3D); + + std::vector> result2D; + result2D.reserve(result3D.size()); + std::transform(result3D.begin(), result3D.end(), std::back_inserter(result2D), + [](const auto& p) { return Dune::FieldVector({p[0], p[1]}); }); + + return result2D; +} + +/*! + * \ingroup Geometry + * \brief Compute the points making up the convex hull around the given set of unordered points + * \note We assume that all points are coplanar and there are no identical points in the list + */ +template +std::vector> grahamConvexHull(std::vector>& points) +{ + return grahamConvexHullImpl(points); +} + +/*! + * \ingroup Geometry + * \brief Compute the points making up the convex hull around the given set of unordered points + * \note We assume that all points are coplanar and there are no identical points in the list + * \note This is the overload if we are not allowed to write into the given points vector + */ +template +std::vector> grahamConvexHull(const std::vector>& points) +{ + auto copyPoints = points; + return grahamConvexHullImpl(copyPoints); +} + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/intersectingentities.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/intersectingentities.hh new file mode 100644 index 0000000..0721df4 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/intersectingentities.hh @@ -0,0 +1,458 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Geometry + * \brief Algorithms that finds which geometric entities intersect + */ +#ifndef DUMUX_GEOMETRY_INTERSECTING_ENTITIES_HH +#define DUMUX_GEOMETRY_INTERSECTING_ENTITIES_HH + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +namespace Dumux { + +/*! + * \ingroup Geometry + * \brief An intersection object resulting from the intersection of two primitives in an entity set + * \note this is used as return type for some of the intersectingEntities overloads below + */ +template +class IntersectionInfo +{ +public: + using ctype = typename Dune::PromotionTraits::PromotedType; + static constexpr int dimensionworld = dimworld; + using GlobalPosition = Dune::FieldVector; + + template + explicit IntersectionInfo(std::size_t a, std::size_t b, Corners&& c) + : a_(a) + , b_(b) + , corners_(c.begin(), c.end()) + {} + + //! Get the index of the intersecting entity belonging to this grid + std::size_t first() const + { return a_; } + + //! Get the index of the intersecting entity belonging to the other grid + std::size_t second() const + { return b_; } + + //! Get the corners of the intersection geometry + const std::vector& corners() const + { return corners_; } + + /*! + * \brief Check if the corners of this intersection match with the given corners + * \note This is useful to check if the intersection geometry of two intersections coincide. + */ + bool cornersMatch(const std::vector& otherCorners) const + { + if (otherCorners.size() != corners_.size()) + return false; + + using std::max; + ctype eps2 = std::numeric_limits::min(); + for (int i = 1; i < corners_.size(); ++i) + eps2 = max(eps2, (corners_[i] - corners_[0]).two_norm2()); + + // We use a base epsilon of 1.5e-7 for comparisons of lengths. + // Since here we compare squared lengths, we multiply by its square. + eps2 *= 1.5e-7*1.5e-7; + + for (int i = 0; i < corners_.size(); ++i) + // early return if none of the other corners are equal to this corner + if (std::none_of(otherCorners.begin(), + otherCorners.end(), + [&] (const auto& other) { return (corners_[i] - other).two_norm2() < eps2; })) + return false; + + return true; + } + +private: + std::size_t a_, b_; //!< Indices of the intersection elements + std::vector corners_; //!< the corner points of the intersection geometry +}; + +/*! + * \ingroup Geometry + * \brief Compute all intersections between entities and a point + */ +template +inline std::vector +intersectingEntities(const Dune::FieldVector& point, + const BoundingBoxTree& tree, + bool isCartesianGrid = false) +{ + // Call the recursive find function to find candidates + std::vector entities; + intersectingEntities(point, tree, tree.numBoundingBoxes() - 1, entities, isCartesianGrid); + return entities; +} + +/*! + * \ingroup Geometry + * \brief Compute intersections with point for all nodes of the bounding box tree recursively + */ +template +void intersectingEntities(const Dune::FieldVector& point, + const BoundingBoxTree& tree, + std::size_t node, + std::vector& entities, + bool isCartesianGrid = false) +{ + // Get the bounding box for the current node + const auto& bBox = tree.getBoundingBoxNode(node); + + // if the point is not in the bounding box we can stop + if (!intersectsPointBoundingBox(point, tree.getBoundingBoxCoordinates(node))) return; + + // now we know it's inside + // if the box is a leaf do the primitive test. + else if (tree.isLeaf(bBox, node)) + { + const std::size_t entityIdx = bBox.child1; + // for structured cube grids skip the primitive test + if (isCartesianGrid) + entities.push_back(entityIdx); + else + { + const auto geometry = tree.entitySet().entity(entityIdx).geometry(); + // if the primitive is positive it intersects the actual geometry, add the entity to the list + if (intersectsPointGeometry(point, geometry)) + entities.push_back(entityIdx); + } + } + + // No leaf. Check both children nodes. + else + { + intersectingEntities(point, tree, bBox.child0, entities, isCartesianGrid); + intersectingEntities(point, tree, bBox.child1, entities, isCartesianGrid); + } +} + +/*! + * \ingroup Geometry + * \brief Compute all intersections between a geometry and a bounding box tree + */ +template +inline std::vector> +intersectingEntities(const Geometry& geometry, + const BoundingBoxTree& tree) +{ + using IP = typename IntersectionPolicy::DefaultPolicy; + return intersectingEntities(geometry, tree, IP{}); +} + +/*! + * \ingroup Geometry + * \brief Compute all intersections between a geometry and a bounding box tree + */ +template +inline std::vector> +intersectingEntities(const Geometry& geometry, + const BoundingBoxTree& tree, + IntersectionPolicy intersectionPolicy) +{ + // check if the world dimensions match + static_assert(int(Geometry::coorddimension) == int(EntitySet::dimensionworld), + "Can only intersect geometry and bounding box tree of same world dimension"); + + // Create data structure for return type + std::vector> intersections; + using ctype = typename IntersectionInfo::ctype; + static constexpr int dimworld = Geometry::coorddimension; + + // compute the bounding box of the given geometry + std::array bBox; + ctype* xMin = bBox.data(); ctype* xMax = xMin + Geometry::coorddimension; + + // Get coordinates of first vertex + auto corner = geometry.corner(0); + for (std::size_t dimIdx = 0; dimIdx < dimworld; ++dimIdx) + xMin[dimIdx] = xMax[dimIdx] = corner[dimIdx]; + + // Compute the min and max over the remaining vertices + for (std::size_t cornerIdx = 1; cornerIdx < geometry.corners(); ++cornerIdx) + { + corner = geometry.corner(cornerIdx); + for (std::size_t dimIdx = 0; dimIdx < dimworld; ++dimIdx) + { + using std::max; + using std::min; + xMin[dimIdx] = min(xMin[dimIdx], corner[dimIdx]); + xMax[dimIdx] = max(xMax[dimIdx], corner[dimIdx]); + } + } + + // Call the recursive find function to find candidates + intersectingEntities(geometry, tree, + bBox, tree.numBoundingBoxes() - 1, + intersections, intersectionPolicy); + + return intersections; +} + +/*! + * \ingroup Geometry + * \brief Compute intersections with point for all nodes of the bounding box tree recursively + */ +template +void intersectingEntities(const Geometry& geometry, + const BoundingBoxTree& tree, + const std::array& bBox, + std::size_t nodeIdx, + std::vector>& intersections) +{ + using IP = typename IntersectionPolicy::DefaultPolicy; + intersectingEntities(geometry, tree, bBox, nodeIdx, intersections, IP{}); +} +/*! + * \ingroup Geometry + * \brief Compute intersections with point for all nodes of the bounding box tree recursively + */ +template +void intersectingEntities(const Geometry& geometry, + const BoundingBoxTree& tree, + const std::array& bBox, + std::size_t nodeIdx, + std::vector>& intersections, + IntersectionPolicy intersectionPolicy) +{ + // if the two bounding boxes don't intersect we can stop searching + static constexpr int dimworld = Geometry::coorddimension; + if (!intersectsBoundingBoxBoundingBox(bBox.data(), tree.getBoundingBoxCoordinates(nodeIdx))) + return; + + // get node info for current bounding box node + const auto& bBoxNode = tree.getBoundingBoxNode(nodeIdx); + + // if the box is a leaf do the primitive test. + if (tree.isLeaf(bBoxNode, nodeIdx)) + { + // eIdxA is always 0 since we intersect with exactly one geometry + const auto eIdxA = 0; + const auto eIdxB = bBoxNode.child1; + + const auto geometryTree = tree.entitySet().entity(eIdxB).geometry(); + using GeometryTree = std::decay_t; + using IntersectionAlgorithm = GeometryIntersection; + using Intersection = typename IntersectionAlgorithm::Intersection; + Intersection intersection; + + if (IntersectionAlgorithm::intersection(geometry, geometryTree, intersection)) + { + static constexpr int dimIntersection = IntersectionPolicy::dimIntersection; + if constexpr (dimIntersection >= 2) + { + const auto triangulation = triangulate(intersection); + for (unsigned int i = 0; i < triangulation.size(); ++i) + intersections.emplace_back(eIdxA, eIdxB, std::move(triangulation[i])); + } + else + intersections.emplace_back(eIdxA, eIdxB, intersection); + } + } + + // No leaf. Check both children nodes. + else + { + intersectingEntities(geometry, tree, bBox, bBoxNode.child0, intersections); + intersectingEntities(geometry, tree, bBox, bBoxNode.child1, intersections); + } +} + +/*! + * \ingroup Geometry + * \brief Compute all intersections between two bounding box trees + */ +template +inline std::vector> +intersectingEntities(const BoundingBoxTree& treeA, + const BoundingBoxTree& treeB) +{ + using IP = typename IntersectionPolicy::DefaultPolicy; + return intersectingEntities(treeA, treeB, IP{}); +} + +/*! + * \ingroup Geometry + * \brief Compute all intersections between two bounding box trees + */ +template +inline std::vector> +intersectingEntities(const BoundingBoxTree& treeA, + const BoundingBoxTree& treeB, + IntersectionPolicy intersectionPolicy) +{ + // check if the world dimensions match + static_assert(int(EntitySet0::dimensionworld) == int(EntitySet1::dimensionworld), + "Can only intersect bounding box trees of same world dimension"); + + // Create data structure for return type + std::vector> intersections; + + // Call the recursive find function to find candidates + intersectingEntities(treeA, treeB, + treeA.numBoundingBoxes() - 1, + treeB.numBoundingBoxes() - 1, + intersections, intersectionPolicy); + + return intersections; +} + +/*! + * \ingroup Geometry + * \brief Compute all intersections between two all bounding box tree nodes recursively + */ +template +void intersectingEntities(const BoundingBoxTree& treeA, + const BoundingBoxTree& treeB, + std::size_t nodeA, std::size_t nodeB, + std::vector>& intersections) +{ + using IP = typename IntersectionPolicy::DefaultPolicy; + intersectingEntities(treeA, treeB, nodeA, nodeB, intersections, IP{}); +} + +/*! + * \ingroup Geometry + * \brief Compute all intersections between two all bounding box tree nodes recursively + */ +template +void intersectingEntities(const BoundingBoxTree& treeA, + const BoundingBoxTree& treeB, + std::size_t nodeA, std::size_t nodeB, + std::vector>& intersections, + IntersectionPolicy intersectionPolicy) +{ + // Get the bounding box for the current node + const auto& bBoxA = treeA.getBoundingBoxNode(nodeA); + const auto& bBoxB = treeB.getBoundingBoxNode(nodeB); + + // if the two bounding boxes of the current nodes don't intersect we can stop searching + static constexpr int dimworld = EntitySet0::dimensionworld; + if (!intersectsBoundingBoxBoundingBox(treeA.getBoundingBoxCoordinates(nodeA), + treeB.getBoundingBoxCoordinates(nodeB))) + return; + + // Check if we have a leaf in treeA or treeB + const bool isLeafA = treeA.isLeaf(bBoxA, nodeA); + const bool isLeafB = treeB.isLeaf(bBoxB, nodeB); + + // If both boxes are leaves do the primitive test + if (isLeafA && isLeafB) + { + const auto eIdxA = bBoxA.child1; + const auto eIdxB = bBoxB.child1; + + const auto geometryA = treeA.entitySet().entity(eIdxA).geometry(); + const auto geometryB = treeB.entitySet().entity(eIdxB).geometry(); + + using GeometryA = std::decay_t; + using GeometryB = std::decay_t; + using IntersectionAlgorithm = GeometryIntersection; + using Intersection = typename IntersectionAlgorithm::Intersection; + + if (Intersection intersection; IntersectionAlgorithm::intersection(geometryA, geometryB, intersection)) + { + static constexpr int dimIntersection = IntersectionPolicy::dimIntersection; + + // intersection is returned as a point cloud for dim >= 2 + // so we have to triangulate first + if constexpr (dimIntersection >= 2) + { + const auto triangulation = triangulate(intersection); + for (unsigned int i = 0; i < triangulation.size(); ++i) + intersections.emplace_back(eIdxA, eIdxB, std::move(triangulation[i])); + } + else + intersections.emplace_back(eIdxA, eIdxB, intersection); + } + } + + // if we reached the leaf in treeA, just continue in treeB + else if (isLeafA) + { + intersectingEntities(treeA, treeB, nodeA, bBoxB.child0, intersections); + intersectingEntities(treeA, treeB, nodeA, bBoxB.child1, intersections); + } + + // if we reached the leaf in treeB, just continue in treeA + else if (isLeafB) + { + intersectingEntities(treeA, treeB, bBoxA.child0, nodeB, intersections); + intersectingEntities(treeA, treeB, bBoxA.child1, nodeB, intersections); + } + + // we know now that both trees didn't reach the leaf yet so + // we continue with the larger tree first (bigger node number) + else if (nodeA > nodeB) + { + intersectingEntities(treeA, treeB, bBoxA.child0, nodeB, intersections); + intersectingEntities(treeA, treeB, bBoxA.child1, nodeB, intersections); + } + else + { + intersectingEntities(treeA, treeB, nodeA, bBoxB.child0, intersections); + intersectingEntities(treeA, treeB, nodeA, bBoxB.child1, intersections); + } +} + +/*! + * \ingroup Geometry + * \brief Compute the index of the intersecting element of a Cartesian grid with a point + * The grid is given by the lower left corner (min), the upper right corner (max) + * and the number of cells in each direction (cells). + * \note If there are several options the lowest matching cell index will be returned + * \note Returns the index i + I*j + I*J*k for the intersecting element i,j,k of a grid with I,J,K cells + */ +template +inline std::size_t intersectingEntityCartesianGrid(const Dune::FieldVector& point, + const Dune::FieldVector& min, + const Dune::FieldVector& max, + const std::array& cells) +{ + std::size_t index = 0; + for (int i = 0; i < dimworld; ++i) + { + using std::clamp; using std::floor; + ctype dimOffset = clamp(floor((point[i]-min[i])*cells[i]/(max[i]-min[i])), 0.0, cells[i]-1); + for (int j = 0; j < i; ++j) + dimOffset *= cells[j]; + index += static_cast(dimOffset); + } + return index; +} + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/intersectspointgeometry.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/intersectspointgeometry.hh new file mode 100644 index 0000000..9796bfe --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/intersectspointgeometry.hh @@ -0,0 +1,111 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Geometry + * \brief Detect if a point intersects a geometry + */ +#ifndef DUMUX_GEOMETRY_INTERSECTS_POINT_GEOMETRY_HH +#define DUMUX_GEOMETRY_INTERSECTS_POINT_GEOMETRY_HH + +#include +#include +#include + +#include + +namespace Dumux { + +/*! + * \ingroup Geometry + * \brief Find out whether a point is inside a three-dimensional geometry + */ +template = 0> +bool intersectsPointGeometry(const Dune::FieldVector& point, const Geometry& g) +{ + // get the g type + const auto type = g.type(); + + // if it's a tetrahedron we can check directly + if (type.isTetrahedron()) + return intersectsPointSimplex(point, g.corner(0), g.corner(1), g.corner(2), g.corner(3)); + + // split hexahedrons into five tetrahedrons + else if (type.isHexahedron()) + { + if (intersectsPointSimplex(point, g.corner(0), g.corner(1), g.corner(3), g.corner(5))) return true; + if (intersectsPointSimplex(point, g.corner(0), g.corner(5), g.corner(6), g.corner(4))) return true; + if (intersectsPointSimplex(point, g.corner(5), g.corner(3), g.corner(6), g.corner(7))) return true; + if (intersectsPointSimplex(point, g.corner(0), g.corner(3), g.corner(2), g.corner(6))) return true; + if (intersectsPointSimplex(point, g.corner(5), g.corner(3), g.corner(0), g.corner(6))) return true; + return false; + } + + // split pyramids into two tetrahedrons + else if (type.isPyramid()) + { + if (intersectsPointSimplex(point, g.corner(0), g.corner(1), g.corner(2), g.corner(4))) return true; + if (intersectsPointSimplex(point, g.corner(1), g.corner(3), g.corner(2), g.corner(4))) return true; + return false; + } + + // split prisms into three tetrahedrons + else if (type.isPrism()) + { + if (intersectsPointSimplex(point, g.corner(0), g.corner(1), g.corner(2), g.corner(4))) return true; + if (intersectsPointSimplex(point, g.corner(3), g.corner(0), g.corner(2), g.corner(4))) return true; + if (intersectsPointSimplex(point, g.corner(2), g.corner(5), g.corner(3), g.corner(4))) return true; + return false; + } + + else + DUNE_THROW(Dune::NotImplemented, + "Intersection for point and geometry type " + << type << " in " << dimworld << "-dimensional world."); +} + +/*! + * \ingroup Geometry + * \brief Find out whether a point is inside a two-dimensional geometry + */ +template = 0> +bool intersectsPointGeometry(const Dune::FieldVector& point, const Geometry& g) +{ + // get the g type + const auto type = g.type(); + + // if it's a triangle we can check directly + if (type.isTriangle()) + return intersectsPointSimplex(point, g.corner(0), g.corner(1), g.corner(2)); + + // split quadrilaterals into two triangles + else if (type.isQuadrilateral()) + { + if (intersectsPointSimplex(point, g.corner(0), g.corner(1), g.corner(3))) return true; + if (intersectsPointSimplex(point, g.corner(0), g.corner(3), g.corner(2))) return true; + return false; + } + + else + DUNE_THROW(Dune::NotImplemented, + "Intersection for point and geometry type " + << type << " in " << dimworld << "-dimensional world."); +} + +/*! + * \ingroup Geometry + * \brief Find out whether a point is inside a one-dimensional geometry + */ +template = 0> +bool intersectsPointGeometry(const Dune::FieldVector& point, const Geometry& g) +{ + return intersectsPointSimplex(point, g.corner(0), g.corner(1)); +} + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/intersectspointsimplex.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/intersectspointsimplex.hh new file mode 100644 index 0000000..bab8a3f --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/intersectspointsimplex.hh @@ -0,0 +1,230 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Geometry + * \brief Detect if a point intersects a simplex (including boundary) + */ +#ifndef DUMUX_GEOMETRY_INTERSECTS_POINT_SIMPLEX_HH +#define DUMUX_GEOMETRY_INTERSECTS_POINT_SIMPLEX_HH + +#include +#include +#include + +namespace Dumux { + +/*! + * \ingroup Geometry + * \brief Find out whether a point is inside the tetrahedron (p0, p1, p2, p3) (dimworld is 3) + */ +template = 0> +bool intersectsPointSimplex(const Dune::FieldVector& point, + const Dune::FieldVector& p0, + const Dune::FieldVector& p1, + const Dune::FieldVector& p2, + const Dune::FieldVector& p3) +{ + // Algorithm from http://www.blackpawn.com/texts/pointinpoly/ + // See also "Real-Time Collision Detection" by Christer Ericson. + using GlobalPosition = Dune::FieldVector; + static constexpr ctype eps_ = 1.0e-7; + + // put the tetrahedron points in an array + const GlobalPosition *p[4] = {&p0, &p1, &p2, &p3}; + + // iterate over all faces + for (int i = 0; i < 4; ++i) + { + // compute all the vectors from vertex (local index 0) to the other points + const GlobalPosition v1 = *p[(i + 1)%4] - *p[i]; + const GlobalPosition v2 = *p[(i + 2)%4] - *p[i]; + const GlobalPosition v3 = *p[(i + 3)%4] - *p[i]; + const GlobalPosition v = point - *p[i]; + // compute the normal to the facet (cross product) + GlobalPosition n1 = crossProduct(v1, v2); + n1 /= n1.two_norm(); + // find out on which side of the plane v and v3 are + const auto t1 = n1.dot(v); + const auto t2 = n1.dot(v3); + // If the point is not exactly on the plane the + // points have to be on the same side + const auto eps = eps_ * v1.two_norm(); + if ((t1 > eps || t1 < -eps) && std::signbit(t1) != std::signbit(t2)) + return false; + } + return true; +} + +/*! + * \ingroup Geometry + * \brief Find out whether a point is inside the triangle (p0, p1, p2) (dimworld is 3) + */ +template = 0> +bool intersectsPointSimplex(const Dune::FieldVector& point, + const Dune::FieldVector& p0, + const Dune::FieldVector& p1, + const Dune::FieldVector& p2) +{ + // adapted from the algorithm from from "Real-Time Collision Detection" by Christer Ericson, + // published by Morgan Kaufmann Publishers, (c) 2005 Elsevier Inc. (Chapter 5.4.2) + constexpr ctype eps_ = 1.0e-7; + + // compute the normal of the triangle + const auto v1 = p0 - p2; + auto n = crossProduct(v1, p1 - p0); + const ctype nnorm = n.two_norm(); + const ctype eps4 = eps_*nnorm*nnorm; // compute an epsilon for later + n /= nnorm; // normalize + + // first check if we are in the plane of the triangle + // if not we can return early + using std::abs; + auto x = p0 - point; + x /= x.two_norm(); // normalize + + if (abs(x*n) > eps_) + return false; + + // translate the triangle so that 'point' is the origin + const auto a = p0 - point; + const auto b = p1 - point; + const auto c = p2 - point; + + // compute the normal vectors for triangles P->A->B and P->B->C + const auto u = crossProduct(b, c); + const auto v = crossProduct(c, a); + + // they have to point in the same direction or be orthogonal + if (u*v < 0.0 - eps4) + return false; + + // compute the normal vector for triangle P->C->A + const auto w = crossProduct(a, b); + + // it also has to point in the same direction or be orthogonal + if (u*w < 0.0 - eps4) + return false; + + // check if point is on the line of one of the edges + if (u.two_norm2() < eps4) + return b*c < 0.0 + eps_*nnorm; + if (v.two_norm2() < eps4) + return a*c < 0.0 + eps_*nnorm; + + // now the point must be in the triangle (or on the faces) + return true; +} + +/*! + * \ingroup Geometry + * \brief Find out whether a point is inside the triangle (p0, p1, p2) (dimworld is 2) + */ +template = 0> +bool intersectsPointSimplex(const Dune::FieldVector& point, + const Dune::FieldVector& p0, + const Dune::FieldVector& p1, + const Dune::FieldVector& p2) +{ + static constexpr ctype eps_ = 1.0e-7; + + // Use barycentric coordinates + const ctype A = 0.5*(-p1[1]*p2[0] + p0[1]*(p2[0] - p1[0]) + +p1[0]*p2[1] + p0[0]*(p1[1] - p2[1])); + const ctype sign = std::copysign(1.0, A); + const ctype s = sign*(p0[1]*p2[0] + point[0]*(p2[1]-p0[1]) + -p0[0]*p2[1] + point[1]*(p0[0]-p2[0])); + const ctype t = sign*(p0[0]*p1[1] + point[0]*(p0[1]-p1[1]) + -p0[1]*p1[0] + point[1]*(p1[0]-p0[0])); + const ctype eps = sign*A*eps_; + + return (s > -eps + && t > -eps + && (s + t) < 2*A*sign + eps); +} + +/*! + * \ingroup Geometry + * \brief Find out whether a point is inside the interval (p0, p1) (dimworld is 2 or 3) + * \note We assume the given interval has non-zero length and use it to scale the epsilon + */ +template = 0> +bool intersectsPointSimplex(const Dune::FieldVector& point, + const Dune::FieldVector& p0, + const Dune::FieldVector& p1) +{ + using GlobalPosition = Dune::FieldVector; + static constexpr ctype eps_ = 1.0e-7; + + // compute the vectors between p0 and the other points + const GlobalPosition v1 = p1 - p0; + const GlobalPosition v2 = point - p0; + + const ctype v1norm = v1.two_norm(); + const ctype v2norm = v2.two_norm(); + + // early exit if point and p0 are the same + if (v2norm < v1norm*eps_) + return true; + + // early exit if the point is outside the segment (no epsilon in the + // first statement because we already did the above equality check) + if (v1.dot(v2) < 0.0 || v2norm > v1norm*(1.0 + eps_)) + return false; + + // If the area spanned by the 2 vectors is zero, the points are colinear. + // If that is the case, the given point is on the segment. + const auto n = crossProduct(v1, v2); + const auto eps2 = v1norm*v1norm*eps_; + if constexpr (dimworld == 3) + return n.two_norm2() < eps2*eps2; + else + { + using std::abs; + return abs(n) < eps2; + } +} + +/*! + * \ingroup Geometry + * \brief Find out whether a point is inside the interval (p0, p1) (dimworld is 1) + */ +template = 0> +bool intersectsPointSimplex(const Dune::FieldVector& point, + const Dune::FieldVector& p0, + const Dune::FieldVector& p1) +{ + static constexpr ctype eps_ = 1.0e-7; + + // sort the interval so interval[1] is the end and interval[0] the start + const ctype *interval[2] = {&p0[0], &p1[0]}; + if (*interval[0] > *interval[1]) + std::swap(interval[0], interval[1]); + + const ctype v1 = point[0] - *interval[0]; + const ctype v2 = *interval[1] - *interval[0]; // always positive + + // the coordinates are the same + using std::abs; + if (abs(v1) < v2*eps_) + return true; + + // the point doesn't coincide with p0 + // so if p0 and p1 are equal it's not inside + if (v2 < 1.0e-30) + return false; + + // the point is inside if the length is + // smaller than the interval length and the + // sign of v1 & v2 are the same + using std::signbit; + return (!signbit(v1) && abs(v1) < v2*(1.0 + eps_)); +} + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/triangulation.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/triangulation.hh new file mode 100644 index 0000000..4ba87e7 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/triangulation.hh @@ -0,0 +1,377 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Geometry + * \brief Functionality to triangulate point clouds + * \note Most of the implemented algorithms currently do not scale for large point clouds + * and are only meant to be used with a small number of points + */ +#ifndef DUMUX_GEOMETRY_TRIANGULATION_HH +#define DUMUX_GEOMETRY_TRIANGULATION_HH + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace Dumux { +namespace TriangulationPolicy { + +//! Policy that expects a point cloud that represents a convex +//! hull. Inserts the mid point and connects it to the points of the hull. +struct MidPointPolicy {}; + +//! Policy that first finds the convex hull +//! and then uses the mid point policy to construct a triangulation +struct ConvexHullPolicy {}; + +//! Delaunay-type triangulations. +struct DelaunayPolicy {}; + +#ifndef DOXYGEN +namespace Detail { +using DefaultDimPolicies = std::tuple; +} // end namespace Detail +#endif + +//! Default policy for a given dimension +template +using DefaultPolicy = std::tuple_element_t; + +} // end namespace TriangulationPolicy + +/*! + * \ingroup Geometry + * \brief The default data type to store triangulations + * \note Stores each simplex separate and without connectivity information + * \note We usually use this for sub-triangulation to allow for quadrature rules and interpolation on intersection geometries + * This is neither meant to be used to store large amounts of data nor as a mesh-like object + */ +template +using Triangulation = std::vector< std::array, dim+1> >; + +/*! + * \ingroup Geometry + * \brief Triangulate area given points of a convex hull (1d) + * \tparam dim Specifies the dimension of the resulting triangulation + * \tparam dimWorld The dimension of the coordinate space + * \tparam Policy Specifies the algorithm to be used for triangulation + * + * \note this specialization is for 1d discretization using segments + * \note sorts the points and connects them via segments + */ +template< int dim, int dimWorld, class Policy = TriangulationPolicy::DefaultPolicy, + class RandomAccessContainer, + std::enable_if_t< std::is_same_v + && dim == 1, int> = 0 > +inline Triangulation +triangulate(const RandomAccessContainer& points) +{ + using ctype = typename RandomAccessContainer::value_type::value_type; + using Point = Dune::FieldVector; + + static_assert(std::is_same_v, + "Triangulation expects Dune::FieldVector as point type"); + + if (points.size() == 2) + return Triangulation({ {points[0], points[1]} }); + + //! \todo sort points and create polyline + assert(points.size() > 1); + DUNE_THROW(Dune::NotImplemented, "1d triangulation for point cloud size > 2"); +} + +/*! + * \ingroup Geometry + * \brief Triangulate area given points of a convex hull (2d) + * \tparam dim Specifies the dimension of the resulting triangulation + * \tparam dimWorld The dimension of the coordinate space + * \tparam Policy Specifies the algorithm to be used for triangulation + * + * \note this specialization is for 2d triangulations using mid point policy + * \note Assumes all points of the convex hull are coplanar + * \note This inserts a mid point and connects all corners with that point to triangles + * \note Assumes points are given as a ordered sequence representing the polyline forming the convex hull + */ +template< int dim, int dimWorld, class Policy = TriangulationPolicy::DefaultPolicy, + class RandomAccessContainer, + std::enable_if_t< std::is_same_v + && dim == 2, int> = 0 > +inline Triangulation +triangulate(const RandomAccessContainer& convexHullPoints) +{ + using ctype = typename RandomAccessContainer::value_type::value_type; + using Point = Dune::FieldVector; + using Triangle = std::array; + + static_assert(std::is_same_v, + "Triangulation expects Dune::FieldVector as point type"); + + if (convexHullPoints.size() < 3) + DUNE_THROW(Dune::InvalidStateException, "Try to triangulate point cloud with less than 3 points!"); + + if (convexHullPoints.size() == 3) + return std::vector(1, {convexHullPoints[0], convexHullPoints[1], convexHullPoints[2]}); + + Point midPoint(0.0); + for (const auto& p : convexHullPoints) + midPoint += p; + midPoint /= convexHullPoints.size(); + + std::vector triangulation; + triangulation.reserve(convexHullPoints.size()); + + for (std::size_t i = 0; i < convexHullPoints.size()-1; ++i) + triangulation.emplace_back(Triangle{midPoint, convexHullPoints[i], convexHullPoints[i+1]}); + + triangulation.emplace_back(Triangle{midPoint, convexHullPoints[convexHullPoints.size()-1], convexHullPoints[0]}); + + return triangulation; +} + +/*! + * \ingroup Geometry + * \brief Triangulate area given points (2d) + * \tparam dim Specifies the dimension of the resulting triangulation + * \tparam dimWorld The dimension of the coordinate space + * \tparam Policy Specifies the algorithm to be used for triangulation + * + * \note this specialization is for 2d triangulations using the convex hull policy + * That means we first construct the convex hull of the points and then + * triangulate the convex hull using the midpoint policy + * \note Assumes points are unique and not all colinear (will throw a Dune::InvalidStateException) + */ +template< int dim, int dimWorld, class Policy = TriangulationPolicy::DefaultPolicy, + class RandomAccessContainer, + std::enable_if_t< std::is_same_v + && dim == 2, int> = 0 > +inline Triangulation +triangulate(const RandomAccessContainer& points) +{ + const auto convexHullPoints = grahamConvexHull<2>(points); + return triangulate(convexHullPoints); +} + +/*! + * \ingroup Geometry + * \brief Triangulate volume given a point cloud (3d) + * \tparam dim Specifies the dimension of the resulting triangulation + * \tparam dimWorld The dimension of the coordinate space + * \tparam Policy Specifies the algorithm to be used for triangulation + * + * \note this specialization is for 3d triangulations using the convex hull policy + * \note Assumes points are unique and not all coplanar + */ +template< int dim, int dimWorld, class Policy = TriangulationPolicy::DefaultPolicy, + class RandomAccessContainer, + std::enable_if_t< std::is_same_v + && dim == 3, int> = 0 > +inline Triangulation +triangulate(const RandomAccessContainer& points) +{ + using ctype = typename RandomAccessContainer::value_type::value_type; + using Point = Dune::FieldVector; + using Tetrahedron = std::array; + + static_assert(std::is_same_v, + "Triangulation expects Dune::FieldVector as point type"); + + const auto numPoints = points.size(); + if (numPoints < 4) + DUNE_THROW(Dune::InvalidStateException, "Trying to create 3D triangulation of point cloud with less than 4 points!"); + + if (numPoints == 4) + return std::vector(1, {points[0], points[1], points[2], points[3]}); + + // compute the mid point of the point cloud (not the midpoint of the convex hull but this + // should not matter too much for the applications we have in mind here) + Point midPoint(0.0); + Point lowerLeft(1e100); + Point upperRight(-1e100); + for (const auto& p : points) + { + midPoint += p; + for (int i = 0; i < dimWorld; ++i) + { + using std::max; using std::min; + lowerLeft[i] = min(p[i], lowerLeft[i]); + upperRight[i] = max(p[i], upperRight[i]); + } + } + midPoint /= numPoints; + + auto magnitude = 0.0; + using std::max; + for (int i = 0; i < dimWorld; ++i) + magnitude = max(upperRight[i] - lowerLeft[i], magnitude); + const auto eps = 1e-7*magnitude; + const auto eps2 = eps*magnitude; + + // reserve memory conservatively to avoid reallocation + std::vector triangulation; + triangulation.reserve(numPoints); + + // make a buffer for storing coplanar points and indices + std::vector coplanarPointBuffer; + coplanarPointBuffer.reserve(std::min(12, numPoints-1)); + + // remember coplanar cluster planes + // coplanar clusters are uniquely identified by a point and a normal (plane) + // we only want to add each cluster once (when handling the first triangle in the cluster) + std::vector> coplanarClusters; + coplanarClusters.reserve(numPoints/3); + + // brute force algorithm: Try all possible triangles and check + // if they are triangles of the convex hull. This is achieved by checking if all + // other points are in the same half-space (side) + // the algorithm is O(n^4), so only do this for very small point clouds + for (int i = 0; i < numPoints; ++i) + { + for (int j = i+1; j < numPoints; ++j) + { + for (int k = j+1; k < numPoints; ++k) + { + const auto pointI = points[i]; + const auto ab = points[j] - pointI; + const auto ac = points[k] - pointI; + const auto normal = crossProduct(ab, ac); + + // clear list of coplanar points w.r.t to triangle ijk + coplanarPointBuffer.clear(); + + // check if this triangle ijk is admissible which means + // it is on the convex hull (all other points in the cloud are in the same half-space/side) + const bool isAdmissible = [&]() + { + // check if points are colinear and we can't form a triangle + // if so, skip this triangle + if (normal.two_norm2() < eps2*eps2) + return false; + + int marker = 0; // 0 means undecided side (or coplanar) + for (int m = 0; m < numPoints; ++m) + { + if (m != i && m != j && m != k) + { + // check scalar product with surface normal to decide side + const auto ad = points[m] - pointI; + const auto sp = normal*ad; + + // if the sign changes wrt the previous sign, the triangle is not part of the convex hull + using std::abs; using std::signbit; + const bool coplanar = abs(sp) < eps2*magnitude; + int newMarker = coplanar ? 0 : signbit(sp) ? -1 : 1; + + // make decision for a side as soon as the next marker is != 0 + // keep track of the previous marker + if (marker == 0 && newMarker != 0) + marker = newMarker; + + // if marker flips, not all points are on one side + // zero marker (undecided side / coplanar point) shouldn't abort the process + if (newMarker != 0 && marker != newMarker) + return false; + + // handle possible coplanar points + if (coplanar) + { + using std::abs; + if (m < k && std::find_if( + coplanarClusters.begin(), coplanarClusters.end(), + [=](const auto& c){ return c.first == std::min(m, i) && abs(ab*c.second) < eps2*magnitude; } + ) != coplanarClusters.end()) + { + // this cluster has already been handled + coplanarPointBuffer.clear(); + return false; + } + else + coplanarPointBuffer.push_back(points[m]); + } + } + } + + // if there are coplanar points complete the cluster with i, j, and k + // and store the cluster information for future lookup + if (!coplanarPointBuffer.empty()) + { + coplanarPointBuffer.insert(coplanarPointBuffer.end(), { points[i], points[j], points[k] }); + coplanarClusters.emplace_back(std::make_pair(i, normal)); + } + + // we require that not all points are coplanar, so + // there will be always at least one non-coplanar point + // to check on which side the other points are. + // Hence, once we get here, the triangle or coplanar point cluster is part of the convex hull + return true; + }(); + + if (isAdmissible) + { + // check if we have a cluster of coplanar points forming on of the + // faces of the convex hull, if yes, compute (2d) convex hull first and triangulate + if (!coplanarPointBuffer.empty()) + { + const auto triangles = triangulate<2, 3, TriangulationPolicy::ConvexHullPolicy>(coplanarPointBuffer); + for (const auto& triangle : triangles) + { + const auto ab = triangle[1] - triangle[0]; + const auto ac = triangle[2] - triangle[0]; + const auto normal = crossProduct(ab, ac); + const auto am = midPoint - triangle[0]; + const auto sp = normal*am; + using std::signbit; + const bool isBelow = signbit(sp); + if (isBelow) + triangulation.emplace_back(Tetrahedron{ + triangle[0], triangle[2], triangle[1], midPoint + }); + else + triangulation.emplace_back(Tetrahedron{ + triangle[0], triangle[1], triangle[2], midPoint + }); + } + } + else + { + const auto am = midPoint - pointI; + const auto sp = normal*am; + using std::signbit; + const bool isBelow = signbit(sp); + if (isBelow) + triangulation.emplace_back(Tetrahedron{ + pointI, points[k], points[j], midPoint + }); + else + triangulation.emplace_back(Tetrahedron{ + pointI, points[j], points[k], midPoint + }); + } + } + } + } + } + + // sanity check: if points are not coplanar, then using the mid point policy, we get at least 4 tetrahedrons + if (triangulation.size() < 4) + DUNE_THROW(Dune::InvalidStateException, "Something went wrong with the triangulation!"); + + return triangulation; +} + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/volume.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/volume.hh new file mode 100644 index 0000000..ec7a144 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/geometry/volume.hh @@ -0,0 +1,187 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Geometry + * \brief Compute the volume of several common geometry types + */ +#ifndef DUMUX_GEOMETRY_VOLUME_HH +#define DUMUX_GEOMETRY_VOLUME_HH + +#include +#include +#include + +#include +#include +#include + +#include + +namespace Dumux { + +/*! + * \ingroup Geometry + * \brief Compute the volume of several common geometry types + * \param type the geometry type + * \param c a function returning the ith corner (in Dune reference element order) + * e.g. `[&](unsigned int i){ return corners[i]; }`, where `corners` is a + * random access container storing the corners, and the returned corner is stored + * in a container (e.g. Dune::FieldVector) that exports `value_type` and `dimension`. + * \tparam dim the dimension of the geometry + * \tparam CornerF the function type (is deduced) + * \return volume of the geometry or NaN signalling not implemented + * \note This is only correct for convex polytopes (flat sides) + */ +template +auto convexPolytopeVolume(Dune::GeometryType type, const CornerF& c) +{ + using ctype = typename std::decay_t::value_type; + static constexpr int coordDim = std::decay_t::dimension; + static_assert(coordDim >= dim, "Coordinate dimension has to be larger than geometry dimension"); + + // not implemented for coordinate dimension larger than 3 + if constexpr (coordDim > 3) + return std::numeric_limits::quiet_NaN(); + + if constexpr (dim == 0) + return 1.0; + + else if constexpr (dim == 1) + return (c(1)-c(0)).two_norm(); + + else if constexpr (dim == 2) + { + if (type == Dune::GeometryTypes::triangle) + { + if constexpr (coordDim == 2) + { + // make sure we are using positive volumes + // the cross product of edge vectors might be negative, + // depending on the element orientation + using std::abs; + return 0.5*abs(Dumux::crossProduct(c(1)-c(0), c(2)-c(0))); + } + else // coordDim == 3 + return 0.5*Dumux::crossProduct(c(1)-c(0), c(2)-c(0)).two_norm(); + + } + else if (type == Dune::GeometryTypes::quadrilateral) + { + if constexpr (coordDim == 2) + { + // make sure we are using positive volumes + // the cross product of diagonals might be negative, + // depending on the element orientation + using std::abs; + return 0.5*abs(Dumux::crossProduct(c(3)-c(0), c(2)-c(1))); + } + else // coordDim == 3 + return 0.5*Dumux::crossProduct(c(3)-c(0), c(2)-c(1)).two_norm(); + + } + else + return std::numeric_limits::quiet_NaN(); + } + + else if constexpr (dim == 3) + { + if (type == Dune::GeometryTypes::tetrahedron) + { + using std::abs; + return 1.0/6.0 * abs( + Dumux::tripleProduct(c(3)-c(0), c(1)-c(0), c(2)-c(0)) + ); + } + else if (type == Dune::GeometryTypes::hexahedron) + { + // after Grandy 1997, Efficient computation of volume of hexahedron + const auto v = c(7)-c(0); + using std::abs; + return 1.0/6.0 * ( + abs(Dumux::tripleProduct(v, c(1)-c(0), c(3)-c(5))) + + abs(Dumux::tripleProduct(v, c(4)-c(0), c(5)-c(6))) + + abs(Dumux::tripleProduct(v, c(2)-c(0), c(6)-c(3))) + ); + } + else if (type == Dune::GeometryTypes::pyramid) + { + // 1/3 * base * height + // for base see case Dune::GeometryTypes::quadrilateral above + // = 1/3 * (1/2 * norm(ADxBC)) * ((ADxBC)/norm(AD x BC) ⋅ AE) + // = 1/6 * (AD x BC) ⋅ AE + using std::abs; + return 1.0/6.0 * abs( + Dumux::tripleProduct(c(3)-c(0), c(2)-c(1), c(4)-c(0)) + ); + } + else if (type == Dune::GeometryTypes::prism) + { + // compute as sum of a pyramid (0-1-3-4-5) and a tetrahedron (2-0-1-5) + using std::abs; + return 1.0/6.0 * ( + abs(Dumux::tripleProduct(c(3)-c(1), c(4)-c(0), c(5)-c(0))) + + abs(Dumux::tripleProduct(c(5)-c(2), c(0)-c(2), c(1)-c(2))) + ); + } + else + return std::numeric_limits::quiet_NaN(); + } + else + return std::numeric_limits::quiet_NaN(); +} + +/*! + * \ingroup Geometry + * \brief The volume of a given geometry + */ +template +auto convexPolytopeVolume(const Geometry& geo) +{ + const auto v = convexPolytopeVolume( + geo.type(), [&](unsigned int i){ return geo.corner(i); } + ); + + // fall back to the method of the geometry if no specialized + // volume function is implemented for the geometry type + return std::isnan(v) ? geo.volume() : v; +} + +/*! + * \ingroup Geometry + * \brief The volume of a given geometry + */ +template +auto volume(const Geometry& geo, unsigned int integrationOrder = 4) +{ + using ctype = typename Geometry::ctype; + ctype volume = 0.0; + const auto rule = Dune::QuadratureRules::rule(geo.type(), integrationOrder); + for (const auto& qp : rule) + volume += geo.integrationElement(qp.position())*qp.weight(); + return volume; +} + +/*! + * \ingroup Geometry + * \brief The volume of a given geometry with an extrusion/transformation policy + * \note depending on the transformation this might not be an accurate quadrature rule anymore + */ +template +auto volume(const Geometry& geo, Transformation transformation, unsigned int integrationOrder = 4) +{ + using ctype = typename Geometry::ctype; + ctype volume = 0.0; + const auto rule = Dune::QuadratureRules::rule(geo.type(), integrationOrder); + for (const auto& qp : rule) + volume += transformation.integrationElement(geo, qp.position())*qp.weight(); + return volume; +} + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/container.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/container.hh new file mode 100644 index 0000000..dc80d51 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/container.hh @@ -0,0 +1,82 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup InputOutput + * \brief Free functions to write and read a sequence container to and from a file + * \note Reading should work for all sequence containers providing begin, end, and push_back + * (e.g. std::vector, std::deque, std::list), so not for e.g. std::array. + * Writing only needs begin and end member functions returning iterators. + */ +#ifndef DUMUX_IO_CONTAINER_HH +#define DUMUX_IO_CONTAINER_HH + +#include +#include +#include +#include +#include + +#include + +namespace Dumux { + +/*! + * \ingroup InputOutput + * \brief Writes a container to file + * \param v The container, requires begin() and end() method + * \param filename The filename to write to + * \param floatPrecision The total number of digits stored, including decimal + * + * usage: std::vector v(5, 0.0); writeContainerToFile(v, "myvector.txt"); + */ +template +void writeContainerToFile(const Container& v, + const std::string& filename, + int floatPrecision = 6) +{ + std::ofstream outfile(filename, std::ios::out); + outfile << std::scientific << std::setprecision(floatPrecision); + std::ostream_iterator it(outfile, "\n"); + std::copy(v.begin(),v.end(), it); +} + +/*! + * \brief Read an input stream into a container + * \param stream A standard input stream + * \tparam Container The container type requires begin(), end(), push_back() functions + * and Container::value_type requires operator>>. + */ +template +Container readStreamToContainer(std::istream& stream) +{ + Container v; + std::istream_iterator it(stream); + std::copy(it, std::istream_iterator(), std::back_inserter(v)); + return v; +} + +/*! + * \brief Read a simple text file into a container + * \param filename The filename to write to + * \tparam Container The container type requires begin(), end(), push_back() functions + * and Container::value_type requires operator>>. + * + * usage: auto v = readFileToContainer>("myvector.txt"); + */ +template +Container readFileToContainer(const std::string& filename) +{ + std::ifstream infile(filename, std::ios::in); + if (!infile) + DUNE_THROW(Dune::IOError, "Could not open file: " << filename); + return readStreamToContainer(infile); +} + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/defaultiofields.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/defaultiofields.hh new file mode 100644 index 0000000..69d3cd5 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/defaultiofields.hh @@ -0,0 +1,42 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup InputOutput + * \brief Adds output fields to a given output module, this is the default if a + model doesn't implement this functionality + */ +#ifndef DUMUX_IO_DEFAULT_IO_FIELDS_HH +#define DUMUX_IO_DEFAULT_IO_FIELDS_HH + +#include + +namespace Dumux { + +/*! + * \ingroup InputOutput + * \brief Adds output fields to a given output module + */ +class DefaultIOFields +{ +public: + template + static void initOutputModule(OutputModule& out) + { + DUNE_THROW(Dune::NotImplemented, "This model doesn't implement default output fields!"); + } + + template + static std::string primaryVariableName(int pvIdx = 0, int state = 0) + { + DUNE_THROW(Dune::NotImplemented, "This model doesn't implement primaryVariableName!"); + } +}; + +} // end namespace Dumux + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/format.hh b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/format.hh new file mode 100644 index 0000000..9c7b0b7 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/format.hh @@ -0,0 +1,53 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup InputOutput + * \brief Formatting based on the fmt-library which implements std::format of C++20 + * + * For documentation of the functions, see https://en.cppreference.com/w/cpp/utility/format + * For a documentation of formatting styles, + * see https://en.cppreference.com/w/cpp/utility/format/formatter#Standard_format_specification + * + * Once std::format / C++20 is available, we can use the standard library here. + */ +#ifndef DUMUX_IO_FORMAT_HH +#define DUMUX_IO_FORMAT_HH + +#if __has_include() // cppcheck-suppress preprocessorErrorDirective +#include +#endif + +#include +#include + +//! Formatting tools in the style of std::format (C++20) +namespace Dumux::Fmt { + +#if __cpp_lib_format +// use std::format from C++20 +using std::format; +using std::format_to; +using std::format_to_n; +using std::formatted_size; +using std::vformat; +using std::vformat_to; +using std::make_format_args; +#else +// use fallback fmt library +using Dumux::Detail::fmt::format; +using Dumux::Detail::fmt::format_to; +using Dumux::Detail::fmt::format_to_n; +using Dumux::Detail::fmt::formatted_size; +using Dumux::Detail::fmt::vformat; +using Dumux::Detail::fmt::vformat_to; +using Dumux::Detail::fmt::make_format_args; +#endif + +} // end namespace Dumux::Fmt + +#endif diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/format/fmt/core.h b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/format/fmt/core.h new file mode 100644 index 0000000..8956181 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/format/fmt/core.h @@ -0,0 +1,3327 @@ +// +// SPDX-FileCopyrightText: Copyright (c) 2012 - present, Victor Zverovich +// SPDX-License-Identifier: LicenseRef-fmt +// +// Formatting library for C++ - the core API for char/UTF-8 +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef DUMUX_FMT_CORE_H_ +#define DUMUX_FMT_CORE_H_ + +#include // std::byte +#include // std::FILE +#include // std::strlen +#include +#include +#include +#include + +// The fmt library version in the form major * 10000 + minor * 100 + patch. +#define FMT_VERSION 90100 + +#if defined(__clang__) && !defined(__ibmxl__) +# define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) +#else +# define FMT_CLANG_VERSION 0 +#endif + +#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) && \ + !defined(__NVCOMPILER) +# define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) +#else +# define FMT_GCC_VERSION 0 +#endif + +#ifndef FMT_GCC_PRAGMA +// Workaround _Pragma bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59884. +# if FMT_GCC_VERSION >= 504 +# define FMT_GCC_PRAGMA(arg) _Pragma(arg) +# else +# define FMT_GCC_PRAGMA(arg) +# endif +#endif + +#ifdef __ICL +# define FMT_ICC_VERSION __ICL +#elif defined(__INTEL_COMPILER) +# define FMT_ICC_VERSION __INTEL_COMPILER +#else +# define FMT_ICC_VERSION 0 +#endif + +#ifdef _MSC_VER +# define FMT_MSC_VERSION _MSC_VER +# define FMT_MSC_WARNING(...) __pragma(warning(__VA_ARGS__)) +#else +# define FMT_MSC_VERSION 0 +# define FMT_MSC_WARNING(...) +#endif + +#ifdef _MSVC_LANG +# define FMT_CPLUSPLUS _MSVC_LANG +#else +# define FMT_CPLUSPLUS __cplusplus +#endif + +#ifdef __has_feature +# define FMT_HAS_FEATURE(x) __has_feature(x) +#else +# define FMT_HAS_FEATURE(x) 0 +#endif + +#if (defined(__has_include) || FMT_ICC_VERSION >= 1600 || \ + FMT_MSC_VERSION > 1900) && \ + !defined(__INTELLISENSE__) +# define FMT_HAS_INCLUDE(x) __has_include(x) +#else +# define FMT_HAS_INCLUDE(x) 0 +#endif + +#ifdef __has_cpp_attribute +# define FMT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) +#else +# define FMT_HAS_CPP_ATTRIBUTE(x) 0 +#endif + +#define FMT_HAS_CPP14_ATTRIBUTE(attribute) \ + (FMT_CPLUSPLUS >= 201402L && FMT_HAS_CPP_ATTRIBUTE(attribute)) + +#define FMT_HAS_CPP17_ATTRIBUTE(attribute) \ + (FMT_CPLUSPLUS >= 201703L && FMT_HAS_CPP_ATTRIBUTE(attribute)) + +// Check if relaxed C++14 constexpr is supported. +// GCC doesn't allow throw in constexpr until version 6 (bug 67371). +#ifndef FMT_USE_CONSTEXPR +# if (FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VERSION >= 1912 || \ + (FMT_GCC_VERSION >= 600 && FMT_CPLUSPLUS >= 201402L)) && \ + !FMT_ICC_VERSION && !defined(__NVCC__) +# define FMT_USE_CONSTEXPR 1 +# else +# define FMT_USE_CONSTEXPR 0 +# endif +#endif +#if FMT_USE_CONSTEXPR +# define FMT_CONSTEXPR constexpr +#else +# define FMT_CONSTEXPR +#endif + +#if ((FMT_CPLUSPLUS >= 202002L) && \ + (!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE > 9)) || \ + (FMT_CPLUSPLUS >= 201709L && FMT_GCC_VERSION >= 1002) +# define FMT_CONSTEXPR20 constexpr +#else +# define FMT_CONSTEXPR20 +#endif + +// Check if constexpr std::char_traits<>::{compare,length} are supported. +#if defined(__GLIBCXX__) +# if FMT_CPLUSPLUS >= 201703L && defined(_GLIBCXX_RELEASE) && \ + _GLIBCXX_RELEASE >= 7 // GCC 7+ libstdc++ has _GLIBCXX_RELEASE. +# define FMT_CONSTEXPR_CHAR_TRAITS constexpr +# endif +#elif defined(_LIBCPP_VERSION) && FMT_CPLUSPLUS >= 201703L && \ + _LIBCPP_VERSION >= 4000 +# define FMT_CONSTEXPR_CHAR_TRAITS constexpr +#elif FMT_MSC_VERSION >= 1914 && FMT_CPLUSPLUS >= 201703L +# define FMT_CONSTEXPR_CHAR_TRAITS constexpr +#endif +#ifndef FMT_CONSTEXPR_CHAR_TRAITS +# define FMT_CONSTEXPR_CHAR_TRAITS +#endif + +// Check if exceptions are disabled. +#ifndef FMT_EXCEPTIONS +# if (defined(__GNUC__) && !defined(__EXCEPTIONS)) || \ + (FMT_MSC_VERSION && !_HAS_EXCEPTIONS) +# define FMT_EXCEPTIONS 0 +# else +# define FMT_EXCEPTIONS 1 +# endif +#endif + +#ifndef FMT_DEPRECATED +# if FMT_HAS_CPP14_ATTRIBUTE(deprecated) || FMT_MSC_VERSION >= 1900 +# define FMT_DEPRECATED [[deprecated]] +# else +# if (defined(__GNUC__) && !defined(__LCC__)) || defined(__clang__) +# define FMT_DEPRECATED __attribute__((deprecated)) +# elif FMT_MSC_VERSION +# define FMT_DEPRECATED __declspec(deprecated) +# else +# define FMT_DEPRECATED /* deprecated */ +# endif +# endif +#endif + +// [[noreturn]] is disabled on MSVC and NVCC because of bogus unreachable code +// warnings. +#if FMT_EXCEPTIONS && FMT_HAS_CPP_ATTRIBUTE(noreturn) && !FMT_MSC_VERSION && \ + !defined(__NVCC__) +# define FMT_NORETURN [[noreturn]] +#else +# define FMT_NORETURN +#endif + +#if FMT_HAS_CPP17_ATTRIBUTE(fallthrough) +# define FMT_FALLTHROUGH [[fallthrough]] +#elif defined(__clang__) +# define FMT_FALLTHROUGH [[clang::fallthrough]] +#elif FMT_GCC_VERSION >= 700 && \ + (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 520) +# define FMT_FALLTHROUGH [[gnu::fallthrough]] +#else +# define FMT_FALLTHROUGH +#endif + +#ifndef FMT_NODISCARD +# if FMT_HAS_CPP17_ATTRIBUTE(nodiscard) +# define FMT_NODISCARD [[nodiscard]] +# else +# define FMT_NODISCARD +# endif +#endif + +#ifndef FMT_USE_FLOAT +# define FMT_USE_FLOAT 1 +#endif +#ifndef FMT_USE_DOUBLE +# define FMT_USE_DOUBLE 1 +#endif +#ifndef FMT_USE_LONG_DOUBLE +# define FMT_USE_LONG_DOUBLE 1 +#endif + +#ifndef FMT_INLINE +# if FMT_GCC_VERSION || FMT_CLANG_VERSION +# define FMT_INLINE inline __attribute__((always_inline)) +# else +# define FMT_INLINE inline +# endif +#endif + +// An inline std::forward replacement. +#define FMT_FORWARD(...) static_cast(__VA_ARGS__) + +#ifdef _MSC_VER +# define FMT_UNCHECKED_ITERATOR(It) \ + using _Unchecked_type = It // Mark iterator as checked. +#else +# define FMT_UNCHECKED_ITERATOR(It) using unchecked_type = It +#endif + +#ifndef FMT_BEGIN_NAMESPACE +# define FMT_BEGIN_NAMESPACE \ + namespace Dumux::Detail::fmt { \ + inline namespace v9 { +# define FMT_END_NAMESPACE \ + } \ + } +#endif + +#ifndef FMT_MODULE_EXPORT +# define FMT_MODULE_EXPORT +# define FMT_MODULE_EXPORT_BEGIN +# define FMT_MODULE_EXPORT_END +# define FMT_BEGIN_DETAIL_NAMESPACE namespace detail { +# define FMT_END_DETAIL_NAMESPACE } +#endif + +#if !defined(FMT_HEADER_ONLY) && defined(_WIN32) +# define FMT_CLASS_API FMT_MSC_WARNING(suppress : 4275) +# ifdef FMT_EXPORT +# define FMT_API __declspec(dllexport) +# elif defined(FMT_SHARED) +# define FMT_API __declspec(dllimport) +# endif +#else +# define FMT_CLASS_API +# if defined(FMT_EXPORT) || defined(FMT_SHARED) +# if defined(__GNUC__) || defined(__clang__) +# define FMT_API __attribute__((visibility("default"))) +# endif +# endif +#endif +#ifndef FMT_API +# define FMT_API +#endif + +// libc++ supports string_view in pre-c++17. +#if FMT_HAS_INCLUDE() && \ + (FMT_CPLUSPLUS >= 201703L || defined(_LIBCPP_VERSION)) +# include +# define FMT_USE_STRING_VIEW +#elif FMT_HAS_INCLUDE("experimental/string_view") && FMT_CPLUSPLUS >= 201402L +# include +# define FMT_USE_EXPERIMENTAL_STRING_VIEW +#endif + +#ifndef FMT_UNICODE +# define FMT_UNICODE !FMT_MSC_VERSION +#endif + +#ifndef FMT_CONSTEVAL +# if ((FMT_GCC_VERSION >= 1000 || FMT_CLANG_VERSION >= 1101) && \ + FMT_CPLUSPLUS >= 202002L && !defined(__apple_build_version__)) || \ + (defined(__cpp_consteval) && \ + (!FMT_MSC_VERSION || _MSC_FULL_VER >= 193030704)) +// consteval is broken in MSVC before VS2022 and Apple clang 13. +# define FMT_CONSTEVAL consteval +# define FMT_HAS_CONSTEVAL +# else +# define FMT_CONSTEVAL +# endif +#endif + +#ifndef FMT_USE_NONTYPE_TEMPLATE_ARGS +# if defined(__cpp_nontype_template_args) && \ + ((FMT_GCC_VERSION >= 903 && FMT_CPLUSPLUS >= 201709L) || \ + __cpp_nontype_template_args >= 201911L) && \ + !defined(__NVCOMPILER) +# define FMT_USE_NONTYPE_TEMPLATE_ARGS 1 +# else +# define FMT_USE_NONTYPE_TEMPLATE_ARGS 0 +# endif +#endif + +// Enable minimal optimizations for more compact code in debug mode. +FMT_GCC_PRAGMA("GCC push_options") +#if !defined(__OPTIMIZE__) && !defined(__NVCOMPILER) +FMT_GCC_PRAGMA("GCC optimize(\"Og\")") +#endif + +FMT_BEGIN_NAMESPACE +FMT_MODULE_EXPORT_BEGIN + +// Implementations of enable_if_t and other metafunctions for older systems. +template +using enable_if_t = typename std::enable_if::type; +template +using conditional_t = typename std::conditional::type; +template using bool_constant = std::integral_constant; +template +using remove_reference_t = typename std::remove_reference::type; +template +using remove_const_t = typename std::remove_const::type; +template +using remove_cvref_t = typename std::remove_cv>::type; +template struct type_identity { using type = T; }; +template using type_identity_t = typename type_identity::type; +template +using underlying_t = typename std::underlying_type::type; + +template struct disjunction : std::false_type {}; +template struct disjunction

: P {}; +template +struct disjunction + : conditional_t> {}; + +template struct conjunction : std::true_type {}; +template struct conjunction

: P {}; +template +struct conjunction + : conditional_t, P1> {}; + +struct monostate { + constexpr monostate() {} +}; + +// An enable_if helper to be used in template parameters which results in much +// shorter symbols: https://godbolt.org/z/sWw4vP. Extra parentheses are needed +// to workaround a bug in MSVC 2019 (see #1140 and #1186). +#ifdef FMT_DOC +# define FMT_ENABLE_IF(...) +#else +# define FMT_ENABLE_IF(...) enable_if_t<(__VA_ARGS__), int> = 0 +#endif + +FMT_BEGIN_DETAIL_NAMESPACE + +// Suppresses "unused variable" warnings with the method described in +// https://herbsutter.com/2009/10/18/mailbag-shutting-up-compiler-warnings/. +// (void)var does not work on many Intel compilers. +template FMT_CONSTEXPR void ignore_unused(const T&...) {} + +constexpr FMT_INLINE auto is_constant_evaluated( + bool default_value = false) noexcept -> bool { +#ifdef __cpp_lib_is_constant_evaluated + ignore_unused(default_value); + return std::is_constant_evaluated(); +#else + return default_value; +#endif +} + +// Suppresses "conditional expression is constant" warnings. +template constexpr FMT_INLINE auto const_check(T value) -> T { + return value; +} + +FMT_NORETURN FMT_API void assert_fail(const char* file, int line, + const char* message); + +#ifndef FMT_ASSERT +# ifdef NDEBUG +// FMT_ASSERT is not empty to avoid -Wempty-body. +# define FMT_ASSERT(condition, message) \ + Dumux::Detail::fmt::detail::ignore_unused((condition), (message)) +# else +# define FMT_ASSERT(condition, message) \ + ((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \ + ? (void)0 \ + : Dumux::Detail::fmt::detail::assert_fail(__FILE__, __LINE__, (message))) +# endif +#endif + +#if defined(FMT_USE_STRING_VIEW) +template using std_string_view = std::basic_string_view; +#elif defined(FMT_USE_EXPERIMENTAL_STRING_VIEW) +template +using std_string_view = std::experimental::basic_string_view; +#else +template struct std_string_view {}; +#endif + +#ifdef FMT_USE_INT128 +// Do nothing. +#elif defined(__SIZEOF_INT128__) && !defined(__NVCC__) && \ + !(FMT_CLANG_VERSION && FMT_MSC_VERSION) +# define FMT_USE_INT128 1 +using int128_opt = __int128_t; // An optional native 128-bit integer. +using uint128_opt = __uint128_t; +template inline auto convert_for_visit(T value) -> T { + return value; +} +#else +# define FMT_USE_INT128 0 +#endif +#if !FMT_USE_INT128 +enum class int128_opt {}; +enum class uint128_opt {}; +// Reduce template instantiations. +template auto convert_for_visit(T) -> monostate { return {}; } +#endif + +// Casts a nonnegative integer to unsigned. +template +FMT_CONSTEXPR auto to_unsigned(Int value) -> + typename std::make_unsigned::type { + FMT_ASSERT(std::is_unsigned::value || value >= 0, "negative value"); + return static_cast::type>(value); +} + +FMT_MSC_WARNING(suppress : 4566) constexpr unsigned char micro[] = "\u00B5"; + +constexpr auto is_utf8() -> bool { + // Avoid buggy sign extensions in MSVC's constant evaluation mode (#2297). + using uchar = unsigned char; + return FMT_UNICODE || (sizeof(micro) == 3 && uchar(micro[0]) == 0xC2 && + uchar(micro[1]) == 0xB5); +} +FMT_END_DETAIL_NAMESPACE + +/** + An implementation of ``std::basic_string_view`` for pre-C++17. It provides a + subset of the API. ``fmt::basic_string_view`` is used for format strings even + if ``std::string_view`` is available to prevent issues when a library is + compiled with a different ``-std`` option than the client code (which is not + recommended). + */ +template class basic_string_view { + private: + const Char* data_; + size_t size_; + + public: + using value_type = Char; + using iterator = const Char*; + + constexpr basic_string_view() noexcept : data_(nullptr), size_(0) {} + + /** Constructs a string reference object from a C string and a size. */ + constexpr basic_string_view(const Char* s, size_t count) noexcept + : data_(s), size_(count) {} + + /** + \rst + Constructs a string reference object from a C string computing + the size with ``std::char_traits::length``. + \endrst + */ + FMT_CONSTEXPR_CHAR_TRAITS + FMT_INLINE + basic_string_view(const Char* s) + : data_(s), + size_(detail::const_check(std::is_same::value && + !detail::is_constant_evaluated(true)) + ? std::strlen(reinterpret_cast(s)) + : std::char_traits::length(s)) {} + + /** Constructs a string reference from a ``std::basic_string`` object. */ + template + FMT_CONSTEXPR basic_string_view( + const std::basic_string& s) noexcept + : data_(s.data()), size_(s.size()) {} + + template >::value)> + FMT_CONSTEXPR basic_string_view(S s) noexcept + : data_(s.data()), size_(s.size()) {} + + /** Returns a pointer to the string data. */ + constexpr auto data() const noexcept -> const Char* { return data_; } + + /** Returns the string size. */ + constexpr auto size() const noexcept -> size_t { return size_; } + + constexpr auto begin() const noexcept -> iterator { return data_; } + constexpr auto end() const noexcept -> iterator { return data_ + size_; } + + constexpr auto operator[](size_t pos) const noexcept -> const Char& { + return data_[pos]; + } + + FMT_CONSTEXPR void remove_prefix(size_t n) noexcept { + data_ += n; + size_ -= n; + } + + // Lexicographically compare this string reference to other. + FMT_CONSTEXPR_CHAR_TRAITS auto compare(basic_string_view other) const -> int { + size_t str_size = size_ < other.size_ ? size_ : other.size_; + int result = std::char_traits::compare(data_, other.data_, str_size); + if (result == 0) + result = size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1); + return result; + } + + FMT_CONSTEXPR_CHAR_TRAITS friend auto operator==(basic_string_view lhs, + basic_string_view rhs) + -> bool { + return lhs.compare(rhs) == 0; + } + friend auto operator!=(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) != 0; + } + friend auto operator<(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) < 0; + } + friend auto operator<=(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) <= 0; + } + friend auto operator>(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) > 0; + } + friend auto operator>=(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) >= 0; + } +}; + +using string_view = basic_string_view; + +/** Specifies if ``T`` is a character type. Can be specialized by users. */ +template struct is_char : std::false_type {}; +template <> struct is_char : std::true_type {}; + +FMT_BEGIN_DETAIL_NAMESPACE + +// A base class for compile-time strings. +struct compile_string {}; + +template +struct is_compile_string : std::is_base_of {}; + +// Returns a string view of `s`. +template ::value)> +FMT_INLINE auto to_string_view(const Char* s) -> basic_string_view { + return s; +} +template +inline auto to_string_view(const std::basic_string& s) + -> basic_string_view { + return s; +} +template +constexpr auto to_string_view(basic_string_view s) + -> basic_string_view { + return s; +} +template >::value)> +inline auto to_string_view(std_string_view s) -> basic_string_view { + return s; +} +template ::value)> +constexpr auto to_string_view(const S& s) + -> basic_string_view { + return basic_string_view(s); +} +void to_string_view(...); + +// Specifies whether S is a string type convertible to fmt::basic_string_view. +// It should be a constexpr function but MSVC 2017 fails to compile it in +// enable_if and MSVC 2015 fails to compile it as an alias template. +// ADL invocation of to_string_view is DEPRECATED! +template +struct is_string : std::is_class()))> { +}; + +template struct char_t_impl {}; +template struct char_t_impl::value>> { + using result = decltype(to_string_view(std::declval())); + using type = typename result::value_type; +}; + +enum class type { + none_type, + // Integer types should go first, + int_type, + uint_type, + long_long_type, + ulong_long_type, + int128_type, + uint128_type, + bool_type, + char_type, + last_integer_type = char_type, + // followed by floating-point types. + float_type, + double_type, + long_double_type, + last_numeric_type = long_double_type, + cstring_type, + string_type, + pointer_type, + custom_type +}; + +// Maps core type T to the corresponding type enum constant. +template +struct type_constant : std::integral_constant {}; + +#define FMT_TYPE_CONSTANT(Type, constant) \ + template \ + struct type_constant \ + : std::integral_constant {} + +FMT_TYPE_CONSTANT(int, int_type); +FMT_TYPE_CONSTANT(unsigned, uint_type); +FMT_TYPE_CONSTANT(long long, long_long_type); +FMT_TYPE_CONSTANT(unsigned long long, ulong_long_type); +FMT_TYPE_CONSTANT(int128_opt, int128_type); +FMT_TYPE_CONSTANT(uint128_opt, uint128_type); +FMT_TYPE_CONSTANT(bool, bool_type); +FMT_TYPE_CONSTANT(Char, char_type); +FMT_TYPE_CONSTANT(float, float_type); +FMT_TYPE_CONSTANT(double, double_type); +FMT_TYPE_CONSTANT(long double, long_double_type); +FMT_TYPE_CONSTANT(const Char*, cstring_type); +FMT_TYPE_CONSTANT(basic_string_view, string_type); +FMT_TYPE_CONSTANT(const void*, pointer_type); + +constexpr bool is_integral_type(type t) { + return t > type::none_type && t <= type::last_integer_type; +} + +constexpr bool is_arithmetic_type(type t) { + return t > type::none_type && t <= type::last_numeric_type; +} + +FMT_NORETURN FMT_API void throw_format_error(const char* message); + +struct error_handler { + constexpr error_handler() = default; + constexpr error_handler(const error_handler&) = default; + + // This function is intentionally not constexpr to give a compile-time error. + FMT_NORETURN void on_error(const char* message) { + throw_format_error(message); + } +}; +FMT_END_DETAIL_NAMESPACE + +/** String's character type. */ +template using char_t = typename detail::char_t_impl::type; + +/** + \rst + Parsing context consisting of a format string range being parsed and an + argument counter for automatic indexing. + You can use the ``format_parse_context`` type alias for ``char`` instead. + \endrst + */ +template +class basic_format_parse_context : private ErrorHandler { + private: + basic_string_view format_str_; + int next_arg_id_; + + FMT_CONSTEXPR void do_check_arg_id(int id); + + public: + using char_type = Char; + using iterator = typename basic_string_view::iterator; + + explicit constexpr basic_format_parse_context( + basic_string_view format_str, ErrorHandler eh = {}, + int next_arg_id = 0) + : ErrorHandler(eh), format_str_(format_str), next_arg_id_(next_arg_id) {} + + /** + Returns an iterator to the beginning of the format string range being + parsed. + */ + constexpr auto begin() const noexcept -> iterator { + return format_str_.begin(); + } + + /** + Returns an iterator past the end of the format string range being parsed. + */ + constexpr auto end() const noexcept -> iterator { return format_str_.end(); } + + /** Advances the begin iterator to ``it``. */ + FMT_CONSTEXPR void advance_to(iterator it) { + format_str_.remove_prefix(detail::to_unsigned(it - begin())); + } + + /** + Reports an error if using the manual argument indexing; otherwise returns + the next argument index and switches to the automatic indexing. + */ + FMT_CONSTEXPR auto next_arg_id() -> int { + if (next_arg_id_ < 0) { + on_error("cannot switch from manual to automatic argument indexing"); + return 0; + } + int id = next_arg_id_++; + do_check_arg_id(id); + return id; + } + + /** + Reports an error if using the automatic argument indexing; otherwise + switches to the manual indexing. + */ + FMT_CONSTEXPR void check_arg_id(int id) { + if (next_arg_id_ > 0) { + on_error("cannot switch from automatic to manual argument indexing"); + return; + } + next_arg_id_ = -1; + do_check_arg_id(id); + } + FMT_CONSTEXPR void check_arg_id(basic_string_view) {} + FMT_CONSTEXPR void check_dynamic_spec(int arg_id); + + FMT_CONSTEXPR void on_error(const char* message) { + ErrorHandler::on_error(message); + } + + constexpr auto error_handler() const -> ErrorHandler { return *this; } +}; + +using format_parse_context = basic_format_parse_context; + +FMT_BEGIN_DETAIL_NAMESPACE +// A parse context with extra data used only in compile-time checks. +template +class compile_parse_context + : public basic_format_parse_context { + private: + int num_args_; + const type* types_; + using base = basic_format_parse_context; + + public: + explicit FMT_CONSTEXPR compile_parse_context( + basic_string_view format_str, int num_args, const type* types, + ErrorHandler eh = {}, int next_arg_id = 0) + : base(format_str, eh, next_arg_id), num_args_(num_args), types_(types) {} + + constexpr auto num_args() const -> int { return num_args_; } + constexpr auto arg_type(int id) const -> type { return types_[id]; } + + FMT_CONSTEXPR auto next_arg_id() -> int { + int id = base::next_arg_id(); + if (id >= num_args_) this->on_error("argument not found"); + return id; + } + + FMT_CONSTEXPR void check_arg_id(int id) { + base::check_arg_id(id); + if (id >= num_args_) this->on_error("argument not found"); + } + using base::check_arg_id; + + FMT_CONSTEXPR void check_dynamic_spec(int arg_id) { + if (arg_id < num_args_ && types_ && !is_integral_type(types_[arg_id])) + this->on_error("width/precision is not integer"); + } +}; +FMT_END_DETAIL_NAMESPACE + +template +FMT_CONSTEXPR void +basic_format_parse_context::do_check_arg_id(int id) { + // Argument id is only checked at compile-time during parsing because + // formatting has its own validation. + if (detail::is_constant_evaluated() && FMT_GCC_VERSION >= 1200) { + using context = detail::compile_parse_context; + if (id >= static_cast(this)->num_args()) + on_error("argument not found"); + } +} + +template +FMT_CONSTEXPR void +basic_format_parse_context::check_dynamic_spec(int arg_id) { + if (detail::is_constant_evaluated()) { + using context = detail::compile_parse_context; + static_cast(this)->check_dynamic_spec(arg_id); + } +} + +template class basic_format_arg; +template class basic_format_args; +template class dynamic_format_arg_store; + +// A formatter for objects of type T. +template +struct formatter { + // A deleted default constructor indicates a disabled formatter. + formatter() = delete; +}; + +// Specifies if T has an enabled formatter specialization. A type can be +// formattable even if it doesn't have a formatter e.g. via a conversion. +template +using has_formatter = + std::is_constructible>; + +// Checks whether T is a container with contiguous storage. +template struct is_contiguous : std::false_type {}; +template +struct is_contiguous> : std::true_type {}; + +class appender; + +FMT_BEGIN_DETAIL_NAMESPACE + +template +constexpr auto has_const_formatter_impl(T*) + -> decltype(typename Context::template formatter_type().format( + std::declval(), std::declval()), + true) { + return true; +} +template +constexpr auto has_const_formatter_impl(...) -> bool { + return false; +} +template +constexpr auto has_const_formatter() -> bool { + return has_const_formatter_impl(static_cast(nullptr)); +} + +// Extracts a reference to the container from back_insert_iterator. +template +inline auto get_container(std::back_insert_iterator it) + -> Container& { + using base = std::back_insert_iterator; + struct accessor : base { + accessor(base b) : base(b) {} + using base::container; + }; + return *accessor(it).container; +} + +template +FMT_CONSTEXPR auto copy_str(InputIt begin, InputIt end, OutputIt out) + -> OutputIt { + while (begin != end) *out++ = static_cast(*begin++); + return out; +} + +template , U>::value&& is_char::value)> +FMT_CONSTEXPR auto copy_str(T* begin, T* end, U* out) -> U* { + if (is_constant_evaluated()) return copy_str(begin, end, out); + auto size = to_unsigned(end - begin); + memcpy(out, begin, size * sizeof(U)); + return out + size; +} + +/** + \rst + A contiguous memory buffer with an optional growing ability. It is an internal + class and shouldn't be used directly, only via `~fmt::basic_memory_buffer`. + \endrst + */ +template class buffer { + private: + T* ptr_; + size_t size_; + size_t capacity_; + + protected: + // Don't initialize ptr_ since it is not accessed to save a few cycles. + FMT_MSC_WARNING(suppress : 26495) + buffer(size_t sz) noexcept : size_(sz), capacity_(sz) {} + + FMT_CONSTEXPR20 buffer(T* p = nullptr, size_t sz = 0, size_t cap = 0) noexcept + : ptr_(p), size_(sz), capacity_(cap) {} + + FMT_CONSTEXPR20 ~buffer() = default; + buffer(buffer&&) = default; + + /** Sets the buffer data and capacity. */ + FMT_CONSTEXPR void set(T* buf_data, size_t buf_capacity) noexcept { + ptr_ = buf_data; + capacity_ = buf_capacity; + } + + /** Increases the buffer capacity to hold at least *capacity* elements. */ + virtual FMT_CONSTEXPR20 void grow(size_t capacity) = 0; + + public: + using value_type = T; + using const_reference = const T&; + + buffer(const buffer&) = delete; + void operator=(const buffer&) = delete; + + auto begin() noexcept -> T* { return ptr_; } + auto end() noexcept -> T* { return ptr_ + size_; } + + auto begin() const noexcept -> const T* { return ptr_; } + auto end() const noexcept -> const T* { return ptr_ + size_; } + + /** Returns the size of this buffer. */ + constexpr auto size() const noexcept -> size_t { return size_; } + + /** Returns the capacity of this buffer. */ + constexpr auto capacity() const noexcept -> size_t { return capacity_; } + + /** Returns a pointer to the buffer data. */ + FMT_CONSTEXPR auto data() noexcept -> T* { return ptr_; } + + /** Returns a pointer to the buffer data. */ + FMT_CONSTEXPR auto data() const noexcept -> const T* { return ptr_; } + + /** Clears this buffer. */ + void clear() { size_ = 0; } + + // Tries resizing the buffer to contain *count* elements. If T is a POD type + // the new elements may not be initialized. + FMT_CONSTEXPR20 void try_resize(size_t count) { + try_reserve(count); + size_ = count <= capacity_ ? count : capacity_; + } + + // Tries increasing the buffer capacity to *new_capacity*. It can increase the + // capacity by a smaller amount than requested but guarantees there is space + // for at least one additional element either by increasing the capacity or by + // flushing the buffer if it is full. + FMT_CONSTEXPR20 void try_reserve(size_t new_capacity) { + if (new_capacity > capacity_) grow(new_capacity); + } + + FMT_CONSTEXPR20 void push_back(const T& value) { + try_reserve(size_ + 1); + ptr_[size_++] = value; + } + + /** Appends data to the end of the buffer. */ + template void append(const U* begin, const U* end); + + template FMT_CONSTEXPR auto operator[](Idx index) -> T& { + return ptr_[index]; + } + template + FMT_CONSTEXPR auto operator[](Idx index) const -> const T& { + return ptr_[index]; + } +}; + +struct buffer_traits { + explicit buffer_traits(size_t) {} + auto count() const -> size_t { return 0; } + auto limit(size_t size) -> size_t { return size; } +}; + +class fixed_buffer_traits { + private: + size_t count_ = 0; + size_t limit_; + + public: + explicit fixed_buffer_traits(size_t limit) : limit_(limit) {} + auto count() const -> size_t { return count_; } + auto limit(size_t size) -> size_t { + size_t n = limit_ > count_ ? limit_ - count_ : 0; + count_ += size; + return size < n ? size : n; + } +}; + +// A buffer that writes to an output iterator when flushed. +template +class iterator_buffer final : public Traits, public buffer { + private: + OutputIt out_; + enum { buffer_size = 256 }; + T data_[buffer_size]; + + protected: + FMT_CONSTEXPR20 void grow(size_t) override { + if (this->size() == buffer_size) flush(); + } + + void flush() { + auto size = this->size(); + this->clear(); + out_ = copy_str(data_, data_ + this->limit(size), out_); + } + + public: + explicit iterator_buffer(OutputIt out, size_t n = buffer_size) + : Traits(n), buffer(data_, 0, buffer_size), out_(out) {} + iterator_buffer(iterator_buffer&& other) + : Traits(other), buffer(data_, 0, buffer_size), out_(other.out_) {} + ~iterator_buffer() { flush(); } + + auto out() -> OutputIt { + flush(); + return out_; + } + auto count() const -> size_t { return Traits::count() + this->size(); } +}; + +template +class iterator_buffer final + : public fixed_buffer_traits, + public buffer { + private: + T* out_; + enum { buffer_size = 256 }; + T data_[buffer_size]; + + protected: + FMT_CONSTEXPR20 void grow(size_t) override { + if (this->size() == this->capacity()) flush(); + } + + void flush() { + size_t n = this->limit(this->size()); + if (this->data() == out_) { + out_ += n; + this->set(data_, buffer_size); + } + this->clear(); + } + + public: + explicit iterator_buffer(T* out, size_t n = buffer_size) + : fixed_buffer_traits(n), buffer(out, 0, n), out_(out) {} + iterator_buffer(iterator_buffer&& other) + : fixed_buffer_traits(other), + buffer(std::move(other)), + out_(other.out_) { + if (this->data() != out_) { + this->set(data_, buffer_size); + this->clear(); + } + } + ~iterator_buffer() { flush(); } + + auto out() -> T* { + flush(); + return out_; + } + auto count() const -> size_t { + return fixed_buffer_traits::count() + this->size(); + } +}; + +template class iterator_buffer final : public buffer { + protected: + FMT_CONSTEXPR20 void grow(size_t) override {} + + public: + explicit iterator_buffer(T* out, size_t = 0) : buffer(out, 0, ~size_t()) {} + + auto out() -> T* { return &*this->end(); } +}; + +// A buffer that writes to a container with the contiguous storage. +template +class iterator_buffer, + enable_if_t::value, + typename Container::value_type>> + final : public buffer { + private: + Container& container_; + + protected: + FMT_CONSTEXPR20 void grow(size_t capacity) override { + container_.resize(capacity); + this->set(&container_[0], capacity); + } + + public: + explicit iterator_buffer(Container& c) + : buffer(c.size()), container_(c) {} + explicit iterator_buffer(std::back_insert_iterator out, size_t = 0) + : iterator_buffer(get_container(out)) {} + + auto out() -> std::back_insert_iterator { + return std::back_inserter(container_); + } +}; + +// A buffer that counts the number of code units written discarding the output. +template class counting_buffer final : public buffer { + private: + enum { buffer_size = 256 }; + T data_[buffer_size]; + size_t count_ = 0; + + protected: + FMT_CONSTEXPR20 void grow(size_t) override { + if (this->size() != buffer_size) return; + count_ += this->size(); + this->clear(); + } + + public: + counting_buffer() : buffer(data_, 0, buffer_size) {} + + auto count() -> size_t { return count_ + this->size(); } +}; + +template +using buffer_appender = conditional_t::value, appender, + std::back_insert_iterator>>; + +// Maps an output iterator to a buffer. +template +auto get_buffer(OutputIt out) -> iterator_buffer { + return iterator_buffer(out); +} + +template +auto get_iterator(Buffer& buf) -> decltype(buf.out()) { + return buf.out(); +} +template auto get_iterator(buffer& buf) -> buffer_appender { + return buffer_appender(buf); +} + +template +struct fallback_formatter { + fallback_formatter() = delete; +}; + +// Specifies if T has an enabled fallback_formatter specialization. +template +using has_fallback_formatter = +#ifdef FMT_DEPRECATED_OSTREAM + std::is_constructible>; +#else + std::false_type; +#endif + +struct view {}; + +template struct named_arg : view { + const Char* name; + const T& value; + named_arg(const Char* n, const T& v) : name(n), value(v) {} +}; + +template struct named_arg_info { + const Char* name; + int id; +}; + +template +struct arg_data { + // args_[0].named_args points to named_args_ to avoid bloating format_args. + // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning. + T args_[1 + (NUM_ARGS != 0 ? NUM_ARGS : +1)]; + named_arg_info named_args_[NUM_NAMED_ARGS]; + + template + arg_data(const U&... init) : args_{T(named_args_, NUM_NAMED_ARGS), init...} {} + arg_data(const arg_data& other) = delete; + auto args() const -> const T* { return args_ + 1; } + auto named_args() -> named_arg_info* { return named_args_; } +}; + +template +struct arg_data { + // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning. + T args_[NUM_ARGS != 0 ? NUM_ARGS : +1]; + + template + FMT_CONSTEXPR FMT_INLINE arg_data(const U&... init) : args_{init...} {} + FMT_CONSTEXPR FMT_INLINE auto args() const -> const T* { return args_; } + FMT_CONSTEXPR FMT_INLINE auto named_args() -> std::nullptr_t { + return nullptr; + } +}; + +template +inline void init_named_args(named_arg_info*, int, int) {} + +template struct is_named_arg : std::false_type {}; +template struct is_statically_named_arg : std::false_type {}; + +template +struct is_named_arg> : std::true_type {}; + +template ::value)> +void init_named_args(named_arg_info* named_args, int arg_count, + int named_arg_count, const T&, const Tail&... args) { + init_named_args(named_args, arg_count + 1, named_arg_count, args...); +} + +template ::value)> +void init_named_args(named_arg_info* named_args, int arg_count, + int named_arg_count, const T& arg, const Tail&... args) { + named_args[named_arg_count++] = {arg.name, arg_count}; + init_named_args(named_args, arg_count + 1, named_arg_count, args...); +} + +template +FMT_CONSTEXPR FMT_INLINE void init_named_args(std::nullptr_t, int, int, + const Args&...) {} + +template constexpr auto count() -> size_t { return B ? 1 : 0; } +template constexpr auto count() -> size_t { + return (B1 ? 1 : 0) + count(); +} + +template constexpr auto count_named_args() -> size_t { + return count::value...>(); +} + +template +constexpr auto count_statically_named_args() -> size_t { + return count::value...>(); +} + +struct unformattable {}; +struct unformattable_char : unformattable {}; +struct unformattable_const : unformattable {}; +struct unformattable_pointer : unformattable {}; + +template struct string_value { + const Char* data; + size_t size; +}; + +template struct named_arg_value { + const named_arg_info* data; + size_t size; +}; + +template struct custom_value { + using parse_context = typename Context::parse_context_type; + void* value; + void (*format)(void* arg, parse_context& parse_ctx, Context& ctx); +}; + +// A formatting argument value. +template class value { + public: + using char_type = typename Context::char_type; + + union { + monostate no_value; + int int_value; + unsigned uint_value; + long long long_long_value; + unsigned long long ulong_long_value; + int128_opt int128_value; + uint128_opt uint128_value; + bool bool_value; + char_type char_value; + float float_value; + double double_value; + long double long_double_value; + const void* pointer; + string_value string; + custom_value custom; + named_arg_value named_args; + }; + + constexpr FMT_INLINE value() : no_value() {} + constexpr FMT_INLINE value(int val) : int_value(val) {} + constexpr FMT_INLINE value(unsigned val) : uint_value(val) {} + constexpr FMT_INLINE value(long long val) : long_long_value(val) {} + constexpr FMT_INLINE value(unsigned long long val) : ulong_long_value(val) {} + FMT_INLINE value(int128_opt val) : int128_value(val) {} + FMT_INLINE value(uint128_opt val) : uint128_value(val) {} + constexpr FMT_INLINE value(float val) : float_value(val) {} + constexpr FMT_INLINE value(double val) : double_value(val) {} + FMT_INLINE value(long double val) : long_double_value(val) {} + constexpr FMT_INLINE value(bool val) : bool_value(val) {} + constexpr FMT_INLINE value(char_type val) : char_value(val) {} + FMT_CONSTEXPR FMT_INLINE value(const char_type* val) { + string.data = val; + if (is_constant_evaluated()) string.size = {}; + } + FMT_CONSTEXPR FMT_INLINE value(basic_string_view val) { + string.data = val.data(); + string.size = val.size(); + } + FMT_INLINE value(const void* val) : pointer(val) {} + FMT_INLINE value(const named_arg_info* args, size_t size) + : named_args{args, size} {} + + template FMT_CONSTEXPR FMT_INLINE value(T& val) { + using value_type = remove_cvref_t; + custom.value = const_cast(&val); + // Get the formatter type through the context to allow different contexts + // have different extension points, e.g. `formatter` for `format` and + // `printf_formatter` for `printf`. + custom.format = format_custom_arg< + value_type, + conditional_t::value, + typename Context::template formatter_type, + fallback_formatter>>; + } + value(unformattable); + value(unformattable_char); + value(unformattable_const); + value(unformattable_pointer); + + private: + // Formats an argument of a custom type, such as a user-defined class. + template + static void format_custom_arg(void* arg, + typename Context::parse_context_type& parse_ctx, + Context& ctx) { + auto f = Formatter(); + parse_ctx.advance_to(f.parse(parse_ctx)); + using qualified_type = + conditional_t(), const T, T>; + ctx.advance_to(f.format(*static_cast(arg), ctx)); + } +}; + +template +FMT_CONSTEXPR auto make_arg(T&& value) -> basic_format_arg; + +// To minimize the number of types we need to deal with, long is translated +// either to int or to long long depending on its size. +enum { long_short = sizeof(long) == sizeof(int) }; +using long_type = conditional_t; +using ulong_type = conditional_t; + +#ifdef __cpp_lib_byte +inline auto format_as(std::byte b) -> unsigned char { + return static_cast(b); +} +#endif + +template struct has_format_as { + template ::value&& std::is_integral::value)> + static auto check(U*) -> std::true_type; + static auto check(...) -> std::false_type; + + enum { value = decltype(check(static_cast(nullptr)))::value }; +}; + +// Maps formatting arguments to core types. +// arg_mapper reports errors by returning unformattable instead of using +// static_assert because it's used in the is_formattable trait. +template struct arg_mapper { + using char_type = typename Context::char_type; + + FMT_CONSTEXPR FMT_INLINE auto map(signed char val) -> int { return val; } + FMT_CONSTEXPR FMT_INLINE auto map(unsigned char val) -> unsigned { + return val; + } + FMT_CONSTEXPR FMT_INLINE auto map(short val) -> int { return val; } + FMT_CONSTEXPR FMT_INLINE auto map(unsigned short val) -> unsigned { + return val; + } + FMT_CONSTEXPR FMT_INLINE auto map(int val) -> int { return val; } + FMT_CONSTEXPR FMT_INLINE auto map(unsigned val) -> unsigned { return val; } + FMT_CONSTEXPR FMT_INLINE auto map(long val) -> long_type { return val; } + FMT_CONSTEXPR FMT_INLINE auto map(unsigned long val) -> ulong_type { + return val; + } + FMT_CONSTEXPR FMT_INLINE auto map(long long val) -> long long { return val; } + FMT_CONSTEXPR FMT_INLINE auto map(unsigned long long val) + -> unsigned long long { + return val; + } + FMT_CONSTEXPR FMT_INLINE auto map(int128_opt val) -> int128_opt { + return val; + } + FMT_CONSTEXPR FMT_INLINE auto map(uint128_opt val) -> uint128_opt { + return val; + } + FMT_CONSTEXPR FMT_INLINE auto map(bool val) -> bool { return val; } + + template ::value || + std::is_same::value)> + FMT_CONSTEXPR FMT_INLINE auto map(T val) -> char_type { + return val; + } + template ::value || +#ifdef __cpp_char8_t + std::is_same::value || +#endif + std::is_same::value || + std::is_same::value) && + !std::is_same::value, + int> = 0> + FMT_CONSTEXPR FMT_INLINE auto map(T) -> unformattable_char { + return {}; + } + + FMT_CONSTEXPR FMT_INLINE auto map(float val) -> float { return val; } + FMT_CONSTEXPR FMT_INLINE auto map(double val) -> double { return val; } + FMT_CONSTEXPR FMT_INLINE auto map(long double val) -> long double { + return val; + } + + FMT_CONSTEXPR FMT_INLINE auto map(char_type* val) -> const char_type* { + return val; + } + FMT_CONSTEXPR FMT_INLINE auto map(const char_type* val) -> const char_type* { + return val; + } + template ::value && !std::is_pointer::value && + std::is_same>::value)> + FMT_CONSTEXPR FMT_INLINE auto map(const T& val) + -> basic_string_view { + return to_string_view(val); + } + template ::value && !std::is_pointer::value && + !std::is_same>::value)> + FMT_CONSTEXPR FMT_INLINE auto map(const T&) -> unformattable_char { + return {}; + } + template >::value && + !is_string::value && !has_formatter::value && + !has_fallback_formatter::value)> + FMT_CONSTEXPR FMT_INLINE auto map(const T& val) + -> basic_string_view { + return basic_string_view(val); + } + template >::value && + !std::is_convertible>::value && + !is_string::value && !has_formatter::value && + !has_fallback_formatter::value)> + FMT_CONSTEXPR FMT_INLINE auto map(const T& val) + -> basic_string_view { + return std_string_view(val); + } + + FMT_CONSTEXPR FMT_INLINE auto map(void* val) -> const void* { return val; } + FMT_CONSTEXPR FMT_INLINE auto map(const void* val) -> const void* { + return val; + } + FMT_CONSTEXPR FMT_INLINE auto map(std::nullptr_t val) -> const void* { + return val; + } + + // We use SFINAE instead of a const T* parameter to avoid conflicting with + // the C array overload. + template < + typename T, + FMT_ENABLE_IF( + std::is_pointer::value || std::is_member_pointer::value || + std::is_function::type>::value || + (std::is_convertible::value && + !std::is_convertible::value && + !has_formatter::value))> + FMT_CONSTEXPR auto map(const T&) -> unformattable_pointer { + return {}; + } + + template ::value)> + FMT_CONSTEXPR FMT_INLINE auto map(const T (&values)[N]) -> const T (&)[N] { + return values; + } + + template ::value&& std::is_convertible::value && + !has_format_as::value && !has_formatter::value && + !has_fallback_formatter::value)> + FMT_DEPRECATED FMT_CONSTEXPR FMT_INLINE auto map(const T& val) + -> decltype(std::declval().map( + static_cast>(val))) { + return map(static_cast>(val)); + } + + template ::value && + !has_formatter::value)> + FMT_CONSTEXPR FMT_INLINE auto map(const T& val) + -> decltype(std::declval().map(format_as(T()))) { + return map(format_as(val)); + } + + template > + struct formattable + : bool_constant() || + !std::is_const>::value || + has_fallback_formatter::value> {}; + +#if (FMT_MSC_VERSION != 0 && FMT_MSC_VERSION < 1910) || \ + FMT_ICC_VERSION != 0 || defined(__NVCC__) + // Workaround a bug in MSVC and Intel (Issue 2746). + template FMT_CONSTEXPR FMT_INLINE auto do_map(T&& val) -> T& { + return val; + } +#else + template ::value)> + FMT_CONSTEXPR FMT_INLINE auto do_map(T&& val) -> T& { + return val; + } + template ::value)> + FMT_CONSTEXPR FMT_INLINE auto do_map(T&&) -> unformattable_const { + return {}; + } +#endif + + template , + FMT_ENABLE_IF(!is_string::value && !is_char::value && + !std::is_array::value && + !std::is_pointer::value && + !has_format_as::value && + (has_formatter::value || + has_fallback_formatter::value))> + FMT_CONSTEXPR FMT_INLINE auto map(T&& val) + -> decltype(this->do_map(std::forward(val))) { + return do_map(std::forward(val)); + } + + template ::value)> + FMT_CONSTEXPR FMT_INLINE auto map(const T& named_arg) + -> decltype(std::declval().map(named_arg.value)) { + return map(named_arg.value); + } + + auto map(...) -> unformattable { return {}; } +}; + +// A type constant after applying arg_mapper. +template +using mapped_type_constant = + type_constant().map(std::declval())), + typename Context::char_type>; + +enum { packed_arg_bits = 4 }; +// Maximum number of arguments with packed types. +enum { max_packed_args = 62 / packed_arg_bits }; +enum : unsigned long long { is_unpacked_bit = 1ULL << 63 }; +enum : unsigned long long { has_named_args_bit = 1ULL << 62 }; + +FMT_END_DETAIL_NAMESPACE + +// An output iterator that appends to a buffer. +// It is used to reduce symbol sizes for the common case. +class appender : public std::back_insert_iterator> { + using base = std::back_insert_iterator>; + + template + friend auto get_buffer(appender out) -> detail::buffer& { + return detail::get_container(out); + } + + public: + using std::back_insert_iterator>::back_insert_iterator; + appender(base it) noexcept : base(it) {} + FMT_UNCHECKED_ITERATOR(appender); + + auto operator++() noexcept -> appender& { return *this; } + auto operator++(int) noexcept -> appender { return *this; } +}; + +// A formatting argument. It is a trivially copyable/constructible type to +// allow storage in basic_memory_buffer. +template class basic_format_arg { + private: + detail::value value_; + detail::type type_; + + template + friend FMT_CONSTEXPR auto detail::make_arg(T&& value) + -> basic_format_arg; + + template + friend FMT_CONSTEXPR auto visit_format_arg(Visitor&& vis, + const basic_format_arg& arg) + -> decltype(vis(0)); + + friend class basic_format_args; + friend class dynamic_format_arg_store; + + using char_type = typename Context::char_type; + + template + friend struct detail::arg_data; + + basic_format_arg(const detail::named_arg_info* args, size_t size) + : value_(args, size) {} + + public: + class handle { + public: + explicit handle(detail::custom_value custom) : custom_(custom) {} + + void format(typename Context::parse_context_type& parse_ctx, + Context& ctx) const { + custom_.format(custom_.value, parse_ctx, ctx); + } + + private: + detail::custom_value custom_; + }; + + constexpr basic_format_arg() : type_(detail::type::none_type) {} + + constexpr explicit operator bool() const noexcept { + return type_ != detail::type::none_type; + } + + auto type() const -> detail::type { return type_; } + + auto is_integral() const -> bool { return detail::is_integral_type(type_); } + auto is_arithmetic() const -> bool { + return detail::is_arithmetic_type(type_); + } +}; + +/** + \rst + Visits an argument dispatching to the appropriate visit method based on + the argument type. For example, if the argument type is ``double`` then + ``vis(value)`` will be called with the value of type ``double``. + \endrst + */ +template +FMT_CONSTEXPR FMT_INLINE auto visit_format_arg( + Visitor&& vis, const basic_format_arg& arg) -> decltype(vis(0)) { + switch (arg.type_) { + case detail::type::none_type: + break; + case detail::type::int_type: + return vis(arg.value_.int_value); + case detail::type::uint_type: + return vis(arg.value_.uint_value); + case detail::type::long_long_type: + return vis(arg.value_.long_long_value); + case detail::type::ulong_long_type: + return vis(arg.value_.ulong_long_value); + case detail::type::int128_type: + return vis(detail::convert_for_visit(arg.value_.int128_value)); + case detail::type::uint128_type: + return vis(detail::convert_for_visit(arg.value_.uint128_value)); + case detail::type::bool_type: + return vis(arg.value_.bool_value); + case detail::type::char_type: + return vis(arg.value_.char_value); + case detail::type::float_type: + return vis(arg.value_.float_value); + case detail::type::double_type: + return vis(arg.value_.double_value); + case detail::type::long_double_type: + return vis(arg.value_.long_double_value); + case detail::type::cstring_type: + return vis(arg.value_.string.data); + case detail::type::string_type: + using sv = basic_string_view; + return vis(sv(arg.value_.string.data, arg.value_.string.size)); + case detail::type::pointer_type: + return vis(arg.value_.pointer); + case detail::type::custom_type: + return vis(typename basic_format_arg::handle(arg.value_.custom)); + } + return vis(monostate()); +} + +FMT_BEGIN_DETAIL_NAMESPACE + +template +auto copy_str(InputIt begin, InputIt end, appender out) -> appender { + get_container(out).append(begin, end); + return out; +} + +template +FMT_CONSTEXPR auto copy_str(R&& rng, OutputIt out) -> OutputIt { + return detail::copy_str(rng.begin(), rng.end(), out); +} + +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 500 +// A workaround for gcc 4.8 to make void_t work in a SFINAE context. +template struct void_t_impl { using type = void; }; +template +using void_t = typename detail::void_t_impl::type; +#else +template using void_t = void; +#endif + +template +struct is_output_iterator : std::false_type {}; + +template +struct is_output_iterator< + It, T, + void_t::iterator_category, + decltype(*std::declval() = std::declval())>> + : std::true_type {}; + +template +struct is_back_insert_iterator : std::false_type {}; +template +struct is_back_insert_iterator> + : std::true_type {}; + +template +struct is_contiguous_back_insert_iterator : std::false_type {}; +template +struct is_contiguous_back_insert_iterator> + : is_contiguous {}; +template <> +struct is_contiguous_back_insert_iterator : std::true_type {}; + +// A type-erased reference to an std::locale to avoid a heavy include. +class locale_ref { + private: + const void* locale_; // A type-erased pointer to std::locale. + + public: + constexpr locale_ref() : locale_(nullptr) {} + template explicit locale_ref(const Locale& loc); + + explicit operator bool() const noexcept { return locale_ != nullptr; } + + template auto get() const -> Locale; +}; + +template constexpr auto encode_types() -> unsigned long long { + return 0; +} + +template +constexpr auto encode_types() -> unsigned long long { + return static_cast(mapped_type_constant::value) | + (encode_types() << packed_arg_bits); +} + +template +FMT_CONSTEXPR FMT_INLINE auto make_value(T&& val) -> value { + const auto& arg = arg_mapper().map(FMT_FORWARD(val)); + + constexpr bool formattable_char = + !std::is_same::value; + static_assert(formattable_char, "Mixing character types is disallowed."); + + constexpr bool formattable_const = + !std::is_same::value; + static_assert(formattable_const, "Cannot format a const argument."); + + // Formatting of arbitrary pointers is disallowed. If you want to output + // a pointer cast it to "void *" or "const void *". In particular, this + // forbids formatting of "[const] volatile char *" which is printed as bool + // by iostreams. + constexpr bool formattable_pointer = + !std::is_same::value; + static_assert(formattable_pointer, + "Formatting of non-void pointers is disallowed."); + + constexpr bool formattable = + !std::is_same::value; + static_assert( + formattable, + "Cannot format an argument. To make type T formattable provide a " + "formatter specialization: https://fmt.dev/latest/api.html#udt"); + return {arg}; +} + +template +FMT_CONSTEXPR auto make_arg(T&& value) -> basic_format_arg { + basic_format_arg arg; + arg.type_ = mapped_type_constant::value; + arg.value_ = make_value(value); + return arg; +} + +// The type template parameter is there to avoid an ODR violation when using +// a fallback formatter in one translation unit and an implicit conversion in +// another (not recommended). +template +FMT_CONSTEXPR FMT_INLINE auto make_arg(T&& val) -> value { + return make_value(val); +} + +template +FMT_CONSTEXPR inline auto make_arg(T&& value) -> basic_format_arg { + return make_arg(value); +} +FMT_END_DETAIL_NAMESPACE + +// Formatting context. +template class basic_format_context { + public: + /** The character type for the output. */ + using char_type = Char; + + private: + OutputIt out_; + basic_format_args args_; + detail::locale_ref loc_; + + public: + using iterator = OutputIt; + using format_arg = basic_format_arg; + using parse_context_type = basic_format_parse_context; + template using formatter_type = formatter; + + basic_format_context(basic_format_context&&) = default; + basic_format_context(const basic_format_context&) = delete; + void operator=(const basic_format_context&) = delete; + /** + Constructs a ``basic_format_context`` object. References to the arguments are + stored in the object so make sure they have appropriate lifetimes. + */ + constexpr basic_format_context( + OutputIt out, basic_format_args ctx_args, + detail::locale_ref loc = detail::locale_ref()) + : out_(out), args_(ctx_args), loc_(loc) {} + + constexpr auto arg(int id) const -> format_arg { return args_.get(id); } + FMT_CONSTEXPR auto arg(basic_string_view name) -> format_arg { + return args_.get(name); + } + FMT_CONSTEXPR auto arg_id(basic_string_view name) -> int { + return args_.get_id(name); + } + auto args() const -> const basic_format_args& { + return args_; + } + + FMT_CONSTEXPR auto error_handler() -> detail::error_handler { return {}; } + void on_error(const char* message) { error_handler().on_error(message); } + + // Returns an iterator to the beginning of the output range. + FMT_CONSTEXPR auto out() -> iterator { return out_; } + + // Advances the begin iterator to ``it``. + void advance_to(iterator it) { + if (!detail::is_back_insert_iterator()) out_ = it; + } + + FMT_CONSTEXPR auto locale() -> detail::locale_ref { return loc_; } +}; + +template +using buffer_context = + basic_format_context, Char>; +using format_context = buffer_context; + +// Workaround an alias issue: https://stackoverflow.com/q/62767544/471164. +#define FMT_BUFFER_CONTEXT(Char) \ + basic_format_context, Char> + +template +using is_formattable = bool_constant< + !std::is_base_of>().map( + std::declval()))>::value && + !detail::has_fallback_formatter::value>; + +/** + \rst + An array of references to arguments. It can be implicitly converted into + `~fmt::basic_format_args` for passing into type-erased formatting functions + such as `~fmt::vformat`. + \endrst + */ +template +class format_arg_store +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 + // Workaround a GCC template argument substitution bug. + : public basic_format_args +#endif +{ + private: + static const size_t num_args = sizeof...(Args); + static const size_t num_named_args = detail::count_named_args(); + static const bool is_packed = num_args <= detail::max_packed_args; + + using value_type = conditional_t, + basic_format_arg>; + + detail::arg_data + data_; + + friend class basic_format_args; + + static constexpr unsigned long long desc = + (is_packed ? detail::encode_types() + : detail::is_unpacked_bit | num_args) | + (num_named_args != 0 + ? static_cast(detail::has_named_args_bit) + : 0); + + public: + template + FMT_CONSTEXPR FMT_INLINE format_arg_store(T&&... args) + : +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 + basic_format_args(*this), +#endif + data_{detail::make_arg< + is_packed, Context, + detail::mapped_type_constant, Context>::value>( + FMT_FORWARD(args))...} { + detail::init_named_args(data_.named_args(), 0, 0, args...); + } +}; + +/** + \rst + Constructs a `~fmt::format_arg_store` object that contains references to + arguments and can be implicitly converted to `~fmt::format_args`. `Context` + can be omitted in which case it defaults to `~fmt::context`. + See `~fmt::arg` for lifetime considerations. + \endrst + */ +template +constexpr auto make_format_args(Args&&... args) + -> format_arg_store...> { + return {FMT_FORWARD(args)...}; +} + +/** + \rst + Returns a named argument to be used in a formatting function. + It should only be used in a call to a formatting function or + `dynamic_format_arg_store::push_back`. + + **Example**:: + + fmt::print("Elapsed time: {s:.2f} seconds", fmt::arg("s", 1.23)); + \endrst + */ +template +inline auto arg(const Char* name, const T& arg) -> detail::named_arg { + static_assert(!detail::is_named_arg(), "nested named arguments"); + return {name, arg}; +} + +/** + \rst + A view of a collection of formatting arguments. To avoid lifetime issues it + should only be used as a parameter type in type-erased functions such as + ``vformat``:: + + void vlog(string_view format_str, format_args args); // OK + format_args args = make_format_args(42); // Error: dangling reference + \endrst + */ +template class basic_format_args { + public: + using size_type = int; + using format_arg = basic_format_arg; + + private: + // A descriptor that contains information about formatting arguments. + // If the number of arguments is less or equal to max_packed_args then + // argument types are passed in the descriptor. This reduces binary code size + // per formatting function call. + unsigned long long desc_; + union { + // If is_packed() returns true then argument values are stored in values_; + // otherwise they are stored in args_. This is done to improve cache + // locality and reduce compiled code size since storing larger objects + // may require more code (at least on x86-64) even if the same amount of + // data is actually copied to stack. It saves ~10% on the bloat test. + const detail::value* values_; + const format_arg* args_; + }; + + constexpr auto is_packed() const -> bool { + return (desc_ & detail::is_unpacked_bit) == 0; + } + auto has_named_args() const -> bool { + return (desc_ & detail::has_named_args_bit) != 0; + } + + FMT_CONSTEXPR auto type(int index) const -> detail::type { + int shift = index * detail::packed_arg_bits; + unsigned int mask = (1 << detail::packed_arg_bits) - 1; + return static_cast((desc_ >> shift) & mask); + } + + constexpr FMT_INLINE basic_format_args(unsigned long long desc, + const detail::value* values) + : desc_(desc), values_(values) {} + constexpr basic_format_args(unsigned long long desc, const format_arg* args) + : desc_(desc), args_(args) {} + + public: + constexpr basic_format_args() : desc_(0), args_(nullptr) {} + + /** + \rst + Constructs a `basic_format_args` object from `~fmt::format_arg_store`. + \endrst + */ + template + constexpr FMT_INLINE basic_format_args( + const format_arg_store& store) + : basic_format_args(format_arg_store::desc, + store.data_.args()) {} + + /** + \rst + Constructs a `basic_format_args` object from + `~fmt::dynamic_format_arg_store`. + \endrst + */ + constexpr FMT_INLINE basic_format_args( + const dynamic_format_arg_store& store) + : basic_format_args(store.get_types(), store.data()) {} + + /** + \rst + Constructs a `basic_format_args` object from a dynamic set of arguments. + \endrst + */ + constexpr basic_format_args(const format_arg* args, int count) + : basic_format_args(detail::is_unpacked_bit | detail::to_unsigned(count), + args) {} + + /** Returns the argument with the specified id. */ + FMT_CONSTEXPR auto get(int id) const -> format_arg { + format_arg arg; + if (!is_packed()) { + if (id < max_size()) arg = args_[id]; + return arg; + } + if (id >= detail::max_packed_args) return arg; + arg.type_ = type(id); + if (arg.type_ == detail::type::none_type) return arg; + arg.value_ = values_[id]; + return arg; + } + + template + auto get(basic_string_view name) const -> format_arg { + int id = get_id(name); + return id >= 0 ? get(id) : format_arg(); + } + + template + auto get_id(basic_string_view name) const -> int { + if (!has_named_args()) return -1; + const auto& named_args = + (is_packed() ? values_[-1] : args_[-1].value_).named_args; + for (size_t i = 0; i < named_args.size; ++i) { + if (named_args.data[i].name == name) return named_args.data[i].id; + } + return -1; + } + + auto max_size() const -> int { + unsigned long long max_packed = detail::max_packed_args; + return static_cast(is_packed() ? max_packed + : desc_ & ~detail::is_unpacked_bit); + } +}; + +/** An alias to ``basic_format_args``. */ +// A separate type would result in shorter symbols but break ABI compatibility +// between clang and gcc on ARM (#1919). +using format_args = basic_format_args; + +// We cannot use enum classes as bit fields because of a gcc bug, so we put them +// in namespaces instead (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61414). +// Additionally, if an underlying type is specified, older gcc incorrectly warns +// that the type is too small. Both bugs are fixed in gcc 9.3. +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 903 +# define FMT_ENUM_UNDERLYING_TYPE(type) +#else +# define FMT_ENUM_UNDERLYING_TYPE(type) : type +#endif +namespace align { +enum type FMT_ENUM_UNDERLYING_TYPE(unsigned char){none, left, right, center, + numeric}; +} +using align_t = align::type; +namespace sign { +enum type FMT_ENUM_UNDERLYING_TYPE(unsigned char){none, minus, plus, space}; +} +using sign_t = sign::type; + +FMT_BEGIN_DETAIL_NAMESPACE + +// Workaround an array initialization issue in gcc 4.8. +template struct fill_t { + private: + enum { max_size = 4 }; + Char data_[max_size] = {Char(' '), Char(0), Char(0), Char(0)}; + unsigned char size_ = 1; + + public: + FMT_CONSTEXPR void operator=(basic_string_view s) { + auto size = s.size(); + if (size > max_size) return throw_format_error("invalid fill"); + for (size_t i = 0; i < size; ++i) data_[i] = s[i]; + size_ = static_cast(size); + } + + constexpr auto size() const -> size_t { return size_; } + constexpr auto data() const -> const Char* { return data_; } + + FMT_CONSTEXPR auto operator[](size_t index) -> Char& { return data_[index]; } + FMT_CONSTEXPR auto operator[](size_t index) const -> const Char& { + return data_[index]; + } +}; +FMT_END_DETAIL_NAMESPACE + +enum class presentation_type : unsigned char { + none, + // Integer types should go first, + dec, // 'd' + oct, // 'o' + hex_lower, // 'x' + hex_upper, // 'X' + bin_lower, // 'b' + bin_upper, // 'B' + hexfloat_lower, // 'a' + hexfloat_upper, // 'A' + exp_lower, // 'e' + exp_upper, // 'E' + fixed_lower, // 'f' + fixed_upper, // 'F' + general_lower, // 'g' + general_upper, // 'G' + chr, // 'c' + string, // 's' + pointer, // 'p' + debug // '?' +}; + +// Format specifiers for built-in and string types. +template struct basic_format_specs { + int width; + int precision; + presentation_type type; + align_t align : 4; + sign_t sign : 3; + bool alt : 1; // Alternate form ('#'). + bool localized : 1; + detail::fill_t fill; + + constexpr basic_format_specs() + : width(0), + precision(-1), + type(presentation_type::none), + align(align::none), + sign(sign::none), + alt(false), + localized(false) {} +}; + +using format_specs = basic_format_specs; + +FMT_BEGIN_DETAIL_NAMESPACE + +enum class arg_id_kind { none, index, name }; + +// An argument reference. +template struct arg_ref { + FMT_CONSTEXPR arg_ref() : kind(arg_id_kind::none), val() {} + + FMT_CONSTEXPR explicit arg_ref(int index) + : kind(arg_id_kind::index), val(index) {} + FMT_CONSTEXPR explicit arg_ref(basic_string_view name) + : kind(arg_id_kind::name), val(name) {} + + FMT_CONSTEXPR auto operator=(int idx) -> arg_ref& { + kind = arg_id_kind::index; + val.index = idx; + return *this; + } + + arg_id_kind kind; + union value { + FMT_CONSTEXPR value(int id = 0) : index{id} {} + FMT_CONSTEXPR value(basic_string_view n) : name(n) {} + + int index; + basic_string_view name; + } val; +}; + +// Format specifiers with width and precision resolved at formatting rather +// than parsing time to allow re-using the same parsed specifiers with +// different sets of arguments (precompilation of format strings). +template +struct dynamic_format_specs : basic_format_specs { + arg_ref width_ref; + arg_ref precision_ref; +}; + +struct auto_id {}; + +// A format specifier handler that sets fields in basic_format_specs. +template class specs_setter { + protected: + basic_format_specs& specs_; + + public: + explicit FMT_CONSTEXPR specs_setter(basic_format_specs& specs) + : specs_(specs) {} + + FMT_CONSTEXPR specs_setter(const specs_setter& other) + : specs_(other.specs_) {} + + FMT_CONSTEXPR void on_align(align_t align) { specs_.align = align; } + FMT_CONSTEXPR void on_fill(basic_string_view fill) { + specs_.fill = fill; + } + FMT_CONSTEXPR void on_sign(sign_t s) { specs_.sign = s; } + FMT_CONSTEXPR void on_hash() { specs_.alt = true; } + FMT_CONSTEXPR void on_localized() { specs_.localized = true; } + + FMT_CONSTEXPR void on_zero() { + if (specs_.align == align::none) specs_.align = align::numeric; + specs_.fill[0] = Char('0'); + } + + FMT_CONSTEXPR void on_width(int width) { specs_.width = width; } + FMT_CONSTEXPR void on_precision(int precision) { + specs_.precision = precision; + } + FMT_CONSTEXPR void end_precision() {} + + FMT_CONSTEXPR void on_type(presentation_type type) { specs_.type = type; } +}; + +// Format spec handler that saves references to arguments representing dynamic +// width and precision to be resolved at formatting time. +template +class dynamic_specs_handler + : public specs_setter { + public: + using char_type = typename ParseContext::char_type; + + FMT_CONSTEXPR dynamic_specs_handler(dynamic_format_specs& specs, + ParseContext& ctx) + : specs_setter(specs), specs_(specs), context_(ctx) {} + + FMT_CONSTEXPR dynamic_specs_handler(const dynamic_specs_handler& other) + : specs_setter(other), + specs_(other.specs_), + context_(other.context_) {} + + template FMT_CONSTEXPR void on_dynamic_width(Id arg_id) { + specs_.width_ref = make_arg_ref(arg_id); + } + + template FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) { + specs_.precision_ref = make_arg_ref(arg_id); + } + + FMT_CONSTEXPR void on_error(const char* message) { + context_.on_error(message); + } + + private: + dynamic_format_specs& specs_; + ParseContext& context_; + + using arg_ref_type = arg_ref; + + FMT_CONSTEXPR auto make_arg_ref(int arg_id) -> arg_ref_type { + context_.check_arg_id(arg_id); + context_.check_dynamic_spec(arg_id); + return arg_ref_type(arg_id); + } + + FMT_CONSTEXPR auto make_arg_ref(auto_id) -> arg_ref_type { + int arg_id = context_.next_arg_id(); + context_.check_dynamic_spec(arg_id); + return arg_ref_type(arg_id); + } + + FMT_CONSTEXPR auto make_arg_ref(basic_string_view arg_id) + -> arg_ref_type { + context_.check_arg_id(arg_id); + basic_string_view format_str( + context_.begin(), to_unsigned(context_.end() - context_.begin())); + return arg_ref_type(arg_id); + } +}; + +template constexpr bool is_ascii_letter(Char c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); +} + +// Converts a character to ASCII. Returns a number > 127 on conversion failure. +template ::value)> +constexpr auto to_ascii(Char c) -> Char { + return c; +} +template ::value)> +constexpr auto to_ascii(Char c) -> underlying_t { + return c; +} + +FMT_CONSTEXPR inline auto code_point_length_impl(char c) -> int { + return "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0\0\0\2\2\2\2\3\3\4" + [static_cast(c) >> 3]; +} + +template +FMT_CONSTEXPR auto code_point_length(const Char* begin) -> int { + if (const_check(sizeof(Char) != 1)) return 1; + int len = code_point_length_impl(static_cast(*begin)); + + // Compute the pointer to the next character early so that the next + // iteration can start working on the next character. Neither Clang + // nor GCC figure out this reordering on their own. + return len + !len; +} + +// Return the result via the out param to workaround gcc bug 77539. +template +FMT_CONSTEXPR auto find(Ptr first, Ptr last, T value, Ptr& out) -> bool { + for (out = first; out != last; ++out) { + if (*out == value) return true; + } + return false; +} + +template <> +inline auto find(const char* first, const char* last, char value, + const char*& out) -> bool { + out = static_cast( + std::memchr(first, value, to_unsigned(last - first))); + return out != nullptr; +} + +// Parses the range [begin, end) as an unsigned integer. This function assumes +// that the range is non-empty and the first character is a digit. +template +FMT_CONSTEXPR auto parse_nonnegative_int(const Char*& begin, const Char* end, + int error_value) noexcept -> int { + FMT_ASSERT(begin != end && '0' <= *begin && *begin <= '9', ""); + unsigned value = 0, prev = 0; + auto p = begin; + do { + prev = value; + value = value * 10 + unsigned(*p - '0'); + ++p; + } while (p != end && '0' <= *p && *p <= '9'); + auto num_digits = p - begin; + begin = p; + if (num_digits <= std::numeric_limits::digits10) + return static_cast(value); + // Check for overflow. + const unsigned max = to_unsigned((std::numeric_limits::max)()); + return num_digits == std::numeric_limits::digits10 + 1 && + prev * 10ull + unsigned(p[-1] - '0') <= max + ? static_cast(value) + : error_value; +} + +// Parses fill and alignment. +template +FMT_CONSTEXPR auto parse_align(const Char* begin, const Char* end, + Handler&& handler) -> const Char* { + FMT_ASSERT(begin != end, ""); + auto align = align::none; + auto p = begin + code_point_length(begin); + if (end - p <= 0) p = begin; + for (;;) { + switch (to_ascii(*p)) { + case '<': + align = align::left; + break; + case '>': + align = align::right; + break; + case '^': + align = align::center; + break; + default: + break; + } + if (align != align::none) { + if (p != begin) { + auto c = *begin; + if (c == '{') + return handler.on_error("invalid fill character '{'"), begin; + handler.on_fill(basic_string_view(begin, to_unsigned(p - begin))); + begin = p + 1; + } else + ++begin; + handler.on_align(align); + break; + } else if (p == begin) { + break; + } + p = begin; + } + return begin; +} + +template FMT_CONSTEXPR bool is_name_start(Char c) { + return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || '_' == c; +} + +template +FMT_CONSTEXPR auto do_parse_arg_id(const Char* begin, const Char* end, + IDHandler&& handler) -> const Char* { + FMT_ASSERT(begin != end, ""); + Char c = *begin; + if (c >= '0' && c <= '9') { + int index = 0; + if (c != '0') + index = + parse_nonnegative_int(begin, end, (std::numeric_limits::max)()); + else + ++begin; + if (begin == end || (*begin != '}' && *begin != ':')) + handler.on_error("invalid format string"); + else + handler(index); + return begin; + } + if (!is_name_start(c)) { + handler.on_error("invalid format string"); + return begin; + } + auto it = begin; + do { + ++it; + } while (it != end && (is_name_start(c = *it) || ('0' <= c && c <= '9'))); + handler(basic_string_view(begin, to_unsigned(it - begin))); + return it; +} + +template +FMT_CONSTEXPR FMT_INLINE auto parse_arg_id(const Char* begin, const Char* end, + IDHandler&& handler) -> const Char* { + Char c = *begin; + if (c != '}' && c != ':') return do_parse_arg_id(begin, end, handler); + handler(); + return begin; +} + +template +FMT_CONSTEXPR auto parse_width(const Char* begin, const Char* end, + Handler&& handler) -> const Char* { + using detail::auto_id; + struct width_adapter { + Handler& handler; + + FMT_CONSTEXPR void operator()() { handler.on_dynamic_width(auto_id()); } + FMT_CONSTEXPR void operator()(int id) { handler.on_dynamic_width(id); } + FMT_CONSTEXPR void operator()(basic_string_view id) { + handler.on_dynamic_width(id); + } + FMT_CONSTEXPR void on_error(const char* message) { + if (message) handler.on_error(message); + } + }; + + FMT_ASSERT(begin != end, ""); + if ('0' <= *begin && *begin <= '9') { + int width = parse_nonnegative_int(begin, end, -1); + if (width != -1) + handler.on_width(width); + else + handler.on_error("number is too big"); + } else if (*begin == '{') { + ++begin; + if (begin != end) begin = parse_arg_id(begin, end, width_adapter{handler}); + if (begin == end || *begin != '}') + return handler.on_error("invalid format string"), begin; + ++begin; + } + return begin; +} + +template +FMT_CONSTEXPR auto parse_precision(const Char* begin, const Char* end, + Handler&& handler) -> const Char* { + using detail::auto_id; + struct precision_adapter { + Handler& handler; + + FMT_CONSTEXPR void operator()() { handler.on_dynamic_precision(auto_id()); } + FMT_CONSTEXPR void operator()(int id) { handler.on_dynamic_precision(id); } + FMT_CONSTEXPR void operator()(basic_string_view id) { + handler.on_dynamic_precision(id); + } + FMT_CONSTEXPR void on_error(const char* message) { + if (message) handler.on_error(message); + } + }; + + ++begin; + auto c = begin != end ? *begin : Char(); + if ('0' <= c && c <= '9') { + auto precision = parse_nonnegative_int(begin, end, -1); + if (precision != -1) + handler.on_precision(precision); + else + handler.on_error("number is too big"); + } else if (c == '{') { + ++begin; + if (begin != end) + begin = parse_arg_id(begin, end, precision_adapter{handler}); + if (begin == end || *begin++ != '}') + return handler.on_error("invalid format string"), begin; + } else { + return handler.on_error("missing precision specifier"), begin; + } + handler.end_precision(); + return begin; +} + +template +FMT_CONSTEXPR auto parse_presentation_type(Char type) -> presentation_type { + switch (to_ascii(type)) { + case 'd': + return presentation_type::dec; + case 'o': + return presentation_type::oct; + case 'x': + return presentation_type::hex_lower; + case 'X': + return presentation_type::hex_upper; + case 'b': + return presentation_type::bin_lower; + case 'B': + return presentation_type::bin_upper; + case 'a': + return presentation_type::hexfloat_lower; + case 'A': + return presentation_type::hexfloat_upper; + case 'e': + return presentation_type::exp_lower; + case 'E': + return presentation_type::exp_upper; + case 'f': + return presentation_type::fixed_lower; + case 'F': + return presentation_type::fixed_upper; + case 'g': + return presentation_type::general_lower; + case 'G': + return presentation_type::general_upper; + case 'c': + return presentation_type::chr; + case 's': + return presentation_type::string; + case 'p': + return presentation_type::pointer; + case '?': + return presentation_type::debug; + default: + return presentation_type::none; + } +} + +// Parses standard format specifiers and sends notifications about parsed +// components to handler. +template +FMT_CONSTEXPR FMT_INLINE auto parse_format_specs(const Char* begin, + const Char* end, + SpecHandler&& handler) + -> const Char* { + if (1 < end - begin && begin[1] == '}' && is_ascii_letter(*begin) && + *begin != 'L') { + presentation_type type = parse_presentation_type(*begin++); + if (type == presentation_type::none) + handler.on_error("invalid type specifier"); + handler.on_type(type); + return begin; + } + + if (begin == end) return begin; + + begin = parse_align(begin, end, handler); + if (begin == end) return begin; + + // Parse sign. + switch (to_ascii(*begin)) { + case '+': + handler.on_sign(sign::plus); + ++begin; + break; + case '-': + handler.on_sign(sign::minus); + ++begin; + break; + case ' ': + handler.on_sign(sign::space); + ++begin; + break; + default: + break; + } + if (begin == end) return begin; + + if (*begin == '#') { + handler.on_hash(); + if (++begin == end) return begin; + } + + // Parse zero flag. + if (*begin == '0') { + handler.on_zero(); + if (++begin == end) return begin; + } + + begin = parse_width(begin, end, handler); + if (begin == end) return begin; + + // Parse precision. + if (*begin == '.') { + begin = parse_precision(begin, end, handler); + if (begin == end) return begin; + } + + if (*begin == 'L') { + handler.on_localized(); + ++begin; + } + + // Parse type. + if (begin != end && *begin != '}') { + presentation_type type = parse_presentation_type(*begin++); + if (type == presentation_type::none) + handler.on_error("invalid type specifier"); + handler.on_type(type); + } + return begin; +} + +template +FMT_CONSTEXPR auto parse_replacement_field(const Char* begin, const Char* end, + Handler&& handler) -> const Char* { + struct id_adapter { + Handler& handler; + int arg_id; + + FMT_CONSTEXPR void operator()() { arg_id = handler.on_arg_id(); } + FMT_CONSTEXPR void operator()(int id) { arg_id = handler.on_arg_id(id); } + FMT_CONSTEXPR void operator()(basic_string_view id) { + arg_id = handler.on_arg_id(id); + } + FMT_CONSTEXPR void on_error(const char* message) { + if (message) handler.on_error(message); + } + }; + + ++begin; + if (begin == end) return handler.on_error("invalid format string"), end; + if (*begin == '}') { + handler.on_replacement_field(handler.on_arg_id(), begin); + } else if (*begin == '{') { + handler.on_text(begin, begin + 1); + } else { + auto adapter = id_adapter{handler, 0}; + begin = parse_arg_id(begin, end, adapter); + Char c = begin != end ? *begin : Char(); + if (c == '}') { + handler.on_replacement_field(adapter.arg_id, begin); + } else if (c == ':') { + begin = handler.on_format_specs(adapter.arg_id, begin + 1, end); + if (begin == end || *begin != '}') + return handler.on_error("unknown format specifier"), end; + } else { + return handler.on_error("missing '}' in format string"), end; + } + } + return begin + 1; +} + +template +FMT_CONSTEXPR FMT_INLINE void parse_format_string( + basic_string_view format_str, Handler&& handler) { + // Workaround a name-lookup bug in MSVC's modules implementation. + using detail::find; + + auto begin = format_str.data(); + auto end = begin + format_str.size(); + if (end - begin < 32) { + // Use a simple loop instead of memchr for small strings. + const Char* p = begin; + while (p != end) { + auto c = *p++; + if (c == '{') { + handler.on_text(begin, p - 1); + begin = p = parse_replacement_field(p - 1, end, handler); + } else if (c == '}') { + if (p == end || *p != '}') + return handler.on_error("unmatched '}' in format string"); + handler.on_text(begin, p); + begin = ++p; + } + } + handler.on_text(begin, end); + return; + } + struct writer { + FMT_CONSTEXPR void operator()(const Char* from, const Char* to) { + if (from == to) return; + for (;;) { + const Char* p = nullptr; + if (!find(from, to, Char('}'), p)) + return handler_.on_text(from, to); + ++p; + if (p == to || *p != '}') + return handler_.on_error("unmatched '}' in format string"); + handler_.on_text(from, p); + from = p + 1; + } + } + Handler& handler_; + } write = {handler}; + while (begin != end) { + // Doing two passes with memchr (one for '{' and another for '}') is up to + // 2.5x faster than the naive one-pass implementation on big format strings. + const Char* p = begin; + if (*begin != '{' && !find(begin + 1, end, Char('{'), p)) + return write(begin, end); + write(begin, p); + begin = parse_replacement_field(p, end, handler); + } +} + +template ::value> struct strip_named_arg { + using type = T; +}; +template struct strip_named_arg { + using type = remove_cvref_t; +}; + +template +FMT_CONSTEXPR auto parse_format_specs(ParseContext& ctx) + -> decltype(ctx.begin()) { + using char_type = typename ParseContext::char_type; + using context = buffer_context; + using stripped_type = typename strip_named_arg::type; + using mapped_type = conditional_t< + mapped_type_constant::value != type::custom_type, + decltype(arg_mapper().map(std::declval())), + stripped_type>; + auto f = conditional_t::value, + formatter, + fallback_formatter>(); + return f.parse(ctx); +} + +template +FMT_CONSTEXPR void check_int_type_spec(presentation_type type, + ErrorHandler&& eh) { + if (type > presentation_type::bin_upper && type != presentation_type::chr) + eh.on_error("invalid type specifier"); +} + +// Checks char specs and returns true if the type spec is char (and not int). +template +FMT_CONSTEXPR auto check_char_specs(const basic_format_specs& specs, + ErrorHandler&& eh = {}) -> bool { + if (specs.type != presentation_type::none && + specs.type != presentation_type::chr && + specs.type != presentation_type::debug) { + check_int_type_spec(specs.type, eh); + return false; + } + if (specs.align == align::numeric || specs.sign != sign::none || specs.alt) + eh.on_error("invalid format specifier for char"); + return true; +} + +// A floating-point presentation format. +enum class float_format : unsigned char { + general, // General: exponent notation or fixed point based on magnitude. + exp, // Exponent notation with the default precision of 6, e.g. 1.2e-3. + fixed, // Fixed point with the default precision of 6, e.g. 0.0012. + hex +}; + +struct float_specs { + int precision; + float_format format : 8; + sign_t sign : 8; + bool upper : 1; + bool locale : 1; + bool binary32 : 1; + bool showpoint : 1; +}; + +template +FMT_CONSTEXPR auto parse_float_type_spec(const basic_format_specs& specs, + ErrorHandler&& eh = {}) + -> float_specs { + auto result = float_specs(); + result.showpoint = specs.alt; + result.locale = specs.localized; + switch (specs.type) { + case presentation_type::none: + result.format = float_format::general; + break; + case presentation_type::general_upper: + result.upper = true; + FMT_FALLTHROUGH; + case presentation_type::general_lower: + result.format = float_format::general; + break; + case presentation_type::exp_upper: + result.upper = true; + FMT_FALLTHROUGH; + case presentation_type::exp_lower: + result.format = float_format::exp; + result.showpoint |= specs.precision != 0; + break; + case presentation_type::fixed_upper: + result.upper = true; + FMT_FALLTHROUGH; + case presentation_type::fixed_lower: + result.format = float_format::fixed; + result.showpoint |= specs.precision != 0; + break; + case presentation_type::hexfloat_upper: + result.upper = true; + FMT_FALLTHROUGH; + case presentation_type::hexfloat_lower: + result.format = float_format::hex; + break; + default: + eh.on_error("invalid type specifier"); + break; + } + return result; +} + +template +FMT_CONSTEXPR auto check_cstring_type_spec(presentation_type type, + ErrorHandler&& eh = {}) -> bool { + if (type == presentation_type::none || type == presentation_type::string || + type == presentation_type::debug) + return true; + if (type != presentation_type::pointer) eh.on_error("invalid type specifier"); + return false; +} + +template +FMT_CONSTEXPR void check_string_type_spec(presentation_type type, + ErrorHandler&& eh = {}) { + if (type != presentation_type::none && type != presentation_type::string && + type != presentation_type::debug) + eh.on_error("invalid type specifier"); +} + +template +FMT_CONSTEXPR void check_pointer_type_spec(presentation_type type, + ErrorHandler&& eh) { + if (type != presentation_type::none && type != presentation_type::pointer) + eh.on_error("invalid type specifier"); +} + +// A parse_format_specs handler that checks if specifiers are consistent with +// the argument type. +template class specs_checker : public Handler { + private: + detail::type arg_type_; + + FMT_CONSTEXPR void require_numeric_argument() { + if (!is_arithmetic_type(arg_type_)) + this->on_error("format specifier requires numeric argument"); + } + + public: + FMT_CONSTEXPR specs_checker(const Handler& handler, detail::type arg_type) + : Handler(handler), arg_type_(arg_type) {} + + FMT_CONSTEXPR void on_align(align_t align) { + if (align == align::numeric) require_numeric_argument(); + Handler::on_align(align); + } + + FMT_CONSTEXPR void on_sign(sign_t s) { + require_numeric_argument(); + if (is_integral_type(arg_type_) && arg_type_ != type::int_type && + arg_type_ != type::long_long_type && arg_type_ != type::int128_type && + arg_type_ != type::char_type) { + this->on_error("format specifier requires signed argument"); + } + Handler::on_sign(s); + } + + FMT_CONSTEXPR void on_hash() { + require_numeric_argument(); + Handler::on_hash(); + } + + FMT_CONSTEXPR void on_localized() { + require_numeric_argument(); + Handler::on_localized(); + } + + FMT_CONSTEXPR void on_zero() { + require_numeric_argument(); + Handler::on_zero(); + } + + FMT_CONSTEXPR void end_precision() { + if (is_integral_type(arg_type_) || arg_type_ == type::pointer_type) + this->on_error("precision not allowed for this argument type"); + } +}; + +constexpr int invalid_arg_index = -1; + +#if FMT_USE_NONTYPE_TEMPLATE_ARGS +template +constexpr auto get_arg_index_by_name(basic_string_view name) -> int { + if constexpr (detail::is_statically_named_arg()) { + if (name == T::name) return N; + } + if constexpr (sizeof...(Args) > 0) + return get_arg_index_by_name(name); + (void)name; // Workaround an MSVC bug about "unused" parameter. + return invalid_arg_index; +} +#endif + +template +FMT_CONSTEXPR auto get_arg_index_by_name(basic_string_view name) -> int { +#if FMT_USE_NONTYPE_TEMPLATE_ARGS + if constexpr (sizeof...(Args) > 0) + return get_arg_index_by_name<0, Args...>(name); +#endif + (void)name; + return invalid_arg_index; +} + +template +class format_string_checker { + private: + // In the future basic_format_parse_context will replace compile_parse_context + // here and will use is_constant_evaluated and downcasting to access the data + // needed for compile-time checks: https://godbolt.org/z/GvWzcTjh1. + using parse_context_type = compile_parse_context; + static constexpr int num_args = sizeof...(Args); + + // Format specifier parsing function. + using parse_func = const Char* (*)(parse_context_type&); + + parse_context_type context_; + parse_func parse_funcs_[num_args > 0 ? static_cast(num_args) : 1]; + type types_[num_args > 0 ? static_cast(num_args) : 1]; + + public: + explicit FMT_CONSTEXPR format_string_checker( + basic_string_view format_str, ErrorHandler eh) + : context_(format_str, num_args, types_, eh), + parse_funcs_{&parse_format_specs...}, + types_{ + mapped_type_constant>::value...} { + } + + FMT_CONSTEXPR void on_text(const Char*, const Char*) {} + + FMT_CONSTEXPR auto on_arg_id() -> int { return context_.next_arg_id(); } + FMT_CONSTEXPR auto on_arg_id(int id) -> int { + return context_.check_arg_id(id), id; + } + FMT_CONSTEXPR auto on_arg_id(basic_string_view id) -> int { +#if FMT_USE_NONTYPE_TEMPLATE_ARGS + auto index = get_arg_index_by_name(id); + if (index == invalid_arg_index) on_error("named argument is not found"); + return context_.check_arg_id(index), index; +#else + (void)id; + on_error("compile-time checks for named arguments require C++20 support"); + return 0; +#endif + } + + FMT_CONSTEXPR void on_replacement_field(int, const Char*) {} + + FMT_CONSTEXPR auto on_format_specs(int id, const Char* begin, const Char*) + -> const Char* { + context_.advance_to(context_.begin() + (begin - &*context_.begin())); + // id >= 0 check is a workaround for gcc 10 bug (#2065). + return id >= 0 && id < num_args ? parse_funcs_[id](context_) : begin; + } + + FMT_CONSTEXPR void on_error(const char* message) { + context_.on_error(message); + } +}; + +// Reports a compile-time error if S is not a valid format string. +template ::value)> +FMT_INLINE void check_format_string(const S&) { +#ifdef FMT_ENFORCE_COMPILE_STRING + static_assert(is_compile_string::value, + "FMT_ENFORCE_COMPILE_STRING requires all format strings to use " + "FMT_STRING."); +#endif +} +template ::value)> +void check_format_string(S format_str) { + FMT_CONSTEXPR auto s = basic_string_view(format_str); + using checker = format_string_checker...>; + FMT_CONSTEXPR bool invalid_format = + (parse_format_string(s, checker(s, {})), true); + ignore_unused(invalid_format); +} + +template +void vformat_to( + buffer& buf, basic_string_view fmt, + basic_format_args)> args, + locale_ref loc = {}); + +FMT_API void vprint_mojibake(std::FILE*, string_view, format_args); +#ifndef _WIN32 +inline void vprint_mojibake(std::FILE*, string_view, format_args) {} +#endif +FMT_END_DETAIL_NAMESPACE + +// A formatter specialization for the core types corresponding to detail::type +// constants. +template +struct formatter::value != + detail::type::custom_type>> { + private: + detail::dynamic_format_specs specs_; + + public: + // Parses format specifiers stopping either at the end of the range or at the + // terminating '}'. + template + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { + auto begin = ctx.begin(), end = ctx.end(); + if (begin == end) return begin; + using handler_type = detail::dynamic_specs_handler; + auto type = detail::type_constant::value; + auto checker = + detail::specs_checker(handler_type(specs_, ctx), type); + auto it = detail::parse_format_specs(begin, end, checker); + auto eh = ctx.error_handler(); + switch (type) { + case detail::type::none_type: + FMT_ASSERT(false, "invalid argument type"); + break; + case detail::type::bool_type: + if (specs_.type == presentation_type::none || + specs_.type == presentation_type::string) { + break; + } + FMT_FALLTHROUGH; + case detail::type::int_type: + case detail::type::uint_type: + case detail::type::long_long_type: + case detail::type::ulong_long_type: + case detail::type::int128_type: + case detail::type::uint128_type: + detail::check_int_type_spec(specs_.type, eh); + break; + case detail::type::char_type: + detail::check_char_specs(specs_, eh); + break; + case detail::type::float_type: + if (detail::const_check(FMT_USE_FLOAT)) + detail::parse_float_type_spec(specs_, eh); + else + FMT_ASSERT(false, "float support disabled"); + break; + case detail::type::double_type: + if (detail::const_check(FMT_USE_DOUBLE)) + detail::parse_float_type_spec(specs_, eh); + else + FMT_ASSERT(false, "double support disabled"); + break; + case detail::type::long_double_type: + if (detail::const_check(FMT_USE_LONG_DOUBLE)) + detail::parse_float_type_spec(specs_, eh); + else + FMT_ASSERT(false, "long double support disabled"); + break; + case detail::type::cstring_type: + detail::check_cstring_type_spec(specs_.type, eh); + break; + case detail::type::string_type: + detail::check_string_type_spec(specs_.type, eh); + break; + case detail::type::pointer_type: + detail::check_pointer_type_spec(specs_.type, eh); + break; + case detail::type::custom_type: + // Custom format specifiers are checked in parse functions of + // formatter specializations. + break; + } + return it; + } + + template ::value, + enable_if_t<(U == detail::type::string_type || + U == detail::type::cstring_type || + U == detail::type::char_type), + int> = 0> + FMT_CONSTEXPR void set_debug_format() { + specs_.type = presentation_type::debug; + } + + template + FMT_CONSTEXPR auto format(const T& val, FormatContext& ctx) const + -> decltype(ctx.out()); +}; + +#define FMT_FORMAT_AS(Type, Base) \ + template \ + struct formatter : formatter { \ + template \ + auto format(Type const& val, FormatContext& ctx) const \ + -> decltype(ctx.out()) { \ + return formatter::format(static_cast(val), ctx); \ + } \ + } + +FMT_FORMAT_AS(signed char, int); +FMT_FORMAT_AS(unsigned char, unsigned); +FMT_FORMAT_AS(short, int); +FMT_FORMAT_AS(unsigned short, unsigned); +FMT_FORMAT_AS(long, long long); +FMT_FORMAT_AS(unsigned long, unsigned long long); +FMT_FORMAT_AS(Char*, const Char*); +FMT_FORMAT_AS(std::basic_string, basic_string_view); +FMT_FORMAT_AS(std::nullptr_t, const void*); +FMT_FORMAT_AS(detail::std_string_view, basic_string_view); + +template struct basic_runtime { basic_string_view str; }; + +/** A compile-time format string. */ +template class basic_format_string { + private: + basic_string_view str_; + + public: + template >::value)> + FMT_CONSTEVAL FMT_INLINE basic_format_string(const S& s) : str_(s) { + static_assert( + detail::count< + (std::is_base_of>::value && + std::is_reference::value)...>() == 0, + "passing views as lvalues is disallowed"); +#ifdef FMT_HAS_CONSTEVAL + if constexpr (detail::count_named_args() == + detail::count_statically_named_args()) { + using checker = detail::format_string_checker...>; + detail::parse_format_string(str_, checker(s, {})); + } +#else + detail::check_format_string(s); +#endif + } + basic_format_string(basic_runtime r) : str_(r.str) {} + + FMT_INLINE operator basic_string_view() const { return str_; } +}; + +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 +// Workaround broken conversion on older gcc. +template using format_string = string_view; +inline auto runtime(string_view s) -> string_view { return s; } +#else +template +using format_string = basic_format_string...>; +/** + \rst + Creates a runtime format string. + + **Example**:: + + // Check format string at runtime instead of compile-time. + fmt::print(fmt::runtime("{:d}"), "I am not a number"); + \endrst + */ +inline auto runtime(string_view s) -> basic_runtime { return {{s}}; } +#endif + +FMT_API auto vformat(string_view fmt, format_args args) -> std::string; + +/** + \rst + Formats ``args`` according to specifications in ``fmt`` and returns the result + as a string. + + **Example**:: + + #include + std::string message = fmt::format("The answer is {}.", 42); + \endrst +*/ +template +FMT_NODISCARD FMT_INLINE auto format(format_string fmt, T&&... args) + -> std::string { + return vformat(fmt, fmt::make_format_args(args...)); +} + +/** Formats a string and writes the output to ``out``. */ +template ::value)> +auto vformat_to(OutputIt out, string_view fmt, format_args args) -> OutputIt { + using detail::get_buffer; + auto&& buf = get_buffer(out); + detail::vformat_to(buf, fmt, args, {}); + return detail::get_iterator(buf); +} + +/** + \rst + Formats ``args`` according to specifications in ``fmt``, writes the result to + the output iterator ``out`` and returns the iterator past the end of the output + range. `format_to` does not append a terminating null character. + + **Example**:: + + auto out = std::vector(); + fmt::format_to(std::back_inserter(out), "{}", 42); + \endrst + */ +template ::value)> +FMT_INLINE auto format_to(OutputIt out, format_string fmt, T&&... args) + -> OutputIt { + return vformat_to(out, fmt, fmt::make_format_args(args...)); +} + +template struct format_to_n_result { + /** Iterator past the end of the output range. */ + OutputIt out; + /** Total (not truncated) output size. */ + size_t size; +}; + +template ::value)> +auto vformat_to_n(OutputIt out, size_t n, string_view fmt, format_args args) + -> format_to_n_result { + using traits = detail::fixed_buffer_traits; + auto buf = detail::iterator_buffer(out, n); + detail::vformat_to(buf, fmt, args, {}); + return {buf.out(), buf.count()}; +} + +/** + \rst + Formats ``args`` according to specifications in ``fmt``, writes up to ``n`` + characters of the result to the output iterator ``out`` and returns the total + (not truncated) output size and the iterator past the end of the output range. + `format_to_n` does not append a terminating null character. + \endrst + */ +template ::value)> +FMT_INLINE auto format_to_n(OutputIt out, size_t n, format_string fmt, + T&&... args) -> format_to_n_result { + return vformat_to_n(out, n, fmt, fmt::make_format_args(args...)); +} + +/** Returns the number of chars in the output of ``format(fmt, args...)``. */ +template +FMT_NODISCARD FMT_INLINE auto formatted_size(format_string fmt, + T&&... args) -> size_t { + auto buf = detail::counting_buffer<>(); + detail::vformat_to(buf, string_view(fmt), fmt::make_format_args(args...), {}); + return buf.count(); +} + +FMT_API void vprint(string_view fmt, format_args args); +FMT_API void vprint(std::FILE* f, string_view fmt, format_args args); + +/** + \rst + Formats ``args`` according to specifications in ``fmt`` and writes the output + to ``stdout``. + + **Example**:: + + fmt::print("Elapsed time: {0:.2f} seconds", 1.23); + \endrst + */ +template +FMT_INLINE void print(format_string fmt, T&&... args) { + const auto& vargs = fmt::make_format_args(args...); + return detail::is_utf8() ? vprint(fmt, vargs) + : detail::vprint_mojibake(stdout, fmt, vargs); +} + +/** + \rst + Formats ``args`` according to specifications in ``fmt`` and writes the + output to the file ``f``. + + **Example**:: + + fmt::print(stderr, "Don't {}!", "panic"); + \endrst + */ +template +FMT_INLINE void print(std::FILE* f, format_string fmt, T&&... args) { + const auto& vargs = fmt::make_format_args(args...); + return detail::is_utf8() ? vprint(f, fmt, vargs) + : detail::vprint_mojibake(f, fmt, vargs); +} + +FMT_MODULE_EXPORT_END +FMT_GCC_PRAGMA("GCC pop_options") +FMT_END_NAMESPACE + +#ifdef FMT_HEADER_ONLY +# include "format.h" +#endif +#endif // DUMUX_FMT_CORE_H_ diff --git a/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/format/fmt/format.h b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/format/fmt/format.h new file mode 100644 index 0000000..eb9a446 --- /dev/null +++ b/benchmarks/rotating-cylinders/dumux/dumuxModule/dumux/io/format/fmt/format.h @@ -0,0 +1,4221 @@ +// +// SPDX-FileCopyrightText: Copyright (c) 2012 - present, Victor Zverovich +// SPDX-License-Identifier: LicenseRef-fmt +// +/* + Formatting library for C++ + + Copyright (c) 2012 - present, Victor Zverovich + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + --- Optional exception to the license --- + + As an exception, if, as a result of your compiling your source code, portions + of this Software are embedded into a machine-executable object form of such + source code, you may redistribute such embedded portions in such object form + without including the above copyright and permission notices. + */ + +#ifndef DUMUX_FMT_FORMAT_H_ +#define DUMUX_FMT_FORMAT_H_ + +#include // std::signbit +#include // uint32_t +#include // std::memcpy +#include // std::numeric_limits +#include // std::uninitialized_copy +#include // std::runtime_error +#include // std::system_error + +#ifdef __cpp_lib_bit_cast +# include // std::bitcast +#endif + +#include "core.h" + +#if FMT_GCC_VERSION +# define FMT_GCC_VISIBILITY_HIDDEN __attribute__((visibility("hidden"))) +#else +# define FMT_GCC_VISIBILITY_HIDDEN +#endif + +#ifdef __NVCC__ +# define FMT_CUDA_VERSION (__CUDACC_VER_MAJOR__ * 100 + __CUDACC_VER_MINOR__) +#else +# define FMT_CUDA_VERSION 0 +#endif + +#ifdef __has_builtin +# define FMT_HAS_BUILTIN(x) __has_builtin(x) +#else +# define FMT_HAS_BUILTIN(x) 0 +#endif + +#if FMT_GCC_VERSION || FMT_CLANG_VERSION +# define FMT_NOINLINE __attribute__((noinline)) +#else +# define FMT_NOINLINE +#endif + +#if FMT_MSC_VERSION +# define FMT_MSC_DEFAULT = default +#else +# define FMT_MSC_DEFAULT +#endif + +#ifndef FMT_THROW +# if FMT_EXCEPTIONS +# if FMT_MSC_VERSION || defined(__NVCC__) +FMT_BEGIN_NAMESPACE +namespace detail { +template inline void do_throw(const Exception& x) { + // Silence unreachable code warnings in MSVC and NVCC because these + // are nearly impossible to fix in a generic code. + volatile bool b = true; + if (b) throw x; +} +} // namespace detail +FMT_END_NAMESPACE +# define FMT_THROW(x) detail::do_throw(x) +# else +# define FMT_THROW(x) throw x +# endif +# else +# define FMT_THROW(x) \ + do { \ + FMT_ASSERT(false, (x).what()); \ + } while (false) +# endif +#endif + +#if FMT_EXCEPTIONS +# define FMT_TRY try +# define FMT_CATCH(x) catch (x) +#else +# define FMT_TRY if (true) +# define FMT_CATCH(x) if (false) +#endif + +#ifndef FMT_MAYBE_UNUSED +# if FMT_HAS_CPP17_ATTRIBUTE(maybe_unused) +# define FMT_MAYBE_UNUSED [[maybe_unused]] +# else +# define FMT_MAYBE_UNUSED +# endif +#endif + +#ifndef FMT_USE_USER_DEFINED_LITERALS +// EDG based compilers (Intel, NVIDIA, Elbrus, etc), GCC and MSVC support UDLs. +# if (FMT_HAS_FEATURE(cxx_user_literals) || FMT_GCC_VERSION >= 407 || \ + FMT_MSC_VERSION >= 1900) && \ + (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= /* UDL feature */ 480) +# define FMT_USE_USER_DEFINED_LITERALS 1 +# else +# define FMT_USE_USER_DEFINED_LITERALS 0 +# endif +#endif + +// Defining FMT_REDUCE_INT_INSTANTIATIONS to 1, will reduce the number of +// integer formatter template instantiations to just one by only using the +// largest integer type. This results in a reduction in binary size but will +// cause a decrease in integer formatting performance. +#if !defined(FMT_REDUCE_INT_INSTANTIATIONS) +# define FMT_REDUCE_INT_INSTANTIATIONS 0 +#endif + +// __builtin_clz is broken in clang with Microsoft CodeGen: +// https://github.com/fmtlib/fmt/issues/519. +#if !FMT_MSC_VERSION +# if FMT_HAS_BUILTIN(__builtin_clz) || FMT_GCC_VERSION || FMT_ICC_VERSION +# define FMT_BUILTIN_CLZ(n) __builtin_clz(n) +# endif +# if FMT_HAS_BUILTIN(__builtin_clzll) || FMT_GCC_VERSION || FMT_ICC_VERSION +# define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n) +# endif +#endif + +// __builtin_ctz is broken in Intel Compiler Classic on Windows: +// https://github.com/fmtlib/fmt/issues/2510. +#ifndef __ICL +# if FMT_HAS_BUILTIN(__builtin_ctz) || FMT_GCC_VERSION || FMT_ICC_VERSION || \ + defined(__NVCOMPILER) +# define FMT_BUILTIN_CTZ(n) __builtin_ctz(n) +# endif +# if FMT_HAS_BUILTIN(__builtin_ctzll) || FMT_GCC_VERSION || \ + FMT_ICC_VERSION || defined(__NVCOMPILER) +# define FMT_BUILTIN_CTZLL(n) __builtin_ctzll(n) +# endif +#endif + +#if FMT_MSC_VERSION +# include // _BitScanReverse[64], _BitScanForward[64], _umul128 +#endif + +// Some compilers masquerade as both MSVC and GCC-likes or otherwise support +// __builtin_clz and __builtin_clzll, so only define FMT_BUILTIN_CLZ using the +// MSVC intrinsics if the clz and clzll builtins are not available. +#if FMT_MSC_VERSION && !defined(FMT_BUILTIN_CLZLL) && \ + !defined(FMT_BUILTIN_CTZLL) +FMT_BEGIN_NAMESPACE +namespace detail { +// Avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning. +# if !defined(__clang__) +# pragma intrinsic(_BitScanForward) +# pragma intrinsic(_BitScanReverse) +# if defined(_WIN64) +# pragma intrinsic(_BitScanForward64) +# pragma intrinsic(_BitScanReverse64) +# endif +# endif + +inline auto clz(uint32_t x) -> int { + unsigned long r = 0; + _BitScanReverse(&r, x); + FMT_ASSERT(x != 0, ""); + // Static analysis complains about using uninitialized data + // "r", but the only way that can happen is if "x" is 0, + // which the callers guarantee to not happen. + FMT_MSC_WARNING(suppress : 6102) + return 31 ^ static_cast(r); +} +# define FMT_BUILTIN_CLZ(n) detail::clz(n) + +inline auto clzll(uint64_t x) -> int { + unsigned long r = 0; +# ifdef _WIN64 + _BitScanReverse64(&r, x); +# else + // Scan the high 32 bits. + if (_BitScanReverse(&r, static_cast(x >> 32))) return 63 ^ (r + 32); + // Scan the low 32 bits. + _BitScanReverse(&r, static_cast(x)); +# endif + FMT_ASSERT(x != 0, ""); + FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. + return 63 ^ static_cast(r); +} +# define FMT_BUILTIN_CLZLL(n) detail::clzll(n) + +inline auto ctz(uint32_t x) -> int { + unsigned long r = 0; + _BitScanForward(&r, x); + FMT_ASSERT(x != 0, ""); + FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. + return static_cast(r); +} +# define FMT_BUILTIN_CTZ(n) detail::ctz(n) + +inline auto ctzll(uint64_t x) -> int { + unsigned long r = 0; + FMT_ASSERT(x != 0, ""); + FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. +# ifdef _WIN64 + _BitScanForward64(&r, x); +# else + // Scan the low 32 bits. + if (_BitScanForward(&r, static_cast(x))) return static_cast(r); + // Scan the high 32 bits. + _BitScanForward(&r, static_cast(x >> 32)); + r += 32; +# endif + return static_cast(r); +} +# define FMT_BUILTIN_CTZLL(n) detail::ctzll(n) +} // namespace detail +FMT_END_NAMESPACE +#endif + +FMT_BEGIN_NAMESPACE +namespace detail { + +FMT_CONSTEXPR inline void abort_fuzzing_if(bool condition) { + ignore_unused(condition); +#ifdef FMT_FUZZ + if (condition) throw std::runtime_error("fuzzing limit reached"); +#endif +} + +template struct string_literal { + static constexpr CharT value[sizeof...(C)] = {C...}; + constexpr operator basic_string_view() const { + return {value, sizeof...(C)}; + } +}; + +#if FMT_CPLUSPLUS < 201703L +template +constexpr CharT string_literal::value[sizeof...(C)]; +#endif + +template class formatbuf : public Streambuf { + private: + using char_type = typename Streambuf::char_type; + using streamsize = decltype(std::declval().sputn(nullptr, 0)); + using int_type = typename Streambuf::int_type; + using traits_type = typename Streambuf::traits_type; + + buffer& buffer_; + + public: + explicit formatbuf(buffer& buf) : buffer_(buf) {} + + protected: + // The put area is always empty. This makes the implementation simpler and has + // the advantage that the streambuf and the buffer are always in sync and + // sputc never writes into uninitialized memory. A disadvantage is that each + // call to sputc always results in a (virtual) call to overflow. There is no + // disadvantage here for sputn since this always results in a call to xsputn. + + auto overflow(int_type ch) -> int_type override { + if (!traits_type::eq_int_type(ch, traits_type::eof())) + buffer_.push_back(static_cast(ch)); + return ch; + } + + auto xsputn(const char_type* s, streamsize count) -> streamsize override { + buffer_.append(s, s + count); + return count; + } +}; + +// Implementation of std::bit_cast for pre-C++20. +template +FMT_CONSTEXPR20 auto bit_cast(const From& from) -> To { +#ifdef __cpp_lib_bit_cast + if (is_constant_evaluated()) return std::bit_cast(from); +#endif + auto to = To(); + // The cast suppresses a bogus -Wclass-memaccess on GCC. + std::memcpy(static_cast(&to), &from, sizeof(to)); + return to; +} + +inline auto is_big_endian() -> bool { +#ifdef _WIN32 + return false; +#elif defined(__BIG_ENDIAN__) + return true; +#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) + return __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__; +#else + struct bytes { + char data[sizeof(int)]; + }; + return bit_cast(1).data[0] == 0; +#endif +} + +class uint128_fallback { + private: + uint64_t lo_, hi_; + + friend uint128_fallback umul128(uint64_t x, uint64_t y) noexcept; + + public: + constexpr uint128_fallback(uint64_t hi, uint64_t lo) : lo_(lo), hi_(hi) {} + constexpr uint128_fallback(uint64_t value = 0) : lo_(value), hi_(0) {} + + constexpr uint64_t high() const noexcept { return hi_; } + constexpr uint64_t low() const noexcept { return lo_; } + + template ::value)> + constexpr explicit operator T() const { + return static_cast(lo_); + } + + friend constexpr auto operator==(const uint128_fallback& lhs, + const uint128_fallback& rhs) -> bool { + return lhs.hi_ == rhs.hi_ && lhs.lo_ == rhs.lo_; + } + friend constexpr auto operator!=(const uint128_fallback& lhs, + const uint128_fallback& rhs) -> bool { + return !(lhs == rhs); + } + friend constexpr auto operator>(const uint128_fallback& lhs, + const uint128_fallback& rhs) -> bool { + return lhs.hi_ != rhs.hi_ ? lhs.hi_ > rhs.hi_ : lhs.lo_ > rhs.lo_; + } + friend constexpr auto operator|(const uint128_fallback& lhs, + const uint128_fallback& rhs) + -> uint128_fallback { + return {lhs.hi_ | rhs.hi_, lhs.lo_ | rhs.lo_}; + } + friend constexpr auto operator&(const uint128_fallback& lhs, + const uint128_fallback& rhs) + -> uint128_fallback { + return {lhs.hi_ & rhs.hi_, lhs.lo_ & rhs.lo_}; + } + friend auto operator+(const uint128_fallback& lhs, + const uint128_fallback& rhs) -> uint128_fallback { + auto result = uint128_fallback(lhs); + result += rhs; + return result; + } + friend auto operator*(const uint128_fallback& lhs, uint32_t rhs) + -> uint128_fallback { + FMT_ASSERT(lhs.hi_ == 0, ""); + uint64_t hi = (lhs.lo_ >> 32) * rhs; + uint64_t lo = (lhs.lo_ & ~uint32_t()) * rhs; + uint64_t new_lo = (hi << 32) + lo; + return {(hi >> 32) + (new_lo < lo ? 1 : 0), new_lo}; + } + friend auto operator-(const uint128_fallback& lhs, uint64_t rhs) + -> uint128_fallback { + return {lhs.hi_ - (lhs.lo_ < rhs ? 1 : 0), lhs.lo_ - rhs}; + } + FMT_CONSTEXPR auto operator>>(int shift) const -> uint128_fallback { + if (shift == 64) return {0, hi_}; + if (shift > 64) return uint128_fallback(0, hi_) >> (shift - 64); + return {hi_ >> shift, (hi_ << (64 - shift)) | (lo_ >> shift)}; + } + FMT_CONSTEXPR auto operator<<(int shift) const -> uint128_fallback { + if (shift == 64) return {lo_, 0}; + if (shift > 64) return uint128_fallback(lo_, 0) << (shift - 64); + return {hi_ << shift | (lo_ >> (64 - shift)), (lo_ << shift)}; + } + FMT_CONSTEXPR auto operator>>=(int shift) -> uint128_fallback& { + return *this = *this >> shift; + } + FMT_CONSTEXPR void operator+=(uint128_fallback n) { + uint64_t new_lo = lo_ + n.lo_; + uint64_t new_hi = hi_ + n.hi_ + (new_lo < lo_ ? 1 : 0); + FMT_ASSERT(new_hi >= hi_, ""); + lo_ = new_lo; + hi_ = new_hi; + } + + FMT_CONSTEXPR20 uint128_fallback& operator+=(uint64_t n) noexcept { + if (is_constant_evaluated()) { + lo_ += n; + hi_ += (lo_ < n ? 1 : 0); + return *this; + } +#if FMT_HAS_BUILTIN(__builtin_addcll) && !defined(__ibmxl__) + unsigned long long carry; + lo_ = __builtin_addcll(lo_, n, 0, &carry); + hi_ += carry; +#elif FMT_HAS_BUILTIN(__builtin_ia32_addcarryx_u64) && !defined(__ibmxl__) + unsigned long long result; + auto carry = __builtin_ia32_addcarryx_u64(0, lo_, n, &result); + lo_ = result; + hi_ += carry; +#elif defined(_MSC_VER) && defined(_M_X64) + auto carry = _addcarry_u64(0, lo_, n, &lo_); + _addcarry_u64(carry, hi_, 0, &hi_); +#else + lo_ += n; + hi_ += (lo_ < n ? 1 : 0); +#endif + return *this; + } +}; + +using uint128_t = conditional_t; + +#ifdef UINTPTR_MAX +using uintptr_t = ::uintptr_t; +#else +using uintptr_t = uint128_t; +#endif + +// Returns the largest possible value for type T. Same as +// std::numeric_limits::max() but shorter and not affected by the max macro. +template constexpr auto max_value() -> T { + return (std::numeric_limits::max)(); +} +template constexpr auto num_bits() -> int { + return std::numeric_limits::digits; +} +// std::numeric_limits::digits may return 0 for 128-bit ints. +template <> constexpr auto num_bits() -> int { return 128; } +template <> constexpr auto num_bits() -> int { return 128; } + +// A heterogeneous bit_cast used for converting 96-bit long double to uint128_t +// and 128-bit pointers to uint128_fallback. +template sizeof(From))> +inline auto bit_cast(const From& from) -> To { + constexpr auto size = static_cast(sizeof(From) / sizeof(unsigned)); + struct data_t { + unsigned value[static_cast(size)]; + } data = bit_cast(from); + auto result = To(); + if (const_check(is_big_endian())) { + for (int i = 0; i < size; ++i) + result = (result << num_bits()) | data.value[i]; + } else { + for (int i = size - 1; i >= 0; --i) + result = (result << num_bits()) | data.value[i]; + } + return result; +} + +FMT_INLINE void assume(bool condition) { + (void)condition; +#if FMT_HAS_BUILTIN(__builtin_assume) && !FMT_ICC_VERSION + __builtin_assume(condition); +#endif +} + +// An approximation of iterator_t for pre-C++20 systems. +template +using iterator_t = decltype(std::begin(std::declval())); +template using sentinel_t = decltype(std::end(std::declval())); + +// A workaround for std::string not having mutable data() until C++17. +template +inline auto get_data(std::basic_string& s) -> Char* { + return &s[0]; +} +template +inline auto get_data(Container& c) -> typename Container::value_type* { + return c.data(); +} + +#if defined(_SECURE_SCL) && _SECURE_SCL +// Make a checked iterator to avoid MSVC warnings. +template using checked_ptr = stdext::checked_array_iterator; +template +constexpr auto make_checked(T* p, size_t size) -> checked_ptr { + return {p, size}; +} +#else +template using checked_ptr = T*; +template constexpr auto make_checked(T* p, size_t) -> T* { + return p; +} +#endif + +// Attempts to reserve space for n extra characters in the output range. +// Returns a pointer to the reserved range or a reference to it. +template ::value)> +#if FMT_CLANG_VERSION >= 307 && !FMT_ICC_VERSION +__attribute__((no_sanitize("undefined"))) +#endif +inline auto +reserve(std::back_insert_iterator it, size_t n) + -> checked_ptr { + Container& c = get_container(it); + size_t size = c.size(); + c.resize(size + n); + return make_checked(get_data(c) + size, n); +} + +template +inline auto reserve(buffer_appender it, size_t n) -> buffer_appender { + buffer& buf = get_container(it); + buf.try_reserve(buf.size() + n); + return it; +} + +template +constexpr auto reserve(Iterator& it, size_t) -> Iterator& { + return it; +} + +template +using reserve_iterator = + remove_reference_t(), 0))>; + +template +constexpr auto to_pointer(OutputIt, size_t) -> T* { + return nullptr; +} +template auto to_pointer(buffer_appender it, size_t n) -> T* { + buffer& buf = get_container(it); + auto size = buf.size(); + if (buf.capacity() < size + n) return nullptr; + buf.try_resize(size + n); + return buf.data() + size; +} + +template ::value)> +inline auto base_iterator(std::back_insert_iterator& it, + checked_ptr) + -> std::back_insert_iterator { + return it; +} + +template +constexpr auto base_iterator(Iterator, Iterator it) -> Iterator { + return it; +} + +// is spectacularly slow to compile in C++20 so use a simple fill_n +// instead (#1998). +template +FMT_CONSTEXPR auto fill_n(OutputIt out, Size count, const T& value) + -> OutputIt { + for (Size i = 0; i < count; ++i) *out++ = value; + return out; +} +template +FMT_CONSTEXPR20 auto fill_n(T* out, Size count, char value) -> T* { + if (is_constant_evaluated()) { + return fill_n(out, count, value); + } + std::memset(out, value, to_unsigned(count)); + return out + count; +} + +#ifdef __cpp_char8_t +using char8_type = char8_t; +#else +enum char8_type : unsigned char {}; +#endif + +template +FMT_CONSTEXPR FMT_NOINLINE auto copy_str_noinline(InputIt begin, InputIt end, + OutputIt out) -> OutputIt { + return copy_str(begin, end, out); +} + +// A public domain branchless UTF-8 decoder by Christopher Wellons: +// https://github.com/skeeto/branchless-utf8 +/* Decode the next character, c, from s, reporting errors in e. + * + * Since this is a branchless decoder, four bytes will be read from the + * buffer regardless of the actual length of the next character. This + * means the buffer _must_ have at least three bytes of zero padding + * following the end of the data stream. + * + * Errors are reported in e, which will be non-zero if the parsed + * character was somehow invalid: invalid byte sequence, non-canonical + * encoding, or a surrogate half. + * + * The function returns a pointer to the next character. When an error + * occurs, this pointer will be a guess that depends on the particular + * error, but it will always advance at least one byte. + */ +FMT_CONSTEXPR inline auto utf8_decode(const char* s, uint32_t* c, int* e) + -> const char* { + constexpr const int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07}; + constexpr const uint32_t mins[] = {4194304, 0, 128, 2048, 65536}; + constexpr const int shiftc[] = {0, 18, 12, 6, 0}; + constexpr const int shifte[] = {0, 6, 4, 2, 0}; + + int len = code_point_length_impl(*s); + // Compute the pointer to the next character early so that the next + // iteration can start working on the next character. Neither Clang + // nor GCC figure out this reordering on their own. + const char* next = s + len + !len; + + using uchar = unsigned char; + + // Assume a four-byte character and load four bytes. Unused bits are + // shifted out. + *c = uint32_t(uchar(s[0]) & masks[len]) << 18; + *c |= uint32_t(uchar(s[1]) & 0x3f) << 12; + *c |= uint32_t(uchar(s[2]) & 0x3f) << 6; + *c |= uint32_t(uchar(s[3]) & 0x3f) << 0; + *c >>= shiftc[len]; + + // Accumulate the various error conditions. + *e = (*c < mins[len]) << 6; // non-canonical encoding + *e |= ((*c >> 11) == 0x1b) << 7; // surrogate half? + *e |= (*c > 0x10FFFF) << 8; // out of range? + *e |= (uchar(s[1]) & 0xc0) >> 2; + *e |= (uchar(s[2]) & 0xc0) >> 4; + *e |= uchar(s[3]) >> 6; + *e ^= 0x2a; // top two bits of each tail byte correct? + *e >>= shifte[len]; + + return next; +} + +constexpr uint32_t invalid_code_point = ~uint32_t(); + +// Invokes f(cp, sv) for every code point cp in s with sv being the string view +// corresponding to the code point. cp is invalid_code_point on error. +template +FMT_CONSTEXPR void for_each_codepoint(string_view s, F f) { + auto decode = [f](const char* buf_ptr, const char* ptr) { + auto cp = uint32_t(); + auto error = 0; + auto end = utf8_decode(buf_ptr, &cp, &error); + bool result = f(error ? invalid_code_point : cp, + string_view(ptr, error ? 1 : to_unsigned(end - buf_ptr))); + return result ? (error ? buf_ptr + 1 : end) : nullptr; + }; + auto p = s.data(); + const size_t block_size = 4; // utf8_decode always reads blocks of 4 chars. + if (s.size() >= block_size) { + for (auto end = p + s.size() - block_size + 1; p < end;) { + p = decode(p, p); + if (!p) return; + } + } + if (auto num_chars_left = s.data() + s.size() - p) { + char buf[2 * block_size - 1] = {}; + copy_str(p, p + num_chars_left, buf); + const char* buf_ptr = buf; + do { + auto end = decode(buf_ptr, p); + if (!end) return; + p += end - buf_ptr; + buf_ptr = end; + } while (buf_ptr - buf < num_chars_left); + } +} + +template +inline auto compute_width(basic_string_view s) -> size_t { + return s.size(); +} + +// Computes approximate display width of a UTF-8 string. +FMT_CONSTEXPR inline size_t compute_width(string_view s) { + size_t num_code_points = 0; + // It is not a lambda for compatibility with C++14. + struct count_code_points { + size_t* count; + FMT_CONSTEXPR auto operator()(uint32_t cp, string_view) const -> bool { + *count += detail::to_unsigned( + 1 + + (cp >= 0x1100 && + (cp <= 0x115f || // Hangul Jamo init. consonants + cp == 0x2329 || // LEFT-POINTING ANGLE BRACKET + cp == 0x232a || // RIGHT-POINTING ANGLE BRACKET + // CJK ... Yi except IDEOGRAPHIC HALF FILL SPACE: + (cp >= 0x2e80 && cp <= 0xa4cf && cp != 0x303f) || + (cp >= 0xac00 && cp <= 0xd7a3) || // Hangul Syllables + (cp >= 0xf900 && cp <= 0xfaff) || // CJK Compatibility Ideographs + (cp >= 0xfe10 && cp <= 0xfe19) || // Vertical Forms + (cp >= 0xfe30 && cp <= 0xfe6f) || // CJK Compatibility Forms + (cp >= 0xff00 && cp <= 0xff60) || // Fullwidth Forms + (cp >= 0xffe0 && cp <= 0xffe6) || // Fullwidth Forms + (cp >= 0x20000 && cp <= 0x2fffd) || // CJK + (cp >= 0x30000 && cp <= 0x3fffd) || + // Miscellaneous Symbols and Pictographs + Emoticons: + (cp >= 0x1f300 && cp <= 0x1f64f) || + // Supplemental Symbols and Pictographs: + (cp >= 0x1f900 && cp <= 0x1f9ff)))); + return true; + } + }; + for_each_codepoint(s, count_code_points{&num_code_points}); + return num_code_points; +} + +inline auto compute_width(basic_string_view s) -> size_t { + return compute_width( + string_view(reinterpret_cast(s.data()), s.size())); +} + +template +inline auto code_point_index(basic_string_view s, size_t n) -> size_t { + size_t size = s.size(); + return n < size ? n : size; +} + +// Calculates the index of the nth code point in a UTF-8 string. +inline auto code_point_index(string_view s, size_t n) -> size_t { + const char* data = s.data(); + size_t num_code_points = 0; + for (size_t i = 0, size = s.size(); i != size; ++i) { + if ((data[i] & 0xc0) != 0x80 && ++num_code_points > n) return i; + } + return s.size(); +} + +inline auto code_point_index(basic_string_view s, size_t n) + -> size_t { + return code_point_index( + string_view(reinterpret_cast(s.data()), s.size()), n); +} + +#ifndef FMT_USE_FLOAT128 +# ifdef __SIZEOF_FLOAT128__ +# define FMT_USE_FLOAT128 1 +# else +# define FMT_USE_FLOAT128 0 +# endif +#endif +#if FMT_USE_FLOAT128 +using float128 = __float128; +#else +using float128 = void; +#endif +template using is_float128 = std::is_same; + +template +using is_floating_point = + bool_constant::value || is_float128::value>; + +template ::value> +struct is_fast_float : bool_constant::is_iec559 && + sizeof(T) <= sizeof(double)> {}; +template struct is_fast_float : std::false_type {}; + +template +using is_double_double = bool_constant::digits == 106>; + +#ifndef FMT_USE_FULL_CACHE_DRAGONBOX +# define FMT_USE_FULL_CACHE_DRAGONBOX 0 +#endif + +template +template +void buffer::append(const U* begin, const U* end) { + while (begin != end) { + auto count = to_unsigned(end - begin); + try_reserve(size_ + count); + auto free_cap = capacity_ - size_; + if (free_cap < count) count = free_cap; + std::uninitialized_copy_n(begin, count, make_checked(ptr_ + size_, count)); + size_ += count; + begin += count; + } +} + +template +struct is_locale : std::false_type {}; +template +struct is_locale> : std::true_type {}; +} // namespace detail + +FMT_MODULE_EXPORT_BEGIN + +// The number of characters to store in the basic_memory_buffer object itself +// to avoid dynamic memory allocation. +enum { inline_buffer_size = 500 }; + +/** + \rst + A dynamically growing memory buffer for trivially copyable/constructible types + with the first ``SIZE`` elements stored in the object itself. + + You can use the ``memory_buffer`` type alias for ``char`` instead. + + **Example**:: + + auto out = fmt::memory_buffer(); + format_to(std::back_inserter(out), "The answer is {}.", 42); + + This will append the following output to the ``out`` object: + + .. code-block:: none + + The answer is 42. + + The output can be converted to an ``std::string`` with ``to_string(out)``. + \endrst + */ +template > +class basic_memory_buffer final : public detail::buffer { + private: + T store_[SIZE]; + + // Don't inherit from Allocator avoid generating type_info for it. + Allocator alloc_; + + // Deallocate memory allocated by the buffer. + FMT_CONSTEXPR20 void deallocate() { + T* data = this->data(); + if (data != store_) alloc_.deallocate(data, this->capacity()); + } + + protected: + FMT_CONSTEXPR20 void grow(size_t size) override; + + public: + using value_type = T; + using const_reference = const T&; + + FMT_CONSTEXPR20 explicit basic_memory_buffer( + const Allocator& alloc = Allocator()) + : alloc_(alloc) { + this->set(store_, SIZE); + if (detail::is_constant_evaluated()) detail::fill_n(store_, SIZE, T()); + } + FMT_CONSTEXPR20 ~basic_memory_buffer() { deallocate(); } + + private: + // Move data from other to this buffer. + FMT_CONSTEXPR20 void move(basic_memory_buffer& other) { + alloc_ = std::move(other.alloc_); + T* data = other.data(); + size_t size = other.size(), capacity = other.capacity(); + if (data == other.store_) { + this->set(store_, capacity); + detail::copy_str(other.store_, other.store_ + size, + detail::make_checked(store_, capacity)); + } else { + this->set(data, capacity); + // Set pointer to the inline array so that delete is not called + // when deallocating. + other.set(other.store_, 0); + other.clear(); + } + this->resize(size); + } + + public: + /** + \rst + Constructs a :class:`fmt::basic_memory_buffer` object moving the content + of the other object to it. + \endrst + */ + FMT_CONSTEXPR20 basic_memory_buffer(basic_memory_buffer&& other) noexcept { + move(other); + } + + /** + \rst + Moves the content of the other ``basic_memory_buffer`` object to this one. + \endrst + */ + auto operator=(basic_memory_buffer&& other) noexcept -> basic_memory_buffer& { + FMT_ASSERT(this != &other, ""); + deallocate(); + move(other); + return *this; + } + + // Returns a copy of the allocator associated with this buffer. + auto get_allocator() const -> Allocator { return alloc_; } + + /** + Resizes the buffer to contain *count* elements. If T is a POD type new + elements may not be initialized. + */ + FMT_CONSTEXPR20 void resize(size_t count) { this->try_resize(count); } + + /** Increases the buffer capacity to *new_capacity*. */ + void reserve(size_t new_capacity) { this->try_reserve(new_capacity); } + + // Directly append data into the buffer + using detail::buffer::append; + template + void append(const ContiguousRange& range) { + append(range.data(), range.data() + range.size()); + } +}; + +template +FMT_CONSTEXPR20 void basic_memory_buffer::grow( + size_t size) { + detail::abort_fuzzing_if(size > 5000); + const size_t max_size = std::allocator_traits::max_size(alloc_); + size_t old_capacity = this->capacity(); + size_t new_capacity = old_capacity + old_capacity / 2; + if (size > new_capacity) + new_capacity = size; + else if (new_capacity > max_size) + new_capacity = size > max_size ? size : max_size; + T* old_data = this->data(); + T* new_data = + std::allocator_traits::allocate(alloc_, new_capacity); + // The following code doesn't throw, so the raw pointer above doesn't leak. + std::uninitialized_copy(old_data, old_data + this->size(), + detail::make_checked(new_data, new_capacity)); + this->set(new_data, new_capacity); + // deallocate must not throw according to the standard, but even if it does, + // the buffer already uses the new storage and will deallocate it in + // destructor. + if (old_data != store_) alloc_.deallocate(old_data, old_capacity); +} + +using memory_buffer = basic_memory_buffer; + +template +struct is_contiguous> : std::true_type { +}; + +namespace detail { +#ifdef _WIN32 +FMT_API bool write_console(std::FILE* f, string_view text); +#endif +FMT_API void print(std::FILE*, string_view); +} // namespace detail + +/** A formatting error such as invalid format string. */ +FMT_CLASS_API +class FMT_API format_error : public std::runtime_error { + public: + explicit format_error(const char* message) : std::runtime_error(message) {} + explicit format_error(const std::string& message) + : std::runtime_error(message) {} + format_error(const format_error&) = default; + format_error& operator=(const format_error&) = default; + format_error(format_error&&) = default; + format_error& operator=(format_error&&) = default; + ~format_error() noexcept override FMT_MSC_DEFAULT; +}; + +namespace detail_exported { +#if FMT_USE_NONTYPE_TEMPLATE_ARGS +template struct fixed_string { + constexpr fixed_string(const Char (&str)[N]) { + detail::copy_str(static_cast(str), + str + N, data); + } + Char data[N] = {}; +}; +#endif + +// Converts a compile-time string to basic_string_view. +template +constexpr auto compile_string_to_view(const Char (&s)[N]) + -> basic_string_view { + // Remove trailing NUL character if needed. Won't be present if this is used + // with a raw character array (i.e. not defined as a string). + return {s, N - (std::char_traits::to_int_type(s[N - 1]) == 0 ? 1 : 0)}; +} +template +constexpr auto compile_string_to_view(detail::std_string_view s) + -> basic_string_view { + return {s.data(), s.size()}; +} +} // namespace detail_exported + +FMT_BEGIN_DETAIL_NAMESPACE + +template struct is_integral : std::is_integral {}; +template <> struct is_integral : std::true_type {}; +template <> struct is_integral : std::true_type {}; + +template +using is_signed = + std::integral_constant::is_signed || + std::is_same::value>; + +// Returns true if value is negative, false otherwise. +// Same as `value < 0` but doesn't produce warnings if T is an unsigned type. +template ::value)> +constexpr auto is_negative(T value) -> bool { + return value < 0; +} +template ::value)> +constexpr auto is_negative(T) -> bool { + return false; +} + +template +FMT_CONSTEXPR auto is_supported_floating_point(T) -> bool { + if (std::is_same()) return FMT_USE_FLOAT; + if (std::is_same()) return FMT_USE_DOUBLE; + if (std::is_same()) return FMT_USE_LONG_DOUBLE; + return true; +} + +// Smallest of uint32_t, uint64_t, uint128_t that is large enough to +// represent all values of an integral type T. +template +using uint32_or_64_or_128_t = + conditional_t() <= 32 && !FMT_REDUCE_INT_INSTANTIATIONS, + uint32_t, + conditional_t() <= 64, uint64_t, uint128_t>>; +template +using uint64_or_128_t = conditional_t() <= 64, uint64_t, uint128_t>; + +#define FMT_POWERS_OF_10(factor) \ + factor * 10, (factor)*100, (factor)*1000, (factor)*10000, (factor)*100000, \ + (factor)*1000000, (factor)*10000000, (factor)*100000000, \ + (factor)*1000000000 + +// Converts value in the range [0, 100) to a string. +constexpr const char* digits2(size_t value) { + // GCC generates slightly better code when value is pointer-size. + return &"0001020304050607080910111213141516171819" + "2021222324252627282930313233343536373839" + "4041424344454647484950515253545556575859" + "6061626364656667686970717273747576777879" + "8081828384858687888990919293949596979899"[value * 2]; +} + +// Sign is a template parameter to workaround a bug in gcc 4.8. +template constexpr Char sign(Sign s) { +#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 604 + static_assert(std::is_same::value, ""); +#endif + return static_cast("\0-+ "[s]); +} + +template FMT_CONSTEXPR auto count_digits_fallback(T n) -> int { + int count = 1; + for (;;) { + // Integer division is slow so do it for a group of four digits instead + // of for every digit. The idea comes from the talk by Alexandrescu + // "Three Optimization Tips for C++". See speed-test for a comparison. + if (n < 10) return count; + if (n < 100) return count + 1; + if (n < 1000) return count + 2; + if (n < 10000) return count + 3; + n /= 10000u; + count += 4; + } +} +#if FMT_USE_INT128 +FMT_CONSTEXPR inline auto count_digits(uint128_opt n) -> int { + return count_digits_fallback(n); +} +#endif + +#ifdef FMT_BUILTIN_CLZLL +// It is a separate function rather than a part of count_digits to workaround +// the lack of static constexpr in constexpr functions. +inline auto do_count_digits(uint64_t n) -> int { + // This has comparable performance to the version by Kendall Willets + // (https://github.com/fmtlib/format-benchmark/blob/master/digits10) + // but uses smaller tables. + // Maps bsr(n) to ceil(log10(pow(2, bsr(n) + 1) - 1)). + static constexpr uint8_t bsr2log10[] = { + 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, + 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, + 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 15, 15, + 15, 16, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 19, 20}; + auto t = bsr2log10[FMT_BUILTIN_CLZLL(n | 1) ^ 63]; + static constexpr const uint64_t zero_or_powers_of_10[] = { + 0, 0, FMT_POWERS_OF_10(1U), FMT_POWERS_OF_10(1000000000ULL), + 10000000000000000000ULL}; + return t - (n < zero_or_powers_of_10[t]); +} +#endif + +// Returns the number of decimal digits in n. Leading zeros are not counted +// except for n == 0 in which case count_digits returns 1. +FMT_CONSTEXPR20 inline auto count_digits(uint64_t n) -> int { +#ifdef FMT_BUILTIN_CLZLL + if (!is_constant_evaluated()) { + return do_count_digits(n); + } +#endif + return count_digits_fallback(n); +} + +// Counts the number of digits in n. BITS = log2(radix). +template +FMT_CONSTEXPR auto count_digits(UInt n) -> int { +#ifdef FMT_BUILTIN_CLZ + if (!is_constant_evaluated() && num_bits() == 32) + return (FMT_BUILTIN_CLZ(static_cast(n) | 1) ^ 31) / BITS + 1; +#endif + // Lambda avoids unreachable code warnings from NVHPC. + return [](UInt m) { + int num_digits = 0; + do { + ++num_digits; + } while ((m >>= BITS) != 0); + return num_digits; + }(n); +} + +#ifdef FMT_BUILTIN_CLZ +// It is a separate function rather than a part of count_digits to workaround +// the lack of static constexpr in constexpr functions. +FMT_INLINE auto do_count_digits(uint32_t n) -> int { +// An optimization by Kendall Willets from https://bit.ly/3uOIQrB. +// This increments the upper 32 bits (log10(T) - 1) when >= T is added. +# define FMT_INC(T) (((sizeof(# T) - 1ull) << 32) - T) + static constexpr uint64_t table[] = { + FMT_INC(0), FMT_INC(0), FMT_INC(0), // 8 + FMT_INC(10), FMT_INC(10), FMT_INC(10), // 64 + FMT_INC(100), FMT_INC(100), FMT_INC(100), // 512 + FMT_INC(1000), FMT_INC(1000), FMT_INC(1000), // 4096 + FMT_INC(10000), FMT_INC(10000), FMT_INC(10000), // 32k + FMT_INC(100000), FMT_INC(100000), FMT_INC(100000), // 256k + FMT_INC(1000000), FMT_INC(1000000), FMT_INC(1000000), // 2048k + FMT_INC(10000000), FMT_INC(10000000), FMT_INC(10000000), // 16M + FMT_INC(100000000), FMT_INC(100000000), FMT_INC(100000000), // 128M + FMT_INC(1000000000), FMT_INC(1000000000), FMT_INC(1000000000), // 1024M + FMT_INC(1000000000), FMT_INC(1000000000) // 4B + }; + auto inc = table[FMT_BUILTIN_CLZ(n | 1) ^ 31]; + return static_cast((n + inc) >> 32); +} +#endif + +// Optional version of count_digits for better performance on 32-bit platforms. +FMT_CONSTEXPR20 inline auto count_digits(uint32_t n) -> int { +#ifdef FMT_BUILTIN_CLZ + if (!is_constant_evaluated()) { + return do_count_digits(n); + } +#endif + return count_digits_fallback(n); +} + +template constexpr auto digits10() noexcept -> int { + return std::numeric_limits::digits10; +} +template <> constexpr auto digits10() noexcept -> int { return 38; } +template <> constexpr auto digits10() noexcept -> int { return 38; } + +template struct thousands_sep_result { + std::string grouping; + Char thousands_sep; +}; + +template +FMT_API auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result; +template +inline auto thousands_sep(locale_ref loc) -> thousands_sep_result { + auto result = thousands_sep_impl(loc); + return {result.grouping, Char(result.thousands_sep)}; +} +template <> +inline auto thousands_sep(locale_ref loc) -> thousands_sep_result { + return thousands_sep_impl(loc); +} + +template +FMT_API auto decimal_point_impl(locale_ref loc) -> Char; +template inline auto decimal_point(locale_ref loc) -> Char { + return Char(decimal_point_impl(loc)); +} +template <> inline auto decimal_point(locale_ref loc) -> wchar_t { + return decimal_point_impl(loc); +} + +// Compares two characters for equality. +template auto equal2(const Char* lhs, const char* rhs) -> bool { + return lhs[0] == Char(rhs[0]) && lhs[1] == Char(rhs[1]); +} +inline auto equal2(const char* lhs, const char* rhs) -> bool { + return memcmp(lhs, rhs, 2) == 0; +} + +// Copies two characters from src to dst. +template +FMT_CONSTEXPR20 FMT_INLINE void copy2(Char* dst, const char* src) { + if (!is_constant_evaluated() && sizeof(Char) == sizeof(char)) { + memcpy(dst, src, 2); + return; + } + *dst++ = static_cast(*src++); + *dst = static_cast(*src); +} + +template struct format_decimal_result { + Iterator begin; + Iterator end; +}; + +// Formats a decimal unsigned integer value writing into out pointing to a +// buffer of specified size. The caller must ensure that the buffer is large +// enough. +template +FMT_CONSTEXPR20 auto format_decimal(Char* out, UInt value, int size) + -> format_decimal_result { + FMT_ASSERT(size >= count_digits(value), "invalid digit count"); + out += size; + Char* end = out; + while (value >= 100) { + // Integer division is slow so do it for a group of two digits instead + // of for every digit. The idea comes from the talk by Alexandrescu + // "Three Optimization Tips for C++". See speed-test for a comparison. + out -= 2; + copy2(out, digits2(static_cast(value % 100))); + value /= 100; + } + if (value < 10) { + *--out = static_cast('0' + value); + return {out, end}; + } + out -= 2; + copy2(out, digits2(static_cast(value))); + return {out, end}; +} + +template >::value)> +FMT_CONSTEXPR inline auto format_decimal(Iterator out, UInt value, int size) + -> format_decimal_result { + // Buffer is large enough to hold all digits (digits10 + 1). + Char buffer[digits10() + 1]; + auto end = format_decimal(buffer, value, size).end; + return {out, detail::copy_str_noinline(buffer, end, out)}; +} + +template +FMT_CONSTEXPR auto format_uint(Char* buffer, UInt value, int num_digits, + bool upper = false) -> Char* { + buffer += num_digits; + Char* end = buffer; + do { + const char* digits = upper ? "0123456789ABCDEF" : "0123456789abcdef"; + unsigned digit = static_cast(value & ((1 << BASE_BITS) - 1)); + *--buffer = static_cast(BASE_BITS < 4 ? static_cast('0' + digit) + : digits[digit]); + } while ((value >>= BASE_BITS) != 0); + return end; +} + +template +inline auto format_uint(It out, UInt value, int num_digits, bool upper = false) + -> It { + if (auto ptr = to_pointer(out, to_unsigned(num_digits))) { + format_uint(ptr, value, num_digits, upper); + return out; + } + // Buffer should be large enough to hold all digits (digits / BASE_BITS + 1). + char buffer[num_bits() / BASE_BITS + 1]; + format_uint(buffer, value, num_digits, upper); + return detail::copy_str_noinline(buffer, buffer + num_digits, out); +} + +// A converter from UTF-8 to UTF-16. +class utf8_to_utf16 { + private: + basic_memory_buffer buffer_; + + public: + FMT_API explicit utf8_to_utf16(string_view s); + operator basic_string_view() const { return {&buffer_[0], size()}; } + auto size() const -> size_t { return buffer_.size() - 1; } + auto c_str() const -> const wchar_t* { return &buffer_[0]; } + auto str() const -> std::wstring { return {&buffer_[0], size()}; } +}; + +namespace dragonbox { + +// Type-specific information that Dragonbox uses. +template struct float_info; + +template <> struct float_info { + using carrier_uint = uint32_t; + static const int exponent_bits = 8; + static const int kappa = 1; + static const int big_divisor = 100; + static const int small_divisor = 10; + static const int min_k = -31; + static const int max_k = 46; + static const int shorter_interval_tie_lower_threshold = -35; + static const int shorter_interval_tie_upper_threshold = -35; +}; + +template <> struct float_info { + using carrier_uint = uint64_t; + static const int exponent_bits = 11; + static const int kappa = 2; + static const int big_divisor = 1000; + static const int small_divisor = 100; + static const int min_k = -292; + static const int max_k = 326; + static const int shorter_interval_tie_lower_threshold = -77; + static const int shorter_interval_tie_upper_threshold = -77; +}; + +// An 80- or 128-bit floating point number. +template +struct float_info::digits == 64 || + std::numeric_limits::digits == 113 || + is_float128::value>> { + using carrier_uint = detail::uint128_t; + static const int exponent_bits = 15; +}; + +// A double-double floating point number. +template +struct float_info::value>> { + using carrier_uint = detail::uint128_t; +}; + +template struct decimal_fp { + using significand_type = typename float_info::carrier_uint; + significand_type significand; + int exponent; +}; + +template FMT_API auto to_decimal(T x) noexcept -> decimal_fp; +} // namespace dragonbox + +// Returns true iff Float has the implicit bit which is not stored. +template constexpr bool has_implicit_bit() { + // An 80-bit FP number has a 64-bit significand an no implicit bit. + return std::numeric_limits::digits != 64; +} + +// Returns the number of significand bits stored in Float. The implicit bit is +// not counted since it is not stored. +template constexpr int num_significand_bits() { + // std::numeric_limits may not support __float128. + return is_float128() ? 112 + : (std::numeric_limits::digits - + (has_implicit_bit() ? 1 : 0)); +} + +template +constexpr auto exponent_mask() -> + typename dragonbox::float_info::carrier_uint { + using uint = typename dragonbox::float_info::carrier_uint; + return ((uint(1) << dragonbox::float_info::exponent_bits) - 1) + << num_significand_bits(); +} +template constexpr auto exponent_bias() -> int { + // std::numeric_limits may not support __float128. + return is_float128() ? 16383 + : std::numeric_limits::max_exponent - 1; +} + +// Writes the exponent exp in the form "[+-]d{2,3}" to buffer. +template +FMT_CONSTEXPR auto write_exponent(int exp, It it) -> It { + FMT_ASSERT(-10000 < exp && exp < 10000, "exponent out of range"); + if (exp < 0) { + *it++ = static_cast('-'); + exp = -exp; + } else { + *it++ = static_cast('+'); + } + if (exp >= 100) { + const char* top = digits2(to_unsigned(exp / 100)); + if (exp >= 1000) *it++ = static_cast(top[0]); + *it++ = static_cast(top[1]); + exp %= 100; + } + const char* d = digits2(to_unsigned(exp)); + *it++ = static_cast(d[0]); + *it++ = static_cast(d[1]); + return it; +} + +// A floating-point number f * pow(2, e) where F is an unsigned type. +template struct basic_fp { + F f; + int e; + + static constexpr const int num_significand_bits = + static_cast(sizeof(F) * num_bits()); + + constexpr basic_fp() : f(0), e(0) {} + constexpr basic_fp(uint64_t f_val, int e_val) : f(f_val), e(e_val) {} + + // Constructs fp from an IEEE754 floating-point number. + template FMT_CONSTEXPR basic_fp(Float n) { assign(n); } + + // Assigns n to this and return true iff predecessor is closer than successor. + template ::value)> + FMT_CONSTEXPR auto assign(Float n) -> bool { + static_assert(std::numeric_limits::digits <= 113, "unsupported FP"); + // Assume Float is in the format [sign][exponent][significand]. + using carrier_uint = typename dragonbox::float_info::carrier_uint; + const auto num_float_significand_bits = + detail::num_significand_bits(); + const auto implicit_bit = carrier_uint(1) << num_float_significand_bits; + const auto significand_mask = implicit_bit - 1; + auto u = bit_cast(n); + f = static_cast(u & significand_mask); + auto biased_e = static_cast((u & exponent_mask()) >> + num_float_significand_bits); + // The predecessor is closer if n is a normalized power of 2 (f == 0) + // other than the smallest normalized number (biased_e > 1). + auto is_predecessor_closer = f == 0 && biased_e > 1; + if (biased_e == 0) + biased_e = 1; // Subnormals use biased exponent 1 (min exponent). + else if (has_implicit_bit()) + f += static_cast(implicit_bit); + e = biased_e - exponent_bias() - num_float_significand_bits; + if (!has_implicit_bit()) ++e; + return is_predecessor_closer; + } + + template ::value)> + FMT_CONSTEXPR auto assign(Float n) -> bool { + static_assert(std::numeric_limits::is_iec559, "unsupported FP"); + return assign(static_cast(n)); + } +}; + +using fp = basic_fp; + +// Normalizes the value converted from double and multiplied by (1 << SHIFT). +template +FMT_CONSTEXPR basic_fp normalize(basic_fp value) { + // Handle subnormals. + const auto implicit_bit = F(1) << num_significand_bits(); + const auto shifted_implicit_bit = implicit_bit << SHIFT; + while ((value.f & shifted_implicit_bit) == 0) { + value.f <<= 1; + --value.e; + } + // Subtract 1 to account for hidden bit. + const auto offset = basic_fp::num_significand_bits - + num_significand_bits() - SHIFT - 1; + value.f <<= offset; + value.e -= offset; + return value; +} + +// Computes lhs * rhs / pow(2, 64) rounded to nearest with half-up tie breaking. +FMT_CONSTEXPR inline uint64_t multiply(uint64_t lhs, uint64_t rhs) { +#if FMT_USE_INT128 + auto product = static_cast<__uint128_t>(lhs) * rhs; + auto f = static_cast(product >> 64); + return (static_cast(product) & (1ULL << 63)) != 0 ? f + 1 : f; +#else + // Multiply 32-bit parts of significands. + uint64_t mask = (1ULL << 32) - 1; + uint64_t a = lhs >> 32, b = lhs & mask; + uint64_t c = rhs >> 32, d = rhs & mask; + uint64_t ac = a * c, bc = b * c, ad = a * d, bd = b * d; + // Compute mid 64-bit of result and round. + uint64_t mid = (bd >> 32) + (ad & mask) + (bc & mask) + (1U << 31); + return ac + (ad >> 32) + (bc >> 32) + (mid >> 32); +#endif +} + +FMT_CONSTEXPR inline fp operator*(fp x, fp y) { + return {multiply(x.f, y.f), x.e + y.e + 64}; +} + +template struct basic_data { + // Normalized 64-bit significands of pow(10, k), for k = -348, -340, ..., 340. + // These are generated by support/compute-powers.py. + static constexpr uint64_t pow10_significands[87] = { + 0xfa8fd5a0081c0288, 0xbaaee17fa23ebf76, 0x8b16fb203055ac76, + 0xcf42894a5dce35ea, 0x9a6bb0aa55653b2d, 0xe61acf033d1a45df, + 0xab70fe17c79ac6ca, 0xff77b1fcbebcdc4f, 0xbe5691ef416bd60c, + 0x8dd01fad907ffc3c, 0xd3515c2831559a83, 0x9d71ac8fada6c9b5, + 0xea9c227723ee8bcb, 0xaecc49914078536d, 0x823c12795db6ce57, + 0xc21094364dfb5637, 0x9096ea6f3848984f, 0xd77485cb25823ac7, + 0xa086cfcd97bf97f4, 0xef340a98172aace5, 0xb23867fb2a35b28e, + 0x84c8d4dfd2c63f3b, 0xc5dd44271ad3cdba, 0x936b9fcebb25c996, + 0xdbac6c247d62a584, 0xa3ab66580d5fdaf6, 0xf3e2f893dec3f126, + 0xb5b5ada8aaff80b8, 0x87625f056c7c4a8b, 0xc9bcff6034c13053, + 0x964e858c91ba2655, 0xdff9772470297ebd, 0xa6dfbd9fb8e5b88f, + 0xf8a95fcf88747d94, 0xb94470938fa89bcf, 0x8a08f0f8bf0f156b, + 0xcdb02555653131b6, 0x993fe2c6d07b7fac, 0xe45c10c42a2b3b06, + 0xaa242499697392d3, 0xfd87b5f28300ca0e, 0xbce5086492111aeb, + 0x8cbccc096f5088cc, 0xd1b71758e219652c, 0x9c40000000000000, + 0xe8d4a51000000000, 0xad78ebc5ac620000, 0x813f3978f8940984, + 0xc097ce7bc90715b3, 0x8f7e32ce7bea5c70, 0xd5d238a4abe98068, + 0x9f4f2726179a2245, 0xed63a231d4c4fb27, 0xb0de65388cc8ada8, + 0x83c7088e1aab65db, 0xc45d1df942711d9a, 0x924d692ca61be758, + 0xda01ee641a708dea, 0xa26da3999aef774a, 0xf209787bb47d6b85, + 0xb454e4a179dd1877, 0x865b86925b9bc5c2, 0xc83553c5c8965d3d, + 0x952ab45cfa97a0b3, 0xde469fbd99a05fe3, 0xa59bc234db398c25, + 0xf6c69a72a3989f5c, 0xb7dcbf5354e9bece, 0x88fcf317f22241e2, + 0xcc20ce9bd35c78a5, 0x98165af37b2153df, 0xe2a0b5dc971f303a, + 0xa8d9d1535ce3b396, 0xfb9b7cd9a4a7443c, 0xbb764c4ca7a44410, + 0x8bab8eefb6409c1a, 0xd01fef10a657842c, 0x9b10a4e5e9913129, + 0xe7109bfba19c0c9d, 0xac2820d9623bf429, 0x80444b5e7aa7cf85, + 0xbf21e44003acdd2d, 0x8e679c2f5e44ff8f, 0xd433179d9c8cb841, + 0x9e19db92b4e31ba9, 0xeb96bf6ebadf77d9, 0xaf87023b9bf0ee6b, + }; + +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wnarrowing" +#endif + // Binary exponents of pow(10, k), for k = -348, -340, ..., 340, corresponding + // to significands above. + static constexpr int16_t pow10_exponents[87] = { + -1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, -954, + -927, -901, -874, -847, -821, -794, -768, -741, -715, -688, -661, + -635, -608, -582, -555, -529, -502, -475, -449, -422, -396, -369, + -343, -316, -289, -263, -236, -210, -183, -157, -130, -103, -77, + -50, -24, 3, 30, 56, 83, 109, 136, 162, 189, 216, + 242, 269, 295, 322, 348, 375, 402, 428, 455, 481, 508, + 534, 561, 588, 614, 641, 667, 694, 720, 747, 774, 800, + 827, 853, 880, 907, 933, 960, 986, 1013, 1039, 1066}; +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 +# pragma GCC diagnostic pop +#endif + + static constexpr uint64_t power_of_10_64[20] = { + 1, FMT_POWERS_OF_10(1ULL), FMT_POWERS_OF_10(1000000000ULL), + 10000000000000000000ULL}; +}; + +#if FMT_CPLUSPLUS < 201703L +template constexpr uint64_t basic_data::pow10_significands[]; +template constexpr int16_t basic_data::pow10_exponents[]; +template constexpr uint64_t basic_data::power_of_10_64[]; +#endif + +// This is a struct rather than an alias to avoid shadowing warnings in gcc. +struct data : basic_data<> {}; + +// Returns a cached power of 10 `c_k = c_k.f * pow(2, c_k.e)` such that its +// (binary) exponent satisfies `min_exponent <= c_k.e <= min_exponent + 28`. +FMT_CONSTEXPR inline fp get_cached_power(int min_exponent, + int& pow10_exponent) { + const int shift = 32; + // log10(2) = 0x0.4d104d427de7fbcc... + const int64_t significand = 0x4d104d427de7fbcc; + int index = static_cast( + ((min_exponent + fp::num_significand_bits - 1) * (significand >> shift) + + ((int64_t(1) << shift) - 1)) // ceil + >> 32 // arithmetic shift + ); + // Decimal exponent of the first (smallest) cached power of 10. + const int first_dec_exp = -348; + // Difference between 2 consecutive decimal exponents in cached powers of 10. + const int dec_exp_step = 8; + index = (index - first_dec_exp - 1) / dec_exp_step + 1; + pow10_exponent = first_dec_exp + index * dec_exp_step; + // Using *(x + index) instead of x[index] avoids an issue with some compilers + // using the EDG frontend (e.g. nvhpc/22.3 in C++17 mode). + return {*(data::pow10_significands + index), + *(data::pow10_exponents + index)}; +} + +#ifndef _MSC_VER +# define FMT_SNPRINTF snprintf +#else +FMT_API auto fmt_snprintf(char* buf, size_t size, const char* fmt, ...) -> int; +# define FMT_SNPRINTF fmt_snprintf +#endif // _MSC_VER + +// Formats a floating-point number with snprintf using the hexfloat format. +template +auto snprintf_float(T value, int precision, float_specs specs, + buffer& buf) -> int { + // Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail. + FMT_ASSERT(buf.capacity() > buf.size(), "empty buffer"); + FMT_ASSERT(specs.format == float_format::hex, ""); + static_assert(!std::is_same::value, ""); + + // Build the format string. + char format[7]; // The longest format is "%#.*Le". + char* format_ptr = format; + *format_ptr++ = '%'; + if (specs.showpoint) *format_ptr++ = '#'; + if (precision >= 0) { + *format_ptr++ = '.'; + *format_ptr++ = '*'; + } + if (std::is_same()) *format_ptr++ = 'L'; + *format_ptr++ = specs.upper ? 'A' : 'a'; + *format_ptr = '\0'; + + // Format using snprintf. + auto offset = buf.size(); + for (;;) { + auto begin = buf.data() + offset; + auto capacity = buf.capacity() - offset; + abort_fuzzing_if(precision > 100000); + // Suppress the warning about a nonliteral format string. + // Cannot use auto because of a bug in MinGW (#1532). + int (*snprintf_ptr)(char*, size_t, const char*, ...) = FMT_SNPRINTF; + int result = precision >= 0 + ? snprintf_ptr(begin, capacity, format, precision, value) + : snprintf_ptr(begin, capacity, format, value); + if (result < 0) { + // The buffer will grow exponentially. + buf.try_reserve(buf.capacity() + 1); + continue; + } + auto size = to_unsigned(result); + // Size equal to capacity means that the last character was truncated. + if (size < capacity) { + buf.try_resize(size + offset); + return 0; + } + buf.try_reserve(size + offset + 1); // Add 1 for the terminating '\0'. + } +} + +template +using convert_float_result = + conditional_t::value || sizeof(T) == sizeof(double), + double, T>; + +template +constexpr auto convert_float(T value) -> convert_float_result { + return static_cast>(value); +} + +template +FMT_NOINLINE FMT_CONSTEXPR auto fill(OutputIt it, size_t n, + const fill_t& fill) -> OutputIt { + auto fill_size = fill.size(); + if (fill_size == 1) return detail::fill_n(it, n, fill[0]); + auto data = fill.data(); + for (size_t i = 0; i < n; ++i) + it = copy_str(data, data + fill_size, it); + return it; +} + +// Writes the output of f, padded according to format specifications in specs. +// size: output size in code units. +// width: output display width in (terminal) column positions. +template +FMT_CONSTEXPR auto write_padded(OutputIt out, + const basic_format_specs& specs, + size_t size, size_t width, F&& f) -> OutputIt { + static_assert(align == align::left || align == align::right, ""); + unsigned spec_width = to_unsigned(specs.width); + size_t padding = spec_width > width ? spec_width - width : 0; + // Shifts are encoded as string literals because static constexpr is not + // supported in constexpr functions. + auto* shifts = align == align::left ? "\x1f\x1f\x00\x01" : "\x00\x1f\x00\x01"; + size_t left_padding = padding >> shifts[specs.align]; + size_t right_padding = padding - left_padding; + auto it = reserve(out, size + padding * specs.fill.size()); + if (left_padding != 0) it = fill(it, left_padding, specs.fill); + it = f(it); + if (right_padding != 0) it = fill(it, right_padding, specs.fill); + return base_iterator(out, it); +} + +template +constexpr auto write_padded(OutputIt out, const basic_format_specs& specs, + size_t size, F&& f) -> OutputIt { + return write_padded(out, specs, size, size, f); +} + +template +FMT_CONSTEXPR auto write_bytes(OutputIt out, string_view bytes, + const basic_format_specs& specs) + -> OutputIt { + return write_padded( + out, specs, bytes.size(), [bytes](reserve_iterator it) { + const char* data = bytes.data(); + return copy_str(data, data + bytes.size(), it); + }); +} + +template +auto write_ptr(OutputIt out, UIntPtr value, + const basic_format_specs* specs) -> OutputIt { + int num_digits = count_digits<4>(value); + auto size = to_unsigned(num_digits) + size_t(2); + auto write = [=](reserve_iterator it) { + *it++ = static_cast('0'); + *it++ = static_cast('x'); + return format_uint<4, Char>(it, value, num_digits); + }; + return specs ? write_padded(out, *specs, size, write) + : base_iterator(out, write(reserve(out, size))); +} + +// Returns true iff the code point cp is printable. +FMT_API auto is_printable(uint32_t cp) -> bool; + +inline auto needs_escape(uint32_t cp) -> bool { + return cp < 0x20 || cp == 0x7f || cp == '"' || cp == '\\' || + !is_printable(cp); +} + +template struct find_escape_result { + const Char* begin; + const Char* end; + uint32_t cp; +}; + +template +using make_unsigned_char = + typename conditional_t::value, + std::make_unsigned, + type_identity>::type; + +template +auto find_escape(const Char* begin, const Char* end) + -> find_escape_result { + for (; begin != end; ++begin) { + uint32_t cp = static_cast>(*begin); + if (const_check(sizeof(Char) == 1) && cp >= 0x80) continue; + if (needs_escape(cp)) return {begin, begin + 1, cp}; + } + return {begin, nullptr, 0}; +} + +inline auto find_escape(const char* begin, const char* end) + -> find_escape_result { + if (!is_utf8()) return find_escape(begin, end); + auto result = find_escape_result{end, nullptr, 0}; + for_each_codepoint(string_view(begin, to_unsigned(end - begin)), + [&](uint32_t cp, string_view sv) { + if (needs_escape(cp)) { + result = {sv.begin(), sv.end(), cp}; + return false; + } + return true; + }); + return result; +} + +#define FMT_STRING_IMPL(s, base, explicit) \ + [] { \ + /* Use the hidden visibility as a workaround for a GCC bug (#1973). */ \ + /* Use a macro-like name to avoid shadowing warnings. */ \ + struct FMT_GCC_VISIBILITY_HIDDEN FMT_COMPILE_STRING : base { \ + using char_type FMT_MAYBE_UNUSED = fmt::remove_cvref_t; \ + FMT_MAYBE_UNUSED FMT_CONSTEXPR explicit \ + operator fmt::basic_string_view() const { \ + return fmt::detail_exported::compile_string_to_view(s); \ + } \ + }; \ + return FMT_COMPILE_STRING(); \ + }() + +/** + \rst + Constructs a compile-time format string from a string literal *s*. + + **Example**:: + + // A compile-time error because 'd' is an invalid specifier for strings. + std::string s = fmt::format(FMT_STRING("{:d}"), "foo"); + \endrst + */ +#define FMT_STRING(s) FMT_STRING_IMPL(s, fmt::detail::compile_string, ) + +template +auto write_codepoint(OutputIt out, char prefix, uint32_t cp) -> OutputIt { + *out++ = static_cast('\\'); + *out++ = static_cast(prefix); + Char buf[width]; + fill_n(buf, width, static_cast('0')); + format_uint<4>(buf, cp, width); + return copy_str(buf, buf + width, out); +} + +template +auto write_escaped_cp(OutputIt out, const find_escape_result& escape) + -> OutputIt { + auto c = static_cast(escape.cp); + switch (escape.cp) { + case '\n': + *out++ = static_cast('\\'); + c = static_cast('n'); + break; + case '\r': + *out++ = static_cast('\\'); + c = static_cast('r'); + break; + case '\t': + *out++ = static_cast('\\'); + c = static_cast('t'); + break; + case '"': + FMT_FALLTHROUGH; + case '\'': + FMT_FALLTHROUGH; + case '\\': + *out++ = static_cast('\\'); + break; + default: + if (is_utf8()) { + if (escape.cp < 0x100) { + return write_codepoint<2, Char>(out, 'x', escape.cp); + } + if (escape.cp < 0x10000) { + return write_codepoint<4, Char>(out, 'u', escape.cp); + } + if (escape.cp < 0x110000) { + return write_codepoint<8, Char>(out, 'U', escape.cp); + } + } + for (Char escape_char : basic_string_view( + escape.begin, to_unsigned(escape.end - escape.begin))) { + out = write_codepoint<2, Char>(out, 'x', + static_cast(escape_char) & 0xFF); + } + return out; + } + *out++ = c; + return out; +} + +template +auto write_escaped_string(OutputIt out, basic_string_view str) + -> OutputIt { + *out++ = static_cast('"'); + auto begin = str.begin(), end = str.end(); + do { + auto escape = find_escape(begin, end); + out = copy_str(begin, escape.begin, out); + begin = escape.end; + if (!begin) break; + out = write_escaped_cp(out, escape); + } while (begin != end); + *out++ = static_cast('"'); + return out; +} + +template +auto write_escaped_char(OutputIt out, Char v) -> OutputIt { + *out++ = static_cast('\''); + if ((needs_escape(static_cast(v)) && v != static_cast('"')) || + v == static_cast('\'')) { + out = write_escaped_cp( + out, find_escape_result{&v, &v + 1, static_cast(v)}); + } else { + *out++ = v; + } + *out++ = static_cast('\''); + return out; +} + +template +FMT_CONSTEXPR auto write_char(OutputIt out, Char value, + const basic_format_specs& specs) + -> OutputIt { + bool is_debug = specs.type == presentation_type::debug; + return write_padded(out, specs, 1, [=](reserve_iterator it) { + if (is_debug) return write_escaped_char(it, value); + *it++ = value; + return it; + }); +} +template +FMT_CONSTEXPR auto write(OutputIt out, Char value, + const basic_format_specs& specs, + locale_ref loc = {}) -> OutputIt { + return check_char_specs(specs) + ? write_char(out, value, specs) + : write(out, static_cast(value), specs, loc); +} + +// Data for write_int that doesn't depend on output iterator type. It is used to +// avoid template code bloat. +template struct write_int_data { + size_t size; + size_t padding; + + FMT_CONSTEXPR write_int_data(int num_digits, unsigned prefix, + const basic_format_specs& specs) + : size((prefix >> 24) + to_unsigned(num_digits)), padding(0) { + if (specs.align == align::numeric) { + auto width = to_unsigned(specs.width); + if (width > size) { + padding = width - size; + size = width; + } + } else if (specs.precision > num_digits) { + size = (prefix >> 24) + to_unsigned(specs.precision); + padding = to_unsigned(specs.precision - num_digits); + } + } +}; + +// Writes an integer in the format +// +// where are written by write_digits(it). +// prefix contains chars in three lower bytes and the size in the fourth byte. +template +FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, int num_digits, + unsigned prefix, + const basic_format_specs& specs, + W write_digits) -> OutputIt { + // Slightly faster check for specs.width == 0 && specs.precision == -1. + if ((specs.width | (specs.precision + 1)) == 0) { + auto it = reserve(out, to_unsigned(num_digits) + (prefix >> 24)); + if (prefix != 0) { + for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) + *it++ = static_cast(p & 0xff); + } + return base_iterator(out, write_digits(it)); + } + auto data = write_int_data(num_digits, prefix, specs); + return write_padded( + out, specs, data.size, [=](reserve_iterator it) { + for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) + *it++ = static_cast(p & 0xff); + it = detail::fill_n(it, data.padding, static_cast('0')); + return write_digits(it); + }); +} + +template class digit_grouping { + private: + thousands_sep_result sep_; + + struct next_state { + std::string::const_iterator group; + int pos; + }; + next_state initial_state() const { return {sep_.grouping.begin(), 0}; } + + // Returns the next digit group separator position. + int next(next_state& state) const { + if (!sep_.thousands_sep) return max_value(); + if (state.group == sep_.grouping.end()) + return state.pos += sep_.grouping.back(); + if (*state.group <= 0 || *state.group == max_value()) + return max_value(); + state.pos += *state.group++; + return state.pos; + } + + public: + explicit digit_grouping(locale_ref loc, bool localized = true) { + if (localized) + sep_ = thousands_sep(loc); + else + sep_.thousands_sep = Char(); + } + explicit digit_grouping(thousands_sep_result sep) : sep_(sep) {} + + Char separator() const { return sep_.thousands_sep; } + + int count_separators(int num_digits) const { + int count = 0; + auto state = initial_state(); + while (num_digits > next(state)) ++count; + return count; + } + + // Applies grouping to digits and write the output to out. + template + Out apply(Out out, basic_string_view digits) const { + auto num_digits = static_cast(digits.size()); + auto separators = basic_memory_buffer(); + separators.push_back(0); + auto state = initial_state(); + while (int i = next(state)) { + if (i >= num_digits) break; + separators.push_back(i); + } + for (int i = 0, sep_index = static_cast(separators.size() - 1); + i < num_digits; ++i) { + if (num_digits - i == separators[sep_index]) { + *out++ = separator(); + --sep_index; + } + *out++ = static_cast(digits[to_unsigned(i)]); + } + return out; + } +}; + +template +auto write_int_localized(OutputIt out, UInt value, unsigned prefix, + const basic_format_specs& specs, + const digit_grouping& grouping) -> OutputIt { + static_assert(std::is_same, UInt>::value, ""); + int num_digits = count_digits(value); + char digits[40]; + format_decimal(digits, value, num_digits); + unsigned size = to_unsigned((prefix != 0 ? 1 : 0) + num_digits + + grouping.count_separators(num_digits)); + return write_padded( + out, specs, size, size, [&](reserve_iterator it) { + if (prefix != 0) { + char sign = static_cast(prefix); + *it++ = static_cast(sign); + } + return grouping.apply(it, string_view(digits, to_unsigned(num_digits))); + }); +} + +template +auto write_int_localized(OutputIt& out, UInt value, unsigned prefix, + const basic_format_specs& specs, locale_ref loc) + -> bool { + auto grouping = digit_grouping(loc); + out = write_int_localized(out, value, prefix, specs, grouping); + return true; +} + +FMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) { + prefix |= prefix != 0 ? value << 8 : value; + prefix += (1u + (value > 0xff ? 1 : 0)) << 24; +} + +template struct write_int_arg { + UInt abs_value; + unsigned prefix; +}; + +template +FMT_CONSTEXPR auto make_write_int_arg(T value, sign_t sign) + -> write_int_arg> { + auto prefix = 0u; + auto abs_value = static_cast>(value); + if (is_negative(value)) { + prefix = 0x01000000 | '-'; + abs_value = 0 - abs_value; + } else { + constexpr const unsigned prefixes[4] = {0, 0, 0x1000000u | '+', + 0x1000000u | ' '}; + prefix = prefixes[sign]; + } + return {abs_value, prefix}; +} + +template +FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg arg, + const basic_format_specs& specs, + locale_ref loc) -> OutputIt { + static_assert(std::is_same>::value, ""); + auto abs_value = arg.abs_value; + auto prefix = arg.prefix; + switch (specs.type) { + case presentation_type::none: + case presentation_type::dec: { + if (specs.localized && + write_int_localized(out, static_cast>(abs_value), + prefix, specs, loc)) { + return out; + } + auto num_digits = count_digits(abs_value); + return write_int( + out, num_digits, prefix, specs, [=](reserve_iterator it) { + return format_decimal(it, abs_value, num_digits).end; + }); + } + case presentation_type::hex_lower: + case presentation_type::hex_upper: { + bool upper = specs.type == presentation_type::hex_upper; + if (specs.alt) + prefix_append(prefix, unsigned(upper ? 'X' : 'x') << 8 | '0'); + int num_digits = count_digits<4>(abs_value); + return write_int( + out, num_digits, prefix, specs, [=](reserve_iterator it) { + return format_uint<4, Char>(it, abs_value, num_digits, upper); + }); + } + case presentation_type::bin_lower: + case presentation_type::bin_upper: { + bool upper = specs.type == presentation_type::bin_upper; + if (specs.alt) + prefix_append(prefix, unsigned(upper ? 'B' : 'b') << 8 | '0'); + int num_digits = count_digits<1>(abs_value); + return write_int(out, num_digits, prefix, specs, + [=](reserve_iterator it) { + return format_uint<1, Char>(it, abs_value, num_digits); + }); + } + case presentation_type::oct: { + int num_digits = count_digits<3>(abs_value); + // Octal prefix '0' is counted as a digit, so only add it if precision + // is not greater than the number of digits. + if (specs.alt && specs.precision <= num_digits && abs_value != 0) + prefix_append(prefix, '0'); + return write_int(out, num_digits, prefix, specs, + [=](reserve_iterator it) { + return format_uint<3, Char>(it, abs_value, num_digits); + }); + } + case presentation_type::chr: + return write_char(out, static_cast(abs_value), specs); + default: + throw_format_error("invalid type specifier"); + } + return out; +} +template +FMT_CONSTEXPR FMT_NOINLINE auto write_int_noinline( + OutputIt out, write_int_arg arg, const basic_format_specs& specs, + locale_ref loc) -> OutputIt { + return write_int(out, arg, specs, loc); +} +template ::value && + !std::is_same::value && + std::is_same>::value)> +FMT_CONSTEXPR FMT_INLINE auto write(OutputIt out, T value, + const basic_format_specs& specs, + locale_ref loc) -> OutputIt { + return write_int_noinline(out, make_write_int_arg(value, specs.sign), specs, + loc); +} +// An inlined version of write used in format string compilation. +template ::value && + !std::is_same::value && + !std::is_same>::value)> +FMT_CONSTEXPR FMT_INLINE auto write(OutputIt out, T value, + const basic_format_specs& specs, + locale_ref loc) -> OutputIt { + return write_int(out, make_write_int_arg(value, specs.sign), specs, loc); +} + +// An output iterator that counts the number of objects written to it and +// discards them. +class counting_iterator { + private: + size_t count_; + + public: + using iterator_category = std::output_iterator_tag; + using difference_type = std::ptrdiff_t; + using pointer = void; + using reference = void; + FMT_UNCHECKED_ITERATOR(counting_iterator); + + struct value_type { + template FMT_CONSTEXPR void operator=(const T&) {} + }; + + FMT_CONSTEXPR counting_iterator() : count_(0) {} + + FMT_CONSTEXPR size_t count() const { return count_; } + + FMT_CONSTEXPR counting_iterator& operator++() { + ++count_; + return *this; + } + FMT_CONSTEXPR counting_iterator operator++(int) { + auto it = *this; + ++*this; + return it; + } + + FMT_CONSTEXPR friend counting_iterator operator+(counting_iterator it, + difference_type n) { + it.count_ += static_cast(n); + return it; + } + + FMT_CONSTEXPR value_type operator*() const { return {}; } +}; + +template +FMT_CONSTEXPR auto write(OutputIt out, basic_string_view s, + const basic_format_specs& specs) -> OutputIt { + auto data = s.data(); + auto size = s.size(); + if (specs.precision >= 0 && to_unsigned(specs.precision) < size) + size = code_point_index(s, to_unsigned(specs.precision)); + bool is_debug = specs.type == presentation_type::debug; + size_t width = 0; + if (specs.width != 0) { + if (is_debug) + width = write_escaped_string(counting_iterator{}, s).count(); + else + width = compute_width(basic_string_view(data, size)); + } + return write_padded(out, specs, size, width, + [=](reserve_iterator it) { + if (is_debug) return write_escaped_string(it, s); + return copy_str(data, data + size, it); + }); +} +template +FMT_CONSTEXPR auto write(OutputIt out, + basic_string_view> s, + const basic_format_specs& specs, locale_ref) + -> OutputIt { + check_string_type_spec(specs.type); + return write(out, s, specs); +} +template +FMT_CONSTEXPR auto write(OutputIt out, const Char* s, + const basic_format_specs& specs, locale_ref) + -> OutputIt { + return check_cstring_type_spec(specs.type) + ? write(out, basic_string_view(s), specs, {}) + : write_ptr(out, bit_cast(s), &specs); +} + +template ::value && + !std::is_same::value && + !std::is_same::value)> +FMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt { + auto abs_value = static_cast>(value); + bool negative = is_negative(value); + // Don't do -abs_value since it trips unsigned-integer-overflow sanitizer. + if (negative) abs_value = ~abs_value + 1; + int num_digits = count_digits(abs_value); + auto size = (negative ? 1 : 0) + static_cast(num_digits); + auto it = reserve(out, size); + if (auto ptr = to_pointer(it, size)) { + if (negative) *ptr++ = static_cast('-'); + format_decimal(ptr, abs_value, num_digits); + return out; + } + if (negative) *it++ = static_cast('-'); + it = format_decimal(it, abs_value, num_digits).end; + return base_iterator(out, it); +} + +template +FMT_CONSTEXPR20 auto write_nonfinite(OutputIt out, bool isnan, + basic_format_specs specs, + const float_specs& fspecs) -> OutputIt { + auto str = + isnan ? (fspecs.upper ? "NAN" : "nan") : (fspecs.upper ? "INF" : "inf"); + constexpr size_t str_size = 3; + auto sign = fspecs.sign; + auto size = str_size + (sign ? 1 : 0); + // Replace '0'-padding with space for non-finite values. + const bool is_zero_fill = + specs.fill.size() == 1 && *specs.fill.data() == static_cast('0'); + if (is_zero_fill) specs.fill[0] = static_cast(' '); + return write_padded(out, specs, size, [=](reserve_iterator it) { + if (sign) *it++ = detail::sign(sign); + return copy_str(str, str + str_size, it); + }); +} + +// A decimal floating-point number significand * pow(10, exp). +struct big_decimal_fp { + const char* significand; + int significand_size; + int exponent; +}; + +constexpr auto get_significand_size(const big_decimal_fp& f) -> int { + return f.significand_size; +} +template +inline auto get_significand_size(const dragonbox::decimal_fp& f) -> int { + return count_digits(f.significand); +} + +template +constexpr auto write_significand(OutputIt out, const char* significand, + int significand_size) -> OutputIt { + return copy_str(significand, significand + significand_size, out); +} +template +inline auto write_significand(OutputIt out, UInt significand, + int significand_size) -> OutputIt { + return format_decimal(out, significand, significand_size).end; +} +template +FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, + int significand_size, int exponent, + const Grouping& grouping) -> OutputIt { + if (!grouping.separator()) { + out = write_significand(out, significand, significand_size); + return detail::fill_n(out, exponent, static_cast('0')); + } + auto buffer = memory_buffer(); + write_significand(appender(buffer), significand, significand_size); + detail::fill_n(appender(buffer), exponent, '0'); + return grouping.apply(out, string_view(buffer.data(), buffer.size())); +} + +template ::value)> +inline auto write_significand(Char* out, UInt significand, int significand_size, + int integral_size, Char decimal_point) -> Char* { + if (!decimal_point) + return format_decimal(out, significand, significand_size).end; + out += significand_size + 1; + Char* end = out; + int floating_size = significand_size - integral_size; + for (int i = floating_size / 2; i > 0; --i) { + out -= 2; + copy2(out, digits2(static_cast(significand % 100))); + significand /= 100; + } + if (floating_size % 2 != 0) { + *--out = static_cast('0' + significand % 10); + significand /= 10; + } + *--out = decimal_point; + format_decimal(out - integral_size, significand, integral_size); + return end; +} + +template >::value)> +inline auto write_significand(OutputIt out, UInt significand, + int significand_size, int integral_size, + Char decimal_point) -> OutputIt { + // Buffer is large enough to hold digits (digits10 + 1) and a decimal point. + Char buffer[digits10() + 2]; + auto end = write_significand(buffer, significand, significand_size, + integral_size, decimal_point); + return detail::copy_str_noinline(buffer, end, out); +} + +template +FMT_CONSTEXPR auto write_significand(OutputIt out, const char* significand, + int significand_size, int integral_size, + Char decimal_point) -> OutputIt { + out = detail::copy_str_noinline(significand, + significand + integral_size, out); + if (!decimal_point) return out; + *out++ = decimal_point; + return detail::copy_str_noinline(significand + integral_size, + significand + significand_size, out); +} + +template +FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, + int significand_size, int integral_size, + Char decimal_point, + const Grouping& grouping) -> OutputIt { + if (!grouping.separator()) { + return write_significand(out, significand, significand_size, integral_size, + decimal_point); + } + auto buffer = basic_memory_buffer(); + write_significand(buffer_appender(buffer), significand, + significand_size, integral_size, decimal_point); + grouping.apply( + out, basic_string_view(buffer.data(), to_unsigned(integral_size))); + return detail::copy_str_noinline(buffer.data() + integral_size, + buffer.end(), out); +} + +template > +FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f, + const basic_format_specs& specs, + float_specs fspecs, locale_ref loc) + -> OutputIt { + auto significand = f.significand; + int significand_size = get_significand_size(f); + const Char zero = static_cast('0'); + auto sign = fspecs.sign; + size_t size = to_unsigned(significand_size) + (sign ? 1 : 0); + using iterator = reserve_iterator; + + Char decimal_point = + fspecs.locale ? detail::decimal_point(loc) : static_cast('.'); + + int output_exp = f.exponent + significand_size - 1; + auto use_exp_format = [=]() { + if (fspecs.format == float_format::exp) return true; + if (fspecs.format != float_format::general) return false; + // Use the fixed notation if the exponent is in [exp_lower, exp_upper), + // e.g. 0.0001 instead of 1e-04. Otherwise use the exponent notation. + const int exp_lower = -4, exp_upper = 16; + return output_exp < exp_lower || + output_exp >= (fspecs.precision > 0 ? fspecs.precision : exp_upper); + }; + if (use_exp_format()) { + int num_zeros = 0; + if (fspecs.showpoint) { + num_zeros = fspecs.precision - significand_size; + if (num_zeros < 0) num_zeros = 0; + size += to_unsigned(num_zeros); + } else if (significand_size == 1) { + decimal_point = Char(); + } + auto abs_output_exp = output_exp >= 0 ? output_exp : -output_exp; + int exp_digits = 2; + if (abs_output_exp >= 100) exp_digits = abs_output_exp >= 1000 ? 4 : 3; + + size += to_unsigned((decimal_point ? 1 : 0) + 2 + exp_digits); + char exp_char = fspecs.upper ? 'E' : 'e'; + auto write = [=](iterator it) { + if (sign) *it++ = detail::sign(sign); + // Insert a decimal point after the first digit and add an exponent. + it = write_significand(it, significand, significand_size, 1, + decimal_point); + if (num_zeros > 0) it = detail::fill_n(it, num_zeros, zero); + *it++ = static_cast(exp_char); + return write_exponent(output_exp, it); + }; + return specs.width > 0 ? write_padded(out, specs, size, write) + : base_iterator(out, write(reserve(out, size))); + } + + int exp = f.exponent + significand_size; + if (f.exponent >= 0) { + // 1234e5 -> 123400000[.0+] + size += to_unsigned(f.exponent); + int num_zeros = fspecs.precision - exp; + abort_fuzzing_if(num_zeros > 5000); + if (fspecs.showpoint) { + ++size; + if (num_zeros <= 0 && fspecs.format != float_format::fixed) num_zeros = 1; + if (num_zeros > 0) size += to_unsigned(num_zeros); + } + auto grouping = Grouping(loc, fspecs.locale); + size += to_unsigned(grouping.count_separators(exp)); + return write_padded(out, specs, size, [&](iterator it) { + if (sign) *it++ = detail::sign(sign); + it = write_significand(it, significand, significand_size, + f.exponent, grouping); + if (!fspecs.showpoint) return it; + *it++ = decimal_point; + return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it; + }); + } else if (exp > 0) { + // 1234e-2 -> 12.34[0+] + int num_zeros = fspecs.showpoint ? fspecs.precision - significand_size : 0; + size += 1 + to_unsigned(num_zeros > 0 ? num_zeros : 0); + auto grouping = Grouping(loc, fspecs.locale); + size += to_unsigned(grouping.count_separators(significand_size)); + return write_padded(out, specs, size, [&](iterator it) { + if (sign) *it++ = detail::sign(sign); + it = write_significand(it, significand, significand_size, exp, + decimal_point, grouping); + return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it; + }); + } + // 1234e-6 -> 0.001234 + int num_zeros = -exp; + if (significand_size == 0 && fspecs.precision >= 0 && + fspecs.precision < num_zeros) { + num_zeros = fspecs.precision; + } + bool pointy = num_zeros != 0 || significand_size != 0 || fspecs.showpoint; + size += 1 + (pointy ? 1 : 0) + to_unsigned(num_zeros); + return write_padded(out, specs, size, [&](iterator it) { + if (sign) *it++ = detail::sign(sign); + *it++ = zero; + if (!pointy) return it; + *it++ = decimal_point; + it = detail::fill_n(it, num_zeros, zero); + return write_significand(it, significand, significand_size); + }); +} + +template class fallback_digit_grouping { + public: + constexpr fallback_digit_grouping(locale_ref, bool) {} + + constexpr Char separator() const { return Char(); } + + constexpr int count_separators(int) const { return 0; } + + template + constexpr Out apply(Out out, basic_string_view) const { + return out; + } +}; + +template +FMT_CONSTEXPR20 auto write_float(OutputIt out, const DecimalFP& f, + const basic_format_specs& specs, + float_specs fspecs, locale_ref loc) + -> OutputIt { + if (is_constant_evaluated()) { + return do_write_float>(out, f, specs, fspecs, + loc); + } else { + return do_write_float(out, f, specs, fspecs, loc); + } +} + +template constexpr bool isnan(T value) { + return !(value >= value); // std::isnan doesn't support __float128. +} + +template +struct has_isfinite : std::false_type {}; + +template +struct has_isfinite> + : std::true_type {}; + +template ::value&& + has_isfinite::value)> +FMT_CONSTEXPR20 bool isfinite(T value) { + constexpr T inf = T(std::numeric_limits::infinity()); + if (is_constant_evaluated()) + return !detail::isnan(value) && value != inf && value != -inf; + return std::isfinite(value); +} +template ::value)> +FMT_CONSTEXPR bool isfinite(T value) { + T inf = T(std::numeric_limits::infinity()); + // std::isfinite doesn't support __float128. + return !detail::isnan(value) && value != inf && value != -inf; +} + +template ::value)> +FMT_INLINE FMT_CONSTEXPR bool signbit(T value) { + if (is_constant_evaluated()) { +#ifdef __cpp_if_constexpr + if constexpr (std::numeric_limits::is_iec559) { + auto bits = detail::bit_cast(static_cast(value)); + return (bits >> (num_bits() - 1)) != 0; + } +#endif + } + return std::signbit(static_cast(value)); +} + +enum class round_direction { unknown, up, down }; + +// Given the divisor (normally a power of 10), the remainder = v % divisor for +// some number v and the error, returns whether v should be rounded up, down, or +// whether the rounding direction can't be determined due to error. +// error should be less than divisor / 2. +FMT_CONSTEXPR inline round_direction get_round_direction(uint64_t divisor, + uint64_t remainder, + uint64_t error) { + FMT_ASSERT(remainder < divisor, ""); // divisor - remainder won't overflow. + FMT_ASSERT(error < divisor, ""); // divisor - error won't overflow. + FMT_ASSERT(error < divisor - error, ""); // error * 2 won't overflow. + // Round down if (remainder + error) * 2 <= divisor. + if (remainder <= divisor - remainder && error * 2 <= divisor - remainder * 2) + return round_direction::down; + // Round up if (remainder - error) * 2 >= divisor. + if (remainder >= error && + remainder - error >= divisor - (remainder - error)) { + return round_direction::up; + } + return round_direction::unknown; +} + +namespace digits { +enum result { + more, // Generate more digits. + done, // Done generating digits. + error // Digit generation cancelled due to an error. +}; +} + +struct gen_digits_handler { + char* buf; + int size; + int precision; + int exp10; + bool fixed; + + FMT_CONSTEXPR digits::result on_digit(char digit, uint64_t divisor, + uint64_t remainder, uint64_t error, + bool integral) { + FMT_ASSERT(remainder < divisor, ""); + buf[size++] = digit; + if (!integral && error >= remainder) return digits::error; + if (size < precision) return digits::more; + if (!integral) { + // Check if error * 2 < divisor with overflow prevention. + // The check is not needed for the integral part because error = 1 + // and divisor > (1 << 32) there. + if (error >= divisor || error >= divisor - error) return digits::error; + } else { + FMT_ASSERT(error == 1 && divisor > 2, ""); + } + auto dir = get_round_direction(divisor, remainder, error); + if (dir != round_direction::up) + return dir == round_direction::down ? digits::done : digits::error; + ++buf[size - 1]; + for (int i = size - 1; i > 0 && buf[i] > '9'; --i) { + buf[i] = '0'; + ++buf[i - 1]; + } + if (buf[0] > '9') { + buf[0] = '1'; + if (fixed) + buf[size++] = '0'; + else + ++exp10; + } + return digits::done; + } +}; + +inline FMT_CONSTEXPR20 void adjust_precision(int& precision, int exp10) { + // Adjust fixed precision by exponent because it is relative to decimal + // point. + if (exp10 > 0 && precision > max_value() - exp10) + FMT_THROW(format_error("number is too big")); + precision += exp10; +} + +// Generates output using the Grisu digit-gen algorithm. +// error: the size of the region (lower, upper) outside of which numbers +// definitely do not round to value (Delta in Grisu3). +FMT_INLINE FMT_CONSTEXPR20 auto grisu_gen_digits(fp value, uint64_t error, + int& exp, + gen_digits_handler& handler) + -> digits::result { + const fp one(1ULL << -value.e, value.e); + // The integral part of scaled value (p1 in Grisu) = value / one. It cannot be + // zero because it contains a product of two 64-bit numbers with MSB set (due + // to normalization) - 1, shifted right by at most 60 bits. + auto integral = static_cast(value.f >> -one.e); + FMT_ASSERT(integral != 0, ""); + FMT_ASSERT(integral == value.f >> -one.e, ""); + // The fractional part of scaled value (p2 in Grisu) c = value % one. + uint64_t fractional = value.f & (one.f - 1); + exp = count_digits(integral); // kappa in Grisu. + // Non-fixed formats require at least one digit and no precision adjustment. + if (handler.fixed) { + adjust_precision(handler.precision, exp + handler.exp10); + // Check if precision is satisfied just by leading zeros, e.g. + // format("{:.2f}", 0.001) gives "0.00" without generating any digits. + if (handler.precision <= 0) { + if (handler.precision < 0) return digits::done; + // Divide by 10 to prevent overflow. + uint64_t divisor = data::power_of_10_64[exp - 1] << -one.e; + auto dir = get_round_direction(divisor, value.f / 10, error * 10); + if (dir == round_direction::unknown) return digits::error; + handler.buf[handler.size++] = dir == round_direction::up ? '1' : '0'; + return digits::done; + } + } + // Generate digits for the integral part. This can produce up to 10 digits. + do { + uint32_t digit = 0; + auto divmod_integral = [&](uint32_t divisor) { + digit = integral / divisor; + integral %= divisor; + }; + // This optimization by Milo Yip reduces the number of integer divisions by + // one per iteration. + switch (exp) { + case 10: + divmod_integral(1000000000); + break; + case 9: + divmod_integral(100000000); + break; + case 8: + divmod_integral(10000000); + break; + case 7: + divmod_integral(1000000); + break; + case 6: + divmod_integral(100000); + break; + case 5: + divmod_integral(10000); + break; + case 4: + divmod_integral(1000); + break; + case 3: + divmod_integral(100); + break; + case 2: + divmod_integral(10); + break; + case 1: + digit = integral; + integral = 0; + break; + default: + FMT_ASSERT(false, "invalid number of digits"); + } + --exp; + auto remainder = (static_cast(integral) << -one.e) + fractional; + auto result = handler.on_digit(static_cast('0' + digit), + data::power_of_10_64[exp] << -one.e, + remainder, error, true); + if (result != digits::more) return result; + } while (exp > 0); + // Generate digits for the fractional part. + for (;;) { + fractional *= 10; + error *= 10; + char digit = static_cast('0' + (fractional >> -one.e)); + fractional &= one.f - 1; + --exp; + auto result = handler.on_digit(digit, one.f, fractional, error, false); + if (result != digits::more) return result; + } +} + +class bigint { + private: + // A bigint is stored as an array of bigits (big digits), with bigit at index + // 0 being the least significant one. + using bigit = uint32_t; + using double_bigit = uint64_t; + enum { bigits_capacity = 32 }; + basic_memory_buffer bigits_; + int exp_; + + FMT_CONSTEXPR20 bigit operator[](int index) const { + return bigits_[to_unsigned(index)]; + } + FMT_CONSTEXPR20 bigit& operator[](int index) { + return bigits_[to_unsigned(index)]; + } + + static constexpr const int bigit_bits = num_bits(); + + friend struct formatter; + + FMT_CONSTEXPR20 void subtract_bigits(int index, bigit other, bigit& borrow) { + auto result = static_cast((*this)[index]) - other - borrow; + (*this)[index] = static_cast(result); + borrow = static_cast(result >> (bigit_bits * 2 - 1)); + } + + FMT_CONSTEXPR20 void remove_leading_zeros() { + int num_bigits = static_cast(bigits_.size()) - 1; + while (num_bigits > 0 && (*this)[num_bigits] == 0) --num_bigits; + bigits_.resize(to_unsigned(num_bigits + 1)); + } + + // Computes *this -= other assuming aligned bigints and *this >= other. + FMT_CONSTEXPR20 void subtract_aligned(const bigint& other) { + FMT_ASSERT(other.exp_ >= exp_, "unaligned bigints"); + FMT_ASSERT(compare(*this, other) >= 0, ""); + bigit borrow = 0; + int i = other.exp_ - exp_; + for (size_t j = 0, n = other.bigits_.size(); j != n; ++i, ++j) + subtract_bigits(i, other.bigits_[j], borrow); + while (borrow > 0) subtract_bigits(i, 0, borrow); + remove_leading_zeros(); + } + + FMT_CONSTEXPR20 void multiply(uint32_t value) { + const double_bigit wide_value = value; + bigit carry = 0; + for (size_t i = 0, n = bigits_.size(); i < n; ++i) { + double_bigit result = bigits_[i] * wide_value + carry; + bigits_[i] = static_cast(result); + carry = static_cast(result >> bigit_bits); + } + if (carry != 0) bigits_.push_back(carry); + } + + template ::value || + std::is_same::value)> + FMT_CONSTEXPR20 void multiply(UInt value) { + using half_uint = + conditional_t::value, uint64_t, uint32_t>; + const int shift = num_bits() - bigit_bits; + const UInt lower = static_cast(value); + const UInt upper = value >> num_bits(); + UInt carry = 0; + for (size_t i = 0, n = bigits_.size(); i < n; ++i) { + UInt result = lower * bigits_[i] + static_cast(carry); + carry = (upper * bigits_[i] << shift) + (result >> bigit_bits) + + (carry >> bigit_bits); + bigits_[i] = static_cast(result); + } + while (carry != 0) { + bigits_.push_back(static_cast(carry)); + carry >>= bigit_bits; + } + } + + template ::value || + std::is_same::value)> + FMT_CONSTEXPR20 void assign(UInt n) { + size_t num_bigits = 0; + do { + bigits_[num_bigits++] = static_cast(n); + n >>= bigit_bits; + } while (n != 0); + bigits_.resize(num_bigits); + exp_ = 0; + } + + public: + FMT_CONSTEXPR20 bigint() : exp_(0) {} + explicit bigint(uint64_t n) { assign(n); } + + bigint(const bigint&) = delete; + void operator=(const bigint&) = delete; + + FMT_CONSTEXPR20 void assign(const bigint& other) { + auto size = other.bigits_.size(); + bigits_.resize(size); + auto data = other.bigits_.data(); + std::copy(data, data + size, make_checked(bigits_.data(), size)); + exp_ = other.exp_; + } + + template FMT_CONSTEXPR20 void operator=(Int n) { + FMT_ASSERT(n > 0, ""); + assign(uint64_or_128_t(n)); + } + + FMT_CONSTEXPR20 int num_bigits() const { + return static_cast(bigits_.size()) + exp_; + } + + FMT_NOINLINE FMT_CONSTEXPR20 bigint& operator<<=(int shift) { + FMT_ASSERT(shift >= 0, ""); + exp_ += shift / bigit_bits; + shift %= bigit_bits; + if (shift == 0) return *this; + bigit carry = 0; + for (size_t i = 0, n = bigits_.size(); i < n; ++i) { + bigit c = bigits_[i] >> (bigit_bits - shift); + bigits_[i] = (bigits_[i] << shift) + carry; + carry = c; + } + if (carry != 0) bigits_.push_back(carry); + return *this; + } + + template FMT_CONSTEXPR20 bigint& operator*=(Int value) { + FMT_ASSERT(value > 0, ""); + multiply(uint32_or_64_or_128_t(value)); + return *this; + } + + friend FMT_CONSTEXPR20 int compare(const bigint& lhs, const bigint& rhs) { + int num_lhs_bigits = lhs.num_bigits(), num_rhs_bigits = rhs.num_bigits(); + if (num_lhs_bigits != num_rhs_bigits) + return num_lhs_bigits > num_rhs_bigits ? 1 : -1; + int i = static_cast(lhs.bigits_.size()) - 1; + int j = static_cast(rhs.bigits_.size()) - 1; + int end = i - j; + if (end < 0) end = 0; + for (; i >= end; --i, --j) { + bigit lhs_bigit = lhs[i], rhs_bigit = rhs[j]; + if (lhs_bigit != rhs_bigit) return lhs_bigit > rhs_bigit ? 1 : -1; + } + if (i != j) return i > j ? 1 : -1; + return 0; + } + + // Returns compare(lhs1 + lhs2, rhs). + friend FMT_CONSTEXPR20 int add_compare(const bigint& lhs1, const bigint& lhs2, + const bigint& rhs) { + auto minimum = [](int a, int b) { return a < b ? a : b; }; + auto maximum = [](int a, int b) { return a > b ? a : b; }; + int max_lhs_bigits = maximum(lhs1.num_bigits(), lhs2.num_bigits()); + int num_rhs_bigits = rhs.num_bigits(); + if (max_lhs_bigits + 1 < num_rhs_bigits) return -1; + if (max_lhs_bigits > num_rhs_bigits) return 1; + auto get_bigit = [](const bigint& n, int i) -> bigit { + return i >= n.exp_ && i < n.num_bigits() ? n[i - n.exp_] : 0; + }; + double_bigit borrow = 0; + int min_exp = minimum(minimum(lhs1.exp_, lhs2.exp_), rhs.exp_); + for (int i = num_rhs_bigits - 1; i >= min_exp; --i) { + double_bigit sum = + static_cast(get_bigit(lhs1, i)) + get_bigit(lhs2, i); + bigit rhs_bigit = get_bigit(rhs, i); + if (sum > rhs_bigit + borrow) return 1; + borrow = rhs_bigit + borrow - sum; + if (borrow > 1) return -1; + borrow <<= bigit_bits; + } + return borrow != 0 ? -1 : 0; + } + + // Assigns pow(10, exp) to this bigint. + FMT_CONSTEXPR20 void assign_pow10(int exp) { + FMT_ASSERT(exp >= 0, ""); + if (exp == 0) return *this = 1; + // Find the top bit. + int bitmask = 1; + while (exp >= bitmask) bitmask <<= 1; + bitmask >>= 1; + // pow(10, exp) = pow(5, exp) * pow(2, exp). First compute pow(5, exp) by + // repeated squaring and multiplication. + *this = 5; + bitmask >>= 1; + while (bitmask != 0) { + square(); + if ((exp & bitmask) != 0) *this *= 5; + bitmask >>= 1; + } + *this <<= exp; // Multiply by pow(2, exp) by shifting. + } + + FMT_CONSTEXPR20 void square() { + int num_bigits = static_cast(bigits_.size()); + int num_result_bigits = 2 * num_bigits; + basic_memory_buffer n(std::move(bigits_)); + bigits_.resize(to_unsigned(num_result_bigits)); + auto sum = uint128_t(); + for (int bigit_index = 0; bigit_index < num_bigits; ++bigit_index) { + // Compute bigit at position bigit_index of the result by adding + // cross-product terms n[i] * n[j] such that i + j == bigit_index. + for (int i = 0, j = bigit_index; j >= 0; ++i, --j) { + // Most terms are multiplied twice which can be optimized in the future. + sum += static_cast(n[i]) * n[j]; + } + (*this)[bigit_index] = static_cast(sum); + sum >>= num_bits(); // Compute the carry. + } + // Do the same for the top half. + for (int bigit_index = num_bigits; bigit_index < num_result_bigits; + ++bigit_index) { + for (int j = num_bigits - 1, i = bigit_index - j; i < num_bigits;) + sum += static_cast(n[i++]) * n[j--]; + (*this)[bigit_index] = static_cast(sum); + sum >>= num_bits(); + } + remove_leading_zeros(); + exp_ *= 2; + } + + // If this bigint has a bigger exponent than other, adds trailing zero to make + // exponents equal. This simplifies some operations such as subtraction. + FMT_CONSTEXPR20 void align(const bigint& other) { + int exp_difference = exp_ - other.exp_; + if (exp_difference <= 0) return; + int num_bigits = static_cast(bigits_.size()); + bigits_.resize(to_unsigned(num_bigits + exp_difference)); + for (int i = num_bigits - 1, j = i + exp_difference; i >= 0; --i, --j) + bigits_[j] = bigits_[i]; + std::uninitialized_fill_n(bigits_.data(), exp_difference, 0); + exp_ -= exp_difference; + } + + // Divides this bignum by divisor, assigning the remainder to this and + // returning the quotient. + FMT_CONSTEXPR20 int divmod_assign(const bigint& divisor) { + FMT_ASSERT(this != &divisor, ""); + if (compare(*this, divisor) < 0) return 0; + FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1u] != 0, ""); + align(divisor); + int quotient = 0; + do { + subtract_aligned(divisor); + ++quotient; + } while (compare(*this, divisor) >= 0); + return quotient; + } +}; + +// format_dragon flags. +enum dragon { + predecessor_closer = 1, + fixup = 2, // Run fixup to correct exp10 which can be off by one. + fixed = 4, +}; + +// Formats a floating-point number using a variation of the Fixed-Precision +// Positive Floating-Point Printout ((FPP)^2) algorithm by Steele & White: +// https://fmt.dev/papers/p372-steele.pdf. +FMT_CONSTEXPR20 inline void format_dragon(basic_fp value, + unsigned flags, int num_digits, + buffer& buf, int& exp10) { + bigint numerator; // 2 * R in (FPP)^2. + bigint denominator; // 2 * S in (FPP)^2. + // lower and upper are differences between value and corresponding boundaries. + bigint lower; // (M^- in (FPP)^2). + bigint upper_store; // upper's value if different from lower. + bigint* upper = nullptr; // (M^+ in (FPP)^2). + // Shift numerator and denominator by an extra bit or two (if lower boundary + // is closer) to make lower and upper integers. This eliminates multiplication + // by 2 during later computations. + bool is_predecessor_closer = (flags & dragon::predecessor_closer) != 0; + int shift = is_predecessor_closer ? 2 : 1; + if (value.e >= 0) { + numerator = value.f; + numerator <<= value.e + shift; + lower = 1; + lower <<= value.e; + if (is_predecessor_closer) { + upper_store = 1; + upper_store <<= value.e + 1; + upper = &upper_store; + } + denominator.assign_pow10(exp10); + denominator <<= shift; + } else if (exp10 < 0) { + numerator.assign_pow10(-exp10); + lower.assign(numerator); + if (is_predecessor_closer) { + upper_store.assign(numerator); + upper_store <<= 1; + upper = &upper_store; + } + numerator *= value.f; + numerator <<= shift; + denominator = 1; + denominator <<= shift - value.e; + } else { + numerator = value.f; + numerator <<= shift; + denominator.assign_pow10(exp10); + denominator <<= shift - value.e; + lower = 1; + if (is_predecessor_closer) { + upper_store = 1ULL << 1; + upper = &upper_store; + } + } + int even = static_cast((value.f & 1) == 0); + if (!upper) upper = &lower; + if ((flags & dragon::fixup) != 0) { + if (add_compare(numerator, *upper, denominator) + even <= 0) { + --exp10; + numerator *= 10; + if (num_digits < 0) { + lower *= 10; + if (upper != &lower) *upper *= 10; + } + } + if ((flags & dragon::fixed) != 0) adjust_precision(num_digits, exp10 + 1); + } + // Invariant: value == (numerator / denominator) * pow(10, exp10). + if (num_digits < 0) { + // Generate the shortest representation. + num_digits = 0; + char* data = buf.data(); + for (;;) { + int digit = numerator.divmod_assign(denominator); + bool low = compare(numerator, lower) - even < 0; // numerator <[=] lower. + // numerator + upper >[=] pow10: + bool high = add_compare(numerator, *upper, denominator) + even > 0; + data[num_digits++] = static_cast('0' + digit); + if (low || high) { + if (!low) { + ++data[num_digits - 1]; + } else if (high) { + int result = add_compare(numerator, numerator, denominator); + // Round half to even. + if (result > 0 || (result == 0 && (digit % 2) != 0)) + ++data[num_digits - 1]; + } + buf.try_resize(to_unsigned(num_digits)); + exp10 -= num_digits - 1; + return; + } + numerator *= 10; + lower *= 10; + if (upper != &lower) *upper *= 10; + } + } + // Generate the given number of digits. + exp10 -= num_digits - 1; + if (num_digits == 0) { + denominator *= 10; + auto digit = add_compare(numerator, numerator, denominator) > 0 ? '1' : '0'; + buf.push_back(digit); + return; + } + buf.try_resize(to_unsigned(num_digits)); + for (int i = 0; i < num_digits - 1; ++i) { + int digit = numerator.divmod_assign(denominator); + buf[i] = static_cast('0' + digit); + numerator *= 10; + } + int digit = numerator.divmod_assign(denominator); + auto result = add_compare(numerator, numerator, denominator); + if (result > 0 || (result == 0 && (digit % 2) != 0)) { + if (digit == 9) { + const auto overflow = '0' + 10; + buf[num_digits - 1] = overflow; + // Propagate the carry. + for (int i = num_digits - 1; i > 0 && buf[i] == overflow; --i) { + buf[i] = '0'; + ++buf[i - 1]; + } + if (buf[0] == overflow) { + buf[0] = '1'; + ++exp10; + } + return; + } + ++digit; + } + buf[num_digits - 1] = static_cast('0' + digit); +} + +template +FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, + buffer& buf) -> int { + // float is passed as double to reduce the number of instantiations. + static_assert(!std::is_same::value, ""); + FMT_ASSERT(value >= 0, "value is negative"); + auto converted_value = convert_float(value); + + const bool fixed = specs.format == float_format::fixed; + if (value <= 0) { // <= instead of == to silence a warning. + if (precision <= 0 || !fixed) { + buf.push_back('0'); + return 0; + } + buf.try_resize(to_unsigned(precision)); + fill_n(buf.data(), precision, '0'); + return -precision; + } + + int exp = 0; + bool use_dragon = true; + unsigned dragon_flags = 0; + if (!is_fast_float()) { + const auto inv_log2_10 = 0.3010299956639812; // 1 / log2(10) + using info = dragonbox::float_info; + const auto f = basic_fp(converted_value); + // Compute exp, an approximate power of 10, such that + // 10^(exp - 1) <= value < 10^exp or 10^exp <= value < 10^(exp + 1). + // This is based on log10(value) == log2(value) / log2(10) and approximation + // of log2(value) by e + num_fraction_bits idea from double-conversion. + exp = static_cast( + std::ceil((f.e + count_digits<1>(f.f) - 1) * inv_log2_10 - 1e-10)); + dragon_flags = dragon::fixup; + } else if (!is_constant_evaluated() && precision < 0) { + // Use Dragonbox for the shortest format. + if (specs.binary32) { + auto dec = dragonbox::to_decimal(static_cast(value)); + write(buffer_appender(buf), dec.significand); + return dec.exponent; + } + auto dec = dragonbox::to_decimal(static_cast(value)); + write(buffer_appender(buf), dec.significand); + return dec.exponent; + } else { + // Use Grisu + Dragon4 for the given precision: + // https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf. + const int min_exp = -60; // alpha in Grisu. + int cached_exp10 = 0; // K in Grisu. + fp normalized = normalize(fp(converted_value)); + const auto cached_pow = get_cached_power( + min_exp - (normalized.e + fp::num_significand_bits), cached_exp10); + normalized = normalized * cached_pow; + gen_digits_handler handler{buf.data(), 0, precision, -cached_exp10, fixed}; + if (grisu_gen_digits(normalized, 1, exp, handler) != digits::error && + !is_constant_evaluated()) { + exp += handler.exp10; + buf.try_resize(to_unsigned(handler.size)); + use_dragon = false; + } else { + exp += handler.size - cached_exp10 - 1; + precision = handler.precision; + } + } + if (use_dragon) { + auto f = basic_fp(); + bool is_predecessor_closer = specs.binary32 + ? f.assign(static_cast(value)) + : f.assign(converted_value); + if (is_predecessor_closer) dragon_flags |= dragon::predecessor_closer; + if (fixed) dragon_flags |= dragon::fixed; + // Limit precision to the maximum possible number of significant digits in + // an IEEE754 double because we don't need to generate zeros. + const int max_double_digits = 767; + if (precision > max_double_digits) precision = max_double_digits; + format_dragon(f, dragon_flags, precision, buf, exp); + } + if (!fixed && !specs.showpoint) { + // Remove trailing zeros. + auto num_digits = buf.size(); + while (num_digits > 0 && buf[num_digits - 1] == '0') { + --num_digits; + ++exp; + } + buf.try_resize(num_digits); + } + return exp; +} + +template ::value)> +FMT_CONSTEXPR20 auto write(OutputIt out, T value, + basic_format_specs specs, locale_ref loc = {}) + -> OutputIt { + if (const_check(!is_supported_floating_point(value))) return out; + float_specs fspecs = parse_float_type_spec(specs); + fspecs.sign = specs.sign; + if (detail::signbit(value)) { // value < 0 is false for NaN so use signbit. + fspecs.sign = sign::minus; + value = -value; + } else if (fspecs.sign == sign::minus) { + fspecs.sign = sign::none; + } + + if (!detail::isfinite(value)) + return write_nonfinite(out, detail::isnan(value), specs, fspecs); + + if (specs.align == align::numeric && fspecs.sign) { + auto it = reserve(out, 1); + *it++ = detail::sign(fspecs.sign); + out = base_iterator(out, it); + fspecs.sign = sign::none; + if (specs.width != 0) --specs.width; + } + + memory_buffer buffer; + if (fspecs.format == float_format::hex) { + if (fspecs.sign) buffer.push_back(detail::sign(fspecs.sign)); + snprintf_float(convert_float(value), specs.precision, fspecs, buffer); + return write_bytes(out, {buffer.data(), buffer.size()}, + specs); + } + int precision = specs.precision >= 0 || specs.type == presentation_type::none + ? specs.precision + : 6; + if (fspecs.format == float_format::exp) { + if (precision == max_value()) + throw_format_error("number is too big"); + else + ++precision; + } else if (fspecs.format != float_format::fixed && precision == 0) { + precision = 1; + } + if (const_check(std::is_same())) fspecs.binary32 = true; + int exp = format_float(convert_float(value), precision, fspecs, buffer); + fspecs.precision = precision; + auto f = big_decimal_fp{buffer.data(), static_cast(buffer.size()), exp}; + return write_float(out, f, specs, fspecs, loc); +} + +template ::value)> +FMT_CONSTEXPR20 auto write(OutputIt out, T value) -> OutputIt { + if (is_constant_evaluated()) + return write(out, value, basic_format_specs()); + if (const_check(!is_supported_floating_point(value))) return out; + + auto fspecs = float_specs(); + if (detail::signbit(value)) { + fspecs.sign = sign::minus; + value = -value; + } + + constexpr auto specs = basic_format_specs(); + using floaty = conditional_t::value, double, T>; + using uint = typename dragonbox::float_info::carrier_uint; + uint mask = exponent_mask(); + if ((bit_cast(value) & mask) == mask) + return write_nonfinite(out, std::isnan(value), specs, fspecs); + + auto dec = dragonbox::to_decimal(static_cast(value)); + return write_float(out, dec, specs, fspecs, {}); +} + +template ::value && + !is_fast_float::value)> +inline auto write(OutputIt out, T value) -> OutputIt { + return write(out, value, basic_format_specs()); +} + +template +auto write(OutputIt out, monostate, basic_format_specs = {}, + locale_ref = {}) -> OutputIt { + FMT_ASSERT(false, ""); + return out; +} + +template +FMT_CONSTEXPR auto write(OutputIt out, basic_string_view value) + -> OutputIt { + auto it = reserve(out, value.size()); + it = copy_str_noinline(value.begin(), value.end(), it); + return base_iterator(out, it); +} + +template ::value)> +constexpr auto write(OutputIt out, const T& value) -> OutputIt { + return write(out, to_string_view(value)); +} + +// FMT_ENABLE_IF() condition separated to workaround an MSVC bug. +template < + typename Char, typename OutputIt, typename T, + bool check = + std::is_enum::value && !std::is_same::value && + mapped_type_constant>::value != + type::custom_type, + FMT_ENABLE_IF(check)> +FMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt { + return write(out, static_cast>(value)); +} + +template ::value)> +FMT_CONSTEXPR auto write(OutputIt out, T value, + const basic_format_specs& specs = {}, + locale_ref = {}) -> OutputIt { + return specs.type != presentation_type::none && + specs.type != presentation_type::string + ? write(out, value ? 1 : 0, specs, {}) + : write_bytes(out, value ? "true" : "false", specs); +} + +template +FMT_CONSTEXPR auto write(OutputIt out, Char value) -> OutputIt { + auto it = reserve(out, 1); + *it++ = value; + return base_iterator(out, it); +} + +template +FMT_CONSTEXPR_CHAR_TRAITS auto write(OutputIt out, const Char* value) + -> OutputIt { + if (!value) { + throw_format_error("string pointer is null"); + } else { + out = write(out, basic_string_view(value)); + } + return out; +} + +template ::value)> +auto write(OutputIt out, const T* value, + const basic_format_specs& specs = {}, locale_ref = {}) + -> OutputIt { + check_pointer_type_spec(specs.type, error_handler()); + return write_ptr(out, bit_cast(value), &specs); +} + +// A write overload that handles implicit conversions. +template > +FMT_CONSTEXPR auto write(OutputIt out, const T& value) -> enable_if_t< + std::is_class::value && !is_string::value && + !is_floating_point::value && !std::is_same::value && + !std::is_same().map(value))>::value, + OutputIt> { + return write(out, arg_mapper().map(value)); +} + +template > +FMT_CONSTEXPR auto write(OutputIt out, const T& value) + -> enable_if_t::value == type::custom_type, + OutputIt> { + using formatter_type = + conditional_t::value, + typename Context::template formatter_type, + fallback_formatter>; + auto ctx = Context(out, {}, {}); + return formatter_type().format(value, ctx); +} + +// An argument visitor that formats the argument and writes it via the output +// iterator. It's a class and not a generic lambda for compatibility with C++11. +template struct default_arg_formatter { + using iterator = buffer_appender; + using context = buffer_context; + + iterator out; + basic_format_args args; + locale_ref loc; + + template auto operator()(T value) -> iterator { + return write(out, value); + } + auto operator()(typename basic_format_arg::handle h) -> iterator { + basic_format_parse_context parse_ctx({}); + context format_ctx(out, args, loc); + h.format(parse_ctx, format_ctx); + return format_ctx.out(); + } +}; + +template struct arg_formatter { + using iterator = buffer_appender; + using context = buffer_context; + + iterator out; + const basic_format_specs& specs; + locale_ref locale; + + template + FMT_CONSTEXPR FMT_INLINE auto operator()(T value) -> iterator { + return detail::write(out, value, specs, locale); + } + auto operator()(typename basic_format_arg::handle) -> iterator { + // User-defined types are handled separately because they require access + // to the parse context. + return out; + } +}; + +template struct custom_formatter { + basic_format_parse_context& parse_ctx; + buffer_context& ctx; + + void operator()( + typename basic_format_arg>::handle h) const { + h.format(parse_ctx, ctx); + } + template void operator()(T) const {} +}; + +template +using is_integer = + bool_constant::value && !std::is_same::value && + !std::is_same::value && + !std::is_same::value>; + +template class width_checker { + public: + explicit FMT_CONSTEXPR width_checker(ErrorHandler& eh) : handler_(eh) {} + + template ::value)> + FMT_CONSTEXPR auto operator()(T value) -> unsigned long long { + if (is_negative(value)) handler_.on_error("negative width"); + return static_cast(value); + } + + template ::value)> + FMT_CONSTEXPR auto operator()(T) -> unsigned long long { + handler_.on_error("width is not integer"); + return 0; + } + + private: + ErrorHandler& handler_; +}; + +template class precision_checker { + public: + explicit FMT_CONSTEXPR precision_checker(ErrorHandler& eh) : handler_(eh) {} + + template ::value)> + FMT_CONSTEXPR auto operator()(T value) -> unsigned long long { + if (is_negative(value)) handler_.on_error("negative precision"); + return static_cast(value); + } + + template ::value)> + FMT_CONSTEXPR auto operator()(T) -> unsigned long long { + handler_.on_error("precision is not integer"); + return 0; + } + + private: + ErrorHandler& handler_; +}; + +template