diff --git a/.github/workflows/Documenter.yml b/.github/workflows/Documenter.yml deleted file mode 100644 index 7d66c333ba..0000000000 --- a/.github/workflows/Documenter.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Documentation - -on: - push: - branches: - - 'main' - tags: '*' - paths-ignore: - - '.github/workflows/ci.yml' - - '.github/workflows/CompatHelper.yml' - pull_request: - paths-ignore: - - '.github/workflows/ci.yml' - - '.github/workflows/CompatHelper.yml' - workflow_dispatch: - -concurrency: - # Skip intermediate builds: always. - # Cancel intermediate builds: only if it is a pull request build. - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} - -jobs: - build-docs: - name: Build and Deploy Documentation - runs-on: ubuntu-latest - steps: - - name: Check out project - uses: actions/checkout@v4 - - name: Set up Julia - uses: julia-actions/setup-julia@v1 - with: - version: '1' - show-versioninfo: true - - uses: julia-actions/cache@v1 - - name: Build package - uses: julia-actions/julia-buildpkg@v1 - - name: Install dependencies - run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' - - name: Build and deploy - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} # For authentication with SSH deploy key - run: julia --project=docs --color=yes docs/make.jl diff --git a/.github/workflows/FormatCheck.yml b/.github/workflows/FormatCheck.yml deleted file mode 100644 index 9a61f2131e..0000000000 --- a/.github/workflows/FormatCheck.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Format Check - -on: - push: - branches: - - 'main' - tags: '*' - pull_request: - -jobs: - check-format: - name: Check format with JuliaFormatter.jl - runs-on: ubuntu-latest - steps: - - name: Check out project - uses: actions/checkout@v4 - - name: Set up Julia - uses: julia-actions/setup-julia@v1 - with: - version: '1' - - run: julia -e 'using InteractiveUtils; versioninfo(verbose=true)' - - uses: julia-actions/cache@v1 - - name: Install JuliaFormatter and format - # This will use the latest version by default but you can set the version like so: - # - # julia -e 'using Pkg; Pkg.add(PackageSpec(name = "JuliaFormatter", version = "0.13.0"))' - run: | - julia -e 'using Pkg; Pkg.add(PackageSpec(name = "JuliaFormatter", version="1.0.45"))' - julia -e 'using JuliaFormatter; format(".")' - - name: Format check - run: | - julia -e ' - out = Cmd(`git diff --name-only`) |> read |> String - if out == "" - exit(0) - else - @error "Some files have not been formatted !!!" - write(stdout, out) - exit(1) - end' diff --git a/.github/workflows/PaperBuild.yml b/.github/workflows/PaperBuild.yml new file mode 100644 index 0000000000..d4b06797b7 --- /dev/null +++ b/.github/workflows/PaperBuild.yml @@ -0,0 +1,38 @@ +name: Build paper + +on: [push] + +jobs: + paper: + runs-on: ubuntu-latest + name: Paper Draft + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Build draft PDF + uses: openjournals/openjournals-draft-action@master + with: + journal: joss + # This should be the path to the paper within your repo. + paper-path: paper/paper.md + - name: Upload + uses: actions/upload-artifact@v1 + with: + name: paper + # This is the output path where Pandoc will write the compiled + # PDF. Note, this should be the same directory as the input + # paper.md + path: paper/paper.pdf + - name: Copy to public folder for deployment + shell: bash + run: | + mkdir -p build + cp paper/paper.pdf build/ + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + with: + deploy_key: ${{ secrets.PAPER_2024_JOSS_PDF }} + external_repository: LasNikas/paper-2024-joss-pdf + publish_dir: ./build + publish_branch: build + force_orphan: true # Ensures that not all previous PDFs are kept around forever diff --git a/.github/workflows/SpellCheck.yml b/.github/workflows/SpellCheck.yml deleted file mode 100644 index 87e34cb50f..0000000000 --- a/.github/workflows/SpellCheck.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: Spell Check - -on: [pull_request, workflow_dispatch] - -jobs: - typos-check: - name: Spell Check with Typos - runs-on: ubuntu-latest - steps: - - name: Checkout Actions Repository - uses: actions/checkout@v4 - - name: Check spelling - uses: crate-ci/typos@v1.18.2 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 6982227923..0000000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,100 +0,0 @@ -name: CI - -on: - push: - branches: - - main - paths-ignore: - - 'AUTHORS.md' - - 'CITATION.bib' - - 'CONTRIBUTING.md' - - 'LICENSE.md' - - 'NEWS.md' - - 'README.md' - - '.github/workflows/CompatHelper.yml' - - '.github/workflows/TagBot.yml' - - 'docs/**' - pull_request: - paths-ignore: - - 'AUTHORS.md' - - 'CITATION.bib' - - 'CONTRIBUTING.md' - - 'LICENSE.md' - - 'NEWS.md' - - 'README.md' - - '.github/workflows/CompatHelper.yml' - - '.github/workflows/TagBot.yml' - - 'docs/**' - workflow_dispatch: - - -# Cancel redundant CI tests automatically -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - build: - name: Run Tests (Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }}) - runs-on: ubuntu-latest - strategy: - # Don't cancel all running jobs when one job fails - fail-fast: false - matrix: - version: - - '1.9' - - '1' - os: - - ubuntu-latest - arch: - - x64 - include: - # Also run tests on Windows and macOS-ARM, but only with the latest Julia version - - version: '1' - os: windows-latest - arch: x64 - - version: '1' - os: macos-14 - arch: arm64 - - steps: - - name: Check out project - uses: actions/checkout@v4 - - name: Set up Julia - uses: julia-actions/setup-julia@v1 - with: - version: ${{ matrix.version }} - - uses: julia-actions/cache@v1 - - name: Build package - uses: julia-actions/julia-buildpkg@v1 - - name: Run unit tests - uses: julia-actions/julia-runtest@v1 - with: - annotate: true - # Only run coverage in one Job (Ubuntu and latest Julia version) - coverage: ${{ matrix.os == 'ubuntu-latest' && matrix.version == '1' }} - env: - TRIXIPARTICLES_TEST: unit - - name: Process coverage results - # Only run coverage in one Job (Ubuntu and latest Julia version) - if: matrix.os == 'ubuntu-latest' && matrix.version == '1' - uses: julia-actions/julia-processcoverage@v1 - with: - directories: src,test - - name: Upload coverage report to Codecov - # Only run coverage in one Job (Ubuntu and latest Julia version) - if: matrix.os == 'ubuntu-latest' && matrix.version == '1' - uses: codecov/codecov-action@v4 - with: - files: lcov.info - fail_ci_if_error: true - flags: unit - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - - name: Run example tests - uses: julia-actions/julia-runtest@v1 - with: - annotate: true - coverage: false - env: - TRIXIPARTICLES_TEST: examples diff --git a/.gitignore b/.gitignore index 982d00835d..a70d6f5a2c 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,7 @@ docs/src/news.md run *.json !.zenodo.json -*.png +#*.png TODO: Find a workaround to deploy images. It doesn't work with a "issue-storage" *.jpg *.gif *.svg diff --git a/.typos.toml b/.typos.toml index 35fc776315..ce360954cf 100644 --- a/.typos.toml +++ b/.typos.toml @@ -2,3 +2,5 @@ ba = "ba" Shepard = "Shepard" shepard = "shepard" +Strack = "Strack" +Lok = "Lok" diff --git a/.zenodo.json b/.zenodo.json index ecb62381e1..1b2cefaeb6 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -20,7 +20,7 @@ "orcid": "0000-0001-6083-7038" }, { - "affiliation": "Applied and Computational Mathematics, RWTH Aachen University, Germany", + "affiliation": "High-Performance Scientific Computing, University of Augsburg, Germany", "name": "Schlottke-Lakemper, Michael", "orcid": "0000-0002-3195-2536" }, diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 5c74bcf79d..e9f99ce1b6 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -60,7 +60,7 @@ representative at an online or offline event. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to -[Michael Schlottke-Lakemper](mailto:m.schlottke-lakemper@acom.rwth-aachen.de), +[Michael Schlottke-Lakemper](mailto:michael.schlottke-lakemper@uni-a.de), [Sven Berger](mailto:sven.berger@hereon.de), or any other of the principal developers responsible for enforcement listed in [AUTHORS.md](AUTHORS.md). @@ -128,4 +128,4 @@ enforcement ladder](https://github.com/mozilla/diversity). For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at -https://www.contributor-covenant.org/translations. \ No newline at end of file +https://www.contributor-covenant.org/translations. diff --git a/NEWS.md b/NEWS.md index 4282613f20..5d7a295d88 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,11 +1,10 @@ # Changelog TrixiParticles.jl follows the interpretation of [semantic versioning (semver)](https://julialang.github.io/Pkg.jl/dev/compatibility/#Version-specifier-format-1) -used in the Julia ecosystem. Notable changes will be documented in this file for human readability. -We aim at 3 to 4 month between major release versions and about 2 weeks between minor versions. +used in the Julia ecosystem. Notable changes will be documented in this file for human readability. +We aim at 3 to 4 month between major release versions and about 2 weeks between minor versions. - -## Version 0.1.x +## Version 0.2.x ### Highlights @@ -15,16 +14,32 @@ We aim at 3 to 4 month between major release versions and about 2 weeks between ### Deprecated +## Version 0.1.3 -## Pre Initial Release (v0.1.0) -This section summarizes the initial features that TrixiParticles.jl was released with. +### Added +Open boundaries using the method of characteristics based on the work of Lastiwka et al., "Permeable and non-reflecting boundary conditions in SPH" (2009) were added for WCSPH and EDAC. + +## Version 0.1.2 + +### Added +A surface tension and adhesion model based on the work by Akinci et al., "Versatile Surface Tension and Adhesion for SPH Fluids" (2013) was added to WCSPH. + +## Version 0.1.1 ### Highlights -#### EDAC + +#### Discrete Element Method +A basic implementation of the discrete element method was added. + +# Pre Initial Release (v0.1.0) +This section summarizes the initial features that TrixiParticles.jl was released with. + +## Highlights +### EDAC An implementation of EDAC (Entropically Damped Artificial Compressibility) was added, which allows for more stable simulations compared to basic WCSPH and reduces spurious pressure oscillations. -#### WCSPH +### WCSPH An implementation of WCSPH (Weakly Compressible Smoothed Particle Hydrodynamics), which is the classical SPH approach. Features: @@ -36,5 +51,5 @@ Features: - Density diffusion based on the models by Molteni & Colagrossi (2009), Ferrari et al. (2009) and Antuono et al. (2010). -#### TLSPH +### TLSPH An implementation of TLSPH (Total Lagrangian Smoothed Particle Hydrodynamics) for solid bodies enabling FSI (Fluid Structure Interactions). diff --git a/Project.toml b/Project.toml index e05dfae013..981c361929 100644 --- a/Project.toml +++ b/Project.toml @@ -1,19 +1,22 @@ name = "TrixiParticles" uuid = "66699cd8-9c01-4e9d-a059-b96c86d16b3a" authors = ["erik.faulhaber <44124897+efaulhaber@users.noreply.github.com>"] -version = "0.1.0" +version = "0.1.4-dev" [deps] +Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" DiffEqCallbacks = "459566f4-90b8-5000-8ac3-15dfb0a30def" FastPow = "c0e83750-1142-43a8-81cf-6c956b72b4d1" ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" +GPUArrays = "0c68f7d7-f131-5f86-a1c3-88cf8149b2d7" JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" -Morton = "2a6d852e-3fac-5a38-885c-fe708af2d09e" MuladdMacro = "46d2c3a1-f734-5fdb-9937-b9b9aeba4221" +PointNeighbors = "1c4d5385-0a27-49de-8e2c-43b175c8985c" Polyester = "f517fe37-dbe3-4b94-8317-1923a5111588" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" @@ -21,28 +24,29 @@ Reexport = "189a3867-3050-52da-a836-e630ba90ab69" SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" StrideArrays = "d1fa6d79-ef01-42a6-86c9-f7c551f8593b" -ThreadingUtilities = "8290d209-cae3-49c0-8002-c8c24d57dab5" TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" TrixiBase = "9a0f1c46-06d5-4909-a5a3-ce25d3fa3284" WriteVTK = "64499a7a-5c06-52f2-abe2-ccb03c286192" [compat] +Adapt = "3, 4" CSV = "0.10" DataFrames = "1.6" DiffEqCallbacks = "2.25, 3" FastPow = "0.1" ForwardDiff = "0.10" +GPUArrays = "9, 10" JSON = "0.21" -Morton = "0.1" +KernelAbstractions = "0.9" MuladdMacro = "0.2" +PointNeighbors = "0.2.3" Polyester = "0.7.5" RecipesBase = "1" Reexport = "1" SciMLBase = "1, 2" StaticArrays = "1" StrideArrays = "0.1" -ThreadingUtilities = "0.5" TimerOutputs = "0.5" -TrixiBase = "0.1" +TrixiBase = "0.1.3" WriteVTK = "1" julia = "1.9" diff --git a/README.md b/README.md index 2cc50bbcb8..76b288ed27 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,9 @@ Its features include: ## Features - Incompressible Navier-Stokes - Methods: Weakly Compressible Smoothed Particle Hydrodynamics (WCSPH), Entropically Damped Artificial Compressibility (EDAC) + - Models: Surface Tension, Open Boundaries - Solid-body mechanics - - Methods: Total Lagrangian SPH (TLSPH) + - Methods: Total Lagrangian SPH (TLSPH), Discrete Element Method (DEM) - Fluid-Structure Interaction - Output formats: - VTK @@ -128,7 +129,7 @@ with the help of TrixiParticles.jl, please cite it as ## Authors Erik Faulhaber (University of Cologne) and Niklas Neher (HLRS) implemented the foundations for TrixiParticles.jl and are principal developers along with Sven Berger (hereon). -The project was started by Michael Schlottke-Lakemper (RWTH Aachen University/HLRS) +The project was started by Michael Schlottke-Lakemper (University of Augsburg) and Gregor Gassner (University of Cologne), who provide scientific direction and technical advice. The full list of contributors can be found in [AUTHORS.md](AUTHORS.md). diff --git a/docs/Project.toml b/docs/Project.toml index b6b5e79939..7ada972972 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -2,7 +2,6 @@ Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" TrixiBase = "9a0f1c46-06d5-4909-a5a3-ce25d3fa3284" -TrixiParticles = "66699cd8-9c01-4e9d-a059-b96c86d16b3a" [compat] Documenter = "1" diff --git a/docs/make.jl b/docs/make.jl index ac3f26ba97..3fcf40ece1 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -92,16 +92,19 @@ DocMeta.setdocmeta!(TrixiParticles, :DocTestSetup, :(using TrixiParticles); recu makedocs(sitename="TrixiParticles.jl", # Run doctests and check docs for the following modules modules=[TrixiParticles], + format=Documenter.HTML(), # Explicitly specify documentation structure pages=[ "Home" => "index.md", "News" => "news.md", "Installation" => "install.md", "Getting started" => "getting_started.md", + "Development" => "development.md", "Tutorial" => "tutorial.md", "Examples" => "examples.md", "Visualization" => "visualization.md", "Components" => [ + "Overview" => "overview.md", "General" => [ "Semidiscretization" => joinpath("general", "semidiscretization.md"), "Initial Condition and Setups" => joinpath("general", @@ -113,6 +116,8 @@ makedocs(sitename="TrixiParticles.jl", "Util" => joinpath("general", "util.md"), ], "Systems" => [ + "Discrete Element Method (Solid)" => joinpath("systems", + "dem.md"), "Weakly Compressible SPH (Fluid)" => joinpath("systems", "weakly_compressible_sph.md"), "Entropically Damped Artificial Compressibility for SPH (Fluid)" => joinpath("systems", @@ -132,5 +137,4 @@ makedocs(sitename="TrixiParticles.jl", ]) deploydocs(repo="github.com/trixi-framework/TrixiParticles.jl", - devbranch="main", - push_preview=true) + devbranch="main", push_preview=true) diff --git a/docs/src/development.md b/docs/src/development.md new file mode 100644 index 0000000000..8ffb75af52 --- /dev/null +++ b/docs/src/development.md @@ -0,0 +1,68 @@ +# [Development](@id development) + + +## Preview of the documentation + +To generate the Documentation, first instantiate the `docs` environment +by executing the following command from the TrixiParticles.jl root directory: +```bash +julia --project=docs -e "using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()" +``` +This command only has to be run once. After that, maintain the `docs` environment +as described under [Installation](@ref installation-issues). + +With an instantiated `docs` environment, generate the docs with the following command (again from the TrixiParticles.jl root directory): +```bash +julia --project=docs --color=yes docs/make.jl +``` +You can then open the generated files in `docs/build` with your webbrowser. +Alternatively, run +```bash +python3 -m http.server -d docs/build +``` +and open `localhost:8000` in your webbrowser. + + +## Release management + +To create a new release for TrixiParticles.jl, perform the following steps: +1) Make sure that all PRs and changes that you want to go into the release are merged to + `main` and that the latest commit on `main` has passed all CI tests. +2) Determine the currently released version of TrixiParticles.jl, e.g., on the + [release page](https://github.com/trixi-framework/TrixiParticles.jl/releases). For this manual, + we will assume that the latest release was `v0.2.3`. +3) Decide on the next version number. We follow [semantic versioning](https://semver.org/), + thus each version is of the form `vX.Y.Z` where `X` is the major version, `Y` the minor + version, and `Z` the patch version. In this manual, we assume that the major version is + always `0`, thus the decision process on the new version is as follows: + * If the new release contains *breaking changes* (i.e., user code might not work as + before without modifications), increase the *minor* version by one and set the + *patch* version to zero. In our example, the new version should thus be `v0.3.0`. + * If the new release only contains minor modifications and/or bug fixes, the *minor* + version is kept as-is and the *patch* version is increased by one. In our example, the + new version should thus be `v0.2.4`. +4) Edit the `version` string in the + [`Project.toml`](https://github.com/trixi-framework/TrixiParticles.jl/blob/main/Project.toml) + and set it to the new version. Push/merge this change to `main`. +5) Go to GitHub and add a comment to the commit that you would like to become the new + release (typically this will be the commit where you just updated the version). You can + comment on a commit by going to the + [commit overview](https://github.com/trixi-framework/TrixiParticles.jl/commits/main/) and clicking + on the title of the commit. The comment should contain the following text: + ``` + @JuliaRegistrator register + ``` +6) Wait for the magic to happen! Specifically, JuliaRegistrator will create a new PR to the + Julia registry with the new release information. After a grace period of ~15 minutes, + this PR will be merged automatically. A short while after, + [TagBot](https://github.com/trixi-framework/TrixiParticles.jl/blob/main/.github/workflows/TagBot.yml) + will create a new release of TrixiParticles.jl in our GitHub repository. +7) Once the new release has been created, the new version can be obtained through the Julia + package manager as usual. +8) To make sure people do not mistake the latest state of `main` as the latest release, we + set the version in the `Project.toml` to a *development* version. The development version + should be the latest released version, with the patch version incremented by one, and the + `-dev` suffix added. For example, if you just released `v0.3.0`, the new development + version should be `v0.3.1-dev`. If you just released `v0.2.4`, the new development + version should be `v0.2.5-dev`. + diff --git a/docs/src/examples.md b/docs/src/examples.md index 32107240c5..57ac2eb7cd 100644 --- a/docs/src/examples.md +++ b/docs/src/examples.md @@ -1,14 +1,68 @@ # Examples - ## Fluid +### Accelerated Tank 2D (`fluid/accelerated_tank_2d.jl`) +```@raw html + +``` -## Structure Mechanics - +### Dam Break 2D (`fluid/dam_break_2d.jl`) +```@raw html + +``` + +### Dam Break 3D (`fluid/dam_break_3d.jl`) +```@raw html + +``` + +### Falling Water Column (`fluid/falling_water_column_2d.jl`) +```@raw html + +``` + +### Hydrostatic Water Column (`fluid/hydrostatic_water_column_*.jl`) +```@raw html + +``` +### Moving Wall (`fluid/moving_wall_2d.jl`) +```@raw html + +``` + +### Oscillating Drop (`fluid/oscillating_drop_2d.jl`) +```@raw html + +``` + +### Periodic Channel (`fluid/periodic_channel_2d.jl`) +```@raw html + +``` + ## Fluid Structure Interaction +### Dam Break with Elastic Plate (`fsi/dam_break_plate_2d.jl`) +```@raw html + +``` + +### Falling Sphere 2D (`fsi/falling_sphere_2d.jl`) +```@raw html + +``` + +### Falling Spheres 2D (`fsi/falling_spheres_2d.jl`) +```@raw html + +``` + +## Structure Mechanics -## Postprocessing - +### Oscillating Beam (`solid/oscillating_beam_2d.jl`) +```@raw html + +``` + \ No newline at end of file diff --git a/docs/src/general/neighborhood_search.md b/docs/src/general/neighborhood_search.md index 5a1512b6d2..e611f53502 100644 --- a/docs/src/general/neighborhood_search.md +++ b/docs/src/general/neighborhood_search.md @@ -1,6 +1,44 @@ # Neighborhood Search -```@autodocs -Modules = [TrixiParticles] -Pages = map(file -> joinpath("neighborhood_search", file), readdir(joinpath("..", "src", "neighborhood_search"))) -``` +The neighborhood search is the most essential component for performance. +We provide several implementations in the package +[PointNeighbors.jl](https://github.com/trixi-framework/PointNeighbors.jl). +See the docs of this package for an overview and a comparison of different implementations. + +!!! note "Usage" + To run a simulation with a neighborhood search implementation, just pass the type + to the constructor of the [`Semidiscretization`](@ref): + ```jldoctest semi_example; output=false, setup = :(using TrixiParticles; trixi_include(@__MODULE__, joinpath(examples_dir(), "fluid", "hydrostatic_water_column_2d.jl"), sol=nothing); system1 = fluid_system; system2 = boundary_system) + semi = Semidiscretization(system1, system2, + neighborhood_search=GridNeighborhoodSearch) + + # output + ┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Semidiscretization │ + │ ══════════════════ │ + │ #spatial dimensions: ………………………… 2 │ + │ #systems: ……………………………………………………… 2 │ + │ neighborhood search: ………………………… GridNeighborhoodSearch │ + │ total #particles: ………………………………… 636 │ + └──────────────────────────────────────────────────────────────────────────────────────────────────┘ + ``` + The keyword arguments `periodic_box_min_corner` and `periodic_box_max_corner` mentioned + in the PointNeighbors.jl docs can also be passed to the + [`Semidiscretization`](@ref) and will internally be forwarded to the neighborhood search. + See the docs of [`Semidiscretization`](@ref) for more details. + ```jldoctest semi_example; output = false + semi = Semidiscretization(system1, system2, + neighborhood_search=GridNeighborhoodSearch, + periodic_box_min_corner=[0.0, -0.25], + periodic_box_max_corner=[1.0, 0.75]) + + # output + ┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Semidiscretization │ + │ ══════════════════ │ + │ #spatial dimensions: ………………………… 2 │ + │ #systems: ……………………………………………………… 2 │ + │ neighborhood search: ………………………… GridNeighborhoodSearch │ + │ total #particles: ………………………………… 636 │ + └──────────────────────────────────────────────────────────────────────────────────────────────────┘ + ``` diff --git a/docs/src/index.md b/docs/src/index.md index 41930cf37b..42c2c42c67 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -5,6 +5,7 @@ TrixiParticles.jl is a numerical simulation framework designed for particle-base ## Features - Incompressible Navier-Stokes - Methods: Weakly Compressible Smoothed Particle Hydrodynamics (WCSPH), Entropically Damped Artificial Compressibility (EDAC) + - Models: Surface Tension - Solid-body mechanics - Methods: Total Lagrangian SPH (TLSPH) - Fluid-Structure Interaction @@ -12,13 +13,36 @@ TrixiParticles.jl is a numerical simulation framework designed for particle-base - VTK ## Examples - +```@raw html + + + + + + + + + +
+
2D Dam Break
+
+
Moving Wall
+
+
Oscillating Beam
+
+
Dam Break with Elastic Plate
+
+``` ## Quickstart 1. [Installation](@ref installation) 2. [Getting started](@ref getting_started) +If you have any questions concerning **TrixiParticles.jl** you can join our community [on Slack](https://join.slack.com/t/trixi-framework/shared_invite/zt-sgkc6ppw-6OXJqZAD5SPjBYqLd8MU~g) or open an issue with your question. ## Start with development +To get started with development have a look at these pages: + 1. [Installation](@ref installation) -2. [Contributing](@ref) +2. [Development](@ref development) +3. [Contributing](@ref) diff --git a/docs/src/install.md b/docs/src/install.md index 09ad164c52..f88a101210 100644 --- a/docs/src/install.md +++ b/docs/src/install.md @@ -45,7 +45,7 @@ and always have a reproducible environment at hand to share with others. - [PythonPlot.jl](https://github.com/JuliaPy/PythonPlot.jl) -- Plotting library that can be used instead of Plots.jl - [ParaView](https://www.paraview.org/) -- Software that can be used for visualization of results -## Common issues +## [Common issues](@id installation-issues) If you followed the [installation instructions for developers](@ref for-developers) and you run into any problems with packages when pulling the latest version of TrixiParticles.jl, diff --git a/docs/src/overview.md b/docs/src/overview.md new file mode 100644 index 0000000000..a113d1f9fa --- /dev/null +++ b/docs/src/overview.md @@ -0,0 +1,23 @@ +# Overview +The following page gives a rough overview of important parts of the code. + +## Program flow + +To initiate a simulation, the goal is to solve an ordinary differential equation, for example, +by employing the time integration schemes provided by OrdinaryDiffEq.jl. These schemes are then +utilized to integrate ``\mathrm{d}u/\mathrm{d}t`` and ``\mathrm{d}v/\mathrm{d}t``, where ``u`` +represents the particles' positions and ``v`` their properties such as velocity and density. +During a single time step or an intermediate step of the time integration scheme, the functions +`drift!` and `kick!` are invoked, followed by the functions depicted in this diagram +(with key parts highlighted in orange/yellow). + +![Main Program Flow](https://github.com/trixi-framework/TrixiParticles.jl/assets/10238714/d7c6eedd-7173-4879-b62e-3e6d4bc5091f) + + +## Structure +What we refer to as schemes are various models such as Weakly Compressible Smoothed Particle Hydrodynamics (WCSPH) +or Total Lagrangian Smoothed Particle Hydrodynamics (TLSPH). These schemes are categorized based on the applicable +physical regimes, namely fluid, solid, gas, and others. Each scheme comprises at least two files: a `system.jl` file +and an `rhs.jl` file. The `system.jl` file provides the data structure holding the particles of this scheme and some +routines, particularly those for allocation and the main update routines, excluding system interactions. +The interactions between particles of this scheme (and with particles of other schemes) are handled in the `rhs.jl` file. diff --git a/docs/src/systems/boundary.md b/docs/src/systems/boundary.md index d11a505839..ae4b9b676e 100644 --- a/docs/src/systems/boundary.md +++ b/docs/src/systems/boundary.md @@ -4,6 +4,10 @@ BoundarySPHSystem ``` +```@docs + BoundaryDEMSystem +``` + ```@docs BoundaryMovement ``` @@ -230,3 +234,86 @@ Pages = [joinpath("schemes", "boundary", "monaghan_kajtar", "monaghan_kajtar.jl" - Alireza Valizadeh, Joseph J. Monaghan. "A study of solid wall models for weakly compressible SPH." In: Journal of Computational Physics 300 (2015), pages 5–19. [doi: 10.1016/J.JCP.2015.07.033](https://doi.org/10.1016/J.JCP.2015.07.033) + +# [Open Boundaries](@id open_boundary) + +```@autodocs +Modules = [TrixiParticles] +Pages = [joinpath("schemes", "boundary", "open_boundary", "system.jl")] +``` + +```@autodocs +Modules = [TrixiParticles] +Pages = [joinpath("schemes", "boundary", "open_boundary", "boundary_zones.jl")] +``` + +### [Method of characteristics](@id method_of_characteristics) + +The difficulty in non-reflecting boundary conditions, also called open boundaries, is to determine +the appropriate boundary values of the exact characteristics of the Euler equations. +Assuming the flow near the boundaries is normal to the boundary +and free of shock waves and significant viscous effects, it can be shown that three characteristic variables exist: + +- ``J_1``, associated with convection of entropy and propagates at flow velocity, +- ``J_2``, downstream-running characteristics, +- ``J_3``, upstream-running characteristics. + +Giles (1990) derived those variables based on a linearized set of governing equations: +```math +J_1 = -c_s^2 (\rho - \rho_{\text{ref}}) + (p - p_{\text{ref}}) +``` +```math +J_2 = \rho c_s (v - v_{\text{ref}}) + (p - p_{\text{ref}}) +``` +```math +J_3 = - \rho c_s (v - v_{\text{ref}}) + (p - p_{\text{ref}}) +``` +where the subscript "ref" denotes the reference flow near the boundaries, which can be prescribed. + +Specifying the reference variables is **not** equivalent to prescription of ``\rho``, ``v`` and ``p`` +directly, since the perturbation from the reference flow is allowed. + +Lastiwka et al. (2009) applied the method of characteristic to SPH and determined the number of variables that should be +**prescribed** at the boundary and the number which should be **propagated** from the fluid domain to the boundary: + +- For an **inflow** boundary: + - Prescribe *downstream*-running characteristics ``J_1`` and ``J_2`` + - Transmit ``J_3`` from the fluid domain (allow ``J_3`` to propagate upstream to the boundary). + +- For an **outflow** boundary: + - Prescribe *upstream*-running characteristic ``J_3`` + - Transmit ``J_1`` and ``J_2`` from the fluid domain. + +Prescribing is done by simply setting the characteristics to zero. To transmit the characteristics from the fluid +domain, or in other words, to carry the information of the fluid to the boundaries, Negi et al. (2020) use a Shepard Interpolation +```math +f_i = \frac{\sum_j^N f_j W_{ij}}{\sum_j^N W_{ij}}, +``` +where the ``i``-th particle is a boundary particle, ``f`` is either ``J_1``, ``J_2`` or ``J_3`` and ``N`` is the set of +neighboring fluid particles. + +To express pressure ``p``, density ``\rho`` and velocity ``v`` as functions of the characteristic variables, the system of equations +from the characteristic variables is inverted and gives +```math + \rho - \rho_{\text{ref}} = \frac{1}{c_s^2} \left( -J_1 + \frac{1}{2} J_2 + \frac{1}{2} J_3 \right), +``` +```math +u - u_{\text{ref}}= \frac{1}{2\rho c_s} \left( J_2 - J_3 \right), +``` +```math +p - p_{\text{ref}} = \frac{1}{2} \left( J_2 + J_3 \right). +``` +With ``J_1``, ``J_2`` and ``J_3`` determined, we can easily solve for the actual variables for each particle. + +### References +- M. B. Giles. "Nonreflecting boundary conditions for Euler equation calculations". + In: AIAA Journal, 28.12 pages 2050--2058. + [doi: 10.2514/3.10521](https://doi.org/10.2514/3.10521) +- M. Lastiwka, M. Basa, N. J. Quinlan. + "Permeable and non-reflecting boundary conditions in SPH". + In: International Journal for Numerical Methods in Fluids 61, (2009), pages 709--724. + [doi: 10.1002/fld.1971](https://doi.org/10.1002/fld.1971) +- P. Negi, P. Ramachandran, A. Haftu. + "An improved non-reflecting outlet boundary condition for weakly-compressible SPH". + In: Computer Methods in Applied Mechanics and Engineering 367, (2020), pages 113--119. + [doi: 10.1016/j.cma.2020.113119](https://doi.org/10.1016/j.cma.2020.113119) diff --git a/docs/src/systems/dem.md b/docs/src/systems/dem.md new file mode 100644 index 0000000000..a593db01e8 --- /dev/null +++ b/docs/src/systems/dem.md @@ -0,0 +1,33 @@ +# [Discrete Element Method](@id dem) +The Discrete Element Method (DEM) is a computational technique widely used in physics, engineering, +and applied mathematics for simulating the mechanical behavior of granular materials, such as powders, +sand, soil, or rock, as well as other discontinua. Unlike continuum mechanics that treats materials as +continuous, DEM considers individual particles or elements and their interactions. This approach provides +detailed insights into the micro-mechanical behavior of materials, making it particularly valuable +in fields such as geomechanics, material science, and mechanical engineering. + +## Fundamental Principles +The core idea behind DEM is the discretization of a material system into a finite set of distinct, +interacting mass elements (particles). These elements (particles) can vary in shape, size, and properties, and +they interact with each other and possibly with their boundaries through contact forces and potential fields. +The motion and behavior of each mass element are governed by Newton's laws of motion, accounting for the forces +and moments acting upon them. + +```@autodocs +Modules = [TrixiParticles] +Pages = [joinpath("schemes", "solid", "discrete_element_method", "system.jl")] +``` + +## References +- N. Bićanić. "Discrete element methods". + In: Encyclopedia of Computational Mechanics (2007). + [doi: 10.1002/0470091355.ecm006.pub2](https://doi.org/10.1002/0470091355.ecm006.pub2) + +- P. Cundall and O. Strack. "A discrete numerical model for granular assemblies". + In: Géotechnique 29.1 (1979), pages 47--65. + [doi: 10.1680/geot.1979.29.1.47](https://doi.org/10.1680/geot.1979.29.1.47) + +- A. Renzo and F. Maio. "Comparison of contact-force models for the simulation of collisions in DEM-based granular flow codes" + In: Chemical Engineering Science 59.3 (2004), pages 525--541. + [doi: 10.1016/j.ces.2003.09.037](https://doi.org/10.1016/j.ces.2003.09.037) + diff --git a/docs/src/systems/weakly_compressible_sph.md b/docs/src/systems/weakly_compressible_sph.md index 69b514950c..353b6b5cbe 100644 --- a/docs/src/systems/weakly_compressible_sph.md +++ b/docs/src/systems/weakly_compressible_sph.md @@ -147,3 +147,65 @@ Pages = [joinpath("schemes", "fluid", "weakly_compressible_sph", "density_diffus Modules = [TrixiParticles] Pages = [joinpath("general", "corrections.jl")] ``` + +## [Surface Tension](@id surface_tension) + +### Akinci-based intra-particle force surface tension and wall adhesion model +The work by Akinci proposes three forces: +- a cohesion force +- a surface area minimization force +- a wall adhesion force + +The classical model is composed of the curvature minimization and cohesion force. + +#### Cohesion force +The model calculates the cohesion force based on the distance between particles and the support radius ``h_c``. +This force is determined using two distinct regimes within the support radius: +- For particles closer than half the support radius, + a repulsive force is calculated to prevent particle clustering too tightly, + enhancing the simulation's stability and realism. +- Beyond half the support radius and within the full support radius, + an attractive force is computed, simulating the effects of surface tension that draw particles together. +The cohesion force, ``F_{\text{cohesion}}``, for a pair of particles is given by: +```math +F_{\text{cohesion}} = -\sigma m_b C(r) \frac{r}{\Vert r \Vert}, +``` +where: +- ``\sigma`` represents the surface tension coefficient, adjusting the overall strength of the cohesion effect. +- ``C`` is a scalar function of the distance between particles. + +The cohesion kernel ``C`` is defined as +```math +C(r)=\frac{32}{\pi h_c^9} +\begin{cases} +(h_c-r)^3 r^3, & \text{if } 2r > h_c \\ +2(h_c-r)^3 r^3 - \frac{h^6}{64}, & \text{if } r > 0 \text{ and } 2r \leq h_c \\ +0, & \text{otherwise} +\end{cases} +``` + +#### Surface area minimization force +To model the minimization of the surface area and curvature of the fluid, a curvature force is used, which is calculated as +```math +F_{\text{curvature}} = -\sigma (n_a - n_b) +``` + +#### Wall adhesion force +The wall adhesion model proposed by Akinci et al. is based on a kernel function which is 0 from 0.0 to 0.5 support radiia with a maximum at 0.75. +With the force calculated with an adhesion coefficient ``\beta`` as +```math +F_{\text{adhesion}} = -\beta m_b A(r) \frac{r}{\Vert r \Vert}, +``` +with ``A`` being the adhesion kernel defined as +```math +A(r)= \frac{0.007}{h_c^{3.25}} +\begin{cases} +\sqrt[4]{- \frac{4r^2}{h_c} + 6r - 2h_c}, & \text{if } 2r > h_c \text{ and } r \leq h_c \\ +0, & \text{otherwise.} +\end{cases} +``` + +```@autodocs +Modules = [TrixiParticles] +Pages = [joinpath("schemes", "fluid", "surface_tension.jl")] +``` diff --git a/examples/dem/rectangular_tank_2d.jl b/examples/dem/rectangular_tank_2d.jl new file mode 100644 index 0000000000..c7daa31e24 --- /dev/null +++ b/examples/dem/rectangular_tank_2d.jl @@ -0,0 +1,50 @@ +using TrixiParticles +using OrdinaryDiffEq + +gravity = -9.81 + +# ========================================================================================== +# ==== Falling rocks + +particle_spacing = 0.1 + +rock_width = 2.0 +rock_height = 2.0 +rock_density = 3000.0 + +tank_width = 2.0 +tank_height = 4.0 + +tank = RectangularTank(particle_spacing, (rock_width, rock_height), + (tank_width, tank_height), rock_density, + n_layers=2) + +# ========================================================================================== +# ==== Systems + +# Move the rocks up to let them fall +tank.fluid.coordinates[2, :] .+= 0.5 +rock_system = DEMSystem(tank.fluid, 2 * 10e5, 10e9, 0.3, acceleration=(0.0, gravity)) +boundary_system = BoundaryDEMSystem(tank.boundary, 10e7) + +# ========================================================================================== +# ==== Simulation + +semi = Semidiscretization(rock_system, boundary_system, + neighborhood_search=GridNeighborhoodSearch) + +tspan = (0.0, 5.0) +ode = semidiscretize(semi, tspan) + +info_callback = InfoCallback(interval=5000) +saving_callback = SolutionSavingCallback(dt=0.02) + +callbacks = CallbackSet(info_callback, saving_callback) + +# Use a Runge-Kutta method with automatic (error based) time step size control +sol = solve(ode, RDPK3SpFSAL49(), + abstol=1e-5, # Default abstol is 1e-6 (may need to be tuned to prevent boundary penetration) + reltol=1e-4, # Default reltol is 1e-3 (may need to be tuned to prevent boundary penetration) + dtmax=1e-3, # Limit stepsize to prevent crashing + dt=1e-7, # Initial step size + save_everystep=false, callback=callbacks); diff --git a/examples/fluid/dam_break_2d.jl b/examples/fluid/dam_break_2d.jl index 40176927c9..bfcfb9a18f 100644 --- a/examples/fluid/dam_break_2d.jl +++ b/examples/fluid/dam_break_2d.jl @@ -48,6 +48,9 @@ smoothing_kernel = WendlandC2Kernel{2}() fluid_density_calculator = ContinuityDensity() viscosity = ArtificialViscosityMonaghan(alpha=0.02, beta=0.0) +# nu = 0.02 * smoothing_length * sound_speed/8 +# viscosity = ViscosityMorris(nu=nu) +# viscosity = ViscosityAdami(nu=nu) # Alternatively the density diffusion model by Molteni & Colagrossi can be used, # which will run faster. # density_diffusion = DensityDiffusionMolteniColagrossi(delta=0.1) @@ -57,8 +60,8 @@ fluid_system = WeaklyCompressibleSPHSystem(tank.fluid, fluid_density_calculator, state_equation, smoothing_kernel, smoothing_length, viscosity=viscosity, density_diffusion=density_diffusion, - acceleration=(0.0, -gravity), - correction=nothing) + acceleration=(0.0, -gravity), correction=nothing, + surface_tension=nothing) # ========================================================================================== # ==== Boundary @@ -69,12 +72,14 @@ boundary_model = BoundaryModelDummyParticles(tank.boundary.density, tank.boundar smoothing_kernel, smoothing_length, correction=nothing) -boundary_system = BoundarySPHSystem(tank.boundary, boundary_model) +boundary_system = BoundarySPHSystem(tank.boundary, boundary_model, adhesion_coefficient=0.0) # ========================================================================================== # ==== Simulation -semi = Semidiscretization(fluid_system, boundary_system, threaded_nhs_update=true) -ode = semidiscretize(semi, tspan) +semi = Semidiscretization(fluid_system, boundary_system, + neighborhood_search=GridNeighborhoodSearch, + threaded_nhs_update=true) +ode = semidiscretize(semi, tspan, data_type=nothing) info_callback = InfoCallback(interval=100) diff --git a/examples/fluid/dam_break_2d_surface_tension.jl b/examples/fluid/dam_break_2d_surface_tension.jl new file mode 100644 index 0000000000..d83eb98654 --- /dev/null +++ b/examples/fluid/dam_break_2d_surface_tension.jl @@ -0,0 +1,31 @@ +# This example shows how surface tension can be applied to existing cases +# and which parameters have to be changed! +using TrixiParticles + +fluid_density = 1000.0 + +H = 0.6 +fluid_particle_spacing = H / 60 + +# Set the surface tension to a value that is accurate in your case. +# Note: This usually requires calibration to be physically accurate! +surface_tension = SurfaceTensionAkinci(surface_tension_coefficient=0.025) + +# `density_diffusion` is deactivated since the interaction with the surface tension model can +# cause stability problems. +# `adhesion_coefficient` needs to be set to a value so that the fluid doesn't separate +# from the boundary. +# Note: The surface tension model leads to an increase in compressibility of the fluid, +# which needs to be rectified by an increase of the `sound_speed`. +# Note: The Wendland Kernels don't work very well here since the `SurfaceTensionAkinci` +# model is optimized for a compact support of `2 * particle_spacing`, which would result +# in a too small `smoothing_length` for the Wendland Kernel functions. +# Note: Adhesion will result in additional friction at the boundary. +trixi_include(@__MODULE__, joinpath(examples_dir(), "fluid", "dam_break_2d.jl"), + surface_tension=surface_tension, + fluid_particle_spacing=fluid_particle_spacing, + smoothing_kernel=SchoenbergCubicSplineKernel{2}(), + smoothing_length=1.0 * fluid_particle_spacing, + correction=AkinciFreeSurfaceCorrection(fluid_density), + density_diffusion=nothing, adhesion_coefficient=0.05, + sound_speed=100.0, tspan=(0.0, 2.0)) diff --git a/examples/fluid/falling_water_spheres_2d.jl b/examples/fluid/falling_water_spheres_2d.jl new file mode 100644 index 0000000000..37132d70b7 --- /dev/null +++ b/examples/fluid/falling_water_spheres_2d.jl @@ -0,0 +1,95 @@ +# In this example two circles of water drop to the floor demonstrating the difference +# between the behavior with and without surface tension modelling. +using TrixiParticles +using OrdinaryDiffEq + +# ========================================================================================== +# ==== Resolution +fluid_particle_spacing = 0.005 + +boundary_layers = 3 +spacing_ratio = 1 + +# ========================================================================================== +# ==== Experiment Setup +gravity = 9.81 +tspan = (0.0, 0.3) + +# Boundary geometry and initial fluid particle positions +initial_fluid_size = (0.0, 0.0) +tank_size = (2.0, 0.5) + +fluid_density = 1000.0 +sound_speed = 100 +state_equation = StateEquationCole(; sound_speed, reference_density=fluid_density, + exponent=1) + +tank = RectangularTank(fluid_particle_spacing, initial_fluid_size, tank_size, fluid_density, + n_layers=boundary_layers, spacing_ratio=spacing_ratio, + faces=(true, true, true, false), + acceleration=(0.0, -gravity), state_equation=state_equation) + +sphere_radius = 0.05 + +sphere1_center = (0.5, 0.2) +sphere2_center = (1.5, 0.2) +sphere1 = SphereShape(fluid_particle_spacing, sphere_radius, sphere1_center, + fluid_density, sphere_type=VoxelSphere(), velocity=(0.0, -3.0)) +sphere2 = SphereShape(fluid_particle_spacing, sphere_radius, sphere2_center, + fluid_density, sphere_type=VoxelSphere(), velocity=(0.0, -3.0)) + +# ========================================================================================== +# ==== Fluid +fluid_smoothing_length = 1.0 * fluid_particle_spacing +fluid_smoothing_kernel = SchoenbergCubicSplineKernel{2}() + +fluid_density_calculator = ContinuityDensity() + +nu = 0.005 +alpha = 8 * nu / (fluid_smoothing_length * sound_speed) +viscosity = ArtificialViscosityMonaghan(alpha=alpha, beta=0.0) +density_diffusion = DensityDiffusionAntuono(sphere2, delta=0.1) + +sphere_surface_tension = WeaklyCompressibleSPHSystem(sphere1, fluid_density_calculator, + state_equation, fluid_smoothing_kernel, + fluid_smoothing_length, + viscosity=viscosity, + acceleration=(0.0, -gravity), + surface_tension=SurfaceTensionAkinci(surface_tension_coefficient=0.05), + correction=AkinciFreeSurfaceCorrection(fluid_density)) + +sphere = WeaklyCompressibleSPHSystem(sphere2, fluid_density_calculator, + state_equation, fluid_smoothing_kernel, + fluid_smoothing_length, viscosity=viscosity, + density_diffusion=density_diffusion, + acceleration=(0.0, -gravity)) + +# ========================================================================================== +# ==== Boundary +boundary_density_calculator = AdamiPressureExtrapolation() +wall_viscosity = nu +boundary_model = BoundaryModelDummyParticles(tank.boundary.density, tank.boundary.mass, + state_equation=state_equation, + boundary_density_calculator, + fluid_smoothing_kernel, fluid_smoothing_length, + viscosity=ViscosityAdami(nu=wall_viscosity)) + +boundary_system = BoundarySPHSystem(tank.boundary, boundary_model, + adhesion_coefficient=0.001) + +# ========================================================================================== +# ==== Simulation +semi = Semidiscretization(boundary_system, sphere_surface_tension, sphere) +ode = semidiscretize(semi, tspan) + +info_callback = InfoCallback(interval=50) +saving_callback = SolutionSavingCallback(dt=0.01, output_directory="out", prefix="", + write_meta_data=true) + +callbacks = CallbackSet(info_callback, saving_callback) + +# Use a Runge-Kutta method with automatic (error based) time step size control. +sol = solve(ode, RDPK3SpFSAL35(), + abstol=1e-6, # Default abstol is 1e-6 + reltol=1e-4, # Default reltol is 1e-3 + save_everystep=false, callback=callbacks); diff --git a/examples/fluid/falling_water_spheres_3d.jl b/examples/fluid/falling_water_spheres_3d.jl new file mode 100644 index 0000000000..a4ebb799e0 --- /dev/null +++ b/examples/fluid/falling_water_spheres_3d.jl @@ -0,0 +1,37 @@ +using TrixiParticles +using OrdinaryDiffEq + +# ========================================================================================== +# ==== Resolution +fluid_particle_spacing = 0.008 + +# ========================================================================================== +# ==== Experiment Setup +gravity = 9.81 +nu = 0.01 +fluid_density = 1000.0 +sound_speed = 50 + +sphere1_radius = 0.05 + +sphere1_center = (0.5, 0.5, 0.2) +sphere2_center = (1.5, 0.5, 0.2) +sphere1 = SphereShape(fluid_particle_spacing, sphere1_radius, sphere1_center, + fluid_density, sphere_type=VoxelSphere(), velocity=(0.0, 0.0, -2.0)) +sphere2 = SphereShape(fluid_particle_spacing, sphere1_radius, sphere2_center, + fluid_density, sphere_type=VoxelSphere(), velocity=(0.0, 0.0, -2.0)) + +# `compact_support` needs to be `2.0 * particle_spacing` to be correct +fluid_smoothing_length = 1.0 * fluid_particle_spacing + +trixi_include(@__MODULE__, + joinpath(examples_dir(), "fluid", "falling_water_spheres_2d.jl"), + fluid_particle_spacing=fluid_particle_spacing, tspan=(0.0, 0.2), + initial_fluid_size=(0.0, 0.0, 0.0), + tank_size=(2.0, 1.0, 0.1), sound_speed=sound_speed, + faces=(true, true, true, true, true, false), + acceleration=(0.0, 0.0, -gravity), sphere1=sphere1, sphere2=sphere2, + fluid_smoothing_length=fluid_smoothing_length, + fluid_smoothing_kernel=SchoenbergCubicSplineKernel{3}(), + nu=nu, alpha=10 * nu / (fluid_smoothing_length * sound_speed), + surface_tension_coefficient=1.5, adhesion_coefficient=0.5) diff --git a/examples/fluid/oscillating_drop_2d.jl b/examples/fluid/oscillating_drop_2d.jl index da4e855ac3..7ed6ec478f 100644 --- a/examples/fluid/oscillating_drop_2d.jl +++ b/examples/fluid/oscillating_drop_2d.jl @@ -89,5 +89,5 @@ exact_solution_ode = ODEProblem(exact_solution_rhs, exact_u0, tspan) sol_exact = solve(exact_solution_ode, RDPK3SpFSAL49(), save_everystep=false) # Error in the semi-major axis of the elliptical drop -error_A = maximum(sol.u[end].x[2]) + 0.5fluid_particle_spacing - +error_A = maximum(sol.u[end].x[2]) + 0.5 * fluid_particle_spacing - maximum(sol_exact.u[end][2:3]) diff --git a/examples/fluid/periodic_channel_2d.jl b/examples/fluid/periodic_channel_2d.jl index cd033717a0..55899df445 100644 --- a/examples/fluid/periodic_channel_2d.jl +++ b/examples/fluid/periodic_channel_2d.jl @@ -42,10 +42,15 @@ fluid_system = WeaklyCompressibleSPHSystem(tank.fluid, fluid_density_calculator, # ========================================================================================== # ==== Boundary boundary_density_calculator = AdamiPressureExtrapolation() +viscosity_wall = nothing +# Activate to switch to no-slip walls +#viscosity_wall = ViscosityAdami(nu=0.0025 * smoothing_length * sound_speed / 8) + boundary_model = BoundaryModelDummyParticles(tank.boundary.density, tank.boundary.mass, state_equation=state_equation, boundary_density_calculator, - smoothing_kernel, smoothing_length) + smoothing_kernel, smoothing_length, + viscosity=viscosity_wall) boundary_system = BoundarySPHSystem(tank.boundary, boundary_model) diff --git a/examples/fluid/pipe_flow_2d.jl b/examples/fluid/pipe_flow_2d.jl new file mode 100644 index 0000000000..3d642696ef --- /dev/null +++ b/examples/fluid/pipe_flow_2d.jl @@ -0,0 +1,131 @@ +# 2D channel flow simulation with open boundaries. + +using TrixiParticles +using OrdinaryDiffEq + +# ========================================================================================== +# ==== Resolution +particle_spacing = 0.05 + +# Make sure that the kernel support of fluid particles at a boundary is always fully sampled +boundary_layers = 3 + +# Make sure that the kernel support of fluid particles at an open boundary is always +# fully sampled. +# Note: Due to the dynamics at the inlets and outlets of open boundaries, +# it is recommended to use `open_boundary_layers > boundary_layers` +open_boundary_layers = 6 + +# ========================================================================================== +# ==== Experiment Setup +tspan = (0.0, 2.0) + +# Boundary geometry and initial fluid particle positions +domain_size = (1.0, 0.4) + +flow_direction = [1.0, 0.0] +reynolds_number = 100 +const prescribed_velocity = 2.0 + +boundary_size = (domain_size[1] + 2 * particle_spacing * open_boundary_layers, + domain_size[2]) + +fluid_density = 1000.0 + +# For this particular example, it is necessary to have a background pressure. +# Otherwise the suction at the outflow is to big and the simulation becomes unstable. +pressure = 1000.0 + +sound_speed = 10 * prescribed_velocity + +state_equation = StateEquationCole(; sound_speed, reference_density=fluid_density, + exponent=7, background_pressure=pressure) + +pipe = RectangularTank(particle_spacing, domain_size, boundary_size, fluid_density, + pressure=pressure, n_layers=boundary_layers, + faces=(false, false, true, true)) + +# Shift pipe walls in negative x-direction for the inflow +pipe.boundary.coordinates[1, :] .-= particle_spacing * open_boundary_layers + +n_buffer_particles = 4 * pipe.n_particles_per_dimension[2] + +# ========================================================================================== +# ==== Fluid +smoothing_length = 3.0 * particle_spacing +smoothing_kernel = WendlandC2Kernel{2}() + +fluid_density_calculator = ContinuityDensity() + +kinematic_viscosity = prescribed_velocity * domain_size[2] / reynolds_number + +viscosity = ViscosityAdami(nu=kinematic_viscosity) + +fluid_system = EntropicallyDampedSPHSystem(pipe.fluid, smoothing_kernel, smoothing_length, + sound_speed, viscosity=viscosity, + density_calculator=fluid_density_calculator, + buffer_size=n_buffer_particles) + +# Alternatively the WCSPH scheme can be used +# alpha = 8 * kinematic_viscosity / (smoothing_length * sound_speed) +# viscosity = ArtificialViscosityMonaghan(; alpha, beta=0.0) + +# fluid_system = WeaklyCompressibleSPHSystem(pipe.fluid, fluid_density_calculator, +# state_equation, smoothing_kernel, +# smoothing_length, viscosity=viscosity, +# buffer_size=n_buffer_particles) + +# ========================================================================================== +# ==== Open Boundary +function velocity_function(pos, t) + # Use this for a time-dependent inflow velocity + # return SVector(0.5prescribed_velocity * sin(2pi * t) + prescribed_velocity, 0) + + return SVector(prescribed_velocity, 0.0) +end + +inflow = InFlow(; plane=([0.0, 0.0], [0.0, domain_size[2]]), flow_direction, + open_boundary_layers, density=fluid_density, particle_spacing) + +open_boundary_in = OpenBoundarySPHSystem(inflow; sound_speed, fluid_system, + buffer_size=n_buffer_particles, + reference_pressure=pressure, + reference_velocity=velocity_function) + +outflow = OutFlow(; plane=([domain_size[1], 0.0], [domain_size[1], domain_size[2]]), + flow_direction, open_boundary_layers, density=fluid_density, + particle_spacing) + +open_boundary_out = OpenBoundarySPHSystem(outflow; sound_speed, fluid_system, + buffer_size=n_buffer_particles, + reference_pressure=pressure, + reference_velocity=velocity_function) + +# ========================================================================================== +# ==== Boundary + +boundary_model = BoundaryModelDummyParticles(pipe.boundary.density, pipe.boundary.mass, + AdamiPressureExtrapolation(), + state_equation=state_equation, + #viscosity=ViscosityAdami(nu=1e-4), + smoothing_kernel, smoothing_length) + +boundary_system = BoundarySPHSystem(pipe.boundary, boundary_model) + +# ========================================================================================== +# ==== Simulation +semi = Semidiscretization(fluid_system, open_boundary_in, open_boundary_out, + boundary_system) + +ode = semidiscretize(semi, tspan) + +info_callback = InfoCallback(interval=100) +saving_callback = SolutionSavingCallback(dt=0.02, prefix="") + +callbacks = CallbackSet(info_callback, saving_callback, UpdateCallback()) + +sol = solve(ode, RDPK3SpFSAL35(), + abstol=1e-5, # Default abstol is 1e-6 (may need to be tuned to prevent boundary penetration) + reltol=1e-3, # Default reltol is 1e-3 (may need to be tuned to prevent boundary penetration) + dtmax=1e-2, # Limit stepsize to prevent crashing + save_everystep=false, callback=callbacks); diff --git a/examples/fluid/sphere_surface_tension_2d.jl b/examples/fluid/sphere_surface_tension_2d.jl new file mode 100644 index 0000000000..9006ebf0e1 --- /dev/null +++ b/examples/fluid/sphere_surface_tension_2d.jl @@ -0,0 +1,59 @@ +# In this example we can observe that the `SurfaceTensionAkinci` surface tension model correctly leads to a +# surface minimization of the water square and approaches a sphere. +using TrixiParticles +using OrdinaryDiffEq + +fluid_density = 1000.0 + +particle_spacing = 0.1 + +# Note: Only square shapes will result in a sphere. +# Furthermore, changes of the coefficients might be necessary for higher resolutions or larger squares. +fluid_size = (0.5, 0.5) + +sound_speed = 20.0 +state_equation = StateEquationCole(; sound_speed, reference_density=fluid_density, + exponent=7, clip_negative_pressure=true) + +# For all surface tension simulations, we need a compact support of `2 * particle_spacing` +# smoothing_length = 2.0 * particle_spacing +# smoothing_kernel = WendlandC2Kernel{2}() +# nu = 0.01 + +smoothing_length = 1.0 * particle_spacing +smoothing_kernel = SchoenbergCubicSplineKernel{2}() +nu = 0.025 + +fluid = RectangularShape(particle_spacing, round.(Int, fluid_size ./ particle_spacing), + zeros(length(fluid_size)), density=fluid_density) + +alpha = 8 * nu / (smoothing_length * sound_speed) +source_terms = SourceTermDamping(; damping_coefficient=0.5) +fluid_system = WeaklyCompressibleSPHSystem(fluid, SummationDensity(), + state_equation, smoothing_kernel, + smoothing_length, + viscosity=ArtificialViscosityMonaghan(alpha=alpha, + beta=0.0), + surface_tension=SurfaceTensionAkinci(surface_tension_coefficient=0.02), + correction=AkinciFreeSurfaceCorrection(fluid_density), + source_terms=source_terms) + +# ========================================================================================== +# ==== Simulation +semi = Semidiscretization(fluid_system) + +tspan = (0.0, 3.0) +ode = semidiscretize(semi, tspan) + +info_callback = InfoCallback(interval=100) + +# For overwriting via `trixi_include` +saving_callback = SolutionSavingCallback(dt=0.02) + +stepsize_callback = StepsizeCallback(cfl=1.0) + +callbacks = CallbackSet(info_callback, saving_callback, stepsize_callback) + +sol = solve(ode, CarpenterKennedy2N54(williamson_condition=false), + dt=1.0, # This is overwritten by the stepsize callback + save_everystep=false, callback=callbacks); diff --git a/examples/fluid/sphere_surface_tension_3d.jl b/examples/fluid/sphere_surface_tension_3d.jl new file mode 100644 index 0000000000..58a5062945 --- /dev/null +++ b/examples/fluid/sphere_surface_tension_3d.jl @@ -0,0 +1,25 @@ +# In this example we can observe that the `SurfaceTensionAkinci` surface tension model correctly leads to a +# surface minimization of the water cube and approaches a sphere. +using TrixiParticles +using OrdinaryDiffEq + +fluid_density = 1000.0 + +particle_spacing = 0.1 +fluid_size = (0.9, 0.9, 0.9) + +sound_speed = 20.0 + +# For all surface tension simulations, we need a compact support of `2 * particle_spacing` +smoothing_length = 1.0 * particle_spacing + +nu = 0.01 + +trixi_include(@__MODULE__, + joinpath(examples_dir(), "fluid", "sphere_surface_tension_2d.jl"), + dt=0.1, cfl=1.2, surface_tension_coefficient=0.1, + tspan=(0.0, 10.0), nu=nu, + alpha=10 * nu / (smoothing_length * sound_speed), + smoothing_kernel=SchoenbergCubicSplineKernel{3}(), + particle_spacing=particle_spacing, sound_speed=sound_speed, + fluid_density=fluid_density, fluid_size=fluid_size) diff --git a/examples/fluid/sphere_surface_tension_wall_2d.jl b/examples/fluid/sphere_surface_tension_wall_2d.jl new file mode 100644 index 0000000000..3e1cbdb80f --- /dev/null +++ b/examples/fluid/sphere_surface_tension_wall_2d.jl @@ -0,0 +1,44 @@ +# In this example we try to approach the static shape of a water droplet on a horizontal plane. +# The shape of a static droplet can be calculated from the Young-Laplace equation. +using TrixiParticles +using OrdinaryDiffEq + +# ========================================================================================== +# ==== Resolution +fluid_particle_spacing = 0.0025 + +# ========================================================================================== +# ==== Experiment Setup +tspan = (0.0, 0.5) + +# Boundary geometry and initial fluid particle positions +tank_size = (0.5, 0.1) + +fluid_density = 1000.0 +sound_speed = 120.0 + +sphere_radius = 0.05 + +sphere1_center = (0.25, sphere_radius) +sphere1 = SphereShape(fluid_particle_spacing, sphere_radius, sphere1_center, + fluid_density, sphere_type=VoxelSphere()) + +# ========================================================================================== +# ==== Fluid +fluid_smoothing_length = 1.0 * fluid_particle_spacing + +# For perfect wetting +# nu = 0.0005 +# For no wetting +nu = 0.001 + +alpha = 8 * nu / (fluid_smoothing_length * sound_speed) +# `adhesion_coefficient = 1.0` and `surface_tension_coefficient = 0.01` for perfect wetting +# `adhesion_coefficient = 0.001` and `surface_tension_coefficient = 2.0` for no wetting +trixi_include(@__MODULE__, + joinpath(examples_dir(), "fluid", "falling_water_spheres_2d.jl"), + sphere=nothing, sphere1=sphere1, adhesion_coefficient=0.001, + wall_viscosity=4.0 * nu, surface_tension_coefficient=2.0, alpha=alpha, + sound_speed=sound_speed, fluid_density=fluid_density, nu=nu, + fluid_particle_spacing=fluid_particle_spacing, tspan=tspan, + tank_size=tank_size) diff --git a/examples/fsi/dam_break_2d.jl b/examples/fsi/dam_break_plate_2d.jl similarity index 97% rename from examples/fsi/dam_break_2d.jl rename to examples/fsi/dam_break_plate_2d.jl index 8126e9759b..4db77b44ac 100644 --- a/examples/fsi/dam_break_2d.jl +++ b/examples/fsi/dam_break_plate_2d.jl @@ -14,7 +14,7 @@ fluid_particle_spacing = 0.01 n_particles_x = 5 # Change spacing ratio to 3 and boundary layers to 1 when using Monaghan-Kajtar boundary model -boundary_layers = 3 +boundary_layers = 4 spacing_ratio = 1 # ========================================================================================== @@ -29,7 +29,7 @@ tank_size = 4 .* initial_fluid_size fluid_density = 1000.0 sound_speed = 20 * sqrt(gravity * initial_fluid_size[2]) state_equation = StateEquationCole(; sound_speed, reference_density=fluid_density, - exponent=7) + exponent=1) tank = RectangularTank(fluid_particle_spacing, initial_fluid_size, tank_size, fluid_density, n_layers=boundary_layers, spacing_ratio=spacing_ratio, @@ -65,8 +65,8 @@ solid = union(plate, fixed_particles) # ========================================================================================== # ==== Fluid -smoothing_length = 1.2 * fluid_particle_spacing -smoothing_kernel = SchoenbergCubicSplineKernel{2}() +smoothing_length = 3.5 * fluid_particle_spacing +smoothing_kernel = WendlandC2Kernel{2}() fluid_density_calculator = ContinuityDensity() viscosity = ArtificialViscosityMonaghan(alpha=0.02, beta=0.0) diff --git a/examples/fsi/falling_sphere_2d.jl b/examples/fsi/falling_sphere_2d.jl index be775ea690..99a69bc2c1 100644 --- a/examples/fsi/falling_sphere_2d.jl +++ b/examples/fsi/falling_sphere_2d.jl @@ -4,4 +4,4 @@ trixi_include(@__MODULE__, joinpath(examples_dir(), "fsi", "falling_spheres_2d.jl"), solid_system_2=nothing, fluid_particle_spacing=0.02, initial_fluid_size=(1.0, 0.9), tank_size=(1.0, 1.0), - tspan=(0.0, 2.0), abstol=1e-6, reltol=1e-3) + tspan=(0.0, 1.0), abstol=1e-6, reltol=1e-3) diff --git a/examples/fsi/falling_sphere_3d.jl b/examples/fsi/falling_sphere_3d.jl index 239836c9d3..d44911409e 100644 --- a/examples/fsi/falling_sphere_3d.jl +++ b/examples/fsi/falling_sphere_3d.jl @@ -11,4 +11,4 @@ trixi_include(@__MODULE__, sphere_type=RoundSphere(), output_directory="out", prefix="", write_meta_data=false, # Files with meta data can't be read by meshio - tspan=(0.0, 2.0), abstol=1e-6, reltol=1e-3) + tspan=(0.0, 1.0), abstol=1e-6, reltol=1e-3) diff --git a/examples/fsi/falling_spheres_2d.jl b/examples/fsi/falling_spheres_2d.jl index 66f769308f..6a2397a4bb 100644 --- a/examples/fsi/falling_spheres_2d.jl +++ b/examples/fsi/falling_spheres_2d.jl @@ -13,7 +13,7 @@ spacing_ratio = 1 # ========================================================================================== # ==== Experiment Setup gravity = 9.81 -tspan = (0.0, 2.0) +tspan = (0.0, 1.0) # Boundary geometry and initial fluid particle positions initial_fluid_size = (2.0, 0.9) diff --git a/examples/n_body/n_body_benchmark_trixi.jl b/examples/n_body/n_body_benchmark_trixi.jl index 17868d201d..b47f689e78 100644 --- a/examples/n_body/n_body_benchmark_trixi.jl +++ b/examples/n_body/n_body_benchmark_trixi.jl @@ -9,8 +9,9 @@ using Polyester include("n_body_system.jl") -# Redefine interact in a more optimized way. -function TrixiParticles.interact!(du, u_particle_system, u_neighbor_system, +# Redefine interact in a more optimized way +function TrixiParticles.interact!(dv, v_particle_system, u_particle_system, + v_neighbor_system, u_neighbor_system, neighborhood_search, particle_system::NBodySystem, neighbor_system::NBodySystem) @@ -34,16 +35,15 @@ function TrixiParticles.interact!(du, u_particle_system, u_neighbor_system, tmp1 = mass[neighbor] * tmp tmp2 = mass[particle] * tmp - for i in 1:ndims(particle_system) - j = i + ndims(particle_system) - # This is slightly faster than du[...] += tmp1 * pos_diff[i] - du[j, particle] = muladd(tmp1, pos_diff[i], du[j, particle]) - du[j, neighbor] = muladd(tmp2, -pos_diff[i], du[j, neighbor]) + @inbounds for i in 1:ndims(particle_system) + # This is slightly faster than dv[...] += tmp1 * pos_diff[i] + dv[i, particle] = muladd(tmp1, pos_diff[i], dv[i, particle]) + dv[i, neighbor] = muladd(tmp2, -pos_diff[i], dv[i, neighbor]) end end end - return du + return dv end # ========================================================================================== @@ -98,6 +98,8 @@ function symplectic_euler!(velocity, coordinates, semi) u[i] += 0.01 * du[i] end end + + return v, u end # One RHS evaluation is so fast that timers make it multiple times slower. @@ -106,7 +108,7 @@ end filename = tempname() open(filename, "w") do f redirect_stderr(f) do - TrixiParticles.TimerOutputs.disable_debug_timings(TrixiParticles) + TrixiParticles.disable_debug_timings() end end @@ -122,6 +124,6 @@ end # Enable timers again open(filename, "w") do f redirect_stderr(f) do - TrixiParticles.TimerOutputs.enable_debug_timings(TrixiParticles) + TrixiParticles.enable_debug_timings() end end diff --git a/examples/n_body/n_body_solar_system.jl b/examples/n_body/n_body_solar_system.jl index 6fa860089e..b4da85aeb9 100644 --- a/examples/n_body/n_body_solar_system.jl +++ b/examples/n_body/n_body_solar_system.jl @@ -49,7 +49,7 @@ callbacks = CallbackSet(info_callback, saving_callback) filename = tempname() open(filename, "w") do f redirect_stderr(f) do - TrixiParticles.TimerOutputs.disable_debug_timings(TrixiParticles) + TrixiParticles.disable_debug_timings() end end @@ -63,6 +63,6 @@ sol = solve(ode, SymplecticEuler(), # Enable timers again open(filename, "w") do f redirect_stderr(f) do - TrixiParticles.TimerOutputs.enable_debug_timings(TrixiParticles) + TrixiParticles.enable_debug_timings() end end diff --git a/examples/n_body/n_body_system.jl b/examples/n_body/n_body_system.jl index 068eae953a..556147ed48 100644 --- a/examples/n_body/n_body_system.jl +++ b/examples/n_body/n_body_system.jl @@ -1,16 +1,19 @@ using TrixiParticles using LinearAlgebra -struct NBodySystem{NDIMS, ELTYPE <: Real} <: TrixiParticles.System{NDIMS} +# The second type parameter of `System` can't be `Nothing`, or TrixiParticles will launch +# GPU kernel for `for_particle_neighbor` loops. +struct NBodySystem{NDIMS, ELTYPE <: Real} <: TrixiParticles.System{NDIMS, 0} initial_condition :: InitialCondition{ELTYPE} mass :: Array{ELTYPE, 1} # [particle] G :: ELTYPE + buffer :: Nothing function NBodySystem(initial_condition, G) mass = copy(initial_condition.mass) new{size(initial_condition.coordinates, 1), - eltype(mass)}(initial_condition, mass, G) + eltype(mass)}(initial_condition, mass, G, nothing) end end @@ -31,9 +34,12 @@ function TrixiParticles.write_v0!(v0, system::NBodySystem) end # NHS update -function TrixiParticles.nhs_coords(system::NBodySystem, - neighbor::NBodySystem, u) - return u +function TrixiParticles.update_nhs!(neighborhood_search, + system::NBodySystem, neighbor::NBodySystem, + u_system, u_neighbor) + TrixiParticles.PointNeighbors.update!(neighborhood_search, + u_system, u_neighbor, + particles_moving=(true, true)) end function TrixiParticles.compact_support(system::NBodySystem, diff --git a/examples/postprocessing/interpolation_plane.jl b/examples/postprocessing/interpolation_plane.jl index 5c94df9969..ea273d0fc7 100644 --- a/examples/postprocessing/interpolation_plane.jl +++ b/examples/postprocessing/interpolation_plane.jl @@ -96,3 +96,25 @@ plot_3d = Plots.plot(scatter_3d, xlabel="X", ylabel="Y", zlabel="Z", combined_plot = Plots.plot(plot1, plot2, plot3, plot_3d, layout=(2, 2), size=(1000, 1500), margin=3mm) + +# If we want to save planes at regular intervals, we can use the postprocessing callback. +function save_interpolated_plane(v, u, t, system) + # Size of the patch to be interpolated + interpolation_start = [0.0, 0.0] + interpolation_end = [tank_size[1], tank_size[2]] + + # The resolution the plane is interpolated to. In this case twice the original resolution. + resolution = 0.5 * fluid_particle_spacing + + file_id = ceil(Int, t * 10000) + interpolate_plane_2d_vtk(interpolation_start, interpolation_end, resolution, + semi, system, v, u, filename="plane_$file_id.vti") + return nothing +end + +save_interpolation_cb = PostprocessCallback(; dt=0.1, write_file_interval=0, + save_interpolated_plane) + +trixi_include(@__MODULE__, + joinpath(examples_dir(), "fluid", "dam_break_2d.jl"), tspan=(0.0, 0.2), + extra_callback=save_interpolation_cb, fluid_particle_spacing=0.01) diff --git a/paper/falling_sphere_combined_nonstick_4k_178.png b/paper/falling_sphere_combined_nonstick_4k_178.png new file mode 100644 index 0000000000..7a81820e67 Binary files /dev/null and b/paper/falling_sphere_combined_nonstick_4k_178.png differ diff --git a/paper/oscillating_beam.png b/paper/oscillating_beam.png new file mode 100644 index 0000000000..08328c24d9 Binary files /dev/null and b/paper/oscillating_beam.png differ diff --git a/paper/paper.bib b/paper/paper.bib new file mode 100644 index 0000000000..561d91ddc6 --- /dev/null +++ b/paper/paper.bib @@ -0,0 +1,222 @@ +@article{Antuono:2010, + url = {http://dx.doi.org/10.1016/j.cpc.2009.11.002}, + Author = {{Antuono}, M. and {Colagrossi}, A. and {Marrone}, S. and {Molteni}, D.}, + doi = {10.1016/j.cpc.2009.11.002}, + Journal = {Computer Physics Communications}, + Month = mar, + volume = 181, + Title = {{Free-Surface Flows Solved by Means of SPH Schemes with Numerical Diffusive Terms}}, + Year = 2010 +} + +@article{Ramachandran:2019, + url = {https://doi.org/10.1016/j.compfluid.2018.11.023}, + Author = {{Ramachandran}, P. and {Puri}, K.}, + doi = {10.1016/j.compfluid.2018.11.023}, + Journal = {Computers and Fluids}, + Month = jan, + volume = 179, + Title = {{Entropically damped artificial compressibility for SPH}}, + Year = 2019 +} + +@article{Adami:2013, + url = {http://dx.doi.org/10.1016/j.jcp.2013.01.043}, + Author = {{Adami}, s. and {Hu}, X.~Y. and {Adams}, N.~A.}, + doi = {10.1016/j.jcp.2013.01.043}, + Journal = {Journal of Computational Physics}, + Month = may, + volume = 241, + Title = {{A transport-velocity formulation for smoothed particle hydrodynamics}}, + Year = 2013 +} + +@Article{O_Connor:2021, + url = {https://doi.org/10.1016/j.jfluidstructs.2021.103312}, + author = {{O'Connor}, J. and {Rogers}, B.~D.}, + journal = {Journal of Fluids and Structures}, + title = {A fluid{\textendash}structure interaction model for free-surface flows and flexible structures using smoothed particle hydrodynamics on a {GPU}}, + month = jul, + volume = 104, + doi = {10.1016/j.jfluidstructs.2021.103312}, + year = 2021 +} + +@Article{Clausen:2013, + author = {{Clausen}, J.~R.}, + journal = {Physical Review E}, + title = {Entropically damped form of artificial compressibility for explicit simulation of incompressible flow}, + year = {2013}, + month = {jan}, + volume = {87}, + doi = {10.1103/physreve.87.013309} +} + +@Article{Lastiwka:2009, + author = {{Lastiwka}, M. and {Basa}, M. and {Quinlan}, N.~J.}, + journal = {International Journal for Numerical Methods in Fluids}, + title = {Permeable and non-reflecting boundary conditions in {SPH}}, + year = {2009}, + month = {nov}, + volume = {61}, + doi = {10.1002/fld.1971} +} + +@Article{Monaghan:2005, + url = {https://doi.org/10.1088/0034-4885/68/8/r01}, + author = {{Monaghan}, J.~J.}, + journal = {Reports on Progress in Physics}, + title = {Smoothed particle hydrodynamics}, + month = jul, + volume = {68}, + doi = {10.1088/0034-4885/68/8/r01}, + year = 2005 +} + +@Misc{Bicanic:2004, + author = {Bićanić, Nenad}, + month = aug, + title = {Discrete Element Methods}, + year = {2004}, + doi = {10.1002/0470091355.ecm006.pub2}, + groups = {DEM}, + isbn = {9780470091357}, + journal = {Encyclopedia of Computational Mechanics}, + publisher = {Wiley} +} + +@Article{Cundall:1979, + author = {Cundall, P. A. and Strack, O. D. L.}, + journal = {Géotechnique}, + title = {A discrete numerical model for granular assemblies}, + year = {1979}, + issn = {1751-7656}, + month = mar, + number = {1}, + pages = {47--65}, + volume = {29}, + doi = {10.1680/geot.1979.29.1.47}, + groups = {DEM}, + publisher = {Thomas Telford Ltd.} +} + +@Article{Monaghan:1977, + author = {Gingold, R. A. and Monaghan, J. J.}, + journal = {Monthly Notices of The Royal Astronomical Society}, + title = {Smoothed particle hydrodynamics: theory and application to non-spherical stars}, + month = nov, + volume = {181}, + doi = {10.1093/mnras/181.3.375}, + year = 1977 +} + +@InBook{Turek:2007, + author = {{Turek}, S. and {Hron}, J.}, + publisher = {Springer Berlin Heidelberg}, + title = {Proposal for Numerical Benchmarking of Fluid-Structure Interaction between an Elastic Object and Laminar Incompressible Flow}, + year = {2007}, + isbn = {9783540345954}, + booktitle = {Fluid-Structure Interaction}, + doi = {10.1007/3-540-34596-5_15} +} + +@misc{schlottkelakemper2020trixi, + title={{T}rixi.jl: {A}daptive high-order numerical simulations + of hyperbolic {PDE}s in {J}ulia}, + author={Schlottke-Lakemper, Michael and Gassner, Gregor J and + Ranocha, Hendrik and Winters, Andrew R and Chan, Jesse}, + year={2021}, + month={09}, + doi={10.5281/zenodo.3996439} +} + +@Article{Dominguez:2021, + author = {J. M. Dom{\'{\i}}nguez and G. Fourtakas and C. Altomare and R. B. Canelas and A. Tafuni and O. Garc{\'{\i}}a-Feal and I. Mart{\'{\i}}nez-Est{\'{e}}vez and A. Mokos and R. Vacondio and A. J. C. Crespo and B. D. Rogers and P. K. Stansby and M. G{\'{o}}mez-Gesteira}, + journal = {Computational Particle Mechanics}, + title = {{DualSPHysics}: from fluid dynamics to multiphysics problems}, + year = {2021}, + issn = {2196-4386}, + month = {mar}, + number = {5}, + pages = {867--895}, + volume = {9}, + doi = {10.1007/s40571-021-00404-2}, + file = {:/home/niki/bwSyncShare/partix/papers/sph/DualSPHysics.pdf:PDF}, + groups = {sph}, + publisher = {Springer Science and Business Media {LLC}}, +} + + +@article{Ramachandran:2021, + title = {{{PySPH}}: {{A Python-based Framework}} for {{Smoothed Particle Hydrodynamics}}}, + shorttitle = {{{PySPH}}}, + author = {Ramachandran, Prabhu and Bhosale, Aditya and Puri, + Kunal and Negi, Pawan and Muta, Abhinav and Dinesh, + A. and Menon, Dileep and Govind, Rahul and Sanka, Suraj and Sebastian, + Amal S. and Sen, Ananyo and Kaushik, Rohan and Kumar, + Anshuman and Kurapati, Vikas and Patil, Mrinalgouda and Tavker, + Deep and Pandey, Pankaj and Kaushik, Chandrashekhar and Dutt, + Arkopal and Agarwal, Arpit}, + year = {2021}, + month = dec, + journal = {ACM Transactions on Mathematical Software}, + volume = {47}, + number = {4}, + pages = {1--38}, + issn = {0098-3500, 1557-7295}, + doi = {10.1145/3460773}, + langid = {english} +} + +@software{Bender, + author = {Bender, Jan and others}, + license = {MIT}, + date = {2024-05-04}, + title = {{SPlisHSPlasH Library}}, + url = {https://github.com/InteractiveComputerGraphics/SPlisHSPlasH} +} + +@article{Zhang:2021, + author = {Chi Zhang and Massoud Rezavand and Yujie Zhu and Yongchuan Yu and Dong Wu and Wenbin Zhang and Jianhang Wang and Xiangyu Hu}, + title = {SPHinXsys: An open-source multi-physics and multi-resolution library based on smoothed particle hydrodynamics}, + journal = {Computer Physics Communications}, + volume = {267}, + pages = {108066}, + year = {2021}, + issn = {0010-4655}, + doi = {10.1016/j.cpc.2021.108066} +} + +@Article{Biriukov:2018, + author = {{Biriukov}, S. and {Price}, D.~J.}, + journal = {Monthly Notices of the Royal Astronomical Society}, + title = {Stable anisotropic heat conduction in smoothed particle hydrodynamics}, + year = {2018}, + month = dec, + volume = {483}, + doi = {10.1093/mnras/sty3413} +} + +@article{Akinci:2013, + author = {Akinci, Nadir and Akinci, Gizem and Teschner, Matthias}, + title = {Versatile surface tension and adhesion for SPH fluids}, + journal = {ACM Trans. Graph.}, + year = {2013}, + publisher = {Association for Computing Machinery}, + volume = {32}, + number = {6}, + doi = {10.1145/2508363.2508395}, + month = {nov} +} + +@Article{Laha:2024, + author = {Laha, Sumanta and Fourtakas, Georgios and Das, Prasanta K. and Keshmiri, Amir}, + journal = {Scientific Reports}, + title = {Smoothed particle hydrodynamics based FSI simulation of the native and mechanical heart valves in a patient-specific aortic model}, + year = {2024}, + month = mar, + number = {1}, + volume = {14}, + doi = {10.1038/s41598-024-57177-w}, + publisher = {Springer Science and Business Media LLC} +} diff --git a/paper/paper.md b/paper/paper.md new file mode 100644 index 0000000000..18697f081a --- /dev/null +++ b/paper/paper.md @@ -0,0 +1,146 @@ +--- +title: 'TrixiParticles.jl: Particle-based multiphysics simulation in Julia' +tags: + - Julia + - SPH + - particle-based + - multiphysics +authors: + - name: Niklas S. Neher + orcid: 0009-0004-2472-0923 + equal-contrib: true + affiliation: "1" # (Multiple affiliations must be quoted) + - name: Erik Faulhaber + orcid: 0000-0001-9788-5949 + equal-contrib: true # (This is how you can denote equal contributions between multiple authors) + affiliation: 2 + - name: Sven Berger + orcid: 0000-0001-6083-7038 + equal-contrib: true # (This is how you can denote equal contributions between multiple authors) + affiliation: 3 + - name: Gregor J. Gassner + orcid: 0000-0002-1752-1158 + affiliation: 2 + - name: Michael Schlottke-Lakemper + orcid: 0000-0002-3195-2536 + affiliation: 1, 4 +affiliations: + - name: High-Performance Computing Center Stuttgart, University of Stuttgart, Germany + index: 1 + - name: Department of Mathematics and Computer Science, University of Cologne, Germany + index: 2 + - name: Institute of Surface Science, Helmholtz-Zentrum hereon, Germany + index: 3 + - name: High-Performance Scientific Computing, University of Augsburg, Germany + index: 4 +date: \today +bibliography: paper.bib +--- + +# Summary + +TrixiParticles.jl is a Julia-based open-source package for particle-based multiphysics simulations and part of the Trixi Framework [@schlottkelakemper2020trixi]. It handles complex geometries and specialized applications, such as computational fluid dynamics (CFD) and structural dynamics, by providing a versatile platform for particle-based methods. TrixiParticles.jl allows for the straightforward addition of new particle systems and their interactions, facilitating the setup of coupled multiphysics simulations such as fluid-structure interaction (FSI). Furthermore, simulations are set up directly with Julia code, simplifying the integration of custom functionalities and promoting rapid prototyping. + +Here, we give a brief overview of the software package TrixiParticles.jl, starting with the scientific background before going on to describe the functionality and benefit in more detail. +Finally, exemplary results and implemented features are briefly presented. + +# Statement of need + +Numerical simulations, such as CFD, structural mechanics, thermodynamics, or magnetohydrodynamics, pose several challenges when simulating real-world problems. +For example, they involve complex geometries, free surfaces, +deformable boundaries, and moving material interfaces, as well as the coupling of multiple systems with different mathematical models. + +One way to address these challenges is to use particle-based methods, in which the particles either represent physical particles or mathematical interpolation points. +The former case refers to methods that model separate, discrete particles with rotational degrees of freedom such as the Discrete Element Method (DEM) proposed by [@Cundall:1979], +whereas the latter case refers to methods such as Smoothed Particle Hydrodynamics (SPH), which is a numerical discretization method for solving problems in continuum mechanics. +SPH was originally developed by [@Monaghan:1977] to simulate astrophysical applications and is now widely used to simulate CFD, structural mechanics, and even heat conduction problems. + +The Lagrangian formalism in particle-based methods allows particles to move along a velocity field without any connection to neighboring particles, +thus eliminating the need for a mesh to discretize the simulation domain. +This mesh-free approach simplifies the preprocessing, making it particularly suitable for simulating complex geometries and also facilitates simulations of large deformations and movements. +By representing each material with its own set of particles, +coupling multiple different physical systems into a single multiphysics setup is straightforward. +In addition, particle-based methods are inherently suited to simulating free surfaces, material interfaces, and moving boundaries. + +There are several open-source software projects specialized for SPH methods, including DualSPHysics [@Dominguez:2021], SPlisHSPlasH [@Bender], and SPHinXsys [@Zhang:2021], +written in C++, and PySPH [@Ramachandran:2021], written in Python. +These softwares utilize the advantages of the SPH methods to simulate problems such as FSI and free surfaces [@O_Connor:2021] or complex geometries [@Laha:2024]. + +TrixiParticles.jl is written in the Julia programming language and combines the advantage of easy and rapid prototyping +with the ability for high-performance computing using multicore parallelization and hardware accelerators. +It provides support for developing and testing new SPH methods and also for simulating and coupling other particle-based methods such as DEM. +Since simulations are configured and set up using only Julia code, custom methods or particle interactions can be added without modifying the original source code. + + +# Overview of particle-based simulation + +In TrixiParticles.jl, particles of a single particle-based method, e.g. SPH or DEM, are grouped into a \emph{system}. +The interaction between two particles is defined entirely by the types of their systems. This approach makes it easy to support new methods and different physics +by adding a new system and defining its pairwise interaction with other systems. + +![Particles of two different systems $\mathcal{S}_1$ and $\mathcal{S}_2$ in a simulation domain. The black and gray dashed circles represent the search radii for neighbors of particles $a$ and $b$, respectively.\label{fig:systems}](systems.png){width=30%} + +To illustrate this, \autoref{fig:systems} depicts particles within a simulation domain. The black particles belong to system $\mathcal{S}_1$ and the gray particles belong to system $\mathcal{S}_2$. +In general, the force $f_a$ experienced by a particle $a$ is calculated as +$$ f_a = \sum_{b \in \mathcal{S}_1} f_{ab}^{\mathcal{S}_1} + \sum_{b \in \mathcal{S}_2} f_{ab}^{\mathcal{S}_2} + \dots + \sum_{b\in \mathcal{S}_n}f_{ab}^{\mathcal{S}_n}, $$ +where the interaction force $f_{ab}^{\mathcal{S}_i}$ that particle $a$ experiences due to particle $b$ depends on the system type of particle $a$, the system type $\mathcal{S}_i$ of particle $b$, and the relative particle distance. +For computational efficiency, only particles with a distance within a system-dependent search radius interact with each other. + +For example, the SPH method determines the force between two SPH particles according to [@Monaghan:2005] as +$$ f_{ab} = -m_a m_b \left( \frac{p_a}{\rho_a^2} + \frac{p_b}{\rho_b^2} \right) \nabla_a W_{ab} + \Pi_{ab},$$ +where $m_a$, $m_b$, $\rho_a$, $\rho_b$, $p_a$, $p_b$ are the mass, density, and pressure of particles $a$ and $b$, respectively. The last term $\Pi_{ab}$ includes dissipative terms such as artificial viscosity [@Monaghan:2005] and is scheme-specific. The weighting function $W_{ab}$, also called kernel-function, depends on the relative distance between particles $a$ and $b$. + +# Code structure + +\autoref{fig:structure} depicts the basic building blocks of TrixiParticles.jl. A simulation essentially consists of spatial discretization (left block) and time integration (center block). For the latter, the Julia package [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) is used. The callbacks (right block) provide additional functionality and communicate with the time integration method during the simulation. + +The semidiscretization couples the systems of a simulation and also manages the corresponding neighborhood searches for each system. +The resulting ordinary differential equation (ODE) problem is then fed into the time integrator and is solved by an appropriate numerical time integration scheme. + +![Main building blocks of TrixiParticles.jl. \label{fig:structure}](structure.png){width=100%} + +## Features + +At the time of writing, the following feature highlights are available in TrixiParticles.jl: + +* *Fluid Systems* + + Weakly compressible SPH (WCSPH): Standard SPH method originally developed by [@Monaghan:1977] to simulate astrophysics applications. + + Entropically damped artificial compressibility (EDAC) for SPH: As opposed to the WCSPH scheme, which uses an equation of state, this scheme uses a pressure evolution equation to calculate the pressure, which is derived by [@Clausen:2013] and adapted to SPH by [@Ramachandran:2019]. + +* *Structure Systems* + + Total lagrangian SPH (TLSPH): Method to simulate elastic structures where all quantities are calculated with respect to the initial configuration [@O_Connor:2021]. + + DEM: Discretization of granular matter or bulk material into a finite set of distinct, interacting mass elements [@Bicanic:2004], [@Cundall:1979]. + +* *Boundary Systems* + + Boundary system with several boundary models, where each model follows a different interaction rule. + + Open boundary system to simulate non-reflecting (open) boundary conditions [@Lastiwka:2009]. + +* *Correction methods and models* + + Density diffusion [@Antuono:2010] + + Transport-velocity formulation (TVF) [@Adami:2013] + + Intra-particle-force surface tension [@Akinci:2013] + +* *Performance and parallelization* + + Shared memory parallelism using multithreading + + Highly optimized neighborhood search providing various approaches + + GPU support + +TrixiParticles.jl is open source and available under the MIT license at [GitHub](https://github.com/trixi-framework/TrixiParticles.jl), along with detailed [documentation](https://trixi-framework.github.io/TrixiParticles.jl/stable/) on how to use it. Additionally, we provide tutorials explaining how to set up a simulation of fluid flows, structure mechanics, or FSI. +A collection of simulation setups to get started with can be found in the examples directory. + +As one of the validation examples, \autoref{fig:beam_y_deflection} compares SPH results of TrixiParticles.jl and [@O_Connor:2021] against benchmark data from the finite element simulation of [@Turek:2007]. +The plots show the y-deflection of the tip of a beam oscillating under its own weight. +The results obtained with TrixiParticles.jl match those of [@O_Connor:2021] well. + +![Comparison of TrixiParticles.jl and [@O_Connor:2021] against [@Turek:2007]: Tip y-deflection of an oscillating beam with different resolutions, where $t_s$ is the thickness of the beam and $dp$ is the particle spacing. \label{fig:beam_y_deflection}](oscillating_beam.png){width=60%} + +\autoref{fig:falling_sphere} illustrates an exemplary simulation result, where an elastic sphere, modeled with TLSPH, falls into a tank filled with water, modeled by WCSPH. + +![TrixiParticles.jl simulation of an elastic sphere falling into a water tank. Left: Results rendered with blender. Right: Underlying particle representation. \label{fig:falling_sphere}](falling_sphere_combined_nonstick_4k_178.png){width=100%} + +# Acknowledgements + +Sven Berger acknowledges funding from [hereon](https://www.hereon.de/) and [HiRSE](https://www.helmholtz-hirse.de/). +Michael Schlottke-Lakemper and Gregor Gassner receive funding through the [DFG research unit FOR 5409](https://snubic.io/) "Structure-Preserving Numerical Methods for Bulk- and Interface Coupling of Heterogeneous Models (SNuBIC)" (project number 463312734). + +# References diff --git a/paper/structure.png b/paper/structure.png new file mode 100644 index 0000000000..eb7b40235b Binary files /dev/null and b/paper/structure.png differ diff --git a/paper/systems.png b/paper/systems.png new file mode 100644 index 0000000000..15eb7b7d28 Binary files /dev/null and b/paper/systems.png differ diff --git a/src/TrixiParticles.jl b/src/TrixiParticles.jl index d623cb9f78..76e3cffc8e 100644 --- a/src/TrixiParticles.jl +++ b/src/TrixiParticles.jl @@ -2,15 +2,17 @@ module TrixiParticles using Reexport: @reexport +using Adapt: Adapt using CSV: CSV using Dates using DataFrames: DataFrame using DiffEqCallbacks: PeriodicCallback, PeriodicCallbackAffect, PresetTimeCallback using FastPow: @fastpow using ForwardDiff: ForwardDiff +using GPUArrays: AbstractGPUArray using JSON: JSON +using KernelAbstractions: KernelAbstractions, @kernel, @index using LinearAlgebra: norm, dot, I, tr, inv, pinv, det -using Morton: cartesian2morton using MuladdMacro: @muladd using Polyester: Polyester, @batch using Printf: @printf, @sprintf @@ -20,48 +22,52 @@ using SciMLBase: CallbackSet, DiscreteCallback, DynamicalODEProblem, u_modified! @reexport using StaticArrays: SVector using StaticArrays: @SMatrix, SMatrix, setindex using StrideArrays: PtrArray, StaticInt -using ThreadingUtilities using TimerOutputs: TimerOutput, TimerOutputs, print_timer, reset_timer! -using TrixiBase: trixi_include +using TrixiBase: trixi_include, @trixi_timeit, timer, timeit_debug_enabled, + disable_debug_timings, enable_debug_timings +@reexport using PointNeighbors: TrivialNeighborhoodSearch, GridNeighborhoodSearch +using PointNeighbors: PointNeighbors, for_particle_neighbor using WriteVTK: vtk_grid, MeshCell, VTKCellTypes, paraview_collection, vtk_save -# util needs to be first because of macro @trixi_timeit +# `util.jl` depends on the `GPUSystem` type defined in `system.jl` +include("general/system.jl") +# `util.jl` needs to be next because of the macros `@trixi_timeit` and `@threaded` include("util.jl") include("callbacks/callbacks.jl") include("general/general.jl") -include("neighborhood_search/neighborhood_search.jl") include("setups/setups.jl") include("schemes/schemes.jl") # Note that `semidiscretization.jl` depends on the system types and has to be -# included separately. +# included separately. `gpu.jl` in turn depends on the semidiscretization type. include("general/semidiscretization.jl") +include("general/gpu.jl") include("visualization/write2vtk.jl") include("visualization/recipes_plots.jl") export Semidiscretization, semidiscretize, restart_with! export InitialCondition export WeaklyCompressibleSPHSystem, EntropicallyDampedSPHSystem, TotalLagrangianSPHSystem, - BoundarySPHSystem + BoundarySPHSystem, DEMSystem, BoundaryDEMSystem, OpenBoundarySPHSystem, InFlow, + OutFlow export InfoCallback, SolutionSavingCallback, DensityReinitializationCallback, - PostprocessCallback, StepsizeCallback + PostprocessCallback, StepsizeCallback, UpdateCallback export ContinuityDensity, SummationDensity export PenaltyForceGanzenmueller export SchoenbergCubicSplineKernel, SchoenbergQuarticSplineKernel, SchoenbergQuinticSplineKernel, GaussianKernel, WendlandC2Kernel, WendlandC4Kernel, WendlandC6Kernel, SpikyKernel, Poly6Kernel export StateEquationCole -export ArtificialViscosityMonaghan, ViscosityAdami +export ArtificialViscosityMonaghan, ViscosityAdami, ViscosityMorris export DensityDiffusion, DensityDiffusionMolteniColagrossi, DensityDiffusionFerrari, DensityDiffusionAntuono export BoundaryModelMonaghanKajtar, BoundaryModelDummyParticles, AdamiPressureExtrapolation, PressureMirroring, PressureZeroing export BoundaryMovement -export GridNeighborhoodSearch, TrivialNeighborhoodSearch export examples_dir, validation_dir, trixi_include export trixi2vtk export RectangularTank, RectangularShape, SphereShape -export VoxelSphere, RoundSphere, reset_wall! +export VoxelSphere, RoundSphere, reset_wall!, extrude_geometry export SourceTermDamping export ShepardKernelCorrection, KernelCorrection, AkinciFreeSurfaceCorrection, GradientCorrection, BlendedGradientCorrection, MixedKernelGradientCorrection @@ -70,5 +76,6 @@ export kinetic_energy, total_mass, max_pressure, min_pressure, avg_pressure, max_density, min_density, avg_density export interpolate_line, interpolate_point, interpolate_plane_3d, interpolate_plane_2d, interpolate_plane_2d_vtk +export SurfaceTensionAkinci, CohesionForceAkinci end # module diff --git a/src/callbacks/callbacks.jl b/src/callbacks/callbacks.jl index 08b28187ac..1aefdff728 100644 --- a/src/callbacks/callbacks.jl +++ b/src/callbacks/callbacks.jl @@ -21,8 +21,7 @@ end # (total #steps) (#accepted steps) # We need to check the number of accepted steps since callbacks are not # activated after a rejected step. - return interval > 0 && (((integrator.stats.naccept % interval == 0) && - !(integrator.stats.naccept == 0 && integrator.iter > 0)) || + return interval > 0 && ((integrator.stats.naccept % interval == 0) || (save_final_solution && isfinished(integrator))) end @@ -31,3 +30,4 @@ include("solution_saving.jl") include("density_reinit.jl") include("post_process.jl") include("stepsize.jl") +include("update.jl") diff --git a/src/callbacks/density_reinit.jl b/src/callbacks/density_reinit.jl index 5e9f980271..74c6fe5c9c 100644 --- a/src/callbacks/density_reinit.jl +++ b/src/callbacks/density_reinit.jl @@ -74,7 +74,7 @@ function initialize_reinit_cb!(cb::DensityReinitializationCallback, u, t, integr # Update systems to compute quantities like density and pressure. semi = integrator.p v_ode, u_ode = u.x - update_systems_and_nhs(v_ode, u_ode, semi, t) + update_systems_and_nhs(v_ode, u_ode, semi, t; update_from_callback=true) # Apply the callback. cb(integrator) @@ -89,13 +89,7 @@ end function (reinit_callback::DensityReinitializationCallback{Int})(u, t, integrator) (; interval) = reinit_callback - # With error-based step size control, some steps can be rejected. Thus, - # `integrator.iter >= integrator.stats.naccept` - # (total #steps) (#accepted steps) - # We need to check the number of accepted steps since callbacks are not - # activated after a rejected step. - return interval > 0 && ((integrator.stats.naccept % interval == 0) && - !(integrator.stats.naccept == 0 && integrator.iter > 0)) + return condition_integrator_interval(integrator, interval, save_final_solution=false) end # condition with dt diff --git a/src/callbacks/post_process.jl b/src/callbacks/post_process.jl index 2437894330..9c39f3eb4b 100644 --- a/src/callbacks/post_process.jl +++ b/src/callbacks/post_process.jl @@ -73,6 +73,7 @@ struct PostprocessCallback{I, F} append_timestamp :: Bool write_csv :: Bool write_json :: Bool + git_hash :: Ref{String} end function PostprocessCallback(; interval::Integer=0, dt=0.0, exclude_boundary=true, @@ -94,14 +95,15 @@ function PostprocessCallback(; interval::Integer=0, dt=0.0, exclude_boundary=tru post_callback = PostprocessCallback(interval, write_file_interval, Dict{String, Vector{Any}}(), Float64[], exclude_boundary, funcs, filename, output_directory, - append_timestamp, write_csv, write_json) + append_timestamp, write_csv, write_json, + Ref("UnknownVersion")) if dt > 0 - # Add a `tstop` every `dt`, and save the final solution. + # Add a `tstop` every `dt`, and save the final solution return PeriodicCallback(post_callback, dt, initialize=initialize_postprocess_callback!, save_positions=(false, false), final_affect=true) else - # The first one is the condition, the second the affect! + # The first one is the `condition`, the second the `affect!` return DiscreteCallback(post_callback, post_callback, save_positions=(false, false), initialize=initialize_postprocess_callback!) @@ -209,12 +211,15 @@ function initialize_postprocess_callback!(cb, u, t, integrator) end function initialize_postprocess_callback!(cb::PostprocessCallback, u, t, integrator) + cb.git_hash[] = compute_git_hash() + # Apply the callback cb(integrator) - return nothing + + return cb end -# condition with interval +# `condition` with interval function (pp::PostprocessCallback)(u, t, integrator) (; interval) = pp @@ -231,7 +236,7 @@ function (pp::PostprocessCallback)(integrator) new_data = false # Update systems to compute quantities like density and pressure - update_systems_and_nhs(v_ode, u_ode, semi, t) + update_systems_and_nhs(v_ode, u_ode, semi, t; update_from_callback=true) foreach_system(semi) do system if system isa BoundarySystem && pp.exclude_boundary @@ -260,7 +265,7 @@ function (pp::PostprocessCallback)(integrator) write_postprocess_callback(pp) end - # Tell OrdinaryDiffEq that u has not been modified + # Tell OrdinaryDiffEq that `u` has not been modified u_modified!(integrator, false) end @@ -276,12 +281,12 @@ end # After the simulation has finished, this function is called to write the data to a JSON file function write_postprocess_callback(pp::PostprocessCallback) - if isempty(pp.data) - return - end + isempty(pp.data) && return + + mkpath(pp.output_directory) data = Dict{String, Any}() - write_meta_data!(data) + write_meta_data!(data, pp.git_hash[]) prepare_series_data!(data, pp) time_stamp = "" @@ -331,9 +336,9 @@ function create_series_dict(values, times, system_name="") "time" => times) end -function write_meta_data!(data) +function write_meta_data!(data, git_hash) meta_data = Dict("solver_name" => "TrixiParticles.jl", - "solver_version" => get_git_hash(), + "solver_version" => git_hash, "julia_version" => string(VERSION)) data["meta"] = meta_data diff --git a/src/callbacks/solution_saving.jl b/src/callbacks/solution_saving.jl index 119f80e800..468a72a1cd 100644 --- a/src/callbacks/solution_saving.jl +++ b/src/callbacks/solution_saving.jl @@ -1,7 +1,8 @@ @doc raw""" SolutionSavingCallback(; interval::Integer=0, dt=0.0, save_times=Array{Float64, 1}([]), save_initial_solution=true, save_final_solution=true, - output_directory="out", append_timestamp=false, max_coordinates=2^15, + output_directory="out", append_timestamp=false, prefix="", + verbose=false, write_meta_data=true, max_coordinates=2^15, custom_quantities...) @@ -25,9 +26,9 @@ To ignore a custom quantity for a specific system, return `nothing`. - `save_final_solution=true`: Save the final solution. - `output_directory="out"`: Directory to save the VTK files. - `append_timestamp=false`: Append current timestamp to the output directory. -- 'prefix': Prefix added to the filename. +- 'prefix=""': Prefix added to the filename. - `custom_quantities...`: Additional user-defined quantities. -- `write_meta_data`: Write meta data. +- `write_meta_data=true`: Write meta data. - `verbose=false`: Print to standard IO when a file is written. - `max_coordinates=2^15`: The coordinates of particles will be clipped if their absolute values exceed this threshold. @@ -76,6 +77,7 @@ mutable struct SolutionSavingCallback{I, CQ} max_coordinates :: Float64 custom_quantities :: CQ latest_saved_iter :: Int + git_hash :: Ref{String} end function SolutionSavingCallback(; interval::Integer=0, dt=0.0, @@ -101,18 +103,19 @@ function SolutionSavingCallback(; interval::Integer=0, dt=0.0, save_initial_solution, save_final_solution, write_meta_data, verbose, output_directory, prefix, max_coordinates, custom_quantities, - -1) + -1, Ref("UnknownVersion")) if length(save_times) > 0 - return PresetTimeCallback(save_times, solution_callback) + # See the large comment below for an explanation why we use `finalize` here + return PresetTimeCallback(save_times, solution_callback, finalize=solution_callback) elseif dt > 0 - # Add a `tstop` every `dt`, and save the final solution. + # Add a `tstop` every `dt`, and save the final solution return PeriodicCallback(solution_callback, dt, initialize=initialize_save_cb!, save_positions=(false, false), final_affect=save_final_solution) else - # The first one is the condition, the second the affect! + # The first one is the `condition`, the second the `affect!` return DiscreteCallback(solution_callback, solution_callback, save_positions=(false, false), initialize=initialize_save_cb!) @@ -129,22 +132,23 @@ end function initialize_save_cb!(solution_callback::SolutionSavingCallback, u, t, integrator) # Reset `latest_saved_iter` solution_callback.latest_saved_iter = -1 + solution_callback.git_hash[] = compute_git_hash() # Save initial solution if solution_callback.save_initial_solution - # Update systems to compute quantities like density and pressure. + # Update systems to compute quantities like density and pressure semi = integrator.p v_ode, u_ode = u.x - update_systems_and_nhs(v_ode, u_ode, semi, t) + update_systems_and_nhs(v_ode, u_ode, semi, t; update_from_callback=true) - # Apply the callback. + # Apply the callback solution_callback(integrator) end return nothing end -# condition +# `condition` function (solution_callback::SolutionSavingCallback)(u, t, integrator) (; interval, save_final_solution) = solution_callback @@ -152,9 +156,9 @@ function (solution_callback::SolutionSavingCallback)(u, t, integrator) save_final_solution=save_final_solution) end -# affect! +# `affect!` function (solution_callback::SolutionSavingCallback)(integrator) - (; interval, output_directory, custom_quantities, write_meta_data, + (; interval, output_directory, custom_quantities, write_meta_data, git_hash, verbose, prefix, latest_saved_iter, max_coordinates) = solution_callback vu_ode = integrator.u @@ -176,19 +180,35 @@ function (solution_callback::SolutionSavingCallback)(integrator) println("Writing solution to $output_directory at t = $(integrator.t)") end - @trixi_timeit timer() "save solution" trixi2vtk(vu_ode, semi, integrator.t; iter=iter, - output_directory=output_directory, - prefix=prefix, - write_meta_data=write_meta_data, - max_coordinates=max_coordinates, - custom_quantities...) + @trixi_timeit timer() "save solution" trixi2vtk(vu_ode, semi, integrator.t; + iter, output_directory, prefix, + write_meta_data, git_hash=git_hash[], + max_coordinates, custom_quantities...) - # Tell OrdinaryDiffEq that u has not been modified + # Tell OrdinaryDiffEq that `u` has not been modified u_modified!(integrator, false) return nothing end +# `finalize` +# This is a hack to make dispatch on a `PresetTimeCallback` possible. +# +# The type of the returned `DiscreteCallback` is +# `DiscreteCallback{typeof(condition), typeof(affect!), typeof(initialize), typeof(finalize)}`. +# For the `PeriodicCallback`, `typeof(affect!)` contains the type of the +# `SolutionSavingCallback`. The `PresetTimeCallback` uses anonymous functions as `condition` +# and `affect!`, so this doesn't work here. +# +# This hacky workaround makes use of the type parameter `typeof(finalize)` above. +# It's set to `FINALIZE_DEFAULT` by default in the `PresetTimeCallback`, which is a function +# that just returns `nothing`. +# Instead, we pass the `SolutionSavingCallback` as `finalize`, and define it to also just +# return `nothing` when called as `initialize`. +function (finalize::SolutionSavingCallback)(c, u, t, integrator) + return nothing +end + function Base.show(io::IO, cb::DiscreteCallback{<:Any, <:SolutionSavingCallback}) @nospecialize cb # reduce precompilation time @@ -205,6 +225,14 @@ function Base.show(io::IO, print(io, "SolutionSavingCallback(dt=", solution_saving.interval, ")") end +function Base.show(io::IO, + cb::DiscreteCallback{<:Any, <:Any, <:Any, <:SolutionSavingCallback}) + @nospecialize cb # reduce precompilation time + + solution_saving = cb.finalize + print(io, "SolutionSavingCallback(save_times=", solution_saving.save_times, ")") +end + function Base.show(io::IO, ::MIME"text/plain", cb::DiscreteCallback{<:Any, <:SolutionSavingCallback}) @nospecialize cb # reduce precompilation time @@ -229,6 +257,30 @@ function Base.show(io::IO, ::MIME"text/plain", end end +function Base.show(io::IO, ::MIME"text/plain", + cb::DiscreteCallback{<:Any, <:Any, <:Any, <:SolutionSavingCallback}) + @nospecialize cb # reduce precompilation time + + if get(io, :compact, false) + show(io, cb) + else + solution_saving = cb.finalize + cq = collect(solution_saving.custom_quantities) + + setup = [ + "save_times" => solution_saving.save_times, + "custom quantities" => isempty(cq) ? nothing : cq, + "save initial solution" => solution_saving.save_initial_solution ? + "yes" : "no", + "save final solution" => solution_saving.save_final_solution ? "yes" : + "no", + "output directory" => abspath(solution_saving.output_directory), + "prefix" => solution_saving.prefix, + ] + summary_box(io, "SolutionSavingCallback", setup) + end +end + function Base.show(io::IO, ::MIME"text/plain", cb::DiscreteCallback{<:Any, <:PeriodicCallbackAffect{<:SolutionSavingCallback}}) diff --git a/src/callbacks/update.jl b/src/callbacks/update.jl new file mode 100644 index 0000000000..3595d5c36a --- /dev/null +++ b/src/callbacks/update.jl @@ -0,0 +1,137 @@ +struct UpdateCallback{I} + interval::I +end + +""" + UpdateCallback(; interval::Integer, dt=0.0) + +Callback to update quantities either at the end of every `interval` time steps or +in intervals of `dt` in terms of integration time by adding additional `tstops` +(note that this may change the solution). + +# Keywords +- `interval=1`: Update quantities at the end of every `interval` time steps. +- `dt`: Update quantities in regular intervals of `dt` in terms of integration time + by adding additional `tstops` (note that this may change the solution). +""" +function UpdateCallback(; interval::Integer=-1, dt=0.0) + if dt > 0 && interval !== -1 + throw(ArgumentError("Setting both interval and dt is not supported!")) + end + + # Update in intervals in terms of simulation time + if dt > 0 + interval = Float64(dt) + + # Update every time step (default) + elseif interval == -1 + interval = 1 + end + + update_callback! = UpdateCallback(interval) + + if dt > 0 + # Add a `tstop` every `dt`, and save the final solution. + return PeriodicCallback(update_callback!, dt, + initialize=initial_update!, + save_positions=(false, false)) + else + # The first one is the `condition`, the second the `affect!` + return DiscreteCallback(update_callback!, update_callback!, + initialize=initial_update!, + save_positions=(false, false)) + end +end + +# `initialize` +function initial_update!(cb, u, t, integrator) + # The `UpdateCallback` is either `cb.affect!` (with `DiscreteCallback`) + # or `cb.affect!.affect!` (with `PeriodicCallback`). + # Let recursive dispatch handle this. + + initial_update!(cb.affect!, u, t, integrator) +end + +function initial_update!(cb::UpdateCallback, u, t, integrator) + semi = integrator.p + + # Tell systems that `UpdateCallback` is used + foreach_system(semi) do system + update_callback_used!(system) + end + + return cb(integrator) +end + +# `condition` +function (update_callback!::UpdateCallback)(u, t, integrator) + (; interval) = update_callback! + + return condition_integrator_interval(integrator, interval) +end + +# `affect!` +function (update_callback!::UpdateCallback)(integrator) + t = integrator.t + semi = integrator.p + v_ode, u_ode = integrator.u.x + + # Update quantities that are stored in the systems. These quantities (e.g. pressure) + # still have the values from the last stage of the previous step if not updated here. + update_systems_and_nhs(v_ode, u_ode, semi, t; update_from_callback=true) + + # Other updates might be added here later (e.g. Transport Velocity Formulation). + @trixi_timeit timer() "update open boundary" foreach_system(semi) do system + update_open_boundary_eachstep!(system, v_ode, u_ode, semi, t) + end + + # Tell OrdinaryDiffEq that `u` has been modified + u_modified!(integrator, true) + + return integrator +end + +function Base.show(io::IO, cb::DiscreteCallback{<:Any, <:UpdateCallback}) + @nospecialize cb # reduce precompilation time + print(io, "UpdateCallback(interval=", cb.affect!.interval, ")") +end + +function Base.show(io::IO, + cb::DiscreteCallback{<:Any, + <:PeriodicCallbackAffect{<:UpdateCallback}}) + @nospecialize cb # reduce precompilation time + print(io, "UpdateCallback(dt=", cb.affect!.affect!.interval, ")") +end + +function Base.show(io::IO, ::MIME"text/plain", + cb::DiscreteCallback{<:Any, <:UpdateCallback}) + @nospecialize cb # reduce precompilation time + + if get(io, :compact, false) + show(io, cb) + else + update_cb = cb.affect! + setup = [ + "interval" => update_cb.interval, + ] + summary_box(io, "UpdateCallback", setup) + end +end + +function Base.show(io::IO, ::MIME"text/plain", + cb::DiscreteCallback{<:Any, + <:PeriodicCallbackAffect{<:UpdateCallback}}) + @nospecialize cb # reduce precompilation time + + if get(io, :compact, false) + show(io, cb) + else + update_cb = cb.affect!.affect! + setup = [ + "dt" => update_cb.interval, + ] + summary_box(io, "UpdateCallback", setup) + end +end + +update_callback_used!(system) = system diff --git a/src/general/buffer.jl b/src/general/buffer.jl new file mode 100644 index 0000000000..b12cf05f3d --- /dev/null +++ b/src/general/buffer.jl @@ -0,0 +1,87 @@ +struct SystemBuffer{V} + active_particle :: BitVector + eachparticle :: V # Vector{Int} + buffer_size :: Int + + function SystemBuffer(active_size, buffer_size::Integer) + active_particle = vcat(trues(active_size), falses(buffer_size)) + eachparticle = collect(1:active_size) + + return new{typeof(eachparticle)}(active_particle, eachparticle, buffer_size) + end +end + +allocate_buffer(initial_condition, buffer) = initial_condition + +function allocate_buffer(initial_condition, buffer::SystemBuffer) + (; buffer_size) = buffer + + # Initialize particles far away from simulation domain + coordinates = fill(1e16, ndims(initial_condition), buffer_size) + + if all(rho -> isapprox(rho, first(initial_condition.density), atol=eps(), rtol=eps()), + initial_condition.density) + density = first(initial_condition.density) + else + throw(ArgumentError("`initial_condition.density` needs to be constant when using `SystemBuffer`")) + end + + particle_spacing = initial_condition.particle_spacing + + buffer_ic = InitialCondition(; coordinates, density, particle_spacing) + + return union(initial_condition, buffer_ic) +end + +@inline update_system_buffer!(buffer::Nothing) = buffer + +# TODO `resize` allocates. Find a non-allocating version +@inline function update_system_buffer!(buffer::SystemBuffer) + (; active_particle) = buffer + + resize!(buffer.eachparticle, count(active_particle)) + + i = 1 + for j in eachindex(active_particle) + if active_particle[j] + buffer.eachparticle[i] = j + i += 1 + end + end + + return buffer +end + +@inline each_moving_particle(system, buffer) = buffer.eachparticle + +@inline active_coordinates(u, system, buffer) = view(u, :, buffer.active_particle) + +@inline active_particles(system, buffer) = buffer.eachparticle + +@inline function activate_next_particle(system) + (; active_particle) = system.buffer + + next_particle = findfirst(x -> !x, active_particle) + + if isnothing(next_particle) + error("0 out of $(system.buffer.buffer_size) buffer particles available") + end + + active_particle[next_particle] = true + + return next_particle +end + +@inline function deactivate_particle!(system, particle, u) + (; active_particle) = system.buffer + + active_particle[particle] = false + + # Set particle far away from simulation domain + for dim in 1:ndims(system) + # Inf or NaN causes instability outcome. + u[dim, particle] = 1e16 + end + + return system +end diff --git a/src/general/corrections.jl b/src/general/corrections.jl index 53b925f488..ee894fec47 100644 --- a/src/general/corrections.jl +++ b/src/general/corrections.jl @@ -440,34 +440,35 @@ function compute_gradient_correction_matrix!(corr_matrix::AbstractArray, system, end function correction_matrix_inversion_step!(corr_matrix, system) - @threaded for particle in eachparticle(system) + @threaded system for particle in eachparticle(system) L = extract_smatrix(corr_matrix, system, particle) - norm_ = norm(L) - # The norm value is quasi-zero, so there are probably no neighbors for this particle - if norm_ < sqrt(eps()) - # The correction matrix is set to an identity matrix, which effectively disables - # the correction for this particle. - @inbounds for j in 1:ndims(system), i in 1:ndims(system) - corr_matrix[i, j, particle] = 0.0 - end - @inbounds for i in 1:ndims(system) - corr_matrix[i, i, particle] = 1.0 - end - continue - end - - det_ = abs(det(L)) - @fastmath if det_ < 1e-6 * norm_ - L_inv = pinv(L) - @inbounds for j in 1:ndims(system), i in 1:ndims(system) - corr_matrix[i, j, particle] = L_inv[i, j] - end + # The matrix `L` only becomes singular when the particle and all neighbors + # are collinear (in 2D) or lie all in the same plane (in 3D). + # This happens only when two (in 2D) or three (in 3D) particles are isolated, + # or in cases where there is only one layer of fluid particles on a wall. + # In these edge cases, we just disable the correction and set the corrected + # gradient to be the uncorrected one by setting `L` to the identity matrix. + # + # Proof: `L` is just a sum of tensor products of relative positions X_ab with + # themselves. According to + # https://en.wikipedia.org/wiki/Outer_product#Connection_with_the_matrix_product + # the sum of tensor products can be rewritten as A A^T, where the columns of A + # are the relative positions X_ab. The rank of A A^T is equal to the rank of A, + # so `L` is singular if and only if the position vectors X_ab don't span the + # full space, i.e., particle a and all neighbors lie on the same line (in 2D) + # or plane (in 3D). + if abs(det(L)) < 1e-9 + L_inv = I else L_inv = inv(L) - @inbounds for j in 1:ndims(system), i in 1:ndims(system) - corr_matrix[i, j, particle] = L_inv[i, j] - end + end + + # Write inverse back to `corr_matrix` + for j in 1:ndims(system), i in 1:ndims(system) + @inbounds corr_matrix[i, j, particle] = L_inv[i, j] end end + + return corr_matrix end diff --git a/src/general/density_calculators.jl b/src/general/density_calculators.jl index ee89c92a69..7244253b08 100644 --- a/src/general/density_calculators.jl +++ b/src/general/density_calculators.jl @@ -35,6 +35,19 @@ end return v[end, particle] end +# WARNING! +# These functions are intended to be used internally to set the density +# of newly activated particles in a callback. +# DO NOT use outside a callback. OrdinaryDiffEq does not allow changing `v` and `u` +# outside of callbacks. +@inline set_particle_density!(v, system, ::SummationDensity, particle, density) = v + +@inline function set_particle_density!(v, system, ::ContinuityDensity, particle, density) + v[end, particle] = density + + return v +end + function summation_density!(system, semi, u, u_ode, density; particles=each_moving_particle(system)) set_zero!(density) diff --git a/src/general/file_system.jl b/src/general/file_system.jl deleted file mode 100644 index 39d37394b5..0000000000 --- a/src/general/file_system.jl +++ /dev/null @@ -1,22 +0,0 @@ -vtkname(system::FluidSystem) = "fluid" -vtkname(system::SolidSystem) = "solid" -vtkname(system::BoundarySystem) = "boundary" - -function system_names(systems) - # Add `_i` to each system name, where `i` is the index of the corresponding - # system type. - # `["fluid", "boundary", "boundary"]` becomes `["fluid_1", "boundary_1", "boundary_2"]`. - cnames = systems .|> vtkname - filenames = [string(cnames[i], "_", count(==(cnames[i]), cnames[1:i])) - for i in eachindex(cnames)] - return filenames -end - -function get_git_hash() - try - return string(readchomp(Cmd(`git describe --tags --always --first-parent --dirty`, - dir=pkgdir(@__MODULE__)))) - catch e - return "Git is not installed or not accessible" - end -end diff --git a/src/general/general.jl b/src/general/general.jl index 6651c03c5a..dfd709861b 100644 --- a/src/general/general.jl +++ b/src/general/general.jl @@ -1,28 +1,11 @@ -abstract type System{NDIMS} end - -abstract type FluidSystem{NDIMS} <: System{NDIMS} end -timer_name(::FluidSystem) = "fluid" - -abstract type SolidSystem{NDIMS} <: System{NDIMS} end -timer_name(::SolidSystem) = "solid" - -abstract type BoundarySystem{NDIMS} <: System{NDIMS} end -timer_name(::BoundarySystem) = "boundary" - -@inline function set_zero!(du) - du .= zero(eltype(du)) - - return du -end - -# Note that `semidiscretization.jl` depends on the system types and has to be -# included later. +# Note that `system.jl` has already been included. +# `semidiscretization.jl` depends on the system types and has to be included later. # `density_calculators.jl` needs to be included before `corrections.jl`. include("density_calculators.jl") include("corrections.jl") include("smoothing_kernels.jl") include("initial_condition.jl") -include("system.jl") +include("buffer.jl") include("interpolation.jl") -include("file_system.jl") include("custom_quantities.jl") +include("neighborhood_search.jl") diff --git a/src/general/gpu.jl b/src/general/gpu.jl new file mode 100644 index 0000000000..8aa481cff8 --- /dev/null +++ b/src/general/gpu.jl @@ -0,0 +1,42 @@ +# Adapt.jl provides a function `adapt(to, x)`, which adapts a value `x` to `to`. +# In practice, this means that we can use `adapt(CuArray, system)` to adapt a system to +# the `CuArray` type. +# What this does is that it converts all `Array`s inside this system to `CuArray`s, +# therefore copying them to the GPU. +# In order to run a simulation on a GPU, we want to call `adapt(T, semi)` to adapt the +# `Semidiscretization` `semi` to the GPU array type `T` (e.g. `CuArray`). +# +# `Adapt.@adapt_structure` automatically generates the `adapt` function for our custom types. +Adapt.@adapt_structure Semidiscretization +Adapt.@adapt_structure WeaklyCompressibleSPHSystem +Adapt.@adapt_structure DensityDiffusionAntuono +Adapt.@adapt_structure BoundarySPHSystem +Adapt.@adapt_structure BoundaryModelDummyParticles +Adapt.@adapt_structure BoundaryModelMonaghanKajtar +Adapt.@adapt_structure TotalLagrangianSPHSystem + +# The initial conditions are only used for initialization, which happens before `adapt`ing +# the semidiscretization, so we don't need to store `InitialCondition`s on the GPU. +# To save precious GPU memory, we replace initial conditions by `nothing`. +function Adapt.adapt_structure(to, ic::InitialCondition) + return nothing +end + +# `adapt(CuArray, ::SVector)::SVector`, but `adapt(Array, ::SVector)::Vector`. +# We don't want to change the type of the `SVector` here. +function Adapt.adapt_structure(to::typeof(Array), svector::SVector) + return svector +end + +# `adapt(CuArray, ::UnitRange)::UnitRange`, but `adapt(Array, ::UnitRange)::Vector`. +# We don't want to change the type of the `UnitRange` here. +function Adapt.adapt_structure(to::typeof(Array), range::UnitRange) + return range +end + +KernelAbstractions.get_backend(::PtrArray) = KernelAbstractions.CPU() +KernelAbstractions.get_backend(system::System) = KernelAbstractions.get_backend(system.mass) + +function KernelAbstractions.get_backend(system::BoundarySPHSystem) + KernelAbstractions.get_backend(system.coordinates) +end diff --git a/src/general/initial_condition.jl b/src/general/initial_condition.jl index 9ede75648a..8e94b16154 100644 --- a/src/general/initial_condition.jl +++ b/src/general/initial_condition.jl @@ -291,11 +291,10 @@ function find_too_close_particles(coords1, coords2, max_distance) result = Int[] nhs = GridNeighborhoodSearch{NDIMS}(max_distance, size(coords2, 2)) - TrixiParticles.initialize!(nhs, coords2) + PointNeighbors.initialize!(nhs, coords1, coords2) # We are modifying the vector `result`, so this cannot be parallel - TrixiParticles.for_particle_neighbor(coords1, coords2, nhs, - parallel=false) do particle, _, _, _ + for_particle_neighbor(coords1, coords2, nhs, parallel=false) do particle, _, _, _ if !(particle in result) append!(result, particle) end diff --git a/src/general/interpolation.jl b/src/general/interpolation.jl index 51b13c681c..80116a6057 100644 --- a/src/general/interpolation.jl +++ b/src/general/interpolation.jl @@ -51,14 +51,32 @@ results = interpolate_plane_2d([0.0, 0.0], [1.0, 1.0], 0.2, semi, ref_system, so (density = ...) ``` """ -function interpolate_plane_2d(min_corner, max_corner, resolution, semi, ref_system, sol; +function interpolate_plane_2d(min_corner, max_corner, resolution, semi, ref_system, + sol::ODESolution; smoothing_length=ref_system.smoothing_length, cut_off_bnd=true, clip_negative_pressure=false) # Filter out particles without neighbors filter_no_neighbors = true + v_ode, u_ode = sol.u[end].x + + results, _, _ = interpolate_plane_2d(min_corner, max_corner, resolution, + semi, ref_system, v_ode, u_ode, + filter_no_neighbors, smoothing_length, cut_off_bnd, + clip_negative_pressure) + + return results +end + +function interpolate_plane_2d(min_corner, max_corner, resolution, semi, ref_system, + v_ode, u_ode; + smoothing_length=ref_system.smoothing_length, + cut_off_bnd=true, clip_negative_pressure=false) + # Filter out particles without neighbors + filter_no_neighbors = true + results, _, _ = interpolate_plane_2d(min_corner, max_corner, resolution, - semi, ref_system, sol, filter_no_neighbors, - smoothing_length, cut_off_bnd, + semi, ref_system, v_ode, u_ode, + filter_no_neighbors, smoothing_length, cut_off_bnd, clip_negative_pressure) return results @@ -112,14 +130,27 @@ See also: [`interpolate_plane_2d`](@ref), [`interpolate_plane_3d`](@ref), results = interpolate_plane_2d([0.0, 0.0], [1.0, 1.0], 0.2, semi, ref_system, sol) ``` """ -function interpolate_plane_2d_vtk(min_corner, max_corner, resolution, semi, ref_system, sol; +function interpolate_plane_2d_vtk(min_corner, max_corner, resolution, semi, ref_system, + sol::ODESolution; clip_negative_pressure=false, + smoothing_length=ref_system.smoothing_length, + cut_off_bnd=true, + output_directory="out", filename="plane") + v_ode, u_ode = sol.u[end].x + + interpolate_plane_2d_vtk(min_corner, max_corner, resolution, semi, ref_system, + v_ode, u_ode; clip_negative_pressure, + smoothing_length, cut_off_bnd, output_directory, filename) +end + +function interpolate_plane_2d_vtk(min_corner, max_corner, resolution, semi, ref_system, + v_ode, u_ode; smoothing_length=ref_system.smoothing_length, cut_off_bnd=true, clip_negative_pressure=false, output_directory="out", filename="plane") # Don't filter out particles without neighbors to keep 2D grid structure filter_no_neighbors = false results, x_range, y_range = interpolate_plane_2d(min_corner, max_corner, resolution, - semi, ref_system, sol, + semi, ref_system, v_ode, u_ode, filter_no_neighbors, smoothing_length, cut_off_bnd, clip_negative_pressure) @@ -135,9 +166,9 @@ function interpolate_plane_2d_vtk(min_corner, max_corner, resolution, semi, ref_ end end -function interpolate_plane_2d(min_corner, max_corner, resolution, semi, ref_system, sol, - filter_no_neighbors, smoothing_length, cut_off_bnd, - clip_negative_pressure) +function interpolate_plane_2d(min_corner, max_corner, resolution, semi, ref_system, + v_ode, u_ode, filter_no_neighbors, smoothing_length, + cut_off_bnd, clip_negative_pressure) dims = length(min_corner) if dims != 2 || length(max_corner) != 2 throw(ArgumentError("function is intended for 2D coordinates only")) @@ -157,7 +188,7 @@ function interpolate_plane_2d(min_corner, max_corner, resolution, semi, ref_syst # Generate points within the plane points_coords = [SVector(x, y) for x in x_range, y in y_range] - results = interpolate_point(points_coords, semi, ref_system, sol, + results = interpolate_point(points_coords, semi, ref_system, v_ode, u_ode, smoothing_length=smoothing_length, cut_off_bnd=cut_off_bnd, clip_negative_pressure=clip_negative_pressure) @@ -226,51 +257,35 @@ results = interpolate_plane_3d([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0] (density = ...) ``` """ -function interpolate_plane_3d(point1, point2, point3, resolution, semi, ref_system, sol; +function interpolate_plane_3d(point1, point2, point3, resolution, semi, ref_system, + sol::ODESolution; smoothing_length=ref_system.smoothing_length, cut_off_bnd=true, clip_negative_pressure=false) - # Verify that points are in 3D space - if length(point1) != 3 || length(point2) != 3 || length(point3) != 3 - throw(ArgumentError("all points must be 3D coordinates")) - end + v_ode, u_ode = sol.u[end].x + interpolate_plane_3d(point1, point2, point3, resolution, semi, ref_system, + v_ode, u_ode; smoothing_length, cut_off_bnd, + clip_negative_pressure) +end + +function interpolate_plane_3d(point1, point2, point3, resolution, semi, ref_system, + v_ode, u_ode; smoothing_length=ref_system.smoothing_length, + cut_off_bnd=true, clip_negative_pressure=false) if ndims(ref_system) != 3 throw(ArgumentError("`interpolate_plane_3d` requires a 3D simulation")) end - point1_ = SVector{3}(point1) - point2_ = SVector{3}(point2) - point3_ = SVector{3}(point3) + coords, resolution_ = sample_plane((point1, point2, point3), resolution) - # Vectors defining the edges of the parallelogram - edge1 = point2_ - point1_ - edge2 = point3_ - point1_ - - # Check if the points are collinear - if norm(cross(edge1, edge2)) == 0 - throw(ArgumentError("the points must not be collinear")) + if !isapprox(resolution, resolution_, rtol=5e-2) + @info "The desired plane size is not a multiple of the resolution $resolution." * + "\nNew resolution is set to $resolution_." end - # Determine the number of points along each edge - num_points_edge1 = ceil(Int, norm(edge1) / resolution) - num_points_edge2 = ceil(Int, norm(edge2) / resolution) - - # Create a set of points on the plane - points_coords = Vector{SVector{3, Float64}}(undef, - (num_points_edge1 + 1) * - (num_points_edge2 + 1)) - index = 1 - for i in 0:num_points_edge1 - for j in 0:num_points_edge2 - point_on_plane = point1 + (i / num_points_edge1) * edge1 + - (j / num_points_edge2) * edge2 - points_coords[index] = point_on_plane - index += 1 - end - end + points_coords = reinterpret(reshape, SVector{3, Float64}, coords) # Interpolate using the generated points - results = interpolate_point(points_coords, semi, ref_system, sol, + results = interpolate_point(points_coords, semi, ref_system, v_ode, u_ode, smoothing_length=smoothing_length, cut_off_bnd=cut_off_bnd, clip_negative_pressure=clip_negative_pressure) @@ -335,8 +350,16 @@ results = interpolate_line([1.0, 0.0], [1.0, 1.0], 5, semi, ref_system, sol) (density = ...) ``` """ -function interpolate_line(start, end_, n_points, semi, ref_system, sol; endpoint=true, - smoothing_length=ref_system.smoothing_length, +function interpolate_line(start, end_, n_points, semi, ref_system, sol::ODESolution; + endpoint=true, smoothing_length=ref_system.smoothing_length, + cut_off_bnd=true, clip_negative_pressure=false) + v_ode, u_ode = sol.u[end].x + + interpolate_line(start, end_, n_points, semi, ref_system, v_ode, u_ode; + endpoint, smoothing_length, cut_off_bnd, clip_negative_pressure) +end +function interpolate_line(start, end_, n_points, semi, ref_system, v_ode, u_ode; + endpoint=true, smoothing_length=ref_system.smoothing_length, cut_off_bnd=true, clip_negative_pressure=false) start_svector = SVector{ndims(ref_system)}(start) end_svector = SVector{ndims(ref_system)}(end_) @@ -346,10 +369,9 @@ function interpolate_line(start, end_, n_points, semi, ref_system, sol; endpoint points_coords = points_coords[2:(end - 1)] end - return interpolate_point(points_coords, semi, ref_system, sol, + return interpolate_point(points_coords, semi, ref_system, v_ode, u_ode; smoothing_length=smoothing_length, - cut_off_bnd=cut_off_bnd, - clip_negative_pressure=clip_negative_pressure) + cut_off_bnd=cut_off_bnd, clip_negative_pressure) end @doc raw""" @@ -411,9 +433,19 @@ results = interpolate_point(points, semi, ref_system, sol) - With `cut_off_bnd`, a density-based estimation of the surface is used which is not as accurate as a real surface reconstruction. """ -function interpolate_point(points_coords::AbstractArray{<:AbstractArray}, semi, ref_system, - sol; smoothing_length=ref_system.smoothing_length, - cut_off_bnd=true, clip_negative_pressure=false) +@inline function interpolate_point(point_coords, semi, ref_system, sol::ODESolution; + smoothing_length=ref_system.smoothing_length, + cut_off_bnd=true, clip_negative_pressure=false) + v_ode, u_ode = sol.u[end].x + + interpolate_point(point_coords, semi, ref_system, v_ode, u_ode; + smoothing_length, cut_off_bnd, clip_negative_pressure) +end + +@inline function interpolate_point(points_coords::AbstractArray{<:AbstractArray}, semi, + ref_system, v_ode, u_ode; + smoothing_length=ref_system.smoothing_length, + cut_off_bnd=true, clip_negative_pressure=false) num_points = length(points_coords) coords = similar(points_coords) velocities = similar(points_coords) @@ -421,14 +453,13 @@ function interpolate_point(points_coords::AbstractArray{<:AbstractArray}, semi, pressures = Vector{Float64}(undef, num_points) neighbor_counts = Vector{Int}(undef, num_points) - neighborhood_searches = process_neighborhood_searches(semi, sol, ref_system, + neighborhood_searches = process_neighborhood_searches(semi, u_ode, ref_system, smoothing_length) for (i, point) in enumerate(points_coords) - result = interpolate_point(SVector{ndims(ref_system)}(point), semi, ref_system, sol, - neighborhood_searches, smoothing_length=smoothing_length, - cut_off_bnd=cut_off_bnd, - clip_negative_pressure=clip_negative_pressure) + result = interpolate_point(SVector{ndims(ref_system)}(point), semi, ref_system, + v_ode, u_ode, neighborhood_searches; + smoothing_length, cut_off_bnd, clip_negative_pressure) densities[i] = result.density neighbor_counts[i] = result.neighbor_count coords[i] = result.coord @@ -440,31 +471,31 @@ function interpolate_point(points_coords::AbstractArray{<:AbstractArray}, semi, velocity=velocities, pressure=pressures) end -function interpolate_point(point_coords, semi, ref_system, sol; +function interpolate_point(point_coords, semi, ref_system, v_ode, u_ode; smoothing_length=ref_system.smoothing_length, cut_off_bnd=true, clip_negative_pressure=false) - neighborhood_searches = process_neighborhood_searches(semi, sol, ref_system, + neighborhood_searches = process_neighborhood_searches(semi, u_ode, ref_system, smoothing_length) return interpolate_point(SVector{ndims(ref_system)}(point_coords), semi, ref_system, - sol, neighborhood_searches, smoothing_length=smoothing_length, - cut_off_bnd=cut_off_bnd, - clip_negative_pressure=clip_negative_pressure) + v_ode, u_ode, neighborhood_searches; + smoothing_length, cut_off_bnd, clip_negative_pressure) end -function process_neighborhood_searches(semi, sol, ref_system, smoothing_length) +function process_neighborhood_searches(semi, u_ode, ref_system, smoothing_length) if isapprox(smoothing_length, ref_system.smoothing_length) # Update existing NHS - update_nhs(sol.u[end].x[2], semi) + update_nhs!(semi, u_ode) neighborhood_searches = semi.neighborhood_searches[system_indices(ref_system, semi)] else ref_smoothing_kernel = ref_system.smoothing_kernel search_radius = compact_support(ref_smoothing_kernel, smoothing_length) neighborhood_searches = map(semi.systems) do system - u = wrap_u(sol.u[end].x[2], system, semi) + u = wrap_u(u_ode, system, semi) system_coords = current_coordinates(u, system) old_nhs = get_neighborhood_search(ref_system, system, semi) - nhs = copy_neighborhood_search(old_nhs, search_radius, system_coords) + nhs = PointNeighbors.copy_neighborhood_search(old_nhs, search_radius, + system_coords, system_coords) return nhs end end @@ -472,7 +503,7 @@ function process_neighborhood_searches(semi, sol, ref_system, smoothing_length) return neighborhood_searches end -@inline function interpolate_point(point_coords, semi, ref_system, sol, +@inline function interpolate_point(point_coords, semi, ref_system, v_ode, u_ode, neighborhood_searches; smoothing_length=ref_system.smoothing_length, cut_off_bnd=true, clip_negative_pressure=false) @@ -498,19 +529,21 @@ end nhs = neighborhood_searches[system_id] (; search_radius, periodic_box) = nhs - v = wrap_v(sol.u[end].x[1], system, semi) - u = wrap_u(sol.u[end].x[2], system, semi) + v = wrap_v(v_ode, system, semi) + u = wrap_u(u_ode, system, semi) system_coords = current_coordinates(u, system) # This is basically `for_particle_neighbor` unrolled - for particle in eachneighbor(point_coords, nhs) + for particle in PointNeighbors.eachneighbor(point_coords, nhs) coords = extract_svector(system_coords, Val(ndims(system)), particle) pos_diff = point_coords - coords distance2 = dot(pos_diff, pos_diff) - pos_diff, distance2 = compute_periodic_distance(pos_diff, distance2, - search_radius, periodic_box) + pos_diff, distance2 = PointNeighbors.compute_periodic_distance(pos_diff, + distance2, + search_radius, + periodic_box) if distance2 > search_radius^2 continue end diff --git a/src/general/neighborhood_search.jl b/src/general/neighborhood_search.jl new file mode 100644 index 0000000000..afcd2c9b38 --- /dev/null +++ b/src/general/neighborhood_search.jl @@ -0,0 +1,22 @@ +# Loop over all pairs of particles and neighbors within the kernel cutoff. +# `f(particle, neighbor, pos_diff, distance)` is called for every particle-neighbor pair. +# By default, loop over `each_moving_particle(system)`. +function PointNeighbors.for_particle_neighbor(f, system, neighbor_system, + system_coords, neighbor_coords, + neighborhood_search; + particles=each_moving_particle(system), + parallel=true) + for_particle_neighbor(f, system_coords, neighbor_coords, neighborhood_search, + particles=particles, parallel=parallel) +end + +function PointNeighbors.for_particle_neighbor(f, system::GPUSystem, neighbor_system, + system_coords, neighbor_coords, + neighborhood_search; + particles=each_moving_particle(system), + parallel=true) + @threaded system for particle in particles + PointNeighbors.foreach_neighbor(f, system_coords, neighbor_coords, + neighborhood_search, particle) + end +end diff --git a/src/general/semidiscretization.jl b/src/general/semidiscretization.jl index ada9275e47..7f6c791dbe 100644 --- a/src/general/semidiscretization.jl +++ b/src/general/semidiscretization.jl @@ -13,8 +13,8 @@ the keyword argument `neighborhood_search`. A value of `nothing` means no neighb # Keywords - `neighborhood_search`: The type of neighborhood search to be used in the simulation. - By default, the [`GridNeighborhoodSearch`](@ref) is used. - Use [`TrivialNeighborhoodSearch`](@ref) or `nothing` to loop + By default, the `GridNeighborhoodSearch` is used. + Use `TrivialNeighborhoodSearch` or `nothing` to loop over all particles (no neighborhood search). - `periodic_box_min_corner`: In order to use a (rectangular) periodic domain, pass the coordinates of the domain corner in negative coordinate @@ -60,6 +60,8 @@ struct Semidiscretization{S, RU, RV, NS} end end +GPUSemidiscretization = Semidiscretization{<:NTuple{<:Any, GPUSystem}} + function Semidiscretization(systems...; neighborhood_search=GridNeighborhoodSearch, periodic_box_min_corner=nothing, periodic_box_max_corner=nothing, threaded_nhs_update=true) @@ -140,7 +142,8 @@ function create_neighborhood_search(system, neighbor, ::Val{GridNeighborhoodSear periodic_box_max_corner=periodic_box_max_corner, threaded_nhs_update=threaded_nhs_update) # Initialize neighborhood search - initialize!(search, initial_coordinates(neighbor)) + PointNeighbors.initialize!(search, initial_coordinates(system), + initial_coordinates(neighbor)) return search end @@ -150,6 +153,27 @@ end return compact_support(smoothing_kernel, smoothing_length) end +@inline function compact_support(system::OpenBoundarySPHSystem, neighbor) + # Use the compact support of the fluid + return compact_support(neighbor, system) +end + +@inline function compact_support(system::OpenBoundarySPHSystem, + neighbor::OpenBoundarySPHSystem) + # This NHS is never used + return 0.0 +end + +@inline function compact_support(system::BoundaryDEMSystem, neighbor::BoundaryDEMSystem) + # This NHS is never used + return 0.0 +end + +@inline function compact_support(system::BoundaryDEMSystem, neighbor::DEMSystem) + # Use the compact support of the DEMSystem + return compact_support(neighbor, system) +end + @inline function compact_support(system::TotalLagrangianSPHSystem, neighbor::TotalLagrangianSPHSystem) (; smoothing_kernel, smoothing_length) = system @@ -248,7 +272,7 @@ timespan: (0.0, 1.0) u0: ([...], [...]) *this line is ignored by filter* ``` """ -function semidiscretize(semi, tspan; reset_threads=true) +function semidiscretize(semi, tspan; reset_threads=true, data_type=nothing) (; systems) = semi @assert all(system -> eltype(system) === eltype(systems[1]), systems) @@ -269,13 +293,24 @@ function semidiscretize(semi, tspan; reset_threads=true) # Initialize this system initialize!(system, neighborhood_search) + + # Only for systems requiring a mandatory callback + reset_callback_flag!(system) end end sizes_u = (u_nvariables(system) * n_moving_particles(system) for system in systems) sizes_v = (v_nvariables(system) * n_moving_particles(system) for system in systems) - u0_ode = Vector{ELTYPE}(undef, sum(sizes_u)) - v0_ode = Vector{ELTYPE}(undef, sum(sizes_v)) + + if isnothing(data_type) + # Use CPU vectors and the optimized CPU code + u0_ode = Vector{ELTYPE}(undef, sum(sizes_u)) + v0_ode = Vector{ELTYPE}(undef, sum(sizes_v)) + else + # Use the specified data type, e.g., `CuArray` or `ROCArray` + u0_ode = data_type{ELTYPE}(undef, sum(sizes_u)) + v0_ode = data_type{ELTYPE}(undef, sum(sizes_v)) + end # Set initial condition foreach_system(semi) do system @@ -286,6 +321,15 @@ function semidiscretize(semi, tspan; reset_threads=true) write_v0!(v0_system, system) end + if !isnothing(data_type) + # Convert all arrays to the correct array type. When e.g. `data_type=CuArray`, + # this will convert all `Array`s to `CuArray`s, moving data to the GPU. + # See the comments in general/gpu.jl for more details. + semi_adapted = Adapt.adapt(data_type, semi) + + return DynamicalODEProblem(kick!, drift!, v0_ode, u0_ode, tspan, semi_adapted) + end + return DynamicalODEProblem(kick!, drift!, v0_ode, u0_ode, tspan, semi) end @@ -314,6 +358,9 @@ function restart_with!(semi, sol; reset_threads=true) u = wrap_u(sol.u[end].x[2], system, semi) restart_with!(system, v, u) + + # Only for systems requiring a mandatory callback + reset_callback_flag!(system) end return semi @@ -321,6 +368,17 @@ end # We have to pass `system` here for type stability, # since the type of `system` determines the return type. +@inline function wrap_v(v_ode, system, semi) + (; ranges_v) = semi + + range = ranges_v[system_indices(system, semi)] + + @boundscheck @assert length(range) == v_nvariables(system) * n_moving_particles(system) + + return wrap_array(v_ode, range, + (StaticInt(v_nvariables(system)), n_moving_particles(system))) +end + @inline function wrap_u(u_ode, system, semi) (; ranges_u) = semi @@ -328,22 +386,23 @@ end @boundscheck @assert length(range) == u_nvariables(system) * n_moving_particles(system) - # This is a non-allocating version of: - # return unsafe_wrap(Array{eltype(u_ode), 2}, pointer(view(u_ode, range)), - # (u_nvariables(system), n_moving_particles(system))) - return PtrArray(pointer(view(u_ode, range)), - (StaticInt(u_nvariables(system)), n_moving_particles(system))) + return wrap_array(u_ode, range, + (StaticInt(u_nvariables(system)), n_moving_particles(system))) end -@inline function wrap_v(v_ode, system, semi) - (; ranges_v) = semi - - range = ranges_v[system_indices(system, semi)] - - @boundscheck @assert length(range) == v_nvariables(system) * n_moving_particles(system) +@inline function wrap_array(array::Array, range, size) + # This is a non-allocating version of: + # return unsafe_wrap(Array{eltype(array), 2}, pointer(view(array, range)), size) + return PtrArray(pointer(view(array, range)), size) +end - return PtrArray(pointer(view(v_ode, range)), - (StaticInt(v_nvariables(system)), n_moving_particles(system))) +@inline function wrap_array(array, range, size) + # For non-`Array`s (typically GPU arrays), just reshape. Calling the `PtrArray` code + # above for a `CuArray` yields another `CuArray` (instead of a `PtrArray`) + # and is 8 times slower with double the allocations. + # + # Note that `size` might contain `StaticInt`s, so convert to `Int` first. + return reshape(view(array, range), Int.(size)) end function calculate_dt(v_ode, u_ode, cfl_number, semi::Semidiscretization) @@ -362,7 +421,7 @@ function drift!(du_ode, v_ode, u_ode, semi, t) du = wrap_u(du_ode, system, semi) v = wrap_v(v_ode, system, semi) - @threaded for particle in each_moving_particle(system) + @threaded system for particle in each_moving_particle(system) # This can be dispatched per system add_velocity!(du, v, particle, system) end @@ -400,7 +459,7 @@ function kick!(dv_ode, v_ode, u_ode, semi, t) end # Update the systems and neighborhood searches (NHS) for a simulation before calling `interact!` to compute forces -function update_systems_and_nhs(v_ode, u_ode, semi, t) +function update_systems_and_nhs(v_ode, u_ode, semi, t; update_from_callback=false) # First update step before updating the NHS # (for example for writing the current coordinates in the solid system) foreach_system(semi) do system @@ -411,7 +470,7 @@ function update_systems_and_nhs(v_ode, u_ode, semi, t) end # Update NHS - @trixi_timeit timer() "update nhs" update_nhs(u_ode, semi) + @trixi_timeit timer() "update nhs" update_nhs!(semi, u_ode) # Second update step. # This is used to calculate density and pressure of the fluid systems @@ -437,18 +496,20 @@ function update_systems_and_nhs(v_ode, u_ode, semi, t) v = wrap_v(v_ode, system, semi) u = wrap_u(u_ode, system, semi) - update_final!(system, v, u, v_ode, u_ode, semi, t) + update_final!(system, v, u, v_ode, u_ode, semi, t; update_from_callback) end end -function update_nhs(u_ode, semi) +function update_nhs!(semi, u_ode) # Update NHS for each pair of systems foreach_system(semi) do system + u_system = wrap_u(u_ode, system, semi) + foreach_system(semi) do neighbor u_neighbor = wrap_u(u_ode, neighbor, semi) neighborhood_search = get_neighborhood_search(system, neighbor, semi) - update!(neighborhood_search, nhs_coords(system, neighbor, u_neighbor)) + update_nhs!(neighborhood_search, system, neighbor, u_system, u_neighbor) end end end @@ -459,7 +520,7 @@ function add_source_terms!(dv_ode, v_ode, u_ode, semi) v = wrap_v(v_ode, system, semi) u = wrap_u(u_ode, system, semi) - @threaded for particle in each_moving_particle(system) + @threaded system for particle in each_moving_particle(system) # Dispatch by system type to exclude boundary systems add_acceleration!(dv, particle, system) add_source_terms_inner!(dv, v, u, particle, system, source_terms(system)) @@ -578,68 +639,154 @@ end end # NHS updates -function nhs_coords(system::FluidSystem, - neighbor::FluidSystem, u) - return current_coordinates(u, neighbor) -end - -function nhs_coords(system::FluidSystem, - neighbor::TotalLagrangianSPHSystem, u) - return current_coordinates(u, neighbor) -end - -function nhs_coords(system::FluidSystem, - neighbor::BoundarySPHSystem, u) - if neighbor.ismoving[1] - return current_coordinates(u, neighbor) - end - - # Don't update - return nothing -end - -function nhs_coords(system::TotalLagrangianSPHSystem, - neighbor::FluidSystem, u) - return current_coordinates(u, neighbor) -end - -function nhs_coords(system::TotalLagrangianSPHSystem, - neighbor::TotalLagrangianSPHSystem, u) - # Don't update - return nothing -end - -function nhs_coords(system::TotalLagrangianSPHSystem, - neighbor::BoundarySPHSystem, u) - if neighbor.ismoving[1] - return current_coordinates(u, neighbor) - end - - # Don't update - return nothing -end - -function nhs_coords(system::BoundarySPHSystem, - neighbor::FluidSystem, u) - # Don't update - return nothing -end - -function nhs_coords(system::BoundarySPHSystem{<:BoundaryModelDummyParticles}, - neighbor::FluidSystem, u) - return current_coordinates(u, neighbor) -end - -function nhs_coords(system::BoundarySPHSystem, - neighbor::TotalLagrangianSPHSystem, u) - # Don't update - return nothing -end - -function nhs_coords(system::BoundarySPHSystem, - neighbor::BoundarySPHSystem, u) - # Don't update - return nothing +# To prevent hard-to-find bugs, there is not default version +function update_nhs!(neighborhood_search, + system::FluidSystem, + neighbor::Union{FluidSystem, TotalLagrangianSPHSystem}, + u_system, u_neighbor) + # The current coordinates of fluids and solids change over time + PointNeighbors.update!(neighborhood_search, + current_coordinates(u_system, system), + current_coordinates(u_neighbor, neighbor), + particles_moving=(true, true)) +end + +function update_nhs!(neighborhood_search, + system::FluidSystem, neighbor::BoundarySPHSystem, + u_system, u_neighbor) + # Boundary coordinates only change over time when `neighbor.ismoving[]` + PointNeighbors.update!(neighborhood_search, + current_coordinates(u_system, system), + current_coordinates(u_neighbor, neighbor), + particles_moving=(true, neighbor.ismoving[])) +end + +function update_nhs!(neighborhood_search, + system::FluidSystem, neighbor::OpenBoundarySPHSystem, + u_system, u_neighbor) + # The current coordinates of fluids and open boundaries change over time. + + # TODO: Update only `active_coordinates` of open boundaries. + # Problem: Removing inactive particles from neighboring lists is necessary. + PointNeighbors.update!(neighborhood_search, + current_coordinates(u_system, system), + current_coordinates(u_neighbor, neighbor), + particles_moving=(true, true)) +end + +function update_nhs!(neighborhood_search, + system::OpenBoundarySPHSystem, neighbor::FluidSystem, + u_system, u_neighbor) + # The current coordinates of both open boundaries and fluids change over time. + + # TODO: Update only `active_coordinates` of open boundaries. + # Problem: Removing inactive particles from neighboring lists is necessary. + PointNeighbors.update!(neighborhood_search, + current_coordinates(u_system, system), + current_coordinates(u_neighbor, neighbor), + particles_moving=(true, true)) +end + +function update_nhs!(neighborhood_search, + system::TotalLagrangianSPHSystem, neighbor::FluidSystem, + u_system, u_neighbor) + # The current coordinates of fluids and solids change over time + PointNeighbors.update!(neighborhood_search, + current_coordinates(u_system, system), + current_coordinates(u_neighbor, neighbor), + particles_moving=(true, true)) +end + +function update_nhs!(neighborhood_search, + system::TotalLagrangianSPHSystem, neighbor::TotalLagrangianSPHSystem, + u_system, u_neighbor) + # Don't update. Neighborhood search works on the initial coordinates, which don't change. + return neighborhood_search +end + +function update_nhs!(neighborhood_search, + system::TotalLagrangianSPHSystem, neighbor::BoundarySPHSystem, + u_system, u_neighbor) + # The current coordinates of solids change over time. + # Boundary coordinates only change over time when `neighbor.ismoving[]`. + PointNeighbors.update!(neighborhood_search, + current_coordinates(u_system, system), + current_coordinates(u_neighbor, neighbor), + particles_moving=(true, neighbor.ismoving[])) +end + +function update_nhs!(neighborhood_search, + system::BoundarySPHSystem, + neighbor::Union{FluidSystem, TotalLagrangianSPHSystem, + BoundarySPHSystem}, + u_system, u_neighbor) + # Don't update. This NHS is never used. + return neighborhood_search +end + +function update_nhs!(neighborhood_search, + system::BoundarySPHSystem{<:BoundaryModelDummyParticles}, + neighbor::Union{FluidSystem, TotalLagrangianSPHSystem}, + u_system, u_neighbor) + # Depending on the density calculator of the boundary model, this NHS is used for + # - kernel summation (`SummationDensity`) + # - continuity equation (`ContinuityDensity`) + # - pressure extrapolation (`AdamiPressureExtrapolation`) + # + # Boundary coordinates only change over time when `neighbor.ismoving[]`. + # The current coordinates of fluids and solids change over time. + PointNeighbors.update!(neighborhood_search, + current_coordinates(u_system, system), + current_coordinates(u_neighbor, neighbor), + particles_moving=(system.ismoving[], true)) +end + +function update_nhs!(neighborhood_search, + system::BoundarySPHSystem{<:BoundaryModelDummyParticles}, + neighbor::BoundarySPHSystem, + u_system, u_neighbor) + # `system` coordinates only change over time when `system.ismoving[]`. + # `neighbor` coordinates only change over time when `neighbor.ismoving[]`. + PointNeighbors.update!(neighborhood_search, + current_coordinates(u_system, system), + current_coordinates(u_neighbor, neighbor), + particles_moving=(system.ismoving[], neighbor.ismoving[])) +end + +function update_nhs!(neighborhood_search, + system::DEMSystem, neighbor::DEMSystem, + u_system, u_neighbor) + # Both coordinates change over time + PointNeighbors.update!(neighborhood_search, + current_coordinates(u_system, system), + current_coordinates(u_neighbor, neighbor), + particles_moving=(true, true)) +end + +function update_nhs!(neighborhood_search, + system::DEMSystem, neighbor::BoundaryDEMSystem, + u_system, u_neighbor) + # DEM coordinates change over time, the boundary coordinates don't + PointNeighbors.update!(neighborhood_search, + current_coordinates(u_system, system), + current_coordinates(u_neighbor, neighbor), + particles_moving=(true, false)) +end + +function update_nhs!(neighborhood_search, + system::BoundaryDEMSystem, + neighbor::Union{DEMSystem, BoundaryDEMSystem}, + u_system, u_neighbor) + # Don't update. This NHS is never used. + return neighborhood_search +end + +function update_nhs!(neighborhood_search, + system::Union{BoundarySPHSystem, OpenBoundarySPHSystem}, + neighbor::Union{BoundarySPHSystem, OpenBoundarySPHSystem}, + u_system, u_neighbor) + # Don't update. This NHS is never used. + return neighborhood_search end function check_configuration(systems) @@ -658,7 +805,7 @@ function check_configuration(boundary_system::BoundarySPHSystem, systems) boundary_model isa BoundaryModelDummyParticles && isnothing(boundary_model.state_equation) throw(ArgumentError("`WeaklyCompressibleSPHSystem` cannot be used without " * - "setting a `state_equation` for all boundary systems")) + "setting a `state_equation` for all boundary models")) end end end diff --git a/src/general/system.jl b/src/general/system.jl index c201bf3a5d..674555aea4 100644 --- a/src/general/system.jl +++ b/src/general/system.jl @@ -1,3 +1,28 @@ +# Abstract supertype for all system types. We additionally store the type of the system's +# initial condition, which is `Nothing` when using KernelAbstractions.jl. +abstract type System{NDIMS, IC} end + +# When using KernelAbstractions.jl, the initial condition has been replaced by `nothing` +GPUSystem = System{NDIMS, Nothing} where {NDIMS} + +abstract type FluidSystem{NDIMS, IC} <: System{NDIMS, IC} end +timer_name(::FluidSystem) = "fluid" +vtkname(system::FluidSystem) = "fluid" + +abstract type SolidSystem{NDIMS, IC} <: System{NDIMS, IC} end +timer_name(::SolidSystem) = "solid" +vtkname(system::SolidSystem) = "solid" + +abstract type BoundarySystem{NDIMS, IC} <: System{NDIMS, IC} end +timer_name(::BoundarySystem) = "boundary" +vtkname(system::BoundarySystem) = "boundary" + +@inline function set_zero!(du) + du .= zero(eltype(du)) + + return du +end + initialize!(system, neighborhood_search) = system @inline Base.ndims(::System{NDIMS}) where {NDIMS} = NDIMS @@ -17,7 +42,16 @@ initialize!(system, neighborhood_search) = system @inline n_moving_particles(system) = nparticles(system) @inline eachparticle(system) = Base.OneTo(nparticles(system)) -@inline each_moving_particle(system) = Base.OneTo(n_moving_particles(system)) + +# Wrapper for systems with `SystemBuffer` +@inline each_moving_particle(system) = each_moving_particle(system, system.buffer) +@inline each_moving_particle(system, ::Nothing) = Base.OneTo(n_moving_particles(system)) + +@inline active_coordinates(u, system) = active_coordinates(u, system, system.buffer) +@inline active_coordinates(u, system, ::Nothing) = current_coordinates(u, system) + +@inline active_particles(system) = active_particles(system, system.buffer) +@inline active_particles(system, ::Nothing) = eachparticle(system) # This should not be dispatched by system type. We always expect to get a column of `A`. @inline function extract_svector(A, system, i) @@ -61,9 +95,13 @@ end @inline function current_acceleration(system, particle) # TODO: Return `dv` of solid particles - return SVector(ntuple(_ -> 0.0, Val(ndims(system)))) + return zero(SVector{ndims(system), eltype(system)}) end +@inline set_particle_density!(v, system, particle, density) = v + +@inline set_particle_pressure!(v, system, particle, pressure) = v + @inline function smoothing_kernel(system, distance) (; smoothing_kernel, smoothing_length) = system return kernel(smoothing_kernel, distance, smoothing_length) @@ -103,6 +141,9 @@ function update_pressure!(system, v, u, v_ode, u_ode, semi, t) return system end -function update_final!(system, v, u, v_ode, u_ode, semi, t) +function update_final!(system, v, u, v_ode, u_ode, semi, t; update_from_callback=false) return system end + +# Only for systems requiring a mandatory callback +reset_callback_flag!(system) = system diff --git a/src/neighborhood_search/grid_nhs.jl b/src/neighborhood_search/grid_nhs.jl deleted file mode 100644 index 82526a6b4c..0000000000 --- a/src/neighborhood_search/grid_nhs.jl +++ /dev/null @@ -1,423 +0,0 @@ -@doc raw""" - GridNeighborhoodSearch{NDIMS}(search_radius, n_particles; periodic_box_min_corner=nothing, - periodic_box_max_corner=nothing, threaded_nhs_update=true) - -Simple grid-based neighborhood search with uniform search radius. -The domain is divided into a regular grid. -For each (non-empty) grid cell, a list of particles in this cell is stored. -Instead of representing a finite domain by an array of cells, a potentially infinite domain -is represented by storing cell lists in a hash table (using Julia's `Dict` data structure), -indexed by the cell index tuple -```math -\left( \left\lfloor \frac{x}{d} \right\rfloor, \left\lfloor \frac{y}{d} \right\rfloor \right) \quad \text{or} \quad -\left( \left\lfloor \frac{x}{d} \right\rfloor, \left\lfloor \frac{y}{d} \right\rfloor, \left\lfloor \frac{z}{d} \right\rfloor \right), -``` -where ``x, y, z`` are the space coordinates and ``d`` is the search radius. - -To find particles within the search radius around a point, only particles in the neighboring -cells are considered. - -See also (Chalela et al., 2021), (Ihmsen et al. 2011, Section 4.4). - -As opposed to (Ihmsen et al. 2011), we do not sort the particles in any way, -since not sorting makes our implementation a lot faster (although less parallelizable). - -# Arguments -- `NDIMS`: Number of dimensions. -- `search_radius`: The uniform search radius. -- `n_particles`: Total number of particles. - -# Keywords -- `periodic_box_min_corner`: In order to use a (rectangular) periodic domain, pass the - coordinates of the domain corner in negative coordinate - directions. -- `periodic_box_max_corner`: In order to use a (rectangular) periodic domain, pass the - coordinates of the domain corner in positive coordinate - directions. -- `threaded_nhs_update=true`: Can be used to deactivate thread parallelization in the neighborhood search update. - This can be one of the largest sources of variations between simulations - with different thread numbers due to particle ordering changes. - -!!! warning "Internal use only" - Please note that this constructor is intended for internal use only. It is *not* part of - the public API of TrixiParticles.jl, and it thus can altered (or be removed) at any time - without it being considered a breaking change. - - To run a simulation with this neighborhood search, just pass the type to the constructor - of [`Semidiscretization`](@ref): - ```jldoctest semi_example; output=false, setup = :(trixi_include(@__MODULE__, joinpath(examples_dir(), "fluid", "hydrostatic_water_column_2d.jl"), sol=nothing); system1 = fluid_system; system2 = boundary_system) - semi = Semidiscretization(system1, system2, - neighborhood_search=GridNeighborhoodSearch) - - # output - ┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ Semidiscretization │ - │ ══════════════════ │ - │ #spatial dimensions: ………………………… 2 │ - │ #systems: ……………………………………………………… 2 │ - │ neighborhood search: ………………………… GridNeighborhoodSearch │ - │ total #particles: ………………………………… 636 │ - └──────────────────────────────────────────────────────────────────────────────────────────────────┘ - ``` - The keyword arguments `periodic_box_min_corner` and `periodic_box_max_corner` explained - above can also be passed to the [`Semidiscretization`](@ref) and will internally be - forwarded to the neighborhood search: - ```jldoctest semi_example; output = false - semi = Semidiscretization(system1, system2, - neighborhood_search=GridNeighborhoodSearch, - periodic_box_min_corner=[0.0, -0.25], - periodic_box_max_corner=[1.0, 0.75]) - - # output - ┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ Semidiscretization │ - │ ══════════════════ │ - │ #spatial dimensions: ………………………… 2 │ - │ #systems: ……………………………………………………… 2 │ - │ neighborhood search: ………………………… GridNeighborhoodSearch │ - │ total #particles: ………………………………… 636 │ - └──────────────────────────────────────────────────────────────────────────────────────────────────┘ - ``` - -## References -- M. Chalela, E. Sillero, L. Pereyra, M.A. Garcia, J.B. Cabral, M. Lares, M. Merchán. - "GriSPy: A Python package for fixed-radius nearest neighbors search". - In: Astronomy and Computing 34 (2021). - [doi: 10.1016/j.ascom.2020.100443](https://doi.org/10.1016/j.ascom.2020.100443) -- Markus Ihmsen, Nadir Akinci, Markus Becker, Matthias Teschner. - "A Parallel SPH Implementation on Multi-Core CPUs". - In: Computer Graphics Forum 30.1 (2011), pages 99–112. - [doi: 10.1111/J.1467-8659.2010.01832.X](https://doi.org/10.1111/J.1467-8659.2010.01832.X) -""" -struct GridNeighborhoodSearch{NDIMS, ELTYPE, PB} - hashtable :: Dict{NTuple{NDIMS, Int}, Vector{Int}} - search_radius :: ELTYPE - empty_vector :: Vector{Int} # Just an empty vector (used in `eachneighbor`) - cell_buffer :: Array{NTuple{NDIMS, Int}, 2} # Multithreaded buffer for `update!` - cell_buffer_indices :: Vector{Int} # Store which entries of `cell_buffer` are initialized - periodic_box :: PB - n_cells :: NTuple{NDIMS, Int} - cell_size :: NTuple{NDIMS, ELTYPE} - threaded_nhs_update :: Bool - - function GridNeighborhoodSearch{NDIMS}(search_radius, n_particles; - periodic_box_min_corner=nothing, - periodic_box_max_corner=nothing, - threaded_nhs_update=true) where {NDIMS} - ELTYPE = typeof(search_radius) - - hashtable = Dict{NTuple{NDIMS, Int}, Vector{Int}}() - empty_vector = Int[] - cell_buffer = Array{NTuple{NDIMS, Int}, 2}(undef, n_particles, Threads.nthreads()) - cell_buffer_indices = zeros(Int, Threads.nthreads()) - - if search_radius < eps() || - (periodic_box_min_corner === nothing && periodic_box_max_corner === nothing) - - # No periodicity - periodic_box = nothing - n_cells = ntuple(_ -> -1, Val(NDIMS)) - cell_size = ntuple(_ -> search_radius, Val(NDIMS)) - elseif periodic_box_min_corner !== nothing && periodic_box_max_corner !== nothing - periodic_box = PeriodicBox(periodic_box_min_corner, periodic_box_max_corner) - - # Round up search radius so that the grid fits exactly into the domain without - # splitting any cells. This might impact performance slightly, since larger - # cells mean that more potential neighbors are considered than necessary. - # Allow small tolerance to avoid inefficient larger cells due to machine - # rounding errors. - n_cells = Tuple(floor.(Int, (periodic_box.size .+ 10eps()) / search_radius)) - cell_size = Tuple(periodic_box.size ./ n_cells) - - if any(i -> i < 3, n_cells) - throw(ArgumentError("the `GridNeighborhoodSearch` needs at least 3 cells " * - "in each dimension when used with periodicity. " * - "Please use no NHS for very small problems.")) - end - else - throw(ArgumentError("`periodic_box_min_corner` and `periodic_box_max_corner` " * - "must either be both `nothing` or both an array or tuple")) - end - - new{NDIMS, ELTYPE, - typeof(periodic_box)}(hashtable, search_radius, empty_vector, - cell_buffer, cell_buffer_indices, - periodic_box, n_cells, cell_size, threaded_nhs_update) - end -end - -@inline Base.ndims(neighborhood_search::GridNeighborhoodSearch{NDIMS}) where {NDIMS} = NDIMS - -@inline function nparticles(neighborhood_search::GridNeighborhoodSearch) - return size(neighborhood_search.cell_buffer, 1) -end - -function initialize!(neighborhood_search::GridNeighborhoodSearch, ::Nothing) - # No particle coordinates function -> don't initialize. - return neighborhood_search -end - -function initialize!(neighborhood_search::GridNeighborhoodSearch{NDIMS}, - x::AbstractArray) where {NDIMS} - initialize!(neighborhood_search, i -> extract_svector(x, Val(NDIMS), i)) -end - -function initialize!(neighborhood_search::GridNeighborhoodSearch, coords_fun) - (; hashtable) = neighborhood_search - - empty!(hashtable) - - # This is needed to prevent lagging on macOS ARM. - # See https://github.com/JuliaSIMD/Polyester.jl/issues/89 - ThreadingUtilities.sleep_all_tasks() - - for particle in 1:nparticles(neighborhood_search) - # Get cell index of the particle's cell - cell = cell_coords(coords_fun(particle), neighborhood_search) - - # Add particle to corresponding cell or create cell if it does not exist - if haskey(hashtable, cell) - append!(hashtable[cell], particle) - else - hashtable[cell] = [particle] - end - end - - return neighborhood_search -end - -function update!(neighborhood_search::GridNeighborhoodSearch, ::Nothing) - # No particle coordinates function -> don't update. - return neighborhood_search -end - -function update!(neighborhood_search::GridNeighborhoodSearch{NDIMS}, - x::AbstractArray) where {NDIMS} - update!(neighborhood_search, i -> extract_svector(x, Val(NDIMS), i)) -end - -# Modify the existing hash table by moving particles into their new cells -function update!(neighborhood_search::GridNeighborhoodSearch, coords_fun) - (; hashtable, cell_buffer, cell_buffer_indices, threaded_nhs_update) = neighborhood_search - - # Reset `cell_buffer` by moving all pointers to the beginning. - cell_buffer_indices .= 0 - - # Find all cells containing particles that now belong to another cell. - # `collect` the keyset to be able to loop over it with `@threaded`. - mark_changed_cell!(neighborhood_search, hashtable, coords_fun, Val(threaded_nhs_update)) - - # This is needed to prevent lagging on macOS ARM. - # See https://github.com/JuliaSIMD/Polyester.jl/issues/89 - ThreadingUtilities.sleep_all_tasks() - - # Iterate over all marked cells and move the particles into their new cells. - for thread in 1:Threads.nthreads() - # Only the entries `1:cell_buffer_indices[thread]` are initialized for `thread`. - for i in 1:cell_buffer_indices[thread] - cell = cell_buffer[i, thread] - particles = hashtable[cell] - - # Find all particles whose coordinates do not match this cell - moved_particle_indices = (i for i in eachindex(particles) - if cell_coords(coords_fun(particles[i]), - neighborhood_search) != cell) - - # Add moved particles to new cell - for i in moved_particle_indices - particle = particles[i] - new_cell_coords = cell_coords(coords_fun(particle), neighborhood_search) - - # Add particle to corresponding cell or create cell if it does not exist - if haskey(hashtable, new_cell_coords) - append!(hashtable[new_cell_coords], particle) - else - hashtable[new_cell_coords] = [particle] - end - end - - # Remove moved particles from this cell or delete the cell if it is now empty - if count(_ -> true, moved_particle_indices) == length(particles) - delete!(hashtable, cell) - else - deleteat!(particles, moved_particle_indices) - end - end - end - - return neighborhood_search -end - -@inline function mark_changed_cell!(neighborhood_search, hashtable, coords_fun, - threaded_nhs_update::Val{true}) - @threaded for cell in collect(keys(hashtable)) - mark_changed_cell!(neighborhood_search, cell, coords_fun) - end -end - -@inline function mark_changed_cell!(neighborhood_search, hashtable, coords_fun, - threaded_nhs_update::Val{false}) - for cell in collect(keys(hashtable)) - mark_changed_cell!(neighborhood_search, cell, coords_fun) - end -end - -# Use this function barrier and unpack inside to avoid passing closures to Polyester.jl -# with `@batch` (`@threaded`). -# Otherwise, `@threaded` does not work here with Julia ARM on macOS. -# See https://github.com/JuliaSIMD/Polyester.jl/issues/88. -@inline function mark_changed_cell!(neighborhood_search, cell, coords_fun) - (; hashtable, cell_buffer, cell_buffer_indices) = neighborhood_search - - for particle in hashtable[cell] - if cell_coords(coords_fun(particle), neighborhood_search) != cell - # Mark this cell and continue with the next one. - # - # `cell_buffer` is preallocated, - # but only the entries 1:i are used for this thread. - i = cell_buffer_indices[Threads.threadid()] += 1 - cell_buffer[i, Threads.threadid()] = cell - break - end - end -end - -# 1D -@inline function eachneighbor(coords, neighborhood_search::GridNeighborhoodSearch{1}) - cell = cell_coords(coords, neighborhood_search) - x = cell[1] - # Generator of all neighboring cells to consider - neighboring_cells = ((x + i) for i in -1:1) - - # Merge all lists of particles in the neighboring cells into one iterator - Iterators.flatten(particles_in_cell(cell, neighborhood_search) - for cell in neighboring_cells) -end - -# 2D -@inline function eachneighbor(coords, neighborhood_search::GridNeighborhoodSearch{2}) - cell = cell_coords(coords, neighborhood_search) - x, y = cell - # Generator of all neighboring cells to consider - neighboring_cells = ((x + i, y + j) for i in -1:1, j in -1:1) - - # Merge all lists of particles in the neighboring cells into one iterator - Iterators.flatten(particles_in_cell(cell, neighborhood_search) - for cell in neighboring_cells) -end - -# 3D -@inline function eachneighbor(coords, neighborhood_search::GridNeighborhoodSearch{3}) - cell = cell_coords(coords, neighborhood_search) - x, y, z = cell - # Generator of all neighboring cells to consider - neighboring_cells = ((x + i, y + j, z + k) for i in -1:1, j in -1:1, k in -1:1) - - # Merge all lists of particles in the neighboring cells into one iterator - Iterators.flatten(particles_in_cell(cell, neighborhood_search) - for cell in neighboring_cells) -end - -@inline function particles_in_cell(cell_index, neighborhood_search) - (; hashtable, empty_vector) = neighborhood_search - - # Return an empty vector when `cell_index` is not a key of `hashtable` and - # reuse the empty vector to avoid allocations - return get(hashtable, periodic_cell_index(cell_index, neighborhood_search), - empty_vector) -end - -@inline function periodic_cell_index(cell_index, neighborhood_search) - (; n_cells, periodic_box) = neighborhood_search - - periodic_cell_index(cell_index, periodic_box, n_cells) -end - -@inline periodic_cell_index(cell_index, ::Nothing, n_cells) = cell_index - -@inline function periodic_cell_index(cell_index, periodic_box, n_cells) - return rem.(cell_index, n_cells, RoundDown) -end - -@inline function cell_coords(coords, neighborhood_search) - (; periodic_box, cell_size) = neighborhood_search - - return cell_coords(coords, periodic_box, cell_size) -end - -@inline function cell_coords(coords, periodic_box::Nothing, cell_size) - return Tuple(floor_to_int.(coords ./ cell_size)) -end - -@inline function cell_coords(coords, periodic_box, cell_size) - # Subtract `min_corner` to offset coordinates so that the min corner of the periodic - # box corresponds to the (0, 0) cell of the NHS. - # This way, there are no partial cells in the domain if the domain size is an integer - # multiple of the cell size (which is required, see the constructor). - offset_coords = periodic_coords(coords, periodic_box) .- periodic_box.min_corner - - return Tuple(floor_to_int.(offset_coords ./ cell_size)) -end - -# When particles end up with coordinates so big that the cell coordinates -# exceed the range of Int, then `floor(Int, i)` will fail with an InexactError. -# In this case, we can just use typemax(Int), since we can assume that particles -# that far away will not interact with anything, anyway. -# This usually indicates an instability, but we don't want the simulation to crash, -# since adaptive time integration methods may detect the instability and reject the -# time step. -# If we threw an error here, we would prevent the time integration method from -# retrying with a smaller time step, and we would thus crash perfectly fine simulations. -@inline function floor_to_int(i) - if isnan(i) || i > typemax(Int) - return typemax(Int) - elseif i < typemin(Int) - return typemin(Int) - end - - return floor(Int, i) -end - -# Sorting only really makes sense in longer simulations where particles -# end up very unordered. -# WARNING: This is currently unmaintained. -function z_index_sort!(coordinates, system) - (; mass, pressure, neighborhood_search) = system - - perm = sortperm(eachparticle(system), - by=(i -> cell_z_index(extract_svector(coordinates, system, i), - neighborhood_search))) - - permute!(mass, perm) - permute!(pressure, perm) - Base.permutecols!!(u, perm) - - return nothing -end - -@inline function cell_z_index(coords, neighborhood_search) - cell = cell_coords(coords, neighborhood_search.search_radius) .+ 1 - - return cartesian2morton(SVector(cell)) -end - -# Create a copy of a neighborhood search but with a different search radius -function copy_neighborhood_search(nhs::GridNeighborhoodSearch, search_radius, u) - if nhs.periodic_box === nothing - search = GridNeighborhoodSearch{ndims(nhs)}(search_radius, nparticles(nhs)) - else - search = GridNeighborhoodSearch{ndims(nhs)}(search_radius, nparticles(nhs), - periodic_box_min_corner=nhs.periodic_box.min_corner, - periodic_box_max_corner=nhs.periodic_box.max_corner) - end - - # Initialize neighborhood search - initialize!(search, u) - - return search -end - -# Create a copy of a neighborhood search but with a different search radius -function copy_neighborhood_search(nhs::TrivialNeighborhoodSearch, search_radius, u) - return nhs -end diff --git a/src/neighborhood_search/neighborhood_search.jl b/src/neighborhood_search/neighborhood_search.jl deleted file mode 100644 index e9062c5db4..0000000000 --- a/src/neighborhood_search/neighborhood_search.jl +++ /dev/null @@ -1,110 +0,0 @@ -struct PeriodicBox{NDIMS, ELTYPE} - min_corner :: SVector{NDIMS, ELTYPE} - max_corner :: SVector{NDIMS, ELTYPE} - size :: SVector{NDIMS, ELTYPE} - - function PeriodicBox(min_corner, max_corner) - new{length(min_corner), eltype(min_corner)}(min_corner, max_corner, - max_corner - min_corner) - end -end - -# Loop over all pairs of particles and neighbors within the kernel cutoff. -# `f(particle, neighbor, pos_diff, distance)` is called for every particle-neighbor pair. -# By default, loop over `each_moving_particle(system)`. -@inline function for_particle_neighbor(f, system, neighbor_system, - system_coords, neighbor_coords, neighborhood_search; - particles=each_moving_particle(system), - parallel=true) - for_particle_neighbor(f, system_coords, neighbor_coords, neighborhood_search, - particles=particles, parallel=parallel) -end - -@inline function for_particle_neighbor(f, system_coords, neighbor_coords, - neighborhood_search; - particles=axes(system_coords, 2), parallel=true) - for_particle_neighbor(f, system_coords, neighbor_coords, neighborhood_search, particles, - Val(parallel)) -end - -@inline function for_particle_neighbor(f, system_coords, neighbor_coords, - neighborhood_search, particles, parallel::Val{true}) - @threaded for particle in particles - for_particle_neighbor_inner(f, system_coords, neighbor_coords, neighborhood_search, - particle) - end - - return nothing -end - -@inline function for_particle_neighbor(f, system_coords, neighbor_coords, - neighborhood_search, particles, parallel::Val{false}) - for particle in particles - for_particle_neighbor_inner(f, system_coords, neighbor_coords, neighborhood_search, - particle) - end - - return nothing -end - -# Use this function barrier and unpack inside to avoid passing closures to Polyester.jl -# with `@batch` (`@threaded`). -# Otherwise, `@threaded` does not work here with Julia ARM on macOS. -# See https://github.com/JuliaSIMD/Polyester.jl/issues/88. -@inline function for_particle_neighbor_inner(f, system_coords, neighbor_system_coords, - neighborhood_search, particle) - (; search_radius, periodic_box) = neighborhood_search - - particle_coords = extract_svector(system_coords, Val(ndims(neighborhood_search)), - particle) - for neighbor in eachneighbor(particle_coords, neighborhood_search) - neighbor_coords = extract_svector(neighbor_system_coords, - Val(ndims(neighborhood_search)), neighbor) - - pos_diff = particle_coords - neighbor_coords - distance2 = dot(pos_diff, pos_diff) - - pos_diff, distance2 = compute_periodic_distance(pos_diff, distance2, search_radius, - periodic_box) - - if distance2 <= search_radius^2 - distance = sqrt(distance2) - - # Inline to avoid loss of performance - # compared to not using `for_particle_neighbor`. - @inline f(particle, neighbor, pos_diff, distance) - end - end -end - -@inline function compute_periodic_distance(pos_diff, distance2, search_radius, - periodic_box::Nothing) - return pos_diff, distance2 -end - -@inline function compute_periodic_distance(pos_diff, distance2, search_radius, - periodic_box) - if distance2 > search_radius^2 - # Use periodic `pos_diff` - pos_diff -= periodic_box.size .* round.(pos_diff ./ periodic_box.size) - distance2 = dot(pos_diff, pos_diff) - end - - return pos_diff, distance2 -end - -@inline function periodic_coords(coords, periodic_box) - (; min_corner, size) = periodic_box - - # Move coordinates into the periodic box - box_offset = floor.((coords .- min_corner) ./ size) - - return coords - box_offset .* size -end - -@inline function periodic_coords(coords, periodic_box::Nothing) - return coords -end - -include("trivial_nhs.jl") -include("grid_nhs.jl") diff --git a/src/neighborhood_search/trivial_nhs.jl b/src/neighborhood_search/trivial_nhs.jl deleted file mode 100644 index 3902f1d3ff..0000000000 --- a/src/neighborhood_search/trivial_nhs.jl +++ /dev/null @@ -1,97 +0,0 @@ -@doc raw""" - TrivialNeighborhoodSearch{NDIMS}(search_radius, eachparticle) - -Trivial neighborhood search that simply loops over all particles. -The search radius still needs to be passed in order to sort out particles outside the -search radius in the internal function `for_particle_neighbor`, but it's not used in the -internal function `eachneighbor`. - -# Arguments -- `NDIMS`: Number of dimensions. -- `search_radius`: The uniform search radius. -- `eachparticle`: `UnitRange` of all particle indices. Usually just `1:n_particles`. - -# Keywords -- `periodic_box_min_corner`: In order to use a (rectangular) periodic domain, pass the - coordinates of the domain corner in negative coordinate - directions. -- `periodic_box_max_corner`: In order to use a (rectangular) periodic domain, pass the - coordinates of the domain corner in positive coordinate - directions. - -!!! warning "Internal use only" - Please note that this constructor is intended for internal use only. It is *not* part of - the public API of TrixiParticles.jl, and it thus can altered (or be removed) at any time - without it being considered a breaking change. - - To run a simulation with this neighborhood search, just pass the type to the constructor - of [`Semidiscretization`](@ref): - ```jldoctest semi_example; output=false, setup = :(trixi_include(@__MODULE__, joinpath(examples_dir(), "fluid", "hydrostatic_water_column_2d.jl"), sol=nothing); system1 = fluid_system; system2 = boundary_system) - semi = Semidiscretization(system1, system2, - neighborhood_search=TrivialNeighborhoodSearch) - - # output - ┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ Semidiscretization │ - │ ══════════════════ │ - │ #spatial dimensions: ………………………… 2 │ - │ #systems: ……………………………………………………… 2 │ - │ neighborhood search: ………………………… TrivialNeighborhoodSearch │ - │ total #particles: ………………………………… 636 │ - └──────────────────────────────────────────────────────────────────────────────────────────────────┘ - ``` - The keyword arguments `periodic_box_min_corner` and `periodic_box_max_corner` explained - above can also be passed to the [`Semidiscretization`](@ref) and will internally be - forwarded to the neighborhood search: - ```jldoctest semi_example; output = false - semi = Semidiscretization(system1, system2, - neighborhood_search=TrivialNeighborhoodSearch, - periodic_box_min_corner=[0.0, -0.25], - periodic_box_max_corner=[1.0, 0.75]) - - # output - ┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ Semidiscretization │ - │ ══════════════════ │ - │ #spatial dimensions: ………………………… 2 │ - │ #systems: ……………………………………………………… 2 │ - │ neighborhood search: ………………………… TrivialNeighborhoodSearch │ - │ total #particles: ………………………………… 636 │ - └──────────────────────────────────────────────────────────────────────────────────────────────────┘ - ``` -""" -struct TrivialNeighborhoodSearch{NDIMS, ELTYPE, EP, PB} - search_radius :: ELTYPE - eachparticle :: EP - periodic_box :: PB - - function TrivialNeighborhoodSearch{NDIMS}(search_radius, eachparticle; - periodic_box_min_corner=nothing, - periodic_box_max_corner=nothing) where {NDIMS} - if search_radius < eps() || - (periodic_box_min_corner === nothing && periodic_box_max_corner === nothing) - - # No periodicity - periodic_box = nothing - elseif periodic_box_min_corner !== nothing && periodic_box_max_corner !== nothing - periodic_box = PeriodicBox(periodic_box_min_corner, periodic_box_max_corner) - else - throw(ArgumentError("`periodic_box_min_corner` and `periodic_box_max_corner` " * - "must either be both `nothing` or both an array or tuple")) - end - - new{NDIMS, typeof(search_radius), - typeof(eachparticle), typeof(periodic_box)}(search_radius, eachparticle, - periodic_box) - end -end - -@inline function Base.ndims(neighborhood_search::TrivialNeighborhoodSearch{NDIMS}) where { - NDIMS - } - return NDIMS -end - -@inline initialize!(search::TrivialNeighborhoodSearch, coords_fun) = search -@inline update!(search::TrivialNeighborhoodSearch, coords_fun) = search -@inline eachneighbor(coords, search::TrivialNeighborhoodSearch) = search.eachparticle diff --git a/src/schemes/boundary/dummy_particles/dummy_particles.jl b/src/schemes/boundary/dummy_particles/dummy_particles.jl index 27c7f924d9..d6ca717efc 100644 --- a/src/schemes/boundary/dummy_particles/dummy_particles.jl +++ b/src/schemes/boundary/dummy_particles/dummy_particles.jl @@ -37,9 +37,9 @@ boundary_model = BoundaryModelDummyParticles(densities, masses, AdamiPressureExt BoundaryModelDummyParticles(AdamiPressureExtrapolation, ViscosityAdami) ``` """ -struct BoundaryModelDummyParticles{DC, ELTYPE <: Real, SE, K, V, COR, C} - pressure :: Vector{ELTYPE} - hydrodynamic_mass :: Vector{ELTYPE} +struct BoundaryModelDummyParticles{DC, ELTYPE <: Real, VECTOR, SE, K, V, COR, C} + pressure :: VECTOR # Vector{ELTYPE} + hydrodynamic_mass :: VECTOR # Vector{ELTYPE} state_equation :: SE density_calculator :: DC smoothing_kernel :: K @@ -47,30 +47,26 @@ struct BoundaryModelDummyParticles{DC, ELTYPE <: Real, SE, K, V, COR, C} viscosity :: V correction :: COR cache :: C +end - function BoundaryModelDummyParticles(initial_density, hydrodynamic_mass, - density_calculator, smoothing_kernel, - smoothing_length; viscosity=nothing, - state_equation=nothing, correction=nothing) - pressure = initial_boundary_pressure(initial_density, density_calculator, - state_equation) - NDIMS = ndims(smoothing_kernel) - - n_particles = length(initial_density) - - cache = (; create_cache_model(viscosity, n_particles, NDIMS)..., - create_cache_model(initial_density, density_calculator)...) - cache = (; - create_cache_model(correction, initial_density, NDIMS, - n_particles)..., cache...) - - new{typeof(density_calculator), eltype(initial_density), - typeof(state_equation), typeof(smoothing_kernel), typeof(viscosity), - typeof(correction), typeof(cache)}(pressure, hydrodynamic_mass, state_equation, - density_calculator, - smoothing_kernel, smoothing_length, - viscosity, correction, cache) - end +# The default constructor needs to be accessible for Adapt.jl to work with this struct. +# See the comments in general/gpu.jl for more details. +function BoundaryModelDummyParticles(initial_density, hydrodynamic_mass, + density_calculator, smoothing_kernel, + smoothing_length; viscosity=nothing, + state_equation=nothing, correction=nothing) + pressure = initial_boundary_pressure(initial_density, density_calculator, + state_equation) + NDIMS = ndims(smoothing_kernel) + n_particles = length(initial_density) + + cache = (; create_cache_model(viscosity, n_particles, NDIMS)..., + create_cache_model(initial_density, density_calculator)..., + create_cache_model(correction, initial_density, NDIMS, n_particles)...) + + return BoundaryModelDummyParticles(pressure, hydrodynamic_mass, state_equation, + density_calculator, smoothing_kernel, + smoothing_length, viscosity, correction, cache) end @doc raw""" @@ -106,7 +102,7 @@ struct PressureMirroring end """ struct PressureZeroing end -create_cache_model(correction, density, NDIMS, nparticles) = (;) +@inline create_cache_model(correction, density, NDIMS, nparticles) = (;) function create_cache_model(::ShepardKernelCorrection, density, NDIMS, n_particles) return (; kernel_correction_coefficient=similar(density)) @@ -136,9 +132,7 @@ function create_cache_model(initial_density, return (; density) end -function create_cache_model(initial_density, ::ContinuityDensity) - return (; initial_density) -end +@inline create_cache_model(initial_density, ::ContinuityDensity) = (; initial_density) function create_cache_model(initial_density, ::AdamiPressureExtrapolation) density = copy(initial_density) @@ -147,9 +141,7 @@ function create_cache_model(initial_density, ::AdamiPressureExtrapolation) return (; density, volume) end -function create_cache_model(viscosity::Nothing, n_particles, n_dims) - return (;) -end +@inline create_cache_model(viscosity::Nothing, n_particles, n_dims) = (;) function create_cache_model(viscosity, n_particles, n_dims) ELTYPE = eltype(viscosity.epsilon) @@ -292,7 +284,7 @@ function compute_pressure!(boundary_model, ::Union{SummationDensity, ContinuityD # Limit pressure to be non-negative to avoid attractive forces between fluid and # boundary particles at free surfaces (sticking artifacts). - @threaded for particle in eachparticle(system) + @threaded system for particle in eachparticle(system) apply_state_equation!(boundary_model, particle_density(v, boundary_model, particle), particle) end @@ -307,10 +299,9 @@ end boundary_model.pressure[particle] = max(boundary_model.state_equation(density), 0.0) end -function compute_pressure!(boundary_model, ::AdamiPressureExtrapolation, - system, v, u, v_ode, u_ode, semi) - (; pressure, state_equation, cache, viscosity) = boundary_model - (; volume, density) = cache +function compute_pressure!(boundary_model, ::AdamiPressureExtrapolation, system, v, u, + v_ode, u_ode, semi) + (; pressure, cache, viscosity) = boundary_model set_zero!(pressure) @@ -324,16 +315,38 @@ function compute_pressure!(boundary_model, ::AdamiPressureExtrapolation, v_neighbor_system = wrap_v(v_ode, neighbor_system, semi) u_neighbor_system = wrap_u(u_ode, neighbor_system, semi) - nhs = get_neighborhood_search(system, neighbor_system, semi) - neighbor_coords = current_coordinates(u_neighbor_system, neighbor_system) - adami_pressure_extrapolation!(boundary_model, system, neighbor_system, - system_coords, neighbor_coords, - v_neighbor_system, nhs) + # This is an optimization for simulations with large and complex boundaries. + # Especially, in 3D simulations with large and/or complex structures outside + # of areas with permanent flow. + # Note: The version iterating neighbors first is not thread parallelizable. + # The factor is based on the achievable speed-up of the thread parallelizable version. + if nparticles(system) > + ceil(Int, 0.5 * Threads.nthreads()) * nparticles(neighbor_system) + nhs = get_neighborhood_search(neighbor_system, system, semi) + + # Loop over fluid particles and then the neighboring boundary particles to extrapolate fluid pressure to the boundaries + adami_pressure_extrapolation_neighbor!(boundary_model, system, neighbor_system, + system_coords, neighbor_coords, + v_neighbor_system, nhs) + else + nhs = get_neighborhood_search(system, neighbor_system, semi) + + # Loop over boundary particles and then the neighboring fluid particles to extrapolate fluid pressure to the boundaries + adami_pressure_extrapolation!(boundary_model, system, neighbor_system, + system_coords, neighbor_coords, + v_neighbor_system, nhs) + end + + @threaded system for particle in eachparticle(system) + # Limit pressure to be non-negative to avoid attractive forces between fluid and + # boundary particles at free surfaces (sticking artifacts). + pressure[particle] = max(pressure[particle], 0.0) + end end - @trixi_timeit timer() "inverse state equation" @threaded for particle in eachparticle(system) + @trixi_timeit timer() "inverse state equation" @threaded system for particle in eachparticle(system) compute_adami_density!(boundary_model, system, system_coords, particle) end end @@ -365,6 +378,40 @@ function compute_pressure!(boundary_model, ::Union{PressureMirroring, PressureZe return boundary_model end +@inline function adami_pressure_extrapolation_neighbor!(boundary_model, system, + neighbor_system, system_coords, + neighbor_coords, v_neighbor_system, + neighborhood_search) + return boundary_model +end + +@inline function adami_pressure_extrapolation_neighbor!(boundary_model, system, + neighbor_system::FluidSystem, + system_coords, neighbor_coords, + v_neighbor_system, + neighborhood_search) + (; pressure, cache, viscosity) = boundary_model + + for_particle_neighbor(neighbor_system, system, + neighbor_coords, system_coords, + neighborhood_search; + particles=eachparticle(neighbor_system), + parallel=false) do neighbor, particle, + pos_diff, distance + # Since neighbor and particle are switched + pos_diff = -pos_diff + adami_pressure_inner!(boundary_model, system, neighbor_system, + v_neighbor_system, particle, neighbor, pos_diff, + distance, viscosity, cache, pressure) + end +end + +@inline function adami_pressure_extrapolation!(boundary_model, system, neighbor_system, + system_coords, neighbor_coords, + v_neighbor_system, neighborhood_search) + return boundary_model +end + @inline function adami_pressure_extrapolation!(boundary_model, system, neighbor_system::FluidSystem, system_coords, neighbor_coords, @@ -377,35 +424,31 @@ end neighborhood_search; particles=eachparticle(system)) do particle, neighbor, pos_diff, distance - density_neighbor = particle_density(v_neighbor_system, neighbor_system, neighbor) - - resulting_acc = neighbor_system.acceleration - - current_acceleration(system, particle) + adami_pressure_inner!(boundary_model, system, neighbor_system, + v_neighbor_system, particle, neighbor, pos_diff, + distance, viscosity, cache, pressure) + end +end - kernel_weight = smoothing_kernel(boundary_model, distance) +@inline function adami_pressure_inner!(boundary_model, system, neighbor_system, + v_neighbor_system, particle, neighbor, pos_diff, + distance, viscosity, cache, pressure) + density_neighbor = particle_density(v_neighbor_system, neighbor_system, neighbor) - pressure[particle] += (particle_pressure(v_neighbor_system, neighbor_system, - neighbor) + - dot(resulting_acc, density_neighbor * pos_diff)) * - kernel_weight + resulting_acc = neighbor_system.acceleration - + current_acceleration(system, particle) - cache.volume[particle] += kernel_weight + kernel_weight = smoothing_kernel(boundary_model, distance) - compute_smoothed_velocity!(cache, viscosity, neighbor_system, v_neighbor_system, - kernel_weight, particle, neighbor) - end + pressure[particle] += (particle_pressure(v_neighbor_system, neighbor_system, + neighbor) + + dot(resulting_acc, density_neighbor * pos_diff)) * + kernel_weight - for particle in eachparticle(system) - # Limit pressure to be non-negative to avoid attractive forces between fluid and - # boundary particles at free surfaces (sticking artifacts). - pressure[particle] = max(pressure[particle], 0.0) - end -end + cache.volume[particle] += kernel_weight -@inline function adami_pressure_extrapolation!(boundary_model, system, neighbor_system, - system_coords, neighbor_coords, - v_neighbor_system, neighborhood_search) - return boundary_model + compute_smoothed_velocity!(cache, viscosity, neighbor_system, v_neighbor_system, + kernel_weight, particle, neighbor) end function compute_smoothed_velocity!(cache, viscosity, neighbor_system, v_neighbor_system, @@ -457,8 +500,7 @@ end return density end -@inline function smoothing_kernel_grad(system::BoundarySystem, pos_diff, - distance, particle) +@inline function smoothing_kernel_grad(system::BoundarySystem, pos_diff, distance, particle) (; smoothing_kernel, smoothing_length, correction) = system.boundary_model return corrected_kernel_grad(smoothing_kernel, pos_diff, distance, diff --git a/src/schemes/boundary/monaghan_kajtar/monaghan_kajtar.jl b/src/schemes/boundary/monaghan_kajtar/monaghan_kajtar.jl index e80740cf5c..6af5c4aabb 100644 --- a/src/schemes/boundary/monaghan_kajtar/monaghan_kajtar.jl +++ b/src/schemes/boundary/monaghan_kajtar/monaghan_kajtar.jl @@ -14,18 +14,21 @@ - `viscosity`: Free-slip (default) or no-slip condition. See description above for further information. """ -struct BoundaryModelMonaghanKajtar{ELTYPE <: Real, V} +struct BoundaryModelMonaghanKajtar{ELTYPE <: Real, VECTOR, V} K :: ELTYPE beta :: ELTYPE boundary_particle_spacing :: ELTYPE - hydrodynamic_mass :: Vector{ELTYPE} + hydrodynamic_mass :: VECTOR # Vector{ELTYPE} viscosity :: V +end - function BoundaryModelMonaghanKajtar(K, beta, boundary_particle_spacing, mass; - viscosity=nothing) - return new{typeof(K), typeof(viscosity)}(K, beta, boundary_particle_spacing, mass, - viscosity) - end +# The default constructor needs to be accessible for Adapt.jl to work with this struct. +# See the comments in general/gpu.jl for more details. +function BoundaryModelMonaghanKajtar(K, beta, boundary_particle_spacing, mass; + viscosity=nothing) + return BoundaryModelMonaghanKajtar(K, convert(typeof(K), beta), + boundary_particle_spacing, + mass, viscosity) end function Base.show(io::IO, model::BoundaryModelMonaghanKajtar) diff --git a/src/schemes/boundary/open_boundary/boundary_zones.jl b/src/schemes/boundary/open_boundary/boundary_zones.jl new file mode 100644 index 0000000000..586d565b12 --- /dev/null +++ b/src/schemes/boundary/open_boundary/boundary_zones.jl @@ -0,0 +1,340 @@ +@doc raw""" + InFlow(; plane, flow_direction, density, particle_spacing, + initial_condition=nothing, extrude_geometry=nothing, + open_boundary_layers::Integer) + +Inflow boundary zone for [`OpenBoundarySPHSystem`](@ref). + +The specified plane (line in 2D or rectangle in 3D) will be extruded in upstream +direction (the direction opposite to `flow_direction`) to create a box for the boundary zone. +There are three ways to specify the actual shape of the inflow: +1. Don't pass `initial_condition` or `extrude_geometry`. The boundary zone box will then + be filled with inflow particles (default). +2. Specify `extrude_geometry` by passing a 1D shape in 2D or a 2D shape in 3D, + which is then extruded in upstream direction to create the inflow particles. + - In 2D, the shape must be either an initial condition with 2D coordinates, which lies + on the line specified by `plane`, or an initial condition with 1D coordinates, which lies + on the line specified by `plane` when a y-coordinate of `0` is added. + - In 3D, the shape must be either an initial condition with 3D coordinates, which lies + in the rectangle specified by `plane`, or an initial condition with 2D coordinates, + which lies in the rectangle specified by `plane` when a z-coordinate of `0` is added. +3. Specify `initial_condition` by passing a 2D initial condition in 2D or a 3D initial condition in 3D, + which will be used for the inflow particles. + +!!! note "Note" + Particles outside the boundary zone box will be removed. + +# Keywords +- `plane`: Tuple of points defining a part of the surface of the domain. + The points must either span a line in 2D or a rectangle in 3D. + This line or rectangle is then extruded in upstream direction to obtain + the boundary zone. + In 2D, pass two points ``(A, B)``, so that the interval ``[A, B]`` is + the inflow surface. + In 3D, pass three points ``(A, B, C)``, so that the rectangular inflow surface + is spanned by the vectors ``\widehat{AB}`` and ``\widehat{AC}``. + These two vectors must be orthogonal. +- `flow_direction`: Vector defining the flow direction. +- `open_boundary_layers`: Number of particle layers in upstream direction. +- `particle_spacing`: The spacing between the particles (see [`InitialCondition`](@ref)). +- `density`: Particle density (see [`InitialCondition`](@ref)). +- `initial_condition=nothing`: `InitialCondition` for the inflow particles. + Particles outside the boundary zone will be removed. + Do not use together with `extrude_geometry`. +- `extrude_geometry=nothing`: 1D shape in 2D or 2D shape in 3D, which lies on the plane + and is extruded upstream to obtain the inflow particles. + See point 2 above for more details. + +# Examples +```julia +# 2D +plane_points = ([0.0, 0.0], [0.0, 1.0]) +flow_direction=[1.0, 0.0] + +inflow = InFlow(; plane=plane_points, particle_spacing=0.1, flow_direction, density=1.0, + open_boundary_layers=4) + +# 3D +plane_points = ([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]) +flow_direction=[0.0, 0.0, 1.0] + +inflow = InFlow(; plane=plane_points, particle_spacing=0.1, flow_direction, density=1.0, + open_boundary_layers=4) + +# 3D particles sampled as cylinder +circle = SphereShape(0.1, 0.5, (0.5, 0.5), 1.0, sphere_type=RoundSphere()) + +inflow = InFlow(; plane=plane_points, particle_spacing=0.1, flow_direction, density=1.0, + extrude_geometry=circle, open_boundary_layers=4) +``` + +!!! warning "Experimental Implementation" + This is an experimental feature and may change in any future releases. +""" +struct InFlow{NDIMS, IC, S, ZO, ZW, FD} + initial_condition :: IC + spanning_set :: S + zone_origin :: ZO + zone_width :: ZW + flow_direction :: FD + + function InFlow(; plane, flow_direction, density, particle_spacing, + initial_condition=nothing, extrude_geometry=nothing, + open_boundary_layers::Integer) + if open_boundary_layers <= 0 + throw(ArgumentError("`open_boundary_layers` must be positive and greater than zero")) + end + + # Unit vector pointing in downstream direction + flow_direction_ = normalize(SVector(flow_direction...)) + + # Sample particles in boundary zone + if isnothing(initial_condition) && isnothing(extrude_geometry) + initial_condition = TrixiParticles.extrude_geometry(plane; particle_spacing, + density, + direction=-flow_direction_, + n_extrude=open_boundary_layers) + elseif !isnothing(extrude_geometry) + initial_condition = TrixiParticles.extrude_geometry(extrude_geometry; + particle_spacing, + density, + direction=-flow_direction_, + n_extrude=open_boundary_layers) + end + + NDIMS = ndims(initial_condition) + ELTYPE = eltype(initial_condition) + + zone_width = open_boundary_layers * initial_condition.particle_spacing + + # Vectors spanning the boundary zone/box + spanning_set, zone_origin = calculate_spanning_vectors(plane, zone_width) + + # First vector of `spanning_vectors` is normal to the inflow plane. + # The normal vector must point in upstream direction for an inflow boundary. + dot_ = dot(normalize(spanning_set[:, 1]), flow_direction_) + + if !isapprox(abs(dot_), 1.0, atol=1e-7) + throw(ArgumentError("`flow_direction` is not normal to inflow plane")) + else + # Flip the normal vector to point in the opposite direction of `flow_direction` + spanning_set[:, 1] .*= -sign(dot_) + end + + spanning_set_ = reinterpret(reshape, SVector{NDIMS, ELTYPE}, spanning_set) + + # Remove particles outside the boundary zone. + # This check is only necessary when `initial_condition` or `extrude_geometry` are passed. + ic = remove_outside_particles(initial_condition, spanning_set_, zone_origin) + + return new{NDIMS, typeof(ic), typeof(spanning_set_), typeof(zone_origin), + typeof(zone_width), + typeof(flow_direction_)}(ic, spanning_set_, zone_origin, zone_width, + flow_direction_) + end +end + +@doc raw""" + OutFlow(; plane, flow_direction, density, particle_spacing, + initial_condition=nothing, extrude_geometry=nothing, + open_boundary_layers::Integer) + +Outflow boundary zone for [`OpenBoundarySPHSystem`](@ref). + +The specified plane (line in 2D or rectangle in 3D) will be extruded in downstream +direction (the direction in `flow_direction`) to create a box for the boundary zone. +There are three ways to specify the actual shape of the outflow: +1. Don't pass `initial_condition` or `extrude_geometry`. The boundary zone box will then + be filled with outflow particles (default). +2. Specify `extrude_geometry` by passing a 1D shape in 2D or a 2D shape in 3D, + which is then extruded in downstream direction to create the outflow particles. + - In 2D, the shape must be either an initial condition with 2D coordinates, which lies + on the line specified by `plane`, or an initial condition with 1D coordinates, which lies + on the line specified by `plane` when a y-coordinate of `0` is added. + - In 3D, the shape must be either an initial condition with 3D coordinates, which lies + in the rectangle specified by `plane`, or an initial condition with 2D coordinates, + which lies in the rectangle specified by `plane` when a z-coordinate of `0` is added. +3. Specify `initial_condition` by passing a 2D initial condition in 2D or a 3D initial condition in 3D, + which will be used for the outflow particles. + +!!! note "Note" + Particles outside the boundary zone box will be removed. + +# Keywords +- `plane`: Tuple of points defining a part of the surface of the domain. + The points must either span a line in 2D or a rectangle in 3D. + This line or rectangle is then extruded in downstream direction to obtain + the boundary zone. + In 2D, pass two points ``(A, B)``, so that the interval ``[A, B]`` is + the outflow surface. + In 3D, pass three points ``(A, B, C)``, so that the rectangular outflow surface + is spanned by the vectors ``\widehat{AB}`` and ``\widehat{AC}``. + These two vectors must be orthogonal. +- `flow_direction`: Vector defining the flow direction. +- `open_boundary_layers`: Number of particle layers in downstream direction. +- `particle_spacing`: The spacing between the particles (see [`InitialCondition`](@ref)). +- `density`: Particle density (see [`InitialCondition`](@ref)). +- `initial_condition=nothing`: `InitialCondition` for the outflow particles. + Particles outside the boundary zone will be removed. + Do not use together with `extrude_geometry`. +- `extrude_geometry=nothing`: 1D shape in 2D or 2D shape in 3D, which lies on the plane + and is extruded downstream to obtain the outflow particles. + See point 2 above for more details. + +# Examples +```julia +# 2D +plane_points = ([0.0, 0.0], [0.0, 1.0]) +flow_direction = [1.0, 0.0] + +outflow = OutFlow(; plane=plane_points, particle_spacing=0.1, flow_direction, density=1.0, + open_boundary_layers=4) + +# 3D +plane_points = ([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]) +flow_direction = [0.0, 0.0, 1.0] + +outflow = OutFlow(; plane=plane_points, particle_spacing=0.1, flow_direction, density=1.0, + open_boundary_layers=4) + +# 3D particles sampled as cylinder +circle = SphereShape(0.1, 0.5, (0.5, 0.5), 1.0, sphere_type=RoundSphere()) + +outflow = OutFlow(; plane=plane_points, particle_spacing=0.1, flow_direction, density=1.0, + extrude_geometry=circle, open_boundary_layers=4) +``` + +!!! warning "Experimental Implementation" + This is an experimental feature and may change in any future releases. +""" +struct OutFlow{NDIMS, IC, S, ZO, ZW, FD} + initial_condition :: IC + spanning_set :: S + zone_origin :: ZO + zone_width :: ZW + flow_direction :: FD + + function OutFlow(; plane, flow_direction, density, particle_spacing, + initial_condition=nothing, extrude_geometry=nothing, + open_boundary_layers::Integer) + if open_boundary_layers <= 0 + throw(ArgumentError("`open_boundary_layers` must be positive and greater than zero")) + end + + # Unit vector pointing in downstream direction + flow_direction_ = normalize(SVector(flow_direction...)) + + # Sample particles in boundary zone + if isnothing(initial_condition) && isnothing(extrude_geometry) + initial_condition = TrixiParticles.extrude_geometry(plane; particle_spacing, + density, + direction=flow_direction_, + n_extrude=open_boundary_layers) + elseif !isnothing(extrude_geometry) + initial_condition = TrixiParticles.extrude_geometry(extrude_geometry; + particle_spacing, density, + direction=-flow_direction_, + n_extrude=open_boundary_layers) + end + + NDIMS = ndims(initial_condition) + ELTYPE = eltype(initial_condition) + + zone_width = open_boundary_layers * initial_condition.particle_spacing + + # Vectors spanning the boundary zone/box + spanning_set, zone_origin = calculate_spanning_vectors(plane, zone_width) + + # First vector of `spanning_vectors` is normal to the outflow plane. + # The normal vector must point in downstream direction for an outflow boundary. + dot_ = dot(normalize(spanning_set[:, 1]), flow_direction_) + + if !isapprox(abs(dot_), 1.0, atol=1e-7) + throw(ArgumentError("`flow_direction` is not normal to outflow plane")) + else + # Flip the normal vector to point in `flow_direction` + spanning_set[:, 1] .*= sign(dot_) + end + + spanning_set_ = reinterpret(reshape, SVector{NDIMS, ELTYPE}, spanning_set) + + # Remove particles outside the boundary zone. + # This check is only necessary when `initial_condition` or `extrude_geometry` are passed. + ic = remove_outside_particles(initial_condition, spanning_set_, zone_origin) + + return new{NDIMS, typeof(ic), typeof(spanning_set_), typeof(zone_origin), + typeof(zone_width), + typeof(flow_direction_)}(ic, spanning_set_, zone_origin, zone_width, + flow_direction_) + end +end + +@inline Base.ndims(::Union{InFlow{NDIMS}, OutFlow{NDIMS}}) where {NDIMS} = NDIMS + +function calculate_spanning_vectors(plane, zone_width) + return spanning_vectors(Tuple(plane), zone_width), SVector(plane[1]...) +end + +function spanning_vectors(plane_points::NTuple{2}, zone_width) + plane_size = plane_points[2] - plane_points[1] + + # Calculate normal vector of plane + b = normalize([-plane_size[2], plane_size[1]]) * zone_width + + return hcat(b, plane_size) +end + +function spanning_vectors(plane_points::NTuple{3}, zone_width) + # Vectors spanning the plane + edge1 = plane_points[2] - plane_points[1] + edge2 = plane_points[3] - plane_points[1] + + if !isapprox(dot(edge1, edge2), 0.0, atol=1e-7) + throw(ArgumentError("the vectors `AB` and `AC` for the provided points `A`, `B`, `C` must be orthogonal")) + end + + # Calculate normal vector of plane + c = Vector(normalize(cross(edge2, edge1)) * zone_width) + + return hcat(c, edge1, edge2) +end + +@inline function is_in_boundary_zone(boundary_zone::Union{InFlow, OutFlow}, particle_coords) + (; zone_origin, spanning_set) = boundary_zone + particle_position = particle_coords - zone_origin + + return is_in_boundary_zone(spanning_set, particle_position) +end + +@inline function is_in_boundary_zone(spanning_set::AbstractArray, + particle_position::SVector{NDIMS}) where {NDIMS} + for dim in 1:NDIMS + span_dim = spanning_set[dim] + # Checks whether the projection of the particle position + # falls within the range of the zone + if !(0 <= dot(particle_position, span_dim) <= dot(span_dim, span_dim)) + + # Particle is not in boundary zone + return false + end + end + + # Particle is in boundary zone + return true +end + +function remove_outside_particles(initial_condition, spanning_set, zone_origin) + (; coordinates, density, particle_spacing) = initial_condition + + in_zone = trues(nparticles(initial_condition)) + + for particle in eachparticle(initial_condition) + current_position = current_coords(coordinates, initial_condition, particle) + particle_position = current_position - zone_origin + + in_zone[particle] = is_in_boundary_zone(spanning_set, particle_position) + end + + return InitialCondition(; coordinates=coordinates[:, in_zone], density=first(density), + particle_spacing) +end diff --git a/src/schemes/boundary/open_boundary/system.jl b/src/schemes/boundary/open_boundary/system.jl new file mode 100644 index 0000000000..8c580cf44d --- /dev/null +++ b/src/schemes/boundary/open_boundary/system.jl @@ -0,0 +1,483 @@ +@doc raw""" + OpenBoundarySPHSystem(boundary_zone::Union{InFlow, OutFlow}; sound_speed, + fluid_system::FluidSystem, buffer_size::Integer, + reference_velocity=zeros(ndims(boundary_zone)), + reference_pressure=0.0, + reference_density=first(boundary_zone.initial_condition.density)) + +Open boundary system for in- and outflow particles. +These open boundaries use the characteristic variables to propagate the appropriate values +to the outlet or inlet and have been proposed by Lastiwka et al. (2009). For more information +about the method see [description below](@ref method_of_characteristics). + +# Arguments +- `boundary_zone`: Use [`InFlow`](@ref) for an inflow and [`OutFlow`](@ref) for an outflow boundary. + +# Keywords +- `sound_speed`: Speed of sound. +- `fluid_system`: The corresponding fluid system +- `buffer_size`: Number of buffer particles. +- `reference_velocity`: Reference velocity is either a function mapping each particle's coordinates + and time to its velocity, an array where the ``i``-th column holds + the velocity of particle ``i`` or, for a constant fluid velocity, + a vector holding this velocity. Velocity is constant zero by default. +- `reference_pressure`: Reference pressure is either a function mapping each particle's coordinates + and time to its pressure, a vector holding the pressure of each particle, + or a scalar for a constant pressure over all particles. + Pressure is constant zero by default. +- `reference_density`: Reference density is either a function mapping each particle's coordinates + and time to its density, a vector holding the density of each particle, + or a scalar for a constant density over all particles. + Density is the density of the first particle in the initial condition by default. + +!!! warning "Experimental Implementation" + This is an experimental feature and may change in any future releases. +""" +struct OpenBoundarySPHSystem{BZ, NDIMS, ELTYPE <: Real, IC, FS, ARRAY1D, ARRAY2D, RV, RP, + RD, B} <: System{NDIMS, IC} + initial_condition :: IC + fluid_system :: FS + mass :: ARRAY1D # Array{ELTYPE, 1}: [particle] + density :: ARRAY1D # Array{ELTYPE, 1}: [particle] + volume :: ARRAY1D # Array{ELTYPE, 1}: [particle] + pressure :: ARRAY1D # Array{ELTYPE, 1}: [particle] + characteristics :: ARRAY2D # Array{ELTYPE, 2}: [characteristic, particle] + previous_characteristics :: ARRAY2D # Array{ELTYPE, 2}: [characteristic, particle] + sound_speed :: ELTYPE + boundary_zone :: BZ + flow_direction :: SVector{NDIMS, ELTYPE} + reference_velocity :: RV + reference_pressure :: RP + reference_density :: RD + buffer :: B + update_callback_used :: Ref{Bool} + + function OpenBoundarySPHSystem(boundary_zone::Union{InFlow, OutFlow}; sound_speed, + fluid_system::FluidSystem, buffer_size::Integer, + reference_velocity=zeros(ndims(boundary_zone)), + reference_pressure=0.0, + reference_density=first(boundary_zone.initial_condition.density)) + (; initial_condition) = boundary_zone + + buffer = SystemBuffer(nparticles(initial_condition), buffer_size) + + initial_condition = allocate_buffer(initial_condition, buffer) + + NDIMS = ndims(initial_condition) + ELTYPE = eltype(initial_condition) + + if !(reference_velocity isa Function || + (reference_velocity isa Vector && length(reference_velocity) == NDIMS)) + throw(ArgumentError("`reference_velocity` must be either a function mapping " * + "each particle's coordinates and time to its velocity, " * + "an array where the ``i``-th column holds the velocity of particle ``i`` " * + "or, for a constant fluid velocity, a vector of length $NDIMS for a $(NDIMS)D problem holding this velocity")) + else + reference_velocity_ = wrap_reference_function(reference_velocity, Val(NDIMS)) + end + + if !(reference_pressure isa Function || reference_pressure isa Real) + throw(ArgumentError("`reference_pressure` must be either a function mapping " * + "each particle's coordinates and time to its pressure, " * + "a vector holding the pressure of each particle, or a scalar")) + else + reference_pressure_ = wrap_reference_function(reference_pressure, Val(NDIMS)) + end + + if !(reference_density isa Function || reference_density isa Real) + throw(ArgumentError("`reference_density` must be either a function mapping " * + "each particle's coordinates and time to its density, " * + "a vector holding the density of each particle, or a scalar")) + else + reference_density_ = wrap_reference_function(reference_density, Val(NDIMS)) + end + + mass = copy(initial_condition.mass) + pressure = [reference_pressure_(initial_condition.coordinates[:, i], 0.0) + for i in eachparticle(initial_condition)] + density = copy(initial_condition.density) + volume = similar(initial_condition.density) + + characteristics = zeros(ELTYPE, 3, length(mass)) + previous_characteristics = zeros(ELTYPE, 3, length(mass)) + + flow_direction_ = boundary_zone.flow_direction + + return new{typeof(boundary_zone), NDIMS, ELTYPE, typeof(initial_condition), + typeof(fluid_system), typeof(mass), typeof(characteristics), + typeof(reference_velocity_), typeof(reference_pressure_), + typeof(reference_density_), + typeof(buffer)}(initial_condition, fluid_system, mass, density, volume, + pressure, characteristics, previous_characteristics, + sound_speed, boundary_zone, flow_direction_, + reference_velocity_, reference_pressure_, + reference_density_, buffer, false) + end +end + +timer_name(::OpenBoundarySPHSystem) = "open_boundary" +vtkname(system::OpenBoundarySPHSystem) = "open_boundary" + +function Base.show(io::IO, system::OpenBoundarySPHSystem) + @nospecialize system # reduce precompilation time + + print(io, "OpenBoundarySPHSystem{", ndims(system), "}(") + print(io, type2string(system.boundary_zone)) + print(io, ") with ", nparticles(system), " particles") +end + +function Base.show(io::IO, ::MIME"text/plain", system::OpenBoundarySPHSystem) + @nospecialize system # reduce precompilation time + + if get(io, :compact, false) + show(io, system) + else + summary_header(io, "OpenBoundarySPHSystem{$(ndims(system))}") + summary_line(io, "#particles", nparticles(system)) + summary_line(io, "#buffer_particles", system.buffer.buffer_size) + summary_line(io, "fluid system", type2string(system.fluid_system)) + summary_line(io, "boundary", type2string(system.boundary_zone)) + summary_line(io, "flow direction", system.flow_direction) + summary_line(io, "prescribed velocity", string(nameof(system.reference_velocity))) + summary_line(io, "prescribed pressure", string(nameof(system.reference_pressure))) + summary_line(io, "prescribed density", string(nameof(system.reference_density))) + summary_line(io, "width", round(system.boundary_zone.zone_width, digits=3)) + summary_footer(io) + end +end + +function reset_callback_flag!(system::OpenBoundarySPHSystem) + system.update_callback_used[] = false + + return system +end + +update_callback_used!(system::OpenBoundarySPHSystem) = system.update_callback_used[] = true + +@inline source_terms(system::OpenBoundarySPHSystem) = nothing + +@inline hydrodynamic_mass(system::OpenBoundarySPHSystem, particle) = system.mass[particle] + +@inline function particle_density(v, system::OpenBoundarySPHSystem, particle) + return system.density[particle] +end + +@inline function particle_pressure(v, system::OpenBoundarySPHSystem, particle) + return system.pressure[particle] +end + +@inline function update_quantities!(system::OpenBoundarySPHSystem, v, u, t) + (; density, pressure, characteristics, flow_direction, sound_speed, + reference_velocity, reference_pressure, reference_density) = system + + # Update quantities based on the characteristic variables + @threaded system for particle in each_moving_particle(system) + particle_position = current_coords(u, system, particle) + + J1 = characteristics[1, particle] + J2 = characteristics[2, particle] + J3 = characteristics[3, particle] + + rho_ref = reference_density(particle_position, t) + density[particle] = rho_ref + ((-J1 + 0.5 * (J2 + J3)) / sound_speed^2) + + p_ref = reference_pressure(particle_position, t) + pressure[particle] = p_ref + 0.5 * (J2 + J3) + + v_ref = reference_velocity(particle_position, t) + rho = density[particle] + v_ = v_ref + ((J2 - J3) / (2 * sound_speed * rho)) * flow_direction + + for dim in 1:ndims(system) + v[dim, particle] = v_[dim] + end + end + + return system +end + +function update_final!(system::OpenBoundarySPHSystem, v, u, v_ode, u_ode, semi, t; + update_from_callback=false) + if !update_from_callback && !(system.update_callback_used[]) + throw(ArgumentError("`UpdateCallback` is required when using `OpenBoundarySPHSystem`")) + end + + @trixi_timeit timer() "evaluate characteristics" evaluate_characteristics!(system, v, u, + v_ode, u_ode, + semi, t) +end + +# ==== Characteristics +# J1: Associated with convection and entropy and propagates at flow velocity. +# J2: Propagates downstream to the local flow +# J3: Propagates upstream to the local flow +function evaluate_characteristics!(system, v, u, v_ode, u_ode, semi, t) + (; volume, characteristics, previous_characteristics, boundary_zone) = system + + for particle in eachparticle(system) + previous_characteristics[1, particle] = characteristics[1, particle] + previous_characteristics[2, particle] = characteristics[2, particle] + previous_characteristics[3, particle] = characteristics[3, particle] + end + + set_zero!(characteristics) + set_zero!(volume) + + # Evaluate the characteristic variables with the fluid system + evaluate_characteristics!(system, system.fluid_system, v, u, v_ode, u_ode, semi, t) + + # Only some of the in-/outlet particles are in the influence of the fluid particles. + # Thus, we compute the characteristics for the particles that are outside the influence + # of fluid particles by using the average of the values of the previous time step. + # See eq. 27 in Negi (2020) https://doi.org/10.1016/j.cma.2020.113119 + @threaded system for particle in each_moving_particle(system) + + # Particle is outside of the influence of fluid particles + if isapprox(volume[particle], 0.0) + + # Using the average of the values at the previous time step for particles which + # are outside of the influence of fluid particles. + avg_J1 = 0.0 + avg_J2 = 0.0 + avg_J3 = 0.0 + counter = 0 + + for neighbor in each_moving_particle(system) + # Make sure that only neighbors in the influence of + # the fluid particles are used. + if volume[neighbor] > sqrt(eps()) + avg_J1 += previous_characteristics[1, neighbor] + avg_J2 += previous_characteristics[2, neighbor] + avg_J3 += previous_characteristics[3, neighbor] + counter += 1 + end + end + + characteristics[1, particle] = avg_J1 / counter + characteristics[2, particle] = avg_J2 / counter + characteristics[3, particle] = avg_J3 / counter + else + characteristics[1, particle] /= volume[particle] + characteristics[2, particle] /= volume[particle] + characteristics[3, particle] /= volume[particle] + end + prescribe_conditions!(characteristics, particle, boundary_zone) + end + + return system +end + +function evaluate_characteristics!(system, neighbor_system::FluidSystem, + v, u, v_ode, u_ode, semi, t) + (; volume, sound_speed, characteristics, flow_direction, + reference_velocity, reference_pressure, reference_density) = system + + v_neighbor_system = wrap_v(v_ode, neighbor_system, semi) + u_neighbor_system = wrap_u(u_ode, neighbor_system, semi) + + nhs = get_neighborhood_search(system, neighbor_system, semi) + + system_coords = current_coordinates(u, system) + neighbor_coords = current_coordinates(u_neighbor_system, neighbor_system) + + # Loop over all fluid neighbors within the kernel cutoff + for_particle_neighbor(system, neighbor_system, system_coords, neighbor_coords, + nhs) do particle, neighbor, pos_diff, distance + neighbor_position = current_coords(u_neighbor_system, neighbor_system, neighbor) + + # Determine current and prescribed quantities + rho_b = particle_density(v_neighbor_system, neighbor_system, neighbor) + rho_ref = reference_density(neighbor_position, t) + + p_b = particle_pressure(v_neighbor_system, neighbor_system, neighbor) + p_ref = reference_pressure(neighbor_position, t) + + v_b = current_velocity(v_neighbor_system, neighbor_system, neighbor) + v_neighbor_ref = reference_velocity(neighbor_position, t) + + # Determine characteristic variables + density_term = -sound_speed^2 * (rho_b - rho_ref) + pressure_term = p_b - p_ref + velocity_term = rho_b * sound_speed * (dot(v_b - v_neighbor_ref, flow_direction)) + + kernel_ = smoothing_kernel(neighbor_system, distance) + + characteristics[1, particle] += (density_term + pressure_term) * kernel_ + characteristics[2, particle] += (velocity_term + pressure_term) * kernel_ + characteristics[3, particle] += (-velocity_term + pressure_term) * kernel_ + + volume[particle] += kernel_ + end + + return system +end + +@inline function prescribe_conditions!(characteristics, particle, ::OutFlow) + # J3 is prescribed (i.e. determined from the exterior of the domain). + # J1 and J2 is transimtted from the domain interior. + characteristics[3, particle] = zero(eltype(characteristics)) + + return characteristics +end + +@inline function prescribe_conditions!(characteristics, particle, ::InFlow) + # Allow only J3 to propagate upstream to the boundary + characteristics[1, particle] = zero(eltype(characteristics)) + characteristics[2, particle] = zero(eltype(characteristics)) + + return characteristics +end + +# This function is called by the `UpdateCallback`, as the integrator array might be modified +function update_open_boundary_eachstep!(system::OpenBoundarySPHSystem, v_ode, u_ode, + semi, t) + u = wrap_u(u_ode, system, semi) + v = wrap_v(v_ode, system, semi) + + # Update density, pressure and velocity based on the characteristic variables. + # See eq. 13-15 in Lastiwka (2009) https://doi.org/10.1002/fld.1971 + @trixi_timeit timer() "update quantities" update_quantities!(system, v, u, t) + + @trixi_timeit timer() "check domain" check_domain!(system, v, u, v_ode, u_ode, semi) + + # Update buffers + update_system_buffer!(system.buffer) + update_system_buffer!(system.fluid_system.buffer) +end + +update_open_boundary_eachstep!(system, v_ode, u_ode, semi, t) = system + +function check_domain!(system, v, u, v_ode, u_ode, semi) + (; boundary_zone, fluid_system) = system + + u_fluid = wrap_u(u_ode, fluid_system, semi) + v_fluid = wrap_v(v_ode, fluid_system, semi) + + neighborhood_search = get_neighborhood_search(system, fluid_system, semi) + + for particle in each_moving_particle(system) + particle_coords = current_coords(u, system, particle) + + # Check if boundary particle is outside the boundary zone + if !is_in_boundary_zone(boundary_zone, particle_coords) + convert_particle!(system, fluid_system, boundary_zone, particle, + v, u, v_fluid, u_fluid) + end + + # Check the neighboring fluid particles whether they're entering the boundary zone + for neighbor in PointNeighbors.eachneighbor(particle_coords, neighborhood_search) + fluid_coords = current_coords(u_fluid, fluid_system, neighbor) + + # Check if neighboring fluid particle is in boundary zone + if is_in_boundary_zone(boundary_zone, fluid_coords) + convert_particle!(fluid_system, system, boundary_zone, neighbor, + v, u, v_fluid, u_fluid) + end + end + end + + return system +end + +# Outflow particle is outside the boundary zone +@inline function convert_particle!(system::OpenBoundarySPHSystem, fluid_system, + boundary_zone::OutFlow, particle, v, u, + v_fluid, u_fluid) + deactivate_particle!(system, particle, u) + + return system +end + +# Inflow particle is outside the boundary zone +@inline function convert_particle!(system::OpenBoundarySPHSystem, fluid_system, + boundary_zone::InFlow, particle, v, u, + v_fluid, u_fluid) + (; spanning_set) = boundary_zone + + # Activate a new particle in simulation domain + transfer_particle!(fluid_system, system, particle, v_fluid, u_fluid, v, u) + + # Reset position of boundary particle + for dim in 1:ndims(system) + u[dim, particle] += spanning_set[1][dim] + end + + return system +end + +# Fluid particle is in boundary zone +@inline function convert_particle!(fluid_system::FluidSystem, system, + boundary_zone, particle, v, u, v_fluid, u_fluid) + # Activate particle in boundary zone + transfer_particle!(system, fluid_system, particle, v, u, v_fluid, u_fluid) + + # Deactivate particle in interior domain + deactivate_particle!(fluid_system, particle, u_fluid) + + return fluid_system +end + +@inline function transfer_particle!(system_new, system_old, particle_old, + v_new, u_new, v_old, u_old) + particle_new = activate_next_particle(system_new) + + # Transfer densities + density = particle_density(v_old, system_old, particle_old) + set_particle_density!(v_new, system_new, particle_new, density) + + # Transfer pressure + pressure = particle_pressure(v_old, system_old, particle_old) + set_particle_pressure!(v_new, system_new, particle_new, pressure) + + # Exchange position and velocity + for dim in 1:ndims(system_new) + u_new[dim, particle_new] = u_old[dim, particle_old] + v_new[dim, particle_new] = v_old[dim, particle_old] + end + + # TODO: Only when using TVF: set tvf + + return system_new +end + +function write_v0!(v0, system::OpenBoundarySPHSystem) + (; initial_condition) = system + + for particle in eachparticle(system) + # Write particle velocities + for dim in 1:ndims(system) + v0[dim, particle] = initial_condition.velocity[dim, particle] + end + end + + return v0 +end + +function write_u0!(u0, system::OpenBoundarySPHSystem) + (; initial_condition) = system + + for particle in eachparticle(system) + # Write particle velocities + for dim in 1:ndims(system) + u0[dim, particle] = initial_condition.coordinates[dim, particle] + end + end + + return u0 +end + +function wrap_reference_function(function_::Function, ::Val) + # Already a function + return function_ +end + +# Name the function so that the summary box does know which kind of function this is +function wrap_reference_function(constant_scalar_::Number, ::Val) + return constant_scalar(coords, t) = constant_scalar_ +end + +# For vectors and tuples +# Name the function so that the summary box does know which kind of function this is +function wrap_reference_function(constant_vector_, ::Val{NDIMS}) where {NDIMS} + return constant_vector(coords, t) = SVector{NDIMS}(constant_vector_) +end diff --git a/src/schemes/boundary/rhs.jl b/src/schemes/boundary/rhs.jl index 965e550621..d9cfb17798 100644 --- a/src/schemes/boundary/rhs.jl +++ b/src/schemes/boundary/rhs.jl @@ -1,7 +1,8 @@ -# Interaction of boundary with other systems +# Interaction of boundary with other systems function interact!(dv, v_particle_system, u_particle_system, v_neighbor_system, u_neighbor_system, neighborhood_search, - particle_system::BoundarySPHSystem, neighbor_system) + particle_system::Union{BoundarySystem, OpenBoundarySPHSystem}, + neighbor_system) # TODO Solids and moving boundaries should be considered in the continuity equation return dv end diff --git a/src/schemes/boundary/system.jl b/src/schemes/boundary/system.jl index b30d8fc416..f36d515ace 100644 --- a/src/schemes/boundary/system.jl +++ b/src/schemes/boundary/system.jl @@ -1,5 +1,5 @@ """ - BoundarySPHSystem(initial_condition, boundary_model; movement=nothing) + BoundarySPHSystem(initial_condition, boundary_model; movement=nothing, adhesion_coefficient=0.0) System for boundaries modeled by boundary particles. The interaction between fluid and boundary particles is specified by the boundary model. @@ -10,32 +10,100 @@ The interaction between fluid and boundary particles is specified by the boundar # Keyword Arguments - `movement`: For moving boundaries, a [`BoundaryMovement`](@ref) can be passed. +- `adhesion_coefficient`: Coefficient specifying the adhesion of a fluid to the surface. + Note: currently it is assumed that all fluids have the same adhesion coefficient. """ -struct BoundarySPHSystem{BM, NDIMS, ELTYPE <: Real, M, C} <: BoundarySystem{NDIMS} - initial_condition :: InitialCondition{ELTYPE} - coordinates :: Array{ELTYPE, 2} - boundary_model :: BM - movement :: M - ismoving :: Vector{Bool} - cache :: C - - function BoundarySPHSystem(initial_condition, model; movement=nothing) - coordinates = copy(initial_condition.coordinates) +struct BoundarySPHSystem{BM, NDIMS, ELTYPE <: Real, IC, CO, M, IM, + CA} <: BoundarySystem{NDIMS, IC} + initial_condition :: IC + coordinates :: CO # Array{ELTYPE, 2} + boundary_model :: BM + movement :: M + ismoving :: IM # Ref{Bool} (to make a mutable field compatible with GPUs) + adhesion_coefficient :: ELTYPE + cache :: CA + buffer :: Nothing + + # This constructor is necessary for Adapt.jl to work with this struct. + # See the comments in general/gpu.jl for more details. + function BoundarySPHSystem(initial_condition, coordinates, boundary_model, movement, + ismoving, adhesion_coefficient, cache, buffer) + ELTYPE = eltype(coordinates) + + new{typeof(boundary_model), size(coordinates, 1), ELTYPE, typeof(initial_condition), + typeof(coordinates), typeof(movement), typeof(ismoving), + typeof(cache)}(initial_condition, coordinates, boundary_model, movement, + ismoving, adhesion_coefficient, cache, buffer) + end +end + +function BoundarySPHSystem(initial_condition, model; movement=nothing, + adhesion_coefficient=0.0) + coordinates = copy(initial_condition.coordinates) + + ismoving = Ref(!isnothing(movement)) + + cache = create_cache_boundary(movement, initial_condition) + + if movement !== nothing && isempty(movement.moving_particles) + # Default is an empty vector, since the number of particles is not known when + # instantiating `BoundaryMovement`. + resize!(movement.moving_particles, nparticles(initial_condition)) + movement.moving_particles .= collect(1:nparticles(initial_condition)) + end + + # Because of dispatches boundary model needs to be first! + return BoundarySPHSystem(initial_condition, coordinates, model, movement, + ismoving, adhesion_coefficient, cache, nothing) +end + +""" + BoundaryDEMSystem(initial_condition, normal_stiffness) + +System for boundaries modeled by boundary particles. +The interaction between fluid and boundary particles is specified by the boundary model. + +!!! warning "Experimental Implementation" + This is an experimental feature and may change in a future releases. + +""" +struct BoundaryDEMSystem{NDIMS, ELTYPE <: Real, IC, + ARRAY1D, ARRAY2D} <: BoundarySystem{NDIMS, IC} + initial_condition :: IC + coordinates :: ARRAY2D # [dimension, particle] + radius :: ARRAY1D # [particle] + normal_stiffness :: ELTYPE + buffer :: Nothing + + function BoundaryDEMSystem(initial_condition, normal_stiffness) + coordinates = initial_condition.coordinates + radius = 0.5 * initial_condition.particle_spacing * + ones(length(initial_condition.mass)) NDIMS = size(coordinates, 1) - ismoving = zeros(Bool, 1) - cache = create_cache_boundary(movement, initial_condition) + return new{NDIMS, eltype(coordinates), typeof(initial_condition), typeof(radius), + typeof(coordinates)}(initial_condition, coordinates, radius, + normal_stiffness, nothing) + end +end - if movement !== nothing && isempty(movement.moving_particles) - # Default is an empty vector, since the number of particles is not known when - # instantiating `BoundaryMovement`. - resize!(movement.moving_particles, nparticles(initial_condition)) - movement.moving_particles .= collect(1:nparticles(initial_condition)) - end +function Base.show(io::IO, system::BoundaryDEMSystem) + @nospecialize system # reduce precompilation time - return new{typeof(model), NDIMS, eltype(coordinates), typeof(movement), - typeof(cache)}(initial_condition, coordinates, model, movement, - ismoving, cache) + print(io, "BoundaryDEMSystem{", ndims(system), "}(") + print(io, system.boundary_model) + print(io, ") with ", nparticles(system), " particles") +end + +function Base.show(io::IO, ::MIME"text/plain", system::BoundaryDEMSystem) + @nospecialize system # reduce precompilation time + + if get(io, :compact, false) + show(io, system) + else + summary_header(io, "BoundaryDEMSystem{$(ndims(system))}") + summary_line(io, "#particles", nparticles(system)) + summary_footer(io) end end @@ -99,6 +167,7 @@ function Base.show(io::IO, system::BoundarySPHSystem) print(io, "BoundarySPHSystem{", ndims(system), "}(") print(io, system.boundary_model) print(io, ", ", system.movement) + print(io, ", ", system.adhesion_coefficient) print(io, ") with ", nparticles(system), " particles") end @@ -114,22 +183,33 @@ function Base.show(io::IO, ::MIME"text/plain", system::BoundarySPHSystem) summary_line(io, "movement function", isnothing(system.movement) ? "nothing" : string(system.movement.movement_function)) + summary_line(io, "adhesion coefficient", system.adhesion_coefficient) summary_footer(io) end end -timer_name(::BoundarySPHSystem) = "boundary" +timer_name(::Union{BoundarySPHSystem, BoundaryDEMSystem}) = "boundary" + +@inline function Base.eltype(system::Union{BoundarySPHSystem, BoundaryDEMSystem}) + eltype(system.coordinates) +end + +# This does not account for moving boundaries, but it's only used to initialize the +# neighborhood search, anyway. +@inline function initial_coordinates(system::Union{BoundarySPHSystem, BoundaryDEMSystem}) + system.coordinates +end function (movement::BoundaryMovement)(system, t) (; coordinates, cache) = system (; movement_function, is_moving, moving_particles) = movement (; acceleration, velocity) = cache - system.ismoving[1] = is_moving(t) + system.ismoving[] = is_moving(t) is_moving(t) || return system - @threaded for particle in moving_particles + @threaded system for particle in moving_particles pos_new = initial_coords(system, particle) + movement_function(t) vel = ForwardDiff.derivative(movement_function, t) acc = ForwardDiff.derivative(t_ -> ForwardDiff.derivative(movement_function, t_), t) @@ -145,18 +225,18 @@ function (movement::BoundaryMovement)(system, t) end function (movement::Nothing)(system, t) - system.ismoving[1] = false + system.ismoving[] = false return system end -@inline function nparticles(system::BoundarySPHSystem) - length(system.boundary_model.hydrodynamic_mass) +@inline function nparticles(system::Union{BoundaryDEMSystem, BoundarySPHSystem}) + size(system.coordinates, 2) end # No particle positions are advanced for boundary systems, # except when using `BoundaryModelDummyParticles` with `ContinuityDensity`. -@inline function n_moving_particles(system::BoundarySPHSystem) +@inline function n_moving_particles(system::Union{BoundarySPHSystem, BoundaryDEMSystem}) return 0 end @@ -164,34 +244,51 @@ end return nparticles(system) end -@inline u_nvariables(system::BoundarySPHSystem) = 0 +@inline u_nvariables(system::Union{BoundarySPHSystem, BoundaryDEMSystem}) = 0 # For BoundaryModelDummyParticles with ContinuityDensity, this needs to be 1. # For all other models and density calculators, it's irrelevant. @inline v_nvariables(system::BoundarySPHSystem) = 1 +@inline v_nvariables(system::BoundaryDEMSystem) = 0 -@inline function current_coordinates(u, system::BoundarySPHSystem) +@inline function current_coordinates(u, system::Union{BoundarySPHSystem, BoundaryDEMSystem}) return system.coordinates end @inline function current_velocity(v, system::BoundarySPHSystem, particle) + return current_velocity(v, system, system.movement, particle) +end + +@inline function current_velocity(v, system, movement, particle) (; cache, ismoving) = system - if ismoving[1] + if ismoving[] return extract_svector(cache.velocity, system, particle) end - return SVector(ntuple(_ -> 0.0, Val(ndims(system)))) + return zero(SVector{ndims(system), eltype(system)}) +end + +@inline function current_velocity(v, system, movement::Nothing, particle) + return zero(SVector{ndims(system), eltype(system)}) end @inline function current_acceleration(system::BoundarySPHSystem, particle) + return current_acceleration(system, system.movement, particle) +end + +@inline function current_acceleration(system, movement, particle) (; cache, ismoving) = system - if ismoving[1] + if ismoving[] return extract_svector(cache.acceleration, system, particle) end - return SVector(ntuple(_ -> 0.0, Val(ndims(system)))) + return zero(SVector{ndims(system), eltype(system)}) +end + +@inline function current_acceleration(system, movement::Nothing, particle) + return zero(SVector{ndims(system), eltype(system)}) end @inline function viscous_velocity(v, system::BoundarySPHSystem, particle) @@ -239,7 +336,8 @@ end # This update depends on the computed quantities of the fluid system and therefore # has to be in `update_final!` after `update_quantities!`. -function update_final!(system::BoundarySPHSystem, v, u, v_ode, u_ode, semi, t) +function update_final!(system::BoundarySPHSystem, v, u, v_ode, u_ode, semi, t; + update_from_callback=false) (; boundary_model) = system update_pressure!(boundary_model, system, v, u, v_ode, u_ode, semi) @@ -247,11 +345,13 @@ function update_final!(system::BoundarySPHSystem, v, u, v_ode, u_ode, semi, t) return system end -function write_u0!(u0, system::BoundarySPHSystem) +function write_u0!(u0, system::Union{BoundarySPHSystem, BoundaryDEMSystem}) return u0 end -function write_v0!(v0, system::BoundarySPHSystem) +function write_v0!(v0, + system::Union{BoundarySPHSystem, + BoundaryDEMSystem}) return v0 end diff --git a/src/schemes/fluid/entropically_damped_sph/rhs.jl b/src/schemes/fluid/entropically_damped_sph/rhs.jl index 16ca94ec08..57bbdc16d5 100644 --- a/src/schemes/fluid/entropically_damped_sph/rhs.jl +++ b/src/schemes/fluid/entropically_damped_sph/rhs.jl @@ -17,7 +17,6 @@ function interact!(dv, v_particle_system, u_particle_system, rho_a = particle_density(v_particle_system, particle_system, particle) rho_b = particle_density(v_neighbor_system, neighbor_system, neighbor) - rho_mean = 0.5 * (rho_a + rho_b) p_a = particle_pressure(v_particle_system, particle_system, particle) p_b = particle_pressure(v_neighbor_system, neighbor_system, neighbor) @@ -34,7 +33,7 @@ function interact!(dv, v_particle_system, u_particle_system, dv_viscosity_ = dv_viscosity(particle_system, neighbor_system, v_particle_system, v_neighbor_system, particle, neighbor, pos_diff, distance, - sound_speed, m_a, m_b, rho_mean) + sound_speed, m_a, m_b, rho_a, rho_b, grad_kernel) for i in 1:ndims(particle_system) dv[i, particle] += dv_pressure[i] + dv_viscosity_[i] diff --git a/src/schemes/fluid/entropically_damped_sph/system.jl b/src/schemes/fluid/entropically_damped_sph/system.jl index bfa07c0510..4093e8ccb4 100644 --- a/src/schemes/fluid/entropically_damped_sph/system.jl +++ b/src/schemes/fluid/entropically_damped_sph/system.jl @@ -4,7 +4,7 @@ pressure_acceleration=inter_particle_averaged_pressure, density_calculator=SummationDensity(), alpha=0.5, viscosity=nothing, - acceleration=ntuple(_ -> 0.0, NDIMS), + acceleration=ntuple(_ -> 0.0, NDIMS), buffer_size=nothing, source_terms=nothing) System for particles of a fluid. @@ -28,6 +28,8 @@ See [Entropically Damped Artificial Compressibility for SPH](@ref edac) for more When set to `nothing`, the pressure acceleration formulation for the corresponding [density calculator](@ref density_calculator) is chosen. - `density_calculator`: [Density calculator](@ref density_calculator) (default: [`SummationDensity`](@ref)) +- `buffer_size`: Number of buffer particles. + This is needed when simulating with [`OpenBoundarySPHSystem`](@ref). - `source_terms`: Additional source terms for this system. Has to be either `nothing` (by default), or a function of `(coords, velocity, density, pressure)` (which are the quantities of a single particle), returning a `Tuple` @@ -39,10 +41,10 @@ See [Entropically Damped Artificial Compressibility for SPH](@ref edac) for more The keyword argument `acceleration` should be used instead for gravity-like source terms. """ -struct EntropicallyDampedSPHSystem{NDIMS, ELTYPE <: Real, DC, K, V, - PF, ST, C} <: FluidSystem{NDIMS} - initial_condition :: InitialCondition{ELTYPE} - mass :: Array{ELTYPE, 1} # [particle] +struct EntropicallyDampedSPHSystem{NDIMS, ELTYPE <: Real, IC, M, DC, K, V, + PF, ST, B, C} <: FluidSystem{NDIMS, IC} + initial_condition :: IC + mass :: M # Vector{ELTYPE}: [particle] density_calculator :: DC smoothing_kernel :: K smoothing_length :: ELTYPE @@ -53,6 +55,7 @@ struct EntropicallyDampedSPHSystem{NDIMS, ELTYPE <: Real, DC, K, V, correction :: Nothing pressure_acceleration_formulation :: PF source_terms :: ST + buffer :: B cache :: C function EntropicallyDampedSPHSystem(initial_condition, smoothing_kernel, @@ -62,7 +65,12 @@ struct EntropicallyDampedSPHSystem{NDIMS, ELTYPE <: Real, DC, K, V, alpha=0.5, viscosity=nothing, acceleration=ntuple(_ -> 0.0, ndims(smoothing_kernel)), - source_terms=nothing) + source_terms=nothing, buffer_size=nothing) + buffer = isnothing(buffer_size) ? nothing : + SystemBuffer(nparticles(initial_condition), buffer_size) + + initial_condition = allocate_buffer(initial_condition, buffer) + NDIMS = ndims(initial_condition) ELTYPE = eltype(initial_condition) @@ -86,11 +94,14 @@ struct EntropicallyDampedSPHSystem{NDIMS, ELTYPE <: Real, DC, K, V, cache = create_cache_density(initial_condition, density_calculator) - new{NDIMS, ELTYPE, typeof(density_calculator), typeof(smoothing_kernel), + new{NDIMS, ELTYPE, typeof(initial_condition), typeof(mass), + typeof(density_calculator), typeof(smoothing_kernel), typeof(viscosity), typeof(pressure_acceleration), typeof(source_terms), + typeof(buffer), typeof(cache)}(initial_condition, mass, density_calculator, smoothing_kernel, - smoothing_length, sound_speed, viscosity, nu_edac, acceleration_, - nothing, pressure_acceleration, source_terms, cache) + smoothing_length, sound_speed, viscosity, nu_edac, + acceleration_, nothing, pressure_acceleration, source_terms, + buffer, cache) end end @@ -112,7 +123,12 @@ function Base.show(io::IO, ::MIME"text/plain", system::EntropicallyDampedSPHSyst show(io, system) else summary_header(io, "EntropicallyDampedSPHSystem{$(ndims(system))}") - summary_line(io, "#particles", nparticles(system)) + if system.buffer isa SystemBuffer + summary_line(io, "#particles", nparticles(system)) + summary_line(io, "#buffer_particles", system.buffer.buffer_size) + else + summary_line(io, "#particles", nparticles(system)) + end summary_line(io, "density calculator", system.density_calculator |> typeof |> nameof) summary_line(io, "viscosity", system.viscosity |> typeof |> nameof) @@ -144,6 +160,18 @@ end return v[end, particle] end +# WARNING! +# These functions are intended to be used internally to set the pressure +# of newly activated particles in a callback. +# DO NOT use outside a callback. OrdinaryDiffEq does not allow changing `v` and `u` +# outside of callbacks. +@inline function set_particle_pressure!(v, system::EntropicallyDampedSPHSystem, particle, + pressure) + v[end, particle] = pressure + + return v +end + @inline system_sound_speed(system::EntropicallyDampedSPHSystem) = system.sound_speed function update_quantities!(system::EntropicallyDampedSPHSystem, v, u, diff --git a/src/schemes/fluid/fluid.jl b/src/schemes/fluid/fluid.jl index e2f08444d6..68391b2854 100644 --- a/src/schemes/fluid/fluid.jl +++ b/src/schemes/fluid/fluid.jl @@ -1,3 +1,7 @@ +@inline function set_particle_density!(v, system::FluidSystem, particle, density) + set_particle_density!(v, system, system.density_calculator, particle, density) +end + function create_cache_density(initial_condition, ::SummationDensity) density = similar(initial_condition.density) @@ -14,23 +18,17 @@ end function write_u0!(u0, system::FluidSystem) (; initial_condition) = system - for particle in eachparticle(system) - # Write particle coordinates - for dim in 1:ndims(system) - u0[dim, particle] = initial_condition.coordinates[dim, particle] - end - end + # This is as fast as a loop with `@inbounds`, but it's GPU-compatible + indices = CartesianIndices(initial_condition.coordinates) + copyto!(u0, indices, initial_condition.coordinates, indices) return u0 end function write_v0!(v0, system::FluidSystem) - for particle in eachparticle(system) - # Write particle velocities - for dim in 1:ndims(system) - v0[dim, particle] = system.initial_condition.velocity[dim, particle] - end - end + # This is as fast as a loop with `@inbounds`, but it's GPU-compatible + indices = CartesianIndices(system.initial_condition.velocity) + copyto!(v0, indices, system.initial_condition.velocity, indices) write_v0!(v0, system, system.density_calculator) @@ -76,5 +74,6 @@ end include("pressure_acceleration.jl") include("viscosity.jl") +include("surface_tension.jl") include("weakly_compressible_sph/weakly_compressible_sph.jl") include("entropically_damped_sph/entropically_damped_sph.jl") diff --git a/src/schemes/fluid/surface_tension.jl b/src/schemes/fluid/surface_tension.jl new file mode 100644 index 0000000000..69e468cd76 --- /dev/null +++ b/src/schemes/fluid/surface_tension.jl @@ -0,0 +1,206 @@ +abstract type AkinciTypeSurfaceTension end + +@doc raw""" + CohesionForceAkinci(surface_tension_coefficient=1.0) + +This model only implements the cohesion force of the Akinci surface tension model. + +# Keywords +- `surface_tension_coefficient=1.0`: Modifies the intensity of the surface tension-induced force, + enabling the tuning of the fluid's surface tension properties within the simulation. + +# References +- Nadir Akinci, Gizem Akinci, Matthias Teschner. + "Versatile Surface Tension and Adhesion for SPH Fluids". + In: ACM Transactions on Graphics 32.6 (2013). + [doi: 10.1145/2508363.2508395](https://doi.org/10.1145/2508363.2508395) +""" +struct CohesionForceAkinci{ELTYPE} <: AkinciTypeSurfaceTension + surface_tension_coefficient::ELTYPE + + function CohesionForceAkinci(; surface_tension_coefficient=1.0) + new{typeof(surface_tension_coefficient)}(surface_tension_coefficient) + end +end + +@doc raw""" + SurfaceTensionAkinci(surface_tension_coefficient=1.0) + +Implements a model for surface tension and adhesion effects drawing upon the +principles outlined by Akinci et al. This model is instrumental in capturing the nuanced +behaviors of fluid surfaces, such as droplet formation and the dynamics of merging or +separation, by utilizing intra-particle forces. + +# Keywords +- `surface_tension_coefficient=1.0`: A parameter to adjust the magnitude of + surface tension forces, facilitating the fine-tuning of how surface tension phenomena + are represented in the simulation. + +# References +- Nadir Akinci, Gizem Akinci, Matthias Teschner. + "Versatile Surface Tension and Adhesion for SPH Fluids". + In: ACM Transactions on Graphics 32.6 (2013). + [doi: 10.1145/2508363.2508395](https://doi.org/10.1145/2508363.2508395) +""" +struct SurfaceTensionAkinci{ELTYPE} <: AkinciTypeSurfaceTension + surface_tension_coefficient::ELTYPE + + function SurfaceTensionAkinci(; surface_tension_coefficient=1.0) + new{typeof(surface_tension_coefficient)}(surface_tension_coefficient) + end +end + +# Note that `floating_point_number^integer_literal` is lowered to `Base.literal_pow`. +# Currently, specializations reducing this to simple multiplications exist only up +# to a power of three, see +# https://github.com/JuliaLang/julia/blob/34934736fa4dcb30697ac1b23d11d5ad394d6a4d/base/intfuncs.jl#L327-L339 +# By using the `@fastpow` macro, we are consciously trading off some precision in the result +# for enhanced computational speed. This is especially useful in scenarios where performance +# is a higher priority than exact precision. +@fastpow @inline function cohesion_force_akinci(surface_tension, support_radius, m_b, + pos_diff, distance) + (; surface_tension_coefficient) = surface_tension + + # Eq. 2 + # We only reach this function when `sqrt(eps()) < distance <= support_radius` + if distance > 0.5 * support_radius + # Attractive force + C = (support_radius - distance)^3 * distance^3 + else + # `distance < 0.5 * support_radius` + # Repulsive force + C = 2 * (support_radius - distance)^3 * distance^3 - support_radius^6 / 64.0 + end + C *= 32.0 / (pi * support_radius^9) + + # Eq. 1 in acceleration form + cohesion_force = -surface_tension_coefficient * m_b * C * pos_diff / distance + + return cohesion_force +end + +@inline function adhesion_force_akinci(surface_tension, support_radius, m_b, pos_diff, + distance, adhesion_coefficient) + + # The neighborhood search has an `<=` check, but for `distance == support_radius` + # the term inside the parentheses might be very slightly negative, causing an error with `^0.25`. + # TODO Change this in the neighborhood search? + # See https://github.com/trixi-framework/PointNeighbors.jl/issues/19 + distance >= support_radius && return zero(pos_diff) + + distance <= 0.5 * support_radius && return zero(pos_diff) + + # Eq. 7 + A = 0.007 / support_radius^3.25 * + (-4 * distance^2 / support_radius + 6 * distance - 2 * support_radius)^0.25 + + # Eq. 6 in acceleration form with `m_b` being the boundary mass calculated as + # `m_b = rho_0 * volume` (Akinci boundary condition treatment) + adhesion_force = -adhesion_coefficient * m_b * A * pos_diff / distance + + return adhesion_force +end + +# Section 2.2 in Akinci et al. 2013 "Versatile Surface Tension and Adhesion for SPH Fluids" +# Note: most of the time this only leads to an approximation of the surface normal + +function calc_normal_akinci!(system, neighbor_system::FluidSystem, + surface_tension::SurfaceTensionAkinci, u_system, + v_neighbor_system, u_neighbor_system, + neighborhood_search) + (; smoothing_length, cache) = system + + system_coords = current_coordinates(u_system, system) + neighbor_system_coords = current_coordinates(u_neighbor_system, neighbor_system) + + for_particle_neighbor(system, neighbor_system, + system_coords, neighbor_system_coords, + neighborhood_search) do particle, neighbor, pos_diff, distance + m_b = hydrodynamic_mass(neighbor_system, neighbor) + density_neighbor = particle_density(v_neighbor_system, + neighbor_system, neighbor) + grad_kernel = smoothing_kernel_grad(system, pos_diff, distance, + particle) + for i in 1:ndims(system) + cache.surface_normal[i, particle] += m_b / density_neighbor * + grad_kernel[i] * smoothing_length + end + end + + return system +end + +function calc_normal_akinci!(system, neighbor_system, surface_tension, u_system, + v_neighbor_system, u_neighbor_system, + neighborhood_search) + # Normal not needed + return system +end + +@inline function surface_tension_force(surface_tension_a::CohesionForceAkinci, + surface_tension_b::CohesionForceAkinci, + particle_system::FluidSystem, + neighbor_system::FluidSystem, particle, neighbor, + pos_diff, distance) + (; smoothing_length) = particle_system + # No cohesion with oneself + distance < sqrt(eps()) && return zero(pos_diff) + + m_b = hydrodynamic_mass(neighbor_system, neighbor) + support_radius = compact_support(smoothing_kernel, smoothing_length) + + return cohesion_force_akinci(surface_tension_a, support_radius, m_b, pos_diff, distance) +end + +@inline function surface_tension_force(surface_tension_a::SurfaceTensionAkinci, + surface_tension_b::SurfaceTensionAkinci, + particle_system::FluidSystem, + neighbor_system::FluidSystem, particle, neighbor, + pos_diff, distance) + (; smoothing_length, smoothing_kernel) = particle_system + (; surface_tension_coefficient) = surface_tension_a + + # No surface tension with oneself + distance < sqrt(eps()) && return zero(pos_diff) + + m_b = hydrodynamic_mass(neighbor_system, neighbor) + n_a = surface_normal(surface_tension_a, particle_system, particle) + n_b = surface_normal(surface_tension_b, neighbor_system, neighbor) + support_radius = compact_support(smoothing_kernel, smoothing_length) + + return cohesion_force_akinci(surface_tension_a, support_radius, m_b, + pos_diff, distance) .- + (surface_tension_coefficient * (n_a - n_b)) +end + +# Skip +@inline function surface_tension_force(surface_tension_a, surface_tension_b, + particle_system, neighbor_system, particle, neighbor, + pos_diff, distance) + return zero(pos_diff) +end + +@inline function adhesion_force(surface_tension::AkinciTypeSurfaceTension, + particle_system::FluidSystem, + neighbor_system::BoundarySystem, particle, neighbor, + pos_diff, distance) + (; smoothing_length, smoothing_kernel) = particle_system + (; adhesion_coefficient, boundary_model) = neighbor_system + + # No adhesion with oneself + distance < sqrt(eps()) && return zero(pos_diff) + + # No reason to calculate the adhesion force if adhesion coefficient is near zero + abs(adhesion_coefficient) < eps() && return zero(pos_diff) + + m_b = hydrodynamic_mass(neighbor_system, neighbor) + + support_radius = compact_support(smoothing_kernel, smoothing_length) + return adhesion_force_akinci(surface_tension, support_radius, m_b, pos_diff, distance, + adhesion_coefficient) +end + +@inline function adhesion_force(surface_tension, particle_system, neighbor_system, particle, + neighbor, pos_diff, distance) + return zero(pos_diff) +end diff --git a/src/schemes/fluid/viscosity.jl b/src/schemes/fluid/viscosity.jl index 3c0c517a17..75ed3ab09a 100644 --- a/src/schemes/fluid/viscosity.jl +++ b/src/schemes/fluid/viscosity.jl @@ -1,44 +1,49 @@ +# Unpack the neighboring systems viscosity to dispatch on the viscosity type function dv_viscosity(particle_system, neighbor_system, v_particle_system, v_neighbor_system, particle, neighbor, pos_diff, distance, - sound_speed, m_a, m_b, rho_mean) + sound_speed, m_a, m_b, rho_a, rho_b, grad_kernel) viscosity = viscosity_model(neighbor_system) return dv_viscosity(viscosity, particle_system, neighbor_system, v_particle_system, v_neighbor_system, particle, neighbor, pos_diff, distance, - sound_speed, m_a, m_b, rho_mean) + sound_speed, m_a, m_b, rho_a, rho_b, grad_kernel) +end + +function dv_viscosity(particle_system, neighbor_system::OpenBoundarySPHSystem, + v_particle_system, v_neighbor_system, + particle, neighbor, pos_diff, distance, + sound_speed, m_a, m_b, rho_a, rho_b, grad_kernel) + # No viscosity in the open boundary system. Use viscosity of the fluid system. + viscosity = viscosity_model(particle_system) + + return dv_viscosity(viscosity, particle_system, neighbor_system, + v_particle_system, v_neighbor_system, + particle, neighbor, pos_diff, distance, + sound_speed, m_a, m_b, rho_a, rho_b, grad_kernel) end function dv_viscosity(viscosity, particle_system, neighbor_system, v_particle_system, v_neighbor_system, particle, neighbor, pos_diff, distance, - sound_speed, m_a, m_b, rho_mean) + sound_speed, m_a, m_b, rho_a, rho_b, grad_kernel) return viscosity(particle_system, neighbor_system, v_particle_system, v_neighbor_system, particle, neighbor, pos_diff, distance, - sound_speed, m_a, m_b, rho_mean) + sound_speed, m_a, m_b, rho_a, rho_b, grad_kernel) end function dv_viscosity(viscosity::Nothing, particle_system, neighbor_system, v_particle_system, v_neighbor_system, particle, neighbor, pos_diff, distance, - sound_speed, m_a, m_b, rho_mean) - return SVector(ntuple(_ -> 0.0, Val(ndims(particle_system)))) + sound_speed, m_a, m_b, rho_a, rho_b, grad_kernel) + return zero(pos_diff) end @doc raw""" - ArtificialViscosityMonaghan(; alpha, beta, epsilon=0.01) - -# Keywords -- `alpha`: A value of `0.02` is usually used for most simulations. For a relation with the - kinematic viscosity, see description below. -- `beta`: A value of `0.0` works well for simulations with shocks of moderate strength. - In simulations where the Mach number can be very high, eg. astrophysical calculation, - good results can be obtained by choosing a value of `beta=2` and `alpha=1`. -- `epsilon=0.01`: Parameter to prevent singularities. - + ArtificialViscosityMonaghan(; alpha, beta=0.0, epsilon=0.01) Artificial viscosity by Monaghan (Monaghan 1992, Monaghan 1989), given by ```math @@ -64,6 +69,15 @@ To do so, Monaghan (Monaghan 2005) defined an equivalent effective physical kine ``` where ``d`` is the dimension. +# Keywords +- `alpha`: A value of `0.02` is usually used for most simulations. For a relation with the + kinematic viscosity, see description above. +- `beta=0.0`: A value of `0.0` works well for most fluid simulations and simulations + with shocks of moderate strength. In simulations where the Mach number can be + very high, eg. astrophysical calculation, good results can be obtained by + choosing a value of `beta=2.0` and `alpha=1.0`. +- `epsilon=0.01`: Parameter to prevent singularities. + ## References - Joseph J. Monaghan. "Smoothed Particle Hydrodynamics". In: Annual Review of Astronomy and Astrophysics 30.1 (1992), pages 543-574. @@ -80,34 +94,75 @@ struct ArtificialViscosityMonaghan{ELTYPE} beta :: ELTYPE epsilon :: ELTYPE - function ArtificialViscosityMonaghan(; alpha, beta, epsilon=0.01) + function ArtificialViscosityMonaghan(; alpha, beta=0.0, epsilon=0.01) new{typeof(alpha)}(alpha, beta, epsilon) end end -@inline function (viscosity::ArtificialViscosityMonaghan)(particle_system, neighbor_system, - v_particle_system, - v_neighbor_system, - particle, neighbor, pos_diff, - distance, sound_speed, m_a, m_b, - rho_mean) +@doc raw""" + ViscosityMorris(; nu, epsilon=0.01) + +Viscosity by Morris et al. (1997). + +To the force ``f_{ab}`` between two particles ``a`` and ``b`` due to pressure gradients, +an additional force term ``\tilde{f}_{ab}`` is added with +```math +\tilde{f}_{ab} = m_a m_b \frac{(\mu_a + \mu_b) r_{ab} \cdot \nabla W_{ab}}{\rho_a \rho_b (\Vert r_{ab} \Vert^2 + \epsilon h^2)} v_{ab}, +``` +where ``\mu_a = \rho_a \nu`` and ``\mu_b = \rho_b \nu`` denote the dynamic viscosity +of particle ``a`` and ``b`` respectively, and ``\nu`` is the kinematic viscosity. + +# Keywords +- `nu`: Kinematic viscosity +- `epsilon=0.01`: Parameter to prevent singularities + +## References +- Joseph P. Morris, Patrick J. Fox, Yi Zhu. + "Modeling Low Reynolds Number Incompressible Flows Using SPH". + In: Journal of Computational Physics, Volume 136, Issue 1 (1997), pages 214--226. + [doi: doi.org/10.1006/jcph.1997.5776](https://doi.org/10.1006/jcph.1997.5776) +- Georgios Fourtakas, Jose M. Dominguez, Renato Vacondio, Benedict D. Rogers. + "Local uniform stencil (LUST) boundary condition for arbitrary + 3-D boundaries in parallel smoothed particle hydrodynamics (SPH) models". + In: Computers & Fluids, Volume 190 (2019), pages 346--361. + [doi: 10.1016/j.compfluid.2019.06.009](https://doi.org/10.1016/j.compfluid.2019.06.009) +""" +struct ViscosityMorris{ELTYPE} + nu::ELTYPE + epsilon::ELTYPE + + function ViscosityMorris(; nu, epsilon=0.01) + new{typeof(nu)}(nu, epsilon) + end +end + +function kinematic_viscosity(system, viscosity::ViscosityMorris) + return viscosity.nu +end + +@inline function (viscosity::Union{ArtificialViscosityMonaghan, + ViscosityMorris})(particle_system, neighbor_system, + v_particle_system, v_neighbor_system, + particle, neighbor, pos_diff, + distance, sound_speed, m_a, m_b, + rho_a, rho_b, grad_kernel) (; smoothing_length) = particle_system + rho_mean = 0.5 * (rho_a + rho_b) + v_a = viscous_velocity(v_particle_system, particle_system, particle) v_b = viscous_velocity(v_neighbor_system, neighbor_system, neighbor) v_diff = v_a - v_b - pi_ab = viscosity(sound_speed, v_diff, pos_diff, distance, rho_mean, smoothing_length) + pi_ab = viscosity(sound_speed, v_diff, pos_diff, distance, rho_mean, rho_a, rho_b, + smoothing_length, grad_kernel) - if pi_ab < eps() - return SVector(ntuple(_ -> 0.0, Val(ndims(particle_system)))) - end - - return -m_b * pi_ab * smoothing_kernel_grad(particle_system, pos_diff, distance) + return m_b * pi_ab end @inline function (viscosity::ArtificialViscosityMonaghan)(c, v_diff, pos_diff, distance, - rho_mean, h) + rho_mean, rho_a, rho_b, h, + grad_kernel) (; alpha, beta, epsilon) = viscosity # v_ab ⋅ r_ab @@ -119,17 +174,28 @@ end # viscosity is used for shocks and not rarefactions." if vr < 0 mu = h * vr / (distance^2 + epsilon * h^2) - return -(alpha * c * mu + beta * mu^2) / rho_mean + return (alpha * c * mu + beta * mu^2) / rho_mean * grad_kernel end - return 0.0 + return zero(v_diff) +end + +@inline function (viscosity::ViscosityMorris)(c, v_diff, pos_diff, distance, rho_mean, + rho_a, rho_b, h, grad_kernel) + (; epsilon, nu) = viscosity + + # TODO This is not correct for two different fluids. It should be `nu_a` and `nu_b`. + mu_a = nu * rho_a + mu_b = nu * rho_b + + return (mu_a + mu_b) / (rho_a * rho_b) * dot(pos_diff, grad_kernel) / + (distance^2 + epsilon * h^2) * v_diff end # See, e.g., -# M. Antuono, A. Colagrossi, S. Marrone. -# "Numerical Diffusive Terms in Weakly-Compressible SPH Schemes." -# In: Computer Physics Communications 183, no. 12 (2012), pages 2570-80. -# https://doi.org/10.1016/j.cpc.2012.07.006 +# Joseph J. Monaghan. "Smoothed Particle Hydrodynamics". +# In: Reports on Progress in Physics (2005), pages 1703-1759. +# [doi: 10.1088/0034-4885/68/8/r01](http://dx.doi.org/10.1088/0034-4885/68/8/R01) function kinematic_viscosity(system, viscosity::ArtificialViscosityMonaghan) (; smoothing_length) = system (; alpha) = viscosity @@ -179,7 +245,8 @@ end @inline function (viscosity::ViscosityAdami)(particle_system, neighbor_system, v_particle_system, v_neighbor_system, particle, neighbor, pos_diff, - distance, sound_speed, m_a, m_b, rho_mean) + distance, sound_speed, m_a, m_b, + rho_a, rho_b, grad_kernel) (; epsilon, nu) = viscosity (; smoothing_length) = particle_system @@ -187,9 +254,7 @@ end v_b = viscous_velocity(v_neighbor_system, neighbor_system, neighbor) v_diff = v_a - v_b - rho_a = particle_density(v_particle_system, particle_system, particle) - rho_b = particle_density(v_neighbor_system, neighbor_system, neighbor) - + # TODO This is not correct for two different fluids. It should be `nu_a` and `nu_b`. eta_a = nu * rho_a eta_b = nu * rho_b @@ -201,8 +266,6 @@ end volume_a = m_a / rho_a volume_b = m_b / rho_b - grad_kernel = smoothing_kernel_grad(particle_system, pos_diff, distance) - # This formulation was introduced by Hu and Adams (2006). https://doi.org/10.1016/j.jcp.2005.09.001 # They argued that the formulation is more flexible because of the possibility to formulate # different inter-particle averages or to assume different inter-particle distributions. diff --git a/src/schemes/fluid/weakly_compressible_sph/density_diffusion.jl b/src/schemes/fluid/weakly_compressible_sph/density_diffusion.jl index 117383b11e..716245be82 100644 --- a/src/schemes/fluid/weakly_compressible_sph/density_diffusion.jl +++ b/src/schemes/fluid/weakly_compressible_sph/density_diffusion.jl @@ -134,22 +134,29 @@ diffusion terms. In: Computer Physics Communications 180.6 (2009), pages 861--872. [doi: 10.1016/j.cpc.2008.12.004](https://doi.org/10.1016/j.cpc.2008.12.004) """ -struct DensityDiffusionAntuono{NDIMS, ELTYPE} <: DensityDiffusion +struct DensityDiffusionAntuono{NDIMS, ELTYPE, ARRAY2D, ARRAY3D} <: DensityDiffusion delta :: ELTYPE - correction_matrix :: Array{ELTYPE, 3} # [i, j, particle] - normalized_density_gradient :: Array{ELTYPE, 2} # [i, particle] + correction_matrix :: ARRAY3D # Array{ELTYPE, 3}: [i, j, particle] + normalized_density_gradient :: ARRAY2D # Array{ELTYPE, 2}: [i, particle] + + function DensityDiffusionAntuono(delta, correction_matrix, normalized_density_gradient) + new{size(correction_matrix, 1), typeof(delta), + typeof(normalized_density_gradient), + typeof(correction_matrix)}(delta, correction_matrix, + normalized_density_gradient) + end +end - function DensityDiffusionAntuono(initial_condition; delta) - NDIMS = ndims(initial_condition) - ELTYPE = eltype(initial_condition) - correction_matrix = Array{ELTYPE, 3}(undef, NDIMS, NDIMS, - nparticles(initial_condition)) +function DensityDiffusionAntuono(initial_condition; delta) + NDIMS = ndims(initial_condition) + ELTYPE = eltype(initial_condition) + correction_matrix = Array{ELTYPE, 3}(undef, NDIMS, NDIMS, + nparticles(initial_condition)) - normalized_density_gradient = Array{ELTYPE, 2}(undef, NDIMS, - nparticles(initial_condition)) + normalized_density_gradient = Array{ELTYPE, 2}(undef, NDIMS, + nparticles(initial_condition)) - new{NDIMS, ELTYPE}(delta, correction_matrix, normalized_density_gradient) - end + return DensityDiffusionAntuono(delta, correction_matrix, normalized_density_gradient) end @inline Base.ndims(::DensityDiffusionAntuono{NDIMS}) where {NDIMS} = NDIMS @@ -217,3 +224,35 @@ function update!(density_diffusion::DensityDiffusionAntuono, neighborhood_search return density_diffusion end + +@inline function density_diffusion!(dv, density_diffusion::DensityDiffusion, + v_particle_system, v_neighbor_system, + particle, neighbor, pos_diff, distance, + m_b, rho_a, rho_b, + particle_system::FluidSystem, + neighbor_system::FluidSystem, + grad_kernel) + # Density diffusion terms are all zero for distance zero + distance < sqrt(eps()) && return + + (; delta) = density_diffusion + (; smoothing_length, state_equation) = particle_system + (; sound_speed) = state_equation + + volume_b = m_b / rho_b + + psi = density_diffusion_psi(density_diffusion, rho_a, rho_b, pos_diff, distance, + particle_system, particle, neighbor) + density_diffusion_term = dot(psi, grad_kernel) * volume_b + + dv[end, particle] += delta * smoothing_length * sound_speed * density_diffusion_term +end + +# Density diffusion `nothing` or interaction other than fluid-fluid +@inline function density_diffusion!(dv, density_diffusion, + v_particle_system, v_neighbor_system, + particle, neighbor, pos_diff, distance, + m_b, rho_a, rho_b, + particle_system, neighbor_system, grad_kernel) + return dv +end diff --git a/src/schemes/fluid/weakly_compressible_sph/rhs.jl b/src/schemes/fluid/weakly_compressible_sph/rhs.jl index 0b913bc23f..12100612fc 100644 --- a/src/schemes/fluid/weakly_compressible_sph/rhs.jl +++ b/src/schemes/fluid/weakly_compressible_sph/rhs.jl @@ -6,9 +6,12 @@ function interact!(dv, v_particle_system, u_particle_system, v_neighbor_system, u_neighbor_system, neighborhood_search, particle_system::WeaklyCompressibleSPHSystem, neighbor_system) - (; density_calculator, state_equation, correction) = particle_system + (; density_calculator, state_equation, correction, surface_tension) = particle_system (; sound_speed) = state_equation + surface_tension_a = surface_tension_model(particle_system) + surface_tension_b = surface_tension_model(neighbor_system) + system_coords = current_coordinates(u_particle_system, particle_system) neighbor_system_coords = current_coordinates(u_neighbor_system, neighbor_system) @@ -25,9 +28,9 @@ function interact!(dv, v_particle_system, u_particle_system, rho_mean = 0.5 * (rho_a + rho_b) # Determine correction values - viscosity_correction, pressure_correction = free_surface_correction(correction, - particle_system, - rho_mean) + viscosity_correction, pressure_correction, surface_tension_correction = free_surface_correction(correction, + particle_system, + rho_mean) grad_kernel = smoothing_kernel_grad(particle_system, pos_diff, distance, particle) @@ -53,10 +56,19 @@ function interact!(dv, v_particle_system, u_particle_system, dv_viscosity(particle_system, neighbor_system, v_particle_system, v_neighbor_system, particle, neighbor, pos_diff, distance, - sound_speed, m_a, m_b, rho_mean) + sound_speed, m_a, m_b, rho_a, rho_b, grad_kernel) + + dv_surface_tension = surface_tension_correction * + surface_tension_force(surface_tension_a, surface_tension_b, + particle_system, neighbor_system, + particle, neighbor, pos_diff, distance) + + dv_adhesion = adhesion_force(surface_tension, particle_system, neighbor_system, + particle, neighbor, pos_diff, distance) - for i in 1:ndims(particle_system) - dv[i, particle] += dv_pressure[i] + dv_viscosity_[i] + @inbounds for i in 1:ndims(particle_system) + dv[i, particle] += dv_pressure[i] + dv_viscosity_[i] + dv_surface_tension[i] + + dv_adhesion[i] # Debug example # debug_array[i, particle] += dv_pressure[i] end @@ -104,38 +116,6 @@ end particle_system, neighbor_system, grad_kernel) end -@inline function density_diffusion!(dv, density_diffusion::DensityDiffusion, - v_particle_system, v_neighbor_system, - particle, neighbor, pos_diff, distance, - m_b, rho_a, rho_b, - particle_system::WeaklyCompressibleSPHSystem, - neighbor_system::WeaklyCompressibleSPHSystem, - grad_kernel) - # Density diffusion terms are all zero for distance zero - distance < sqrt(eps()) && return - - (; delta) = density_diffusion - (; smoothing_length, state_equation) = particle_system - (; sound_speed) = state_equation - - volume_b = m_b / rho_b - - psi = density_diffusion_psi(density_diffusion, rho_a, rho_b, pos_diff, distance, - particle_system, particle, neighbor) - density_diffusion_term = dot(psi, grad_kernel) * volume_b - - dv[end, particle] += delta * smoothing_length * sound_speed * density_diffusion_term -end - -# Density diffusion `nothing` or interaction other than fluid-fluid -@inline function density_diffusion!(dv, density_diffusion, - v_particle_system, v_neighbor_system, - particle, neighbor, pos_diff, distance, - m_b, rho_a, rho_b, - particle_system, neighbor_system, grad_kernel) - return dv -end - @inline function particle_neighbor_pressure(v_particle_system, v_neighbor_system, particle_system, neighbor_system, particle, neighbor) diff --git a/src/schemes/fluid/weakly_compressible_sph/system.jl b/src/schemes/fluid/weakly_compressible_sph/system.jl index 8e98e3a132..44e71695b9 100644 --- a/src/schemes/fluid/weakly_compressible_sph/system.jl +++ b/src/schemes/fluid/weakly_compressible_sph/system.jl @@ -4,6 +4,7 @@ smoothing_kernel, smoothing_length; viscosity=nothing, density_diffusion=nothing, acceleration=ntuple(_ -> 0.0, NDIMS), + buffer_size=nothing, correction=nothing, source_terms=nothing) System for particles of a fluid. @@ -26,6 +27,8 @@ See [Weakly Compressible SPH](@ref wcsph) for more details on the method. See [`ArtificialViscosityMonaghan`](@ref) or [`ViscosityAdami`](@ref). - `density_diffusion`: Density diffusion terms for this system. See [`DensityDiffusion`](@ref). - `acceleration`: Acceleration vector for the system. (default: zero vector) +- `buffer_size`: Number of buffer particles. + This is needed when simulating with [`OpenBoundarySPHSystem`](@ref). - `correction`: Correction method used for this system. (default: no correction, see [Corrections](@ref corrections)) - `source_terms`: Additional source terms for this system. Has to be either `nothing` (by default), or a function of `(coords, velocity, density, pressure)` @@ -37,13 +40,15 @@ See [Weakly Compressible SPH](@ref wcsph) for more details on the method. [`BoundaryModelDummyParticles`](@ref) and [`AdamiPressureExtrapolation`](@ref). The keyword argument `acceleration` should be used instead for gravity-like source terms. +- `surface_tension`: Surface tension model used for this SPH system. (default: no surface tension) + """ -struct WeaklyCompressibleSPHSystem{NDIMS, ELTYPE <: Real, DC, SE, K, - V, DD, COR, PF, ST, C} <: FluidSystem{NDIMS} - initial_condition :: InitialCondition{ELTYPE} - mass :: Array{ELTYPE, 1} # [particle] - pressure :: Array{ELTYPE, 1} # [particle] +struct WeaklyCompressibleSPHSystem{NDIMS, ELTYPE <: Real, IC, MA, P, DC, SE, K, + V, DD, COR, PF, ST, B, SRFT, C} <: FluidSystem{NDIMS, IC} + initial_condition :: IC + mass :: MA # Array{ELTYPE, 1} + pressure :: P # Array{ELTYPE, 1} density_calculator :: DC state_equation :: SE smoothing_kernel :: K @@ -54,60 +59,70 @@ struct WeaklyCompressibleSPHSystem{NDIMS, ELTYPE <: Real, DC, SE, K, correction :: COR pressure_acceleration_formulation :: PF source_terms :: ST + surface_tension :: SRFT + buffer :: B cache :: C +end - function WeaklyCompressibleSPHSystem(initial_condition, - density_calculator, state_equation, - smoothing_kernel, smoothing_length; - pressure_acceleration=nothing, - viscosity=nothing, density_diffusion=nothing, - acceleration=ntuple(_ -> 0.0, - ndims(smoothing_kernel)), - correction=nothing, source_terms=nothing) - NDIMS = ndims(initial_condition) - ELTYPE = eltype(initial_condition) - n_particles = nparticles(initial_condition) - - mass = copy(initial_condition.mass) - pressure = similar(initial_condition.pressure) - - if ndims(smoothing_kernel) != NDIMS - throw(ArgumentError("smoothing kernel dimensionality must be $NDIMS for a $(NDIMS)D problem")) - end - - # Make acceleration an SVector - acceleration_ = SVector(acceleration...) - if length(acceleration_) != NDIMS - throw(ArgumentError("`acceleration` must be of length $NDIMS for a $(NDIMS)D problem")) - end +# The default constructor needs to be accessible for Adapt.jl to work with this struct. +# See the comments in general/gpu.jl for more details. +function WeaklyCompressibleSPHSystem(initial_condition, + density_calculator, state_equation, + smoothing_kernel, smoothing_length; + pressure_acceleration=nothing, + buffer_size=nothing, + viscosity=nothing, density_diffusion=nothing, + acceleration=ntuple(_ -> 0.0, + ndims(smoothing_kernel)), + correction=nothing, source_terms=nothing, + surface_tension=nothing) + buffer = isnothing(buffer_size) ? nothing : + SystemBuffer(nparticles(initial_condition), buffer_size) + + initial_condition = allocate_buffer(initial_condition, buffer) + + NDIMS = ndims(initial_condition) + ELTYPE = eltype(initial_condition) + n_particles = nparticles(initial_condition) + + mass = copy(initial_condition.mass) + pressure = similar(initial_condition.pressure) + + if ndims(smoothing_kernel) != NDIMS + throw(ArgumentError("smoothing kernel dimensionality must be $NDIMS for a $(NDIMS)D problem")) + end - if correction isa ShepardKernelCorrection && - density_calculator isa ContinuityDensity - throw(ArgumentError("`ShepardKernelCorrection` cannot be used with `ContinuityDensity`")) - end + # Make acceleration an SVector + acceleration_ = SVector(acceleration...) + if length(acceleration_) != NDIMS + throw(ArgumentError("`acceleration` must be of length $NDIMS for a $(NDIMS)D problem")) + end - pressure_acceleration = choose_pressure_acceleration_formulation(pressure_acceleration, - density_calculator, - NDIMS, ELTYPE, - correction) - - cache = create_cache_density(initial_condition, density_calculator) - cache = (; - create_cache_wcsph(correction, initial_condition.density, NDIMS, - n_particles)..., cache...) - - return new{NDIMS, ELTYPE, typeof(density_calculator), - typeof(state_equation), typeof(smoothing_kernel), - typeof(viscosity), typeof(density_diffusion), - typeof(correction), typeof(pressure_acceleration), - typeof(source_terms), typeof(cache)}(initial_condition, mass, pressure, - density_calculator, state_equation, - smoothing_kernel, smoothing_length, - acceleration_, viscosity, - density_diffusion, correction, - pressure_acceleration, - source_terms, cache) + if correction isa ShepardKernelCorrection && + density_calculator isa ContinuityDensity + throw(ArgumentError("`ShepardKernelCorrection` cannot be used with `ContinuityDensity`")) end + + pressure_acceleration = choose_pressure_acceleration_formulation(pressure_acceleration, + density_calculator, + NDIMS, ELTYPE, + correction) + + cache = create_cache_density(initial_condition, density_calculator) + cache = (; + create_cache_wcsph(correction, initial_condition.density, NDIMS, + n_particles)..., cache...) + cache = (; + create_cache_wcsph(surface_tension, ELTYPE, NDIMS, n_particles)..., + cache...) + + return WeaklyCompressibleSPHSystem(initial_condition, mass, pressure, + density_calculator, state_equation, + smoothing_kernel, smoothing_length, + acceleration_, viscosity, + density_diffusion, correction, + pressure_acceleration, + source_terms, surface_tension, buffer, cache) end create_cache_wcsph(correction, density, NDIMS, nparticles) = (;) @@ -134,6 +149,11 @@ function create_cache_wcsph(::MixedKernelGradientCorrection, density, NDIMS, n_p return (; kernel_correction_coefficient=similar(density), dw_gamma, correction_matrix) end +function create_cache_wcsph(::SurfaceTensionAkinci, ELTYPE, NDIMS, nparticles) + surface_normal = Array{ELTYPE, 2}(undef, NDIMS, nparticles) + return (; surface_normal) +end + function Base.show(io::IO, system::WeaklyCompressibleSPHSystem) @nospecialize system # reduce precompilation time @@ -144,6 +164,7 @@ function Base.show(io::IO, system::WeaklyCompressibleSPHSystem) print(io, ", ", system.smoothing_kernel) print(io, ", ", system.viscosity) print(io, ", ", system.density_diffusion) + print(io, ", ", system.surface_tension) print(io, ", ", system.acceleration) print(io, ", ", system.source_terms) print(io, ") with ", nparticles(system), " particles") @@ -156,7 +177,12 @@ function Base.show(io::IO, ::MIME"text/plain", system::WeaklyCompressibleSPHSyst show(io, system) else summary_header(io, "WeaklyCompressibleSPHSystem{$(ndims(system))}") - summary_line(io, "#particles", nparticles(system)) + if system.buffer isa SystemBuffer + summary_line(io, "#particles", nparticles(system)) + summary_line(io, "#buffer_particles", system.buffer.buffer_size) + else + summary_line(io, "#particles", nparticles(system)) + end summary_line(io, "density calculator", system.density_calculator |> typeof |> nameof) summary_line(io, "correction method", @@ -165,6 +191,7 @@ function Base.show(io::IO, ::MIME"text/plain", system::WeaklyCompressibleSPHSyst summary_line(io, "smoothing kernel", system.smoothing_kernel |> typeof |> nameof) summary_line(io, "viscosity", system.viscosity) summary_line(io, "density diffusion", system.density_diffusion) + summary_line(io, "surface tension", system.surface_tension) summary_line(io, "acceleration", system.acceleration) summary_line(io, "source terms", system.source_terms |> typeof |> nameof) summary_footer(io) @@ -203,7 +230,7 @@ function update_quantities!(system::WeaklyCompressibleSPHSystem, v, u, end function update_pressure!(system::WeaklyCompressibleSPHSystem, v, u, v_ode, u_ode, semi, t) - (; density_calculator, correction) = system + (; density_calculator, correction, surface_tension) = system compute_correction_values!(system, correction, u, v_ode, u_ode, semi) @@ -213,6 +240,7 @@ function update_pressure!(system::WeaklyCompressibleSPHSystem, v, u, v_ode, u_od kernel_correct_density!(system, v, u, v_ode, u_ode, semi, correction, density_calculator) compute_pressure!(system, v) + compute_surface_normal!(system, surface_tension, v, u, v_ode, u_ode, semi, t) return system end @@ -283,7 +311,7 @@ function reinit_density!(system, v, u, v_ode, u_ode, semi) end function compute_pressure!(system, v) - @threaded for particle in eachparticle(system) + @threaded system for particle in eachparticle(system) apply_state_equation!(system, particle_density(v, system, particle), particle) end end @@ -297,10 +325,8 @@ end end function write_v0!(v0, system::WeaklyCompressibleSPHSystem, ::ContinuityDensity) - for particle in eachparticle(system) - # Set particle densities - v0[end, particle] = system.initial_condition.density[particle] - end + # Note that `.=` is very slightly faster, but not GPU-compatible + v0[end, :] = system.initial_condition.density return v0 end @@ -329,3 +355,39 @@ end @inline function correction_matrix(system::WeaklyCompressibleSPHSystem, particle) extract_smatrix(system.cache.correction_matrix, system, particle) end + +function compute_surface_normal!(system, surface_tension, v, u, v_ode, u_ode, semi, t) + return system +end + +function compute_surface_normal!(system, surface_tension::SurfaceTensionAkinci, v, u, v_ode, + u_ode, semi, t) + (; cache) = system + + # Reset surface normal + set_zero!(cache.surface_normal) + + @trixi_timeit timer() "compute surface normal" foreach_system(semi) do neighbor_system + u_neighbor_system = wrap_u(u_ode, neighbor_system, semi) + v_neighbor_system = wrap_v(v_ode, neighbor_system, semi) + nhs = get_neighborhood_search(system, semi) + + calc_normal_akinci!(system, neighbor_system, surface_tension, u, v_neighbor_system, + u_neighbor_system, nhs) + end + return system +end + +@inline function surface_normal(::SurfaceTensionAkinci, particle_system::FluidSystem, + particle) + (; cache) = particle_system + return extract_svector(cache.surface_normal, particle_system, particle) +end + +@inline function surface_tension_model(system::WeaklyCompressibleSPHSystem) + return system.surface_tension +end + +@inline function surface_tension_model(system) + return nothing +end diff --git a/src/schemes/schemes.jl b/src/schemes/schemes.jl index 5eabef80e6..fadcb240e5 100644 --- a/src/schemes/schemes.jl +++ b/src/schemes/schemes.jl @@ -1,8 +1,12 @@ # Include all schemes without rhs first. The rhs depends on the systems to define # interactions between the different system types. +# Viscosity requires the open boundary system. +include("boundary/open_boundary/boundary_zones.jl") +include("boundary/open_boundary/system.jl") include("fluid/fluid.jl") include("boundary/boundary.jl") include("solid/total_lagrangian_sph/total_lagrangian_sph.jl") +include("solid/discrete_element_method/discrete_element_method.jl") # Monaghan-Kajtar repulsive boundary particles require the `BoundarySPHSystem` # and the `TotalLagrangianSPHSystem`. include("boundary/monaghan_kajtar/monaghan_kajtar.jl") @@ -12,3 +16,4 @@ include("fluid/weakly_compressible_sph/rhs.jl") include("fluid/entropically_damped_sph/rhs.jl") include("boundary/rhs.jl") include("solid/total_lagrangian_sph/rhs.jl") +include("solid/discrete_element_method/rhs.jl") diff --git a/src/schemes/solid/discrete_element_method/discrete_element_method.jl b/src/schemes/solid/discrete_element_method/discrete_element_method.jl new file mode 100644 index 0000000000..c67930ab8f --- /dev/null +++ b/src/schemes/solid/discrete_element_method/discrete_element_method.jl @@ -0,0 +1 @@ +include("system.jl") diff --git a/src/schemes/solid/discrete_element_method/rhs.jl b/src/schemes/solid/discrete_element_method/rhs.jl new file mode 100644 index 0000000000..0362c3a04b --- /dev/null +++ b/src/schemes/solid/discrete_element_method/rhs.jl @@ -0,0 +1,110 @@ +# Calculate the interaction forces between particles in a Discrete Element Method (DEM) system. +# +# This function loops over all pairs of particles and their neighbors within a set distance. +# When particles overlap (i.e., they come into contact), a normal force is applied to resolve the overlap. +# This force is computed based on Hertzian contact mechanics typical for DEM simulations. +# The force is proportional to the amount of overlap and is directed along the normal between the particle centers. +# The magnitude of the force is determined by the stiffness constant `normal_stiffness` and the overlap distance. +function interact!(dv, v_particle_system, u_particle_system, v_neighbor_system, + u_neighbor_system, neighborhood_search, particle_system::DEMSystem, + neighbor_system::Union{BoundaryDEMSystem, DEMSystem}) + (; damping_coefficient) = particle_system + + E_a = particle_system.elastic_modulus + nu_a = particle_system.poissons_ratio + + system_coords = current_coordinates(u_particle_system, particle_system) + neighbor_coords = current_coordinates(u_neighbor_system, neighbor_system) + + for_particle_neighbor(particle_system, neighbor_system, system_coords, neighbor_coords, + neighborhood_search) do particle, neighbor, pos_diff, distance + m_a = particle_system.mass[particle] + + r_a = particle_system.radius[particle] + r_b = neighbor_system.radius[neighbor] + + # Only consider particles with a distance > 0 + distance < sqrt(eps()) && return + + # Calculate the overlap (penetration depth) between the two particles + overlap = r_a + r_b - distance + + # If there's no overlap, no force needs to be applied + overlap <= 0 && return + + # Normal direction from neighbor to particle + normal = pos_diff / distance + + interaction_force = collision_force(particle_system, neighbor_system, overlap, + normal, v_particle_system, + v_neighbor_system, E_a, + nu_a, r_a, r_b, m_a, + damping_coefficient, particle, neighbor) + + # Update the acceleration of the particle based on the force and its mass + for i in 1:ndims(particle_system) + dv[i, particle] += interaction_force[i] / m_a + end + + # TODO: use update callback + position_correction!(neighbor_system, u_particle_system, overlap, normal, particle) + end + + return dv +end + +@inline function collision_force(particle_system, neighbor_system::BoundaryDEMSystem, + overlap, normal, v_particle_system, + v_neighbor_system, E_a, nu_a, + r_a, r_b, m_a, damping_coefficient, + particle, neighbor) + return neighbor_system.normal_stiffness * overlap * normal +end + +@inline function collision_force(particle_system, neighbor_system::DEMSystem, overlap, + normal, v_particle_system, v_neighbor_system, + E_a, nu_a, r_a, r_b, + m_a, damping_coefficient, particle, neighbor) + m_b = neighbor_system.mass[neighbor] + E_b = neighbor_system.elastic_modulus + nu_b = neighbor_system.poissons_ratio + + v_a = current_velocity(v_particle_system, particle_system, particle) + v_b = current_velocity(v_neighbor_system, neighbor_system, neighbor) + + v_ab = v_a - v_b + rel_vel_normal = dot(v_ab, normal) + + # Compute effective modulus for both systems + E_star = 1 / ((1 - nu_a^2) / E_a + (1 - nu_b^2) / E_b) + + # Compute effective radius for the interaction + r_star = (r_a * r_b) / (r_a + r_b) + + # Compute stiffness constant for the interaction + normal_stiffness = (4 / 3) * E_star * sqrt(r_star * overlap) + + # Calculate effective mass for the interaction + m_star = (m_a * m_b) / (m_a + m_b) + + # Calculate critical damping coefficient + gamma_c = 2 * sqrt(m_star * normal_stiffness) + + # Compute the force magnitude using Hertzian contact mechanics with damping + force_magnitude = normal_stiffness * overlap + + damping_coefficient * gamma_c * rel_vel_normal + + return force_magnitude * normal +end + +@inline function position_correction!(neighbor_system, u_particle_system, overlap, normal, + particle) +end + +@inline function position_correction!(neighbor_system::BoundaryDEMSystem, u_particle_system, + overlap, normal, particle) + for i in 1:ndims(neighbor_system) + # Position correction to prevent penetration + u_particle_system[i, particle] -= 0.5 * overlap * normal[i] + end +end diff --git a/src/schemes/solid/discrete_element_method/system.jl b/src/schemes/solid/discrete_element_method/system.jl new file mode 100644 index 0000000000..eb30dfb34c --- /dev/null +++ b/src/schemes/solid/discrete_element_method/system.jl @@ -0,0 +1,118 @@ +""" + DEMSystem(initial_condition, normal_stiffness, elastic_modulus, poissons_ratio; + damping_coefficient=0.0001, acceleration=ntuple(_ -> 0.0, NDIMS), source_terms=nothing) + +Constructs a Discrete Element Method (DEM) system for numerically simulating the dynamics of +granular and particulate matter. DEM is employed to simulate and analyze the motion, +interactions, and collective behavior of assemblies of discrete, solid particles, typically +under mechanical loading. The model accounts for individual particle characteristics +and implements interaction laws that govern contact forces (normal and tangential), based on +specified material properties and contact mechanics. + +# Arguments + - `initial_condition`: Initial condition of the system, encapsulating the initial positions, + velocities, masses, and radii of particles. + - `normal_stiffness`: Normal stiffness coefficient for particle-particle and particle-wall contacts. + - `elastic_modulus`: Elastic modulus for this particle system. + - `poissons_ratio`: Poisson ratio for this particle system. + +# Keywords + - `acceleration`: Global acceleration vector applied to the system, such as gravity. Specified as + an `SVector` of length `NDIMS`, with a default of zero in each dimension. + - `source_terms`: Optional; additional forces or modifications to particle dynamics not + captured by standard DEM interactions, such as electromagnetic forces or user-defined perturbations. + - `damping_coefficient=0.0001`: Set a damping coefficient for the collision interactions. + + !!! warning "Experimental Implementation" + This is an experimental feature and may change in a future releases. +""" +struct DEMSystem{NDIMS, ELTYPE <: Real, IC, ARRAY1D, ST} <: SolidSystem{NDIMS, IC} + initial_condition :: IC + mass :: ARRAY1D # [particle] + radius :: ARRAY1D # [particle] + elastic_modulus :: ELTYPE + poissons_ratio :: ELTYPE + normal_stiffness :: ELTYPE + damping_coefficient :: ELTYPE + acceleration :: SVector{NDIMS, ELTYPE} + source_terms :: ST + buffer :: Nothing + + function DEMSystem(initial_condition, normal_stiffness, elastic_modulus, poissons_ratio; + damping_coefficient=0.0001, + acceleration=ntuple(_ -> 0.0, + ndims(initial_condition)), source_terms=nothing) + NDIMS = ndims(initial_condition) + ELTYPE = eltype(initial_condition) + + mass = copy(initial_condition.mass) + radius = 0.5 * initial_condition.particle_spacing * ones(length(mass)) + + # Make acceleration an SVector + acceleration_ = SVector(acceleration...) + if length(acceleration_) != NDIMS + throw(ArgumentError("`acceleration` must be of length $NDIMS for a $(NDIMS)D problem")) + end + + return new{NDIMS, ELTYPE, typeof(initial_condition), typeof(mass), + typeof(source_terms)}(initial_condition, mass, radius, elastic_modulus, + poissons_ratio, normal_stiffness, + damping_coefficient, acceleration_, source_terms, + nothing) + end +end + +function Base.show(io::IO, system::DEMSystem) + @nospecialize system # reduce precompilation time + + print(io, "DEMSystem{", ndims(system), "}(") + print(io, system.initial_condition) + print(io, ", ", system.elastic_modulus) + print(io, ", ", system.poissons_ratio) + print(io, ", ", system.normal_stiffness) + print(io, ", ", system.damping_coefficient) + print(io, ") with ", TrixiParticles.nparticles(system), " particles") +end + +function Base.show(io::IO, ::MIME"text/plain", system::DEMSystem) + @nospecialize system # reduce precompilation time + + if get(io, :compact, false) + show(io, system) + else + TrixiParticles.summary_header(io, "DEMSystem{$(ndims(system))}") + TrixiParticles.summary_line(io, "#particles", TrixiParticles.nparticles(system)) + TrixiParticles.summary_line(io, "elastic_modulus", system.elastic_modulus) + TrixiParticles.summary_line(io, "poissons_ratio", system.poissons_ratio) + TrixiParticles.summary_line(io, "normal_stiffness", system.normal_stiffness) + TrixiParticles.summary_line(io, "damping_coefficient", system.damping_coefficient) + TrixiParticles.summary_footer(io) + end +end + +timer_name(::DEMSystem) = "solid" + +function TrixiParticles.write_u0!(u0, system::DEMSystem) + u0 .= system.initial_condition.coordinates + return u0 +end + +function TrixiParticles.write_v0!(v0, system::DEMSystem) + v0 .= system.initial_condition.velocity + return v0 +end + +# Nothing to initialize for this system +initialize!(system::DEMSystem, neighborhood_search) = system + +function compact_support(system::DEMSystem, neighbor::DEMSystem) + # we for now assume that the compact support is 3 * radius + # todo: needs to be changed for more complex simulations + return 3 * max(maximum(system.radius), maximum(neighbor.radius)) +end + +function compact_support(system::DEMSystem, neighbor) + # we for now assume that the compact support is 3 * radius + # todo: needs to be changed for more complex simulations + return 3 * maximum(system.radius) +end diff --git a/src/schemes/solid/total_lagrangian_sph/rhs.jl b/src/schemes/solid/total_lagrangian_sph/rhs.jl index deb9b9112e..8057022e56 100644 --- a/src/schemes/solid/total_lagrangian_sph/rhs.jl +++ b/src/schemes/solid/total_lagrangian_sph/rhs.jl @@ -75,7 +75,7 @@ function interact!(dv, v_particle_system, u_particle_system, distance < sqrt(eps()) && return # Apply the same force to the solid particle - # that the fluid particle experiences due to the soild particle. + # that the fluid particle experiences due to the solid particle. # Note that the same arguments are passed here as in fluid-solid interact!, # except that pos_diff has a flipped sign. # @@ -109,7 +109,7 @@ function interact!(dv, v_particle_system, u_particle_system, dv_viscosity_ = dv_viscosity(neighbor_system, particle_system, v_neighbor_system, v_particle_system, neighbor, particle, pos_diff, distance, - sound_speed, m_b, m_a, rho_mean) + sound_speed, m_b, m_a, rho_a, rho_b, grad_kernel) dv_particle = dv_boundary + dv_viscosity_ diff --git a/src/schemes/solid/total_lagrangian_sph/system.jl b/src/schemes/solid/total_lagrangian_sph/system.jl index 452a745066..125bb55074 100644 --- a/src/schemes/solid/total_lagrangian_sph/system.jl +++ b/src/schemes/solid/total_lagrangian_sph/system.jl @@ -48,15 +48,16 @@ See [Total Lagrangian SPH](@ref tlsph) for more details on the method. ``` where `beam` and `fixed_particles` are of type `InitialCondition`. """ -struct TotalLagrangianSPHSystem{BM, NDIMS, ELTYPE <: Real, K, PF, ST} <: SolidSystem{NDIMS} - initial_condition :: InitialCondition{ELTYPE} - initial_coordinates :: Array{ELTYPE, 2} # [dimension, particle] - current_coordinates :: Array{ELTYPE, 2} # [dimension, particle] - mass :: Array{ELTYPE, 1} # [particle] - correction_matrix :: Array{ELTYPE, 3} # [i, j, particle] - pk1_corrected :: Array{ELTYPE, 3} # [i, j, particle] - deformation_grad :: Array{ELTYPE, 3} # [i, j, particle] - material_density :: Array{ELTYPE, 1} # [particle] +struct TotalLagrangianSPHSystem{BM, NDIMS, ELTYPE <: Real, IC, ARRAY1D, ARRAY2D, ARRAY3D, + K, PF, ST} <: SolidSystem{NDIMS, IC} + initial_condition :: IC + initial_coordinates :: ARRAY2D # Array{ELTYPE, 2}: [dimension, particle] + current_coordinates :: ARRAY2D # Array{ELTYPE, 2}: [dimension, particle] + mass :: ARRAY1D # Array{ELTYPE, 1}: [particle] + correction_matrix :: ARRAY3D # Array{ELTYPE, 3}: [i, j, particle] + pk1_corrected :: ARRAY3D # Array{ELTYPE, 3}: [i, j, particle] + deformation_grad :: ARRAY3D # Array{ELTYPE, 3}: [i, j, particle] + material_density :: ARRAY1D # Array{ELTYPE, 1}: [particle] n_moving_particles :: Int64 young_modulus :: ELTYPE poisson_ratio :: ELTYPE @@ -68,53 +69,51 @@ struct TotalLagrangianSPHSystem{BM, NDIMS, ELTYPE <: Real, K, PF, ST} <: SolidSy boundary_model :: BM penalty_force :: PF source_terms :: ST + buffer :: Nothing +end + +function TotalLagrangianSPHSystem(initial_condition, + smoothing_kernel, smoothing_length, + young_modulus, poisson_ratio; + n_fixed_particles=0, boundary_model=nothing, + acceleration=ntuple(_ -> 0.0, + ndims(smoothing_kernel)), + penalty_force=nothing, source_terms=nothing) + NDIMS = ndims(initial_condition) + ELTYPE = eltype(initial_condition) + n_particles = nparticles(initial_condition) + + if ndims(smoothing_kernel) != NDIMS + throw(ArgumentError("smoothing kernel dimensionality must be $NDIMS for a $(NDIMS)D problem")) + end - function TotalLagrangianSPHSystem(initial_condition, - smoothing_kernel, smoothing_length, - young_modulus, poisson_ratio; - n_fixed_particles=0, boundary_model=nothing, - acceleration=ntuple(_ -> 0.0, - ndims(smoothing_kernel)), - penalty_force=nothing, source_terms=nothing) - NDIMS = ndims(initial_condition) - ELTYPE = eltype(initial_condition) - n_particles = nparticles(initial_condition) - - if ndims(smoothing_kernel) != NDIMS - throw(ArgumentError("smoothing kernel dimensionality must be $NDIMS for a $(NDIMS)D problem")) - end + # Make acceleration an SVector + acceleration_ = SVector(acceleration...) + if length(acceleration_) != NDIMS + throw(ArgumentError("`acceleration` must be of length $NDIMS for a $(NDIMS)D problem")) + end - # Make acceleration an SVector - acceleration_ = SVector(acceleration...) - if length(acceleration_) != NDIMS - throw(ArgumentError("`acceleration` must be of length $NDIMS for a $(NDIMS)D problem")) - end + initial_coordinates = copy(initial_condition.coordinates) + current_coordinates = copy(initial_condition.coordinates) + mass = copy(initial_condition.mass) + material_density = copy(initial_condition.density) + correction_matrix = Array{ELTYPE, 3}(undef, NDIMS, NDIMS, n_particles) + pk1_corrected = Array{ELTYPE, 3}(undef, NDIMS, NDIMS, n_particles) + deformation_grad = Array{ELTYPE, 3}(undef, NDIMS, NDIMS, n_particles) - initial_coordinates = copy(initial_condition.coordinates) - current_coordinates = copy(initial_condition.coordinates) - mass = copy(initial_condition.mass) - material_density = copy(initial_condition.density) - correction_matrix = Array{ELTYPE, 3}(undef, NDIMS, NDIMS, n_particles) - pk1_corrected = Array{ELTYPE, 3}(undef, NDIMS, NDIMS, n_particles) - deformation_grad = Array{ELTYPE, 3}(undef, NDIMS, NDIMS, n_particles) - - n_moving_particles = n_particles - n_fixed_particles - - lame_lambda = young_modulus * poisson_ratio / - ((1 + poisson_ratio) * (1 - 2 * poisson_ratio)) - lame_mu = 0.5 * young_modulus / (1 + poisson_ratio) - - return new{typeof(boundary_model), NDIMS, ELTYPE, - typeof(smoothing_kernel), - typeof(penalty_force), - typeof(source_terms)}(initial_condition, initial_coordinates, - current_coordinates, mass, correction_matrix, - pk1_corrected, deformation_grad, material_density, - n_moving_particles, young_modulus, poisson_ratio, - lame_lambda, lame_mu, smoothing_kernel, - smoothing_length, acceleration_, boundary_model, - penalty_force, source_terms) - end + n_moving_particles = n_particles - n_fixed_particles + + lame_lambda = young_modulus * poisson_ratio / + ((1 + poisson_ratio) * (1 - 2 * poisson_ratio)) + lame_mu = 0.5 * young_modulus / (1 + poisson_ratio) + + return TotalLagrangianSPHSystem(initial_condition, initial_coordinates, + current_coordinates, mass, correction_matrix, + pk1_corrected, deformation_grad, material_density, + n_moving_particles, young_modulus, poisson_ratio, + lame_lambda, lame_mu, smoothing_kernel, + smoothing_length, acceleration_, boundary_model, + penalty_force, source_terms, nothing) end function Base.show(io::IO, system::TotalLagrangianSPHSystem) @@ -178,7 +177,7 @@ end @inline function current_velocity(v, system::TotalLagrangianSPHSystem, particle) if particle > n_moving_particles(system) - return SVector(ntuple(_ -> 0.0, Val(ndims(system)))) + return zero(SVector{ndims(system), eltype(system)}) end return extract_svector(v, system, particle) @@ -243,7 +242,8 @@ function update_quantities!(system::TotalLagrangianSPHSystem, v, u, v_ode, u_ode return system end -function update_final!(system::TotalLagrangianSPHSystem, v, u, v_ode, u_ode, semi, t) +function update_final!(system::TotalLagrangianSPHSystem, v, u, v_ode, u_ode, semi, t; + update_from_callback=false) (; boundary_model) = system # Only update boundary model @@ -255,7 +255,7 @@ end calc_deformation_grad!(deformation_grad, neighborhood_search, system) - @threaded for particle in eachparticle(system) + @threaded system for particle in eachparticle(system) F_particle = deformation_gradient(system, particle) pk1_particle = pk1_stress_tensor(F_particle, system) pk1_particle_corrected = pk1_particle * correction_matrix(system, particle) @@ -390,21 +390,29 @@ end # The von-Mises stress is one form of equivalent stress, where sigma is the deviatoric stress. # See pages 32 and 123. function von_mises_stress(system::TotalLagrangianSPHSystem) - von_mises_stress = zeros(eltype(system.pk1_corrected), nparticles(system)) + von_mises_stress_vector = zeros(eltype(system.pk1_corrected), nparticles(system)) - @threaded for particle in each_moving_particle(system) - F = deformation_gradient(system, particle) - J = det(F) - P = pk1_corrected(system, particle) - sigma = (1.0 / J) * P * F' + @threaded system for particle in each_moving_particle(system) + von_mises_stress_vector[particle] = von_mises_stress(system, particle) + end - # Calculate deviatoric stress tensor - s = sigma - (1.0 / 3.0) * tr(sigma) * I + return von_mises_stress_vector +end - von_mises_stress[particle] = sqrt(3.0 / 2.0 * sum(s .^ 2)) - end +# Use this function barrier and unpack inside to avoid passing closures to Polyester.jl +# with `@batch` (`@threaded`). +# Otherwise, `@threaded` does not work here with Julia ARM on macOS. +# See https://github.com/JuliaSIMD/Polyester.jl/issues/88. +@inline function von_mises_stress(system, particle) + F = deformation_gradient(system, particle) + J = det(F) + P = pk1_corrected(system, particle) + sigma = (1.0 / J) * P * F' + + # Calculate deviatoric stress tensor + s = sigma - (1.0 / 3.0) * tr(sigma) * I - return von_mises_stress + return sqrt(3.0 / 2.0 * sum(s .^ 2)) end # An explanation of these equation can be found in @@ -417,7 +425,7 @@ function cauchy_stress(system::TotalLagrangianSPHSystem) cauchy_stress_tensors = zeros(eltype(system.pk1_corrected), NDIMS, NDIMS, nparticles(system)) - @threaded for particle in each_moving_particle(system) + @threaded system for particle in each_moving_particle(system) F = deformation_gradient(system, particle) J = det(F) P = pk1_corrected(system, particle) diff --git a/src/setups/extrude_geometry.jl b/src/setups/extrude_geometry.jl new file mode 100644 index 0000000000..82df9aaa31 --- /dev/null +++ b/src/setups/extrude_geometry.jl @@ -0,0 +1,259 @@ +@doc raw""" + extrude_geometry(geometry; particle_spacing, direction, n_extrude::Integer, + velocity=zeros(length(direction)), + mass=nothing, density=nothing, pressure=0.0) + +Extrude either a line, a plane or a shape along a specific direction. + +# Arguments +- `geometry`: Either particle coordinates or an [`InitialCondition`](@ref) + defining a 2D shape to extrude to a 3D volume, or two 2D points + ``(A, B)`` defining the interval ``[A, B]`` to extrude to a plane + in 2D, or three 3D points ``(A, B, C)`` defining the parallelogram + spanned by the vectors ``\widehat{AB}`` and ``\widehat {AC}`` to extrude + to a parallelepiped. + +# Keywords +- `particle_spacing`: Spacing between the particles. Can be omitted when `geometry` is an + `InitialCondition` (unless `geometry.particle_spacing == -1`). +- `direction`: A vector that specifies the direction in which to extrude. +- `n_extrude`: Number of layers of particles created in the direction of extrusion. +- `velocity`: Either a function mapping each particle's coordinates to its velocity, + or, for a constant fluid velocity, a vector holding this velocity. + Velocity is constant zero by default. +- `mass`: Either `nothing` (default) to automatically compute particle mass from particle + density and spacing, or a function mapping each particle's coordinates to its mass, + or a scalar for a constant mass over all particles. +- `density`: Either a function mapping each particle's coordinates to its density, + or a scalar for a constant density over all particles. + Obligatory when not using a state equation. Cannot be used together with + `state_equation`. +- `pressure`: Scalar to set the pressure of all particles to this value. + This is only used by the [`EntropicallyDampedSPHSystem`](@ref) and + will be overwritten when using an initial pressure function in the system. + Cannot be used together with hydrostatic pressure gradient. +- `tlsph`: With the [`TotalLagrangianSPHSystem`](@ref), particles need to be placed + on the boundary of the shape and not one particle radius away, as for fluids. + When `tlsph=true`, particles will be placed on the boundary of the shape. + +# Examples +```jldoctest; output = false +# Extrude a line in 2D to a plane in 2D +p1 = [0.0, 0.0] +p2 = [1.0, 1.0] + +direction = [-1.0, 1.0] + +shape = extrude_geometry((p1, p2); direction, particle_spacing=0.1, n_extrude=4, density=1000.0) + +# Extrude a parallelogram in 3D to a parallelepiped in 3D +p1 = [0.0, 0.0, 0.0] +p2 = [0.5, 1.0, 0.0] +p3 = [1.0, 0.2, 0.0] + +direction = [0.0, 0.0, 1.0] + +shape = extrude_geometry((p1, p2, p3); direction, particle_spacing=0.1, n_extrude=4, density=1000.0) + +# Extrude a 2D shape (here: a disc) to a 3D shape (here: a cylinder) +shape = SphereShape(0.1, 0.5, (0.2, 0.4), 1000.0, n_layers=3, + sphere_type=RoundSphere(end_angle=pi)) + +direction = [0.0, 0.0, 1.0] + +shape = extrude_geometry(shape; direction, particle_spacing=0.1, n_extrude=4, density=1000.0) + +# output +┌ Info: The desired size is not a multiple of the particle spacing 0.1. +└ New particle spacing is set to 0.09387239731236392. +┌ Info: The desired size is not a multiple of the particle spacing 0.1. +└ New particle spacing is set to 0.09198039027185569. +InitialCondition{Float64}(0.1, [0.44999999999999996 0.43096988312782164 … -0.23871756048182058 -0.24999999999999994; 0.4 0.4956708580912724 … 0.5001344202803415 0.4000000000000001; 0.05 0.05 … 0.35000000000000003 0.35000000000000003], [0.0 0.0 … 0.0 0.0; 0.0 0.0 … 0.0 0.0; 0.0 0.0 … 0.0 0.0], [1.0000000000000002, 1.0000000000000002, 1.0000000000000002, 1.0000000000000002, 1.0000000000000002, 1.0000000000000002, 1.0000000000000002, 1.0000000000000002, 1.0000000000000002, 1.0000000000000002 … 1.0000000000000002, 1.0000000000000002, 1.0000000000000002, 1.0000000000000002, 1.0000000000000002, 1.0000000000000002, 1.0000000000000002, 1.0000000000000002, 1.0000000000000002, 1.0000000000000002], [1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0 … 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0, 1000.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 … 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) +``` + +!!! warning "Experimental Implementation" + This is an experimental feature and may change in any future releases. +""" +function extrude_geometry(geometry; particle_spacing=-1, direction, n_extrude::Integer, + velocity=zeros(length(direction)), tlsph=false, + mass=nothing, density=nothing, pressure=0.0) + direction_ = normalize(direction) + NDIMS = length(direction_) + + if geometry isa InitialCondition && geometry.particle_spacing > 0 + if particle_spacing > 0 && particle_spacing != geometry.particle_spacing + throw(ArgumentError("`particle_spacing` must be -1 when using an `InitialCondition`")) + end + particle_spacing = geometry.particle_spacing + end + + if particle_spacing <= 0 + throw(ArgumentError("`particle_spacing` must be specified when not extruding an `InitialCondition`")) + end + + geometry = shift_plane_corners(geometry, direction_, particle_spacing, tlsph) + + face_coords, particle_spacing_ = sample_plane(geometry, particle_spacing; tlsph=tlsph) + + if !isapprox(particle_spacing, particle_spacing_, rtol=5e-2) + @info "The desired size is not a multiple of the particle spacing $particle_spacing." * + "\nNew particle spacing is set to $particle_spacing_." + end + + coords = (face_coords .+ i * particle_spacing_ * direction_ for i in 0:(n_extrude - 1)) + + # In this context, `stack` is faster than `hcat(coords...)` + coordinates = reshape(stack(coords), (NDIMS, size(face_coords, 2) * n_extrude)) + + if geometry isa InitialCondition + density = vcat(geometry.density, (geometry.density for i in 1:(n_extrude - 1))...) + end + + return InitialCondition(; coordinates, velocity, density, mass, pressure, + particle_spacing=particle_spacing_) +end + +# For corners/endpoints of a plane/line, sample the plane/line with particles. +# For 2D coordinates or an `InitialCondition`, add a third dimension. +function sample_plane(geometry::AbstractMatrix, particle_spacing; tlsph) + if size(geometry, 1) == 2 + # Extruding a 2D shape results in a 3D shape + + # When `tlsph=true`, particles will be placed on the x-y plane + coords = vcat(geometry, fill(tlsph ? 0.0 : 0.5particle_spacing, size(geometry, 2))') + + # TODO: 2D shapes not only in x-y plane but in any user-defined plane + return coords, particle_spacing + end + + return geometry, particle_spacing +end + +function sample_plane(shape::InitialCondition, particle_spacing; tlsph) + if ndims(shape) == 2 + # Extruding a 2D shape results in a 3D shape + + # When `tlsph=true`, particles will be placed on the x-y plane + coords = vcat(shape.coordinates, + fill(tlsph ? 0.0 : 0.5particle_spacing, size(shape.coordinates, 2))') + + # TODO: 2D shapes not only in x-y plane but in any user-defined plane + return coords, particle_spacing + end + + return shape.coordinates, particle_spacing +end + +function sample_plane(plane_points, particle_spacing; tlsph=nothing) + + # Convert to tuple + return sample_plane(tuple(plane_points...), particle_spacing; tlsph=nothing) +end + +function sample_plane(plane_points::NTuple{2}, particle_spacing; tlsph=nothing) + # Verify that points are in 2D space + if any(length.(plane_points) .!= 2) + throw(ArgumentError("all points must be 2D coordinates")) + end + + n_points = ceil(Int, norm(plane_points[2] - plane_points[1]) / particle_spacing) + 1 + + coords = stack(range(plane_points[1], plane_points[2], length=n_points)) + particle_spacing_new = norm(coords[:, 1] - coords[:, 2]) + + return coords, particle_spacing_new +end + +function sample_plane(plane_points::NTuple{3}, particle_spacing; tlsph=nothing) + # Verify that points are in 3D space + if any(length.(plane_points) .!= 3) + throw(ArgumentError("all points must be 3D coordinates")) + end + + point1_ = SVector{3}(plane_points[1]) + point2_ = SVector{3}(plane_points[2]) + point3_ = SVector{3}(plane_points[3]) + + # Vectors defining the edges of the parallelogram + edge1 = point2_ - point1_ + edge2 = point3_ - point1_ + + # Check if the points are collinear + if norm(cross(edge1, edge2)) == 0 + throw(ArgumentError("the points must not be collinear")) + end + + # Determine the number of points along each edge + num_points_edge1 = ceil(Int, norm(edge1) / particle_spacing) + num_points_edge2 = ceil(Int, norm(edge2) / particle_spacing) + + coords = zeros(3, (num_points_edge1 + 1) * (num_points_edge2 + 1)) + + index = 1 + for i in 0:num_points_edge1 + for j in 0:num_points_edge2 + point_on_plane = point1_ + (i / num_points_edge1) * edge1 + + (j / num_points_edge2) * edge2 + coords[:, index] = point_on_plane + index += 1 + end + end + + particle_spacing_new = min(norm(edge1 / num_points_edge1), + norm(edge2 / num_points_edge2)) + + return coords, particle_spacing_new +end + +# Shift corners of the plane/line inwards by half a particle spacing with `tlsph=false` +# because fluid particles need to be half a particle spacing away from the boundary of the shape. +function shift_plane_corners(geometry::Union{AbstractMatrix, InitialCondition}, + direction, particle_spacing, tlsph) + return geometry +end + +function shift_plane_corners(plane_points, direction, particle_spacing, tlsph) + shift_plane_corners(tuple(plane_points...), direction, particle_spacing, tlsph) +end + +function shift_plane_corners(plane_points::NTuple{2}, direction, particle_spacing, tlsph) + # With TLSPH, particles need to be AT the min coordinates and not half a particle + # spacing away from it. + (tlsph) && (return plane_points) + + plane_point1 = copy(plane_points[1]) + plane_point2 = copy(plane_points[2]) + + # Vectors shifting the points in the corresponding direction + dir1 = 0.5 * particle_spacing * direction + dir2 = 0.5 * particle_spacing * normalize(plane_point2 - plane_point1) + + plane_point1 .+= dir1 + dir2 + plane_point2 .+= dir1 - dir2 + + return (plane_point1, plane_point2) +end + +function shift_plane_corners(plane_points::NTuple{3}, direction, particle_spacing, tlsph) + # With TLSPH, particles need to be AT the min coordinates and not half a particle + # spacing away from it. + (tlsph) && (return plane_points) + + plane_point1 = copy(plane_points[1]) + plane_point2 = copy(plane_points[2]) + plane_point3 = copy(plane_points[3]) + + edge1 = normalize(plane_point2 - plane_point1) + edge2 = normalize(plane_point3 - plane_point1) + + # Vectors shifting the points in the corresponding direction + dir1 = 0.5 * particle_spacing * direction + dir2 = 0.5 * particle_spacing * edge1 + dir3 = 0.5 * particle_spacing * edge2 + + plane_point1 .+= dir1 + dir2 + dir3 + plane_point2 .+= dir1 - dir2 + dir3 + plane_point3 .+= dir1 + dir2 - dir3 + + return (plane_point1, plane_point2, plane_point3) +end diff --git a/src/setups/rectangular_shape.jl b/src/setups/rectangular_shape.jl index 5e79ebbe91..d520bbfa5f 100644 --- a/src/setups/rectangular_shape.jl +++ b/src/setups/rectangular_shape.jl @@ -77,7 +77,7 @@ function RectangularShape(particle_spacing, n_particles_per_dimension, min_coord throw(ArgumentError("`min_coordinates` must be of length $NDIMS for a $(NDIMS)D problem")) end - if density !== nothing && density < eps() + if density !== nothing && any(density .< eps()) throw(ArgumentError("`density` needs to be positive and larger than $(eps())")) end diff --git a/src/setups/rectangular_tank.jl b/src/setups/rectangular_tank.jl index f3d260191a..70b179e3be 100644 --- a/src/setups/rectangular_tank.jl +++ b/src/setups/rectangular_tank.jl @@ -149,27 +149,35 @@ struct RectangularTank{NDIMS, NDIMSt2, ELTYPE <: Real} particle_spacing, n_particles_per_dim) - if state_equation !== nothing - # Use hydrostatic pressure gradient and calculate density from inverse state - # equation, so don't pass fluid density. - fluid = RectangularShape(particle_spacing, n_particles_per_dim, zeros(NDIMS); - velocity, pressure, acceleration, state_equation, - mass=fluid_mass) - else - fluid = RectangularShape(particle_spacing, n_particles_per_dim, zeros(NDIMS); - density=fluid_density, velocity, pressure, - acceleration, state_equation, mass=fluid_mass) - end - boundary = InitialCondition(coordinates=boundary_coordinates, velocity=boundary_velocities, mass=boundary_masses, density=boundary_densities, particle_spacing=boundary_spacing) # Move the tank corner in the negative coordinate directions to the desired position - fluid.coordinates .+= min_coordinates boundary.coordinates .+= min_coordinates + if norm(fluid_size) > eps() + if state_equation !== nothing + # Use hydrostatic pressure gradient and calculate density from inverse state + # equation, so don't pass fluid density. + fluid = RectangularShape(particle_spacing, n_particles_per_dim, + zeros(NDIMS); + velocity, pressure, acceleration, state_equation, + mass=fluid_mass) + else + fluid = RectangularShape(particle_spacing, n_particles_per_dim, + zeros(NDIMS); + density=fluid_density, velocity, pressure, + acceleration, state_equation, mass=fluid_mass) + end + # Move the tank corner in the negative coordinate directions to the desired position + fluid.coordinates .+= min_coordinates + else + # Fluid is empty + fluid = InitialCondition(coordinates=zeros(ELTYPE, NDIMS, 0), density=1.0) + end + return new{NDIMS, 2 * NDIMS, ELTYPE}(fluid, boundary, fluid_size_, tank_size_, faces, face_indices, particle_spacing, spacing_ratio, n_layers, @@ -302,7 +310,7 @@ function initialize_boundaries(particle_spacing, tank_size::NTuple{2}, face_indices_4 = Array{Int, 2}(undef, n_layers, n_particles_x) # Create empty array to extend later depending on faces and corners to build - boundary_coordinates = Array{Float64, 2}(undef, 2, 0) + boundary_coordinates = Array{typeof(particle_spacing), 2}(undef, 2, 0) # Counts the global index of the particles index = 0 @@ -433,7 +441,7 @@ function initialize_boundaries(particle_spacing, tank_size::NTuple{3}, face_indices_6 = Array{Int, 2}(undef, n_layers, n_particles_x * n_particles_y) # Create empty array to extend later depending on faces and corners to build - boundary_coordinates = Array{Float64, 2}(undef, 3, 0) + boundary_coordinates = Array{typeof(particle_spacing), 2}(undef, 3, 0) # Counts the global index of the particles index = 0 diff --git a/src/setups/setups.jl b/src/setups/setups.jl index a4754edb56..f8c6258745 100644 --- a/src/setups/setups.jl +++ b/src/setups/setups.jl @@ -1,3 +1,4 @@ include("sphere_shape.jl") include("rectangular_shape.jl") include("rectangular_tank.jl") +include("extrude_geometry.jl") diff --git a/src/util.jl b/src/util.jl index 2dc86a2673..172f0a8003 100644 --- a/src/util.jl +++ b/src/util.jl @@ -26,100 +26,75 @@ function print_startup_message() println(s) end -# Enable debug timings `@trixi_timeit timer() "name" stuff...`. -# This allows us to disable timings completely by executing -# `TimerOutputs.disable_debug_timings(TrixiParticles)` -# and to enable them again by executing -# `TimerOutputs.enable_debug_timings(TrixiParticles)` -timeit_debug_enabled() = true - -# Store main timer for global timing of functions -const main_timer = TimerOutput() - -# Always call timer() to hide implementation details -timer() = main_timer - -# @trixi_timeit timer() "some label" expression -# -# Basically the same as a special case of `@timeit_debug` from -# [TimerOutputs.jl](https://github.com/KristofferC/TimerOutputs.jl), -# but without `try ... finally ... end` block. Thus, it's not exception-safe, -# but it also avoids some related performance problems. Since we do not use -# exception handling in TrixiParticles, that's not really an issue. -# -# Copied from [Trixi.jl](https://github.com/trixi-framework/Trixi.jl). -macro trixi_timeit(timer_output, label, expr) - timeit_block = quote - if timeit_debug_enabled() - local to = $(esc(timer_output)) - local enabled = to.enabled - if enabled - local accumulated_data = $(TimerOutputs.push!)(to, $(esc(label))) - end - local b0 = $(TimerOutputs.gc_bytes)() - local t0 = $(TimerOutputs.time_ns)() - end - local val = $(esc(expr)) - if timeit_debug_enabled() && enabled - $(TimerOutputs.do_accumulate!)(accumulated_data, t0, b0) - $(TimerOutputs.pop!)(to) - end - val - end -end - """ - @threaded for ... end + @threaded system for ... end +Run either a threaded CPU loop or launch a kernel on the GPU, depending on the type of `system`. Semantically the same as `Threads.@threads` when iterating over a `AbstractUnitRange` but without guarantee that the underlying implementation uses `Threads.@threads` or works for more general `for` loops. -In particular, there may be an additional check whether only one thread is used -to reduce the overhead of serial execution or the underlying threading capabilities -might be provided by other packages such as [Polyester.jl](https://github.com/JuliaSIMD/Polyester.jl). +The first argument must either be a particle system or an array from which can be derived +if the loop has to be run threaded on the CPU or launched as a kernel on the GPU. + +In particular, the underlying threading capabilities might be provided by other packages +such as [Polyester.jl](https://github.com/JuliaSIMD/Polyester.jl). !!! warn This macro does not necessarily work for general `for` loops. For example, it does not necessarily support general iterables such as `eachline(filename)`. - -Some discussion can be found at -[https://discourse.julialang.org/t/overhead-of-threads-threads/53964](https://discourse.julialang.org/t/overhead-of-threads-threads/53964) -and -[https://discourse.julialang.org/t/threads-threads-with-one-thread-how-to-remove-the-overhead/58435](https://discourse.julialang.org/t/threads-threads-with-one-thread-how-to-remove-the-overhead/58435). - -Copied from [Trixi.jl](https://github.com/trixi-framework/Trixi.jl). """ -macro threaded(expr) - # Use `esc(quote ... end)` for nested macro calls as suggested in - # https://github.com/JuliaLang/julia/issues/23221 - # - # The following code is a simple version using only `Threads.@threads` from the - # standard library with an additional check whether only a single thread is used - # to reduce some overhead (and allocations) for serial execution. - # - # return esc(quote - # let - # if Threads.nthreads() == 1 - # $(expr) - # else - # Threads.@threads $(expr) - # end - # end - # end) - # - # However, the code below using `@batch` from Polyester.jl is more efficient, - # since this packages provides threads with less overhead. Since it is written - # by Chris Elrod, the author of LoopVectorization.jl, we expect this package - # to provide the most efficient and useful implementation of threads (as we use - # them) available in Julia. - # !!! danger "Heisenbug" - # Look at the comments for `wrap_array` when considering to change this macro. - +macro threaded(system, expr) + # Reverse-engineer the for loop. + # `expr.args[1]` is the head of the for loop, like `i = eachindex(x)`. + # So, `expr.args[1].args[2]` is the iterator `eachindex(x)` + # and `expr.args[1].args[1]` is the loop variable `i`. + iterator = expr.args[1].args[2] + i = expr.args[1].args[1] + inner_loop = expr.args[2] + + # Assemble the for loop again as a call to `parallel_foreach`, using `$i` to use the + # same loop variable as used in the for loop. return esc(quote - TrixiParticles.@batch $(expr) + TrixiParticles.parallel_foreach($iterator, $system) do $i + $inner_loop + end end) end +# Use `Polyester.@batch` for low-overhead threading +@inline function parallel_foreach(f, iterator, system) + Polyester.@batch for i in iterator + @inline f(i) + end +end + +# On GPUs, execute `f` inside a GPU kernel with KernelAbstractions.jl +@inline function parallel_foreach(f, iterator, system::Union{GPUSystem, AbstractGPUArray}) + # On the GPU, we can only loop over `1:N`. Therefore, we loop over `1:length(iterator)` + # and index with `iterator[eachindex(iterator)[i]]`. + # Note that this only works with vector-like iterators that support arbitrary indexing. + indices = eachindex(iterator) + ndrange = length(indices) + + # Skip empty loops + ndrange == 0 && return + + backend = KernelAbstractions.get_backend(system) + + # Call the generic kernel that is defined below, which only calls a function with + # the global GPU index. + generic_kernel(backend)(ndrange=ndrange) do i + @inline f(iterator[indices[i]]) + end + + KernelAbstractions.synchronize(backend) +end + +@kernel function generic_kernel(f) + i = @index(Global) + @inline f(i) +end + @doc raw""" examples_dir() @@ -206,3 +181,21 @@ end function type2string(type) return string(nameof(typeof(type))) end + +function compute_git_hash() + pkg_directory = pkgdir(@__MODULE__) + git_directory = joinpath(pkg_directory, ".git") + + # Check if the .git directory exists + if !isdir(git_directory) + return "UnknownVersion" + end + + try + git_cmd = Cmd(`git describe --tags --always --first-parent --dirty`, + dir=pkg_directory) + return string(readchomp(git_cmd)) + catch e + return "UnknownVersion" + end +end diff --git a/src/visualization/recipes_plots.jl b/src/visualization/recipes_plots.jl index a3fb7de7eb..f7e4d5b234 100644 --- a/src/visualization/recipes_plots.jl +++ b/src/visualization/recipes_plots.jl @@ -12,10 +12,8 @@ end RecipesBase.@recipe function f(v_ode, u_ode, semi::Semidiscretization; size=(600, 400)) # Default size systems_data = map(semi.systems) do system - (; initial_condition) = system - u = wrap_u(u_ode, system, semi) - coordinates = current_coordinates(u, system) + coordinates = active_coordinates(u, system) x = collect(coordinates[1, :]) y = collect(coordinates[2, :]) @@ -24,8 +22,8 @@ RecipesBase.@recipe function f(v_ode, u_ode, semi::Semidiscretization; particle_spacing = 0.0 end - x_min, y_min = minimum(initial_condition.coordinates, dims=2) .- 0.5particle_spacing - x_max, y_max = maximum(initial_condition.coordinates, dims=2) .+ 0.5particle_spacing + x_min, y_min = minimum(coordinates, dims=2) .- 0.5particle_spacing + x_max, y_max = maximum(coordinates, dims=2) .+ 0.5particle_spacing return (; x, y, x_min, x_max, y_min, y_max, particle_spacing, label=timer_name(system)) diff --git a/src/visualization/write2vtk.jl b/src/visualization/write2vtk.jl index 9db300269a..c316e8c9c0 100644 --- a/src/visualization/write2vtk.jl +++ b/src/visualization/write2vtk.jl @@ -1,3 +1,13 @@ +function system_names(systems) + # Add `_i` to each system name, where `i` is the index of the corresponding + # system type. + # `["fluid", "boundary", "boundary"]` becomes `["fluid_1", "boundary_1", "boundary_2"]`. + cnames = vtkname.(systems) + filenames = [string(cnames[i], "_", count(==(cnames[i]), cnames[1:i])) + for i in eachindex(cnames)] + return filenames +end + """ trixi2vtk(vu_ode, semi, t; iter=nothing, output_directory="out", prefix="", write_meta_data=true, max_coordinates=Inf, custom_quantities...) @@ -39,13 +49,15 @@ trixi2vtk(sol.u[end], semi, 0.0, iter=1, my_custom_quantity=kinetic_energy) ``` """ function trixi2vtk(vu_ode, semi, t; iter=nothing, output_directory="out", prefix="", - write_meta_data=true, max_coordinates=Inf, custom_quantities...) + write_meta_data=true, git_hash=compute_git_hash(), + max_coordinates=Inf, custom_quantities...) (; systems) = semi v_ode, u_ode = vu_ode.x # Update quantities that are stored in the systems. These quantities (e.g. pressure) # still have the values from the last stage of the previous step if not updated here. - update_systems_and_nhs(v_ode, u_ode, semi, t) + @trixi_timeit timer() "update systems" update_systems_and_nhs(v_ode, u_ode, semi, t; + update_from_callback=true) filenames = system_names(systems) @@ -57,17 +69,15 @@ function trixi2vtk(vu_ode, semi, t; iter=nothing, output_directory="out", prefix periodic_box = get_neighborhood_search(system, semi).periodic_box trixi2vtk(v, u, t, system, periodic_box; - output_directory=output_directory, - system_name=filenames[system_index], iter=iter, prefix=prefix, - write_meta_data=write_meta_data, max_coordinates=max_coordinates, - custom_quantities...) + system_name=filenames[system_index], output_directory, iter, prefix, + write_meta_data, git_hash, max_coordinates, custom_quantities...) end end # Convert data for a single TrixiParticle system to VTK format function trixi2vtk(v, u, t, system, periodic_box; output_directory="out", prefix="", iter=nothing, system_name=vtkname(system), write_meta_data=true, - max_coordinates=Inf, + max_coordinates=Inf, git_hash=compute_git_hash(), custom_quantities...) mkpath(output_directory) @@ -85,7 +95,8 @@ function trixi2vtk(v, u, t, system, periodic_box; output_directory="out", prefix # Reset the collection when the iteration is 0 pvd = paraview_collection(collection_file; append=iter > 0) - points = periodic_coords(current_coordinates(u, system), periodic_box) + points = PointNeighbors.periodic_coords(active_coordinates(u, system), + periodic_box) cells = [MeshCell(VTKCellTypes.VTK_VERTEX, (i,)) for i in axes(points, 2)] if abs(maximum(points)) > max_coordinates || abs(minimum(points)) > max_coordinates @@ -97,15 +108,16 @@ function trixi2vtk(v, u, t, system, periodic_box; output_directory="out", prefix end end - vtk_grid(file, points, cells) do vtk + @trixi_timeit timer() "write to vtk" vtk_grid(file, points, cells) do vtk + # dispatches based on the different system types e.g. FluidSystem, TotalLagrangianSPHSystem write2vtk!(vtk, v, u, t, system, write_meta_data=write_meta_data) # Store particle index - vtk["index"] = eachparticle(system) + vtk["index"] = active_particles(system) vtk["time"] = t if write_meta_data - vtk["solver_version"] = get_git_hash() + vtk["solver_version"] = git_hash vtk["julia_version"] = string(VERSION) end @@ -133,7 +145,8 @@ function custom_quantity(quantity, v, u, t, system) end """ - trixi2vtk(coordinates; output_directory="out", prefix="", filename="coordinates") + trixi2vtk(coordinates; output_directory="out", prefix="", filename="coordinates", + custom_quantities...) Convert coordinate data to VTK format. @@ -144,6 +157,7 @@ Convert coordinate data to VTK format. - `output_directory="out"`: Output directory path. - `prefix=""`: Prefix for the output file. - `filename="coordinates"`: Name of the output file. +- `custom_quantities...`: Additional custom quantities to include in the VTK output. # Returns - `file::AbstractString`: Path to the generated VTK file. @@ -172,12 +186,46 @@ function trixi2vtk(coordinates; output_directory="out", prefix="", filename="coo return file end -function write2vtk!(vtk, v, u, t, system::FluidSystem; write_meta_data=true) +""" + trixi2vtk(initial_condition::InitialCondition; output_directory="out", + prefix="", filename="initial_condition", custom_quantities...) + +Convert [`InitialCondition`](@ref) data to VTK format. + +# Arguments +- `initial_condition`: [`InitialCondition`](@ref) to be saved. + +# Keywords +- `output_directory="out"`: Output directory path. +- `prefix=""`: Prefix for the output file. +- `filename="coordinates"`: Name of the output file. +- `custom_quantities...`: Additional custom quantities to include in the VTK output. + +# Returns +- `file::AbstractString`: Path to the generated VTK file. +""" +function trixi2vtk(initial_condition::InitialCondition; output_directory="out", + prefix="", filename="initial_condition", custom_quantities...) + (; coordinates, velocity, density, mass, pressure) = initial_condition + + return trixi2vtk(coordinates; output_directory, prefix, filename, + density=density, initial_velocity=velocity, mass=mass, + pressure=pressure, custom_quantities...) +end + +function write2vtk!(vtk, v, u, t, system; write_meta_data=true) vtk["velocity"] = view(v, 1:ndims(system), :) + + return vtk +end + +function write2vtk!(vtk, v, u, t, system::FluidSystem; write_meta_data=true) + vtk["velocity"] = [current_velocity(v, system, particle) + for particle in active_particles(system)] vtk["density"] = [particle_density(v, system, particle) - for particle in eachparticle(system)] + for particle in active_particles(system)] vtk["pressure"] = [particle_pressure(v, system, particle) - for particle in eachparticle(system)] + for particle in active_particles(system)] if write_meta_data vtk["acceleration"] = system.acceleration @@ -210,7 +258,7 @@ end write2vtk!(vtk, viscosity::Nothing) = vtk -function write2vtk!(vtk, viscosity::ViscosityAdami) +function write2vtk!(vtk, viscosity::Union{ViscosityAdami, ViscosityMorris}) vtk["viscosity_nu"] = viscosity.nu vtk["viscosity_epsilon"] = viscosity.epsilon end @@ -252,6 +300,37 @@ function write2vtk!(vtk, v, u, t, system::TotalLagrangianSPHSystem; write_meta_d write2vtk!(vtk, v, u, t, system.boundary_model, system, write_meta_data=write_meta_data) end +function write2vtk!(vtk, v, u, t, system::OpenBoundarySPHSystem; write_meta_data=true) + (; reference_velocity, reference_pressure, reference_density) = system + + vtk["velocity"] = [current_velocity(v, system, particle) + for particle in active_particles(system)] + vtk["density"] = [particle_density(v, system, particle) + for particle in active_particles(system)] + vtk["pressure"] = [particle_pressure(v, system, particle) + for particle in active_particles(system)] + + NDIMS = ndims(system) + ELTYPE = eltype(system) + coords = reinterpret(reshape, SVector{NDIMS, ELTYPE}, active_coordinates(u, system)) + + vtk["prescribed_velocity"] = stack(reference_velocity.(coords, t)) + vtk["prescribed_density"] = reference_density.(coords, t) + vtk["prescribed_pressure"] = reference_pressure.(coords, t) + + if write_meta_data + vtk["boundary_zone"] = type2string(system.boundary_zone) + vtk["sound_speed"] = system.sound_speed + vtk["width"] = round(system.boundary_zone.zone_width, digits=3) + vtk["flow_direction"] = system.flow_direction + vtk["velocity_function"] = string(nameof(system.reference_velocity)) + vtk["pressure_function"] = string(nameof(system.reference_pressure)) + vtk["density_function"] = string(nameof(system.reference_density)) + end + + return vtk +end + function write2vtk!(vtk, v, u, t, system::BoundarySPHSystem; write_meta_data=true) write2vtk!(vtk, v, u, t, system.boundary_model, system, write_meta_data=write_meta_data) end @@ -277,35 +356,14 @@ function write2vtk!(vtk, v, u, t, model::BoundaryModelDummyParticles, system; vtk["smoothing_length"] = system.boundary_model.smoothing_length vtk["density_calculator"] = type2string(system.boundary_model.density_calculator) vtk["state_equation"] = type2string(system.boundary_model.state_equation) + vtk["viscosity_model"] = type2string(model.viscosity) end - write2vtk!(vtk, v, u, t, model, model.viscosity, system, - write_meta_data=write_meta_data) -end - -function write2vtk!(vtk, v, u, t, model::BoundaryModelDummyParticles, viscosity, system; - write_meta_data=true) - vtk["hydrodynamic_density"] = [particle_density(v, system, particle) - for particle in eachparticle(system)] - vtk["pressure"] = model.pressure - - if write_meta_data - vtk["viscosity_model"] = type2string(viscosity) - end - - return vtk -end - -function write2vtk!(vtk, v, u, t, model::BoundaryModelDummyParticles, - viscosity::ViscosityAdami, system; write_meta_data=true) vtk["hydrodynamic_density"] = [particle_density(v, system, particle) for particle in eachparticle(system)] vtk["pressure"] = model.pressure - vtk["wall_velocity"] = view(model.cache.wall_velocity, 1:ndims(system), :) - if write_meta_data - vtk["viscosity_model"] = "ViscosityAdami" + if model.viscosity isa ViscosityAdami + vtk["wall_velocity"] = view(model.cache.wall_velocity, 1:ndims(system), :) end - - return vtk end diff --git a/test/Project.toml b/test/Project.toml index a591c74153..ef78271e39 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -16,7 +16,7 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [compat] CSV = "0.10" -CairoMakie = "0.11" +CairoMakie = "0.12" DataFrames = "1.6" GLM = "1.9" Glob = "1.3" diff --git a/test/callbacks/callbacks.jl b/test/callbacks/callbacks.jl index 3076b92743..274fde65d4 100644 --- a/test/callbacks/callbacks.jl +++ b/test/callbacks/callbacks.jl @@ -2,4 +2,6 @@ include("info.jl") include("stepsize.jl") include("postprocess.jl") + include("update.jl") + include("solution_saving.jl") end diff --git a/test/callbacks/solution_saving.jl b/test/callbacks/solution_saving.jl new file mode 100644 index 0000000000..3cc765b658 --- /dev/null +++ b/test/callbacks/solution_saving.jl @@ -0,0 +1,68 @@ +@testset verbose=true "SolutionSavingCallback" begin + @testset verbose=true "show" begin + out = joinpath(pkgdir(TrixiParticles), "out") + output_directory_padded = out * " "^(65 - length(out)) + + @testset verbose=true "dt" begin + callback = SolutionSavingCallback(dt=0.02, prefix="test", output_directory=out) + + show_compact = "SolutionSavingCallback(dt=0.02)" + @test repr(callback) == show_compact + + show_box = """ + ┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ SolutionSavingCallback │ + │ ══════════════════════ │ + │ dt: ……………………………………………………………………… 0.02 │ + │ custom quantities: ……………………………… nothing │ + │ save initial solution: …………………… yes │ + │ save final solution: ………………………… yes │ + │ output directory: ………………………………… $(output_directory_padded)│ + │ prefix: …………………………………………………………… test │ + └──────────────────────────────────────────────────────────────────────────────────────────────────┘""" + @test repr("text/plain", callback) == show_box + end + + @testset verbose=true "interval" begin + callback = SolutionSavingCallback(interval=100, prefix="test", + output_directory=out) + + show_compact = "SolutionSavingCallback(interval=100)" + @test repr(callback) == show_compact + + show_box = """ + ┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ SolutionSavingCallback │ + │ ══════════════════════ │ + │ interval: ……………………………………………………… 100 │ + │ custom quantities: ……………………………… nothing │ + │ save initial solution: …………………… yes │ + │ save final solution: ………………………… yes │ + │ output directory: ………………………………… $(output_directory_padded)│ + │ prefix: …………………………………………………………… test │ + └──────────────────────────────────────────────────────────────────────────────────────────────────┘""" + @test repr("text/plain", callback) == show_box + end + + @testset verbose=true "interval" begin + callback = SolutionSavingCallback(save_times=[1.0, 2.0, 3.0], prefix="test", + output_directory=out) + + show_compact = "SolutionSavingCallback(save_times=[1.0, 2.0, 3.0])" + @test repr(callback) == show_compact + + show_box = """ + ┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ SolutionSavingCallback │ + │ ══════════════════════ │ + │ save_times: ………………………………………………… [1.0, 2.0, 3.0] │ + │ custom quantities: ……………………………… nothing │ + │ save initial solution: …………………… yes │ + │ save final solution: ………………………… yes │ + │ output directory: ………………………………… $(output_directory_padded)│ + │ prefix: …………………………………………………………… test │ + └──────────────────────────────────────────────────────────────────────────────────────────────────┘""" + @test repr("text/plain", callback) == show_box + end + end +end diff --git a/test/callbacks/update.jl b/test/callbacks/update.jl new file mode 100644 index 0000000000..180f0bb97a --- /dev/null +++ b/test/callbacks/update.jl @@ -0,0 +1,48 @@ +@testset verbose=true "UpdateCallback" begin + @testset verbose=true "show" begin + # Default + callback0 = UpdateCallback() + + show_compact = "UpdateCallback(interval=1)" + @test repr(callback0) == show_compact + + show_box = """ + ┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ UpdateCallback │ + │ ══════════════ │ + │ interval: ……………………………………………………… 1 │ + └──────────────────────────────────────────────────────────────────────────────────────────────────┘""" + @test repr("text/plain", callback0) == show_box + + callback1 = UpdateCallback(interval=11) + + show_compact = "UpdateCallback(interval=11)" + @test repr(callback1) == show_compact + + show_box = """ + ┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ UpdateCallback │ + │ ══════════════ │ + │ interval: ……………………………………………………… 11 │ + └──────────────────────────────────────────────────────────────────────────────────────────────────┘""" + @test repr("text/plain", callback1) == show_box + + callback2 = UpdateCallback(dt=1.2) + + show_compact = "UpdateCallback(dt=1.2)" + @test repr(callback2) == show_compact + + show_box = """ + ┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ UpdateCallback │ + │ ══════════════ │ + │ dt: ……………………………………………………………………… 1.2 │ + └──────────────────────────────────────────────────────────────────────────────────────────────────┘""" + @test repr("text/plain", callback2) == show_box + end + + @testset "Illegal Input" begin + error_str = "Setting both interval and dt is not supported!" + @test_throws ArgumentError(error_str) UpdateCallback(dt=0.1, interval=1) + end +end diff --git a/test/count_allocations.jl b/test/count_allocations.jl index 6a87e9868c..5957bc67a9 100644 --- a/test/count_allocations.jl +++ b/test/count_allocations.jl @@ -16,18 +16,23 @@ function copy_semi_with_no_update_nhs(semi) neighborhood_searches) end -# Forward `for_particle_neighbor` to wrapped neighborhood search -@inline function TrixiParticles.for_particle_neighbor(f, system_coords, neighbor_coords, - neighborhood_search::NoUpdateNeighborhoodSearch; - particles=axes(system_coords, 2), - parallel=true) - TrixiParticles.for_particle_neighbor(f, system_coords, neighbor_coords, - neighborhood_search.nhs, - particles=particles, parallel=parallel) +# Forward `foreach_neighbor` to wrapped neighborhood search +@inline function TrixiParticles.PointNeighbors.foreach_neighbor(f, system_coords, + neighbor_coords, + neighborhood_search::NoUpdateNeighborhoodSearch, + particle; + search_radius=neighborhood_search.nhs.search_radius) + TrixiParticles.PointNeighbors.foreach_neighbor(f, system_coords, neighbor_coords, + neighborhood_search.nhs, particle, + search_radius=search_radius) end # No update -@inline TrixiParticles.update!(search::NoUpdateNeighborhoodSearch, coords_fun) = search +@inline function TrixiParticles.PointNeighbors.update!(search::NoUpdateNeighborhoodSearch, + x, y; + particles_moving=(true, true)) + return search +end # Count allocations of one call to the right-hand side (`kick!` + `drift!`) function count_rhs_allocations(sol, semi) @@ -41,7 +46,7 @@ function count_rhs_allocations(sol, semi) try # Disable timers, which cause extra allocations - TrixiParticles.TimerOutputs.disable_debug_timings(TrixiParticles) + TrixiParticles.disable_debug_timings() # Disable multithreading, which causes extra allocations return disable_polyester_threads() do @@ -53,7 +58,7 @@ function count_rhs_allocations(sol, semi) end finally # Enable timers again - @invokelatest TrixiParticles.TimerOutputs.enable_debug_timings(TrixiParticles) + @invokelatest TrixiParticles.enable_debug_timings() end end diff --git a/test/examples/examples.jl b/test/examples/examples.jl index 3611cf0930..08d3bc9095 100644 --- a/test/examples/examples.jl +++ b/test/examples/examples.jl @@ -8,7 +8,7 @@ "oscillating_drop_2d.jl")) @test sol.retcode == ReturnCode.Success # This error varies between serial and multithreaded runs - @test isapprox(error_A, 0.0001717690010767381, atol=5e-7) + @test isapprox(error_A, 0.0, atol=1.73e-4) @test count_rhs_allocations(sol, semi) == 0 end @@ -76,18 +76,23 @@ @test count_rhs_allocations(sol, semi) == 0 end - @trixi_testset "fluid/moving_wall_2d.jl" begin - @test_nowarn_mod trixi_include(@__MODULE__, tspan=(0.0, 0.5), + @trixi_testset "fluid/dam_break_2d.jl" begin + @test_nowarn_mod trixi_include(@__MODULE__, joinpath(examples_dir(), "fluid", - "moving_wall_2d.jl")) + "dam_break_2d.jl"), tspan=(0.0, 0.1)) [ + r"┌ Info: The desired tank length in y-direction .*\n", + r"└ New tank length in y-direction.*\n", + ] @test sol.retcode == ReturnCode.Success @test count_rhs_allocations(sol, semi) == 0 end - @trixi_testset "fluid/dam_break_2d.jl" begin + @trixi_testset "fluid/dam_break_2d.jl with KernelAbstractions.jl" begin + # Emulate the GPU code on the CPU by passing `data_type = Array` @test_nowarn_mod trixi_include(@__MODULE__, joinpath(examples_dir(), "fluid", - "dam_break_2d.jl"), tspan=(0.0, 0.1)) [ + "dam_break_2d.jl"), tspan=(0.0, 0.1), + data_type=Array) [ r"┌ Info: The desired tank length in y-direction .*\n", r"└ New tank length in y-direction.*\n", ] @@ -122,6 +127,76 @@ @test count_rhs_allocations(sol, semi) == 0 end + @trixi_testset "fluid/pipe_flow_2d.jl" begin + @test_nowarn_mod trixi_include(@__MODULE__, tspan=(0.0, 0.5), + joinpath(examples_dir(), "fluid", + "pipe_flow_2d.jl")) + @test sol.retcode == ReturnCode.Success + @test count_rhs_allocations(sol, semi) == 0 + end + + @trixi_testset "fluid/dam_break_2d_surface_tension.jl" begin + @test_nowarn_mod trixi_include(@__MODULE__, + joinpath(examples_dir(), "fluid", + "dam_break_2d_surface_tension.jl"), + tspan=(0.0, 0.1)) + @test sol.retcode == ReturnCode.Success + @test count_rhs_allocations(sol, semi) == 0 + end + + @trixi_testset "fluid/sphere_surface_tension_2d.jl" begin + @test_nowarn_mod trixi_include(@__MODULE__, + joinpath(examples_dir(), "fluid", + "sphere_surface_tension_2d.jl")) + @test sol.retcode == ReturnCode.Success + @test count_rhs_allocations(sol, semi) == 0 + end + + @trixi_testset "fluid/sphere_surface_tension_3d.jl" begin + @test_nowarn_mod trixi_include(@__MODULE__, + joinpath(examples_dir(), "fluid", + "sphere_surface_tension_3d.jl")) + @test sol.retcode == ReturnCode.Success + @test count_rhs_allocations(sol, semi) == 0 + end + + @trixi_testset "fluid/falling_water_spheres_2d.jl" begin + @test_nowarn_mod trixi_include(@__MODULE__, + joinpath(examples_dir(), "fluid", + "falling_water_spheres_2d.jl")) + @test sol.retcode == ReturnCode.Success + @test count_rhs_allocations(sol, semi) == 0 + end + + @trixi_testset "fluid/falling_water_spheres_3d.jl" begin + @test_nowarn_mod trixi_include(@__MODULE__, + joinpath(examples_dir(), "fluid", + "falling_water_spheres_3d.jl")) [ + r"┌ Info: The desired tank length in x-direction .*\n", + r"└ New tank length in x-direction.*\n", + r"┌ Info: The desired tank length in y-direction .*\n", + r"└ New tank length in y-direction.*\n", + r"┌ Info: The desired tank length in z-direction .*\n", + r"└ New tank length in z-direction.*\n", + ] + @test sol.retcode == ReturnCode.Success + @test count_rhs_allocations(sol, semi) == 0 + end + + @trixi_testset "fluid/sphere_surface_tension_wall_2d.jl" begin + @test_nowarn_mod trixi_include(@__MODULE__, + joinpath(examples_dir(), "fluid", + "sphere_surface_tension_wall_2d.jl")) + end + + @trixi_testset "fluid/moving_wall_2d.jl" begin + @test_nowarn_mod trixi_include(@__MODULE__, tspan=(0.0, 0.5), + joinpath(examples_dir(), "fluid", + "moving_wall_2d.jl")) + @test sol.retcode == ReturnCode.Success + @test count_rhs_allocations(sol, semi) == 0 + end + include("dam_break_2d_corrections.jl") end @@ -146,11 +221,11 @@ @test count_rhs_allocations(sol, semi) == 0 end - @trixi_testset "fsi/dam_break_2d.jl" begin + @trixi_testset "fsi/dam_break_plate_2d.jl" begin # Use rounded dimensions to avoid warnings @test_nowarn_mod trixi_include(@__MODULE__, joinpath(examples_dir(), "fsi", - "dam_break_2d.jl"), + "dam_break_plate_2d.jl"), initial_fluid_size=(0.15, 0.29), tspan=(0.0, 0.4), dtmax=1e-3) @@ -190,7 +265,9 @@ @trixi_testset "n_body/n_body_benchmark_trixi.jl" begin @test_nowarn_mod trixi_include(@__MODULE__, joinpath(examples_dir(), "n_body", - "n_body_benchmark_trixi.jl")) + "n_body_benchmark_trixi.jl")) [ + r"WARNING: Method definition interact!.*\n", + ] end @trixi_testset "n_body/n_body_benchmark_reference.jl" begin @@ -235,3 +312,14 @@ end end end + +@testset verbose=true "DEM" begin + @trixi_testset "dem/rectangular_tank_2d.jl" begin + @test_nowarn_mod trixi_include(@__MODULE__, + joinpath(examples_dir(), "dem", + "rectangular_tank_2d.jl"), + tspan=(0.0, 0.1)) + @test sol.retcode == ReturnCode.Success + @test count_rhs_allocations(sol, semi) == 0 + end +end diff --git a/test/general/buffer.jl b/test/general/buffer.jl new file mode 100644 index 0000000000..7a8371ea72 --- /dev/null +++ b/test/general/buffer.jl @@ -0,0 +1,68 @@ +@testset verbose=true "`SystemBuffer`" begin + # Mock fluid system + struct FluidSystemMock3 <: TrixiParticles.FluidSystem{2, Nothing} end + + inflow = InFlow(; plane=([0.0, 0.0], [0.0, 1.0]), particle_spacing=0.2, + open_boundary_layers=2, density=1.0, flow_direction=[1.0, 0.0]) + system = OpenBoundarySPHSystem(inflow; sound_speed=1.0, fluid_system=FluidSystemMock3(), + buffer_size=0) + system_buffer = OpenBoundarySPHSystem(inflow; sound_speed=1.0, buffer_size=5, + fluid_system=FluidSystemMock3()) + + n_particles = nparticles(system) + + @testset "Iterators" begin + @test TrixiParticles.each_moving_particle(system) == 1:n_particles + + @test TrixiParticles.each_moving_particle(system_buffer) == 1:n_particles + + particle_id = TrixiParticles.activate_next_particle(system_buffer) + + TrixiParticles.update_system_buffer!(system_buffer.buffer) + + @test TrixiParticles.each_moving_particle(system_buffer) == 1:(n_particles + 1) + + TrixiParticles.deactivate_particle!(system_buffer, particle_id, + ones(2, particle_id)) + + TrixiParticles.update_system_buffer!(system_buffer.buffer) + + @test TrixiParticles.each_moving_particle(system_buffer) == 1:n_particles + + particle_id = 5 + TrixiParticles.deactivate_particle!(system_buffer, particle_id, + ones(2, particle_id)) + + TrixiParticles.update_system_buffer!(system_buffer.buffer) + + @test TrixiParticles.each_moving_particle(system_buffer) == + setdiff(1:n_particles, particle_id) + end + + @testset "Allocate Buffer" begin + initial_condition = rectangular_patch(0.1, (3, 3), perturbation_factor=0.0) + buffer = TrixiParticles.SystemBuffer(nparticles(initial_condition), 7) + + ic_with_buffer = TrixiParticles.allocate_buffer(initial_condition, buffer) + + @test nparticles(ic_with_buffer) == nparticles(initial_condition) + 7 + + masses = initial_condition.mass[1] .* ones(nparticles(ic_with_buffer)) + @test masses == ic_with_buffer.mass + + densities = initial_condition.density[1] .* ones(nparticles(ic_with_buffer)) + @test densities == ic_with_buffer.density + + pressures = initial_condition.pressure[1] .* ones(nparticles(ic_with_buffer)) + @test pressures == ic_with_buffer.pressure + + @testset "Illegal Input" begin + # The rectangular patch has a perturbed, non-constant density + ic = rectangular_patch(0.1, (3, 3)) + buffer = TrixiParticles.SystemBuffer(9, 7) + + error_str = "`initial_condition.density` needs to be constant when using `SystemBuffer`" + @test_throws ArgumentError(error_str) TrixiParticles.allocate_buffer(ic, buffer) + end + end +end diff --git a/test/general/general.jl b/test/general/general.jl index b59cff7892..27f60ebba8 100644 --- a/test/general/general.jl +++ b/test/general/general.jl @@ -3,3 +3,4 @@ include("smoothing_kernels.jl") include("density_calculator.jl") include("semidiscretization.jl") include("interpolation.jl") +include("buffer.jl") diff --git a/test/general/interpolation.jl b/test/general/interpolation.jl index f45534f161..899e605283 100644 --- a/test/general/interpolation.jl +++ b/test/general/interpolation.jl @@ -115,16 +115,12 @@ # Density is integrated with `ContinuityDensity` v_no_bnd = vcat(fluid.velocity, fluid.density') - sol_no_boundary = (; u=[(; x=(v_no_bnd, u_no_bnd))]) - u_bnd = hcat(fluid.coordinates, bnd.coordinates) v_bnd_velocity = hcat(fluid.velocity, bnd.velocity) v_bnd_density = vcat(fluid.density, bnd.density) v_bnd = vcat(v_bnd_velocity, v_bnd_density') - sol_boundary = (; u=[(; x=(v_bnd, u_bnd))]) - semi_no_boundary = Semidiscretization(fluid_system, neighborhood_search=GridNeighborhoodSearch) semi_boundary = Semidiscretization(fluid_system, boundary_system, @@ -139,7 +135,8 @@ interpolation_walldistance(y) = TrixiParticles.interpolate_point([0.0, y], semi_no_boundary, fluid_system, - sol_no_boundary, + v_no_bnd, + u_no_bnd, cut_off_bnd=cut_off_bnd) # top outside @@ -205,7 +202,7 @@ result_multipoint = TrixiParticles.interpolate_point(multi_point_coords, semi_no_boundary, fluid_system, - sol_no_boundary, + v_no_bnd, u_no_bnd, cut_off_bnd=cut_off_bnd) expected_multi = (density=[666.0, 666.0000000000001, 666.0], @@ -224,13 +221,13 @@ result_endpoint = TrixiParticles.interpolate_line([1.0, -0.05], [1.0, 1.0], 5, semi_no_boundary, fluid_system, - sol_no_boundary, + v_no_bnd, u_no_bnd, endpoint=true, cut_off_bnd=cut_off_bnd) result = TrixiParticles.interpolate_line([1.0, -0.05], [1.0, 1.0], 5, semi_no_boundary, - fluid_system, sol_no_boundary, + fluid_system, v_no_bnd, u_no_bnd, endpoint=false, cut_off_bnd=cut_off_bnd) @@ -282,7 +279,7 @@ result = interpolate_plane_2d(interpolation_start, interpolation_end, resolution, semi_no_boundary, - fluid_system, sol_no_boundary) + fluid_system, v_no_bnd, u_no_bnd) expected_res = (density=[ 666.0, 666.0, 666.0, 666.0, 666.0, 666.0, 666.0, @@ -403,7 +400,7 @@ result = interpolate_plane_2d(interpolation_start, interpolation_end, resolution, semi_no_boundary, fluid_system, - sol_no_boundary, + v_no_bnd, u_no_bnd, smoothing_length=0.5 * smoothing_length) expected_res = (density=[ 666.0, 666.0, 666.0, 666.0, 666.0, 666.0, 666.0, 666.0, @@ -490,7 +487,8 @@ interpolation_walldistance(y) = TrixiParticles.interpolate_point([0.0, y], semi_boundary, fluid_system, - sol_boundary, + v_bnd, + u_bnd, cut_off_bnd=cut_off_bnd) # top outside @@ -578,7 +576,7 @@ result_multipoint = TrixiParticles.interpolate_point(multi_point_coords, semi_boundary, fluid_system, - sol_boundary, + v_bnd, u_bnd, cut_off_bnd=cut_off_bnd) if cut_off_bnd expected_multi = (density=[666.0, 666.0000000000001, 666.0], @@ -618,13 +616,13 @@ result_endpoint = TrixiParticles.interpolate_line([1.0, -0.05], [1.0, 1.0], 5, semi_boundary, fluid_system, - sol_boundary, + v_bnd, u_bnd, endpoint=true, cut_off_bnd=cut_off_bnd) result = TrixiParticles.interpolate_line([1.0, -0.05], [1.0, 1.0], 5, semi_no_boundary, - fluid_system, sol_no_boundary, + fluid_system, v_no_bnd, u_no_bnd, endpoint=false, cut_off_bnd=cut_off_bnd) if cut_off_bnd @@ -823,16 +821,12 @@ # Density is integrated with `ContinuityDensity` v_no_bnd = vcat(fluid.velocity, fluid.density') - sol_no_boundary = (; u=[(; x=(v_no_bnd, u_no_bnd))]) - u_bnd = hcat(fluid.coordinates, bnd.coordinates) v_bnd_velocity = hcat(fluid.velocity, bnd.velocity) v_bnd_density = vcat(fluid.density, bnd.density) v_bnd = vcat(v_bnd_velocity, v_bnd_density') - sol_boundary = (; u=[(; x=(v_bnd, u_bnd))]) - semi_no_boundary = Semidiscretization(fluid_system, neighborhood_search=GridNeighborhoodSearch) semi_boundary = Semidiscretization(fluid_system, boundary_system, @@ -851,7 +845,8 @@ ], semi_no_boundary, fluid_system, - sol_no_boundary, + v_no_bnd, + u_no_bnd, cut_off_bnd=cut_off_bnd) # top outside @@ -920,7 +915,7 @@ result_multipoint = TrixiParticles.interpolate_point(multi_point_coords, semi_no_boundary, fluid_system, - sol_no_boundary, + v_no_bnd, u_no_bnd, cut_off_bnd=cut_off_bnd) expected_multi = (density=[666.0, 666.0, 666.0], neighbor_count=[4, 4, 9], @@ -944,7 +939,8 @@ ], semi_boundary, fluid_system, - sol_boundary, + v_no_bnd, + u_no_bnd, cut_off_bnd=cut_off_bnd) # top outside @@ -1031,7 +1027,7 @@ result_multipoint = TrixiParticles.interpolate_point(multi_point_coords, semi_no_boundary, fluid_system, - sol_no_boundary, + v_no_bnd, u_no_bnd, cut_off_bnd=cut_off_bnd) expected_multi = (density=[666.0, 666.0, 666.0], neighbor_count=[4, 4, 9], @@ -1053,7 +1049,7 @@ result = interpolate_plane_3d(p1, p2, p3, resolution, semi_no_boundary, - fluid_system, sol_no_boundary) + fluid_system, v_no_bnd, u_no_bnd) expected_res = (density=[ 666.0, diff --git a/test/general/semidiscretization.jl b/test/general/semidiscretization.jl index ac0f343003..457b5b84dc 100644 --- a/test/general/semidiscretization.jl +++ b/test/general/semidiscretization.jl @@ -1,8 +1,14 @@ # Use `@trixi_testset` to isolate the mock functions in a separate namespace @trixi_testset "Semidiscretization" begin - # Mock systems - struct System1 <: TrixiParticles.System{3} end - struct System2 <: TrixiParticles.System{3} end + # Mock systems. `System1` will use the CPU backend, `System2` is a `GPUSystem`, using + # the GPU backend (emulated on the CPU). + struct System1 <: TrixiParticles.System{3, String} end + struct System2 <: TrixiParticles.System{3, Nothing} end + + # `System2` has no field `mass`, so we have to manually define the backend + function TrixiParticles.KernelAbstractions.get_backend(::System2) + return TrixiParticles.KernelAbstractions.CPU() + end system1 = System1() system2 = System2() @@ -39,7 +45,7 @@ struct BoundaryModelMock end # Mock fluid system - struct FluidSystemMock <: TrixiParticles.FluidSystem{2} end + struct FluidSystemMock <: TrixiParticles.FluidSystem{2, Nothing} end kernel = Val(:smoothing_kernel) Base.ndims(::Val{:smoothing_kernel}) = 2 @@ -90,7 +96,7 @@ kernel, 1.0) error_str = "`WeaklyCompressibleSPHSystem` cannot be used without setting a " * - "`state_equation` for all boundary systems" + "`state_equation` for all boundary models" @test_throws ArgumentError(error_str) Semidiscretization(fluid_system, boundary_system) end @@ -131,6 +137,9 @@ v2 = zeros(4 * 3) v_ode = vcat(vec(v1), v2) + # Avoid `SystemBuffer` barrier + TrixiParticles.each_moving_particle(system::Union{System1, System2}) = TrixiParticles.eachparticle(system) + TrixiParticles.add_source_terms!(dv_ode, v_ode, u_ode, semi) dv1 = TrixiParticles.wrap_v(dv_ode, system1, semi) diff --git a/test/neighborhood_search/grid_nhs.jl b/test/neighborhood_search/grid_nhs.jl deleted file mode 100644 index d686efc5bc..0000000000 --- a/test/neighborhood_search/grid_nhs.jl +++ /dev/null @@ -1,272 +0,0 @@ -@testset verbose=true "GridNeighborhoodSearch" begin - @testset "Coordinate Limits" begin - # Test the threshold for very large and very small coordinates. - coords1 = [Inf, -Inf] - coords2 = [NaN, 0] - coords3 = [typemax(Int) + 1.0, -typemax(Int) - 1.0] - - @test TrixiParticles.cell_coords(coords1, nothing, (1.0, 1.0)) == - (typemax(Int), typemin(Int)) - @test TrixiParticles.cell_coords(coords2, nothing, (1.0, 1.0)) == (typemax(Int), 0) - @test TrixiParticles.cell_coords(coords3, nothing, (1.0, 1.0)) == - (typemax(Int), typemin(Int)) - end - - @testset "Rectangular Point Cloud 2D" begin - #### Setup - # Rectangular filled with equidistant spaced particles - # from (x, y) = (-0.25, -0.25) to (x, y) = (0.35, 0.35) - range = -0.25:0.1:0.35 - coordinates1 = hcat(collect.(Iterators.product(range, range))...) - nparticles = size(coordinates1, 2) - - particle_position1 = [0.05, 0.05] - particle_spacing = 0.1 - radius = particle_spacing - - # Create neighborhood search - nhs1 = GridNeighborhoodSearch{2}(radius, nparticles) - - coords_fun(i) = coordinates1[:, i] - TrixiParticles.initialize!(nhs1, coords_fun) - - # Get each neighbor for `particle_position1` - neighbors1 = sort(collect(TrixiParticles.eachneighbor(particle_position1, nhs1))) - - # Move particles - coordinates2 = coordinates1 .+ [1.4, -3.5] - - # Update neighborhood search - coords_fun2(i) = coordinates2[:, i] - TrixiParticles.update!(nhs1, coords_fun2) - - # Get each neighbor for updated NHS - neighbors2 = sort(collect(TrixiParticles.eachneighbor(particle_position1, nhs1))) - - # Change position - particle_position2 = particle_position1 .+ [1.4, -3.5] - - # Get each neighbor for `particle_position2` - neighbors3 = sort(collect(TrixiParticles.eachneighbor(particle_position2, nhs1))) - - # Double search radius - nhs2 = GridNeighborhoodSearch{2}(2 * radius, size(coordinates1, 2)) - TrixiParticles.initialize!(nhs2, coords_fun) - - # Get each neighbor in double search radius - neighbors4 = sort(collect(TrixiParticles.eachneighbor(particle_position1, nhs2))) - - # Move particles - coordinates2 = coordinates1 .+ [0.4, -0.4] - - # Update neighborhood search - TrixiParticles.update!(nhs2, coords_fun2) - - # Get each neighbor in double search radius - neighbors5 = sort(collect(TrixiParticles.eachneighbor(particle_position1, nhs2))) - - #### Verification - @test neighbors1 == [17, 18, 19, 24, 25, 26, 31, 32, 33] - - @test neighbors2 == Int[] - - @test neighbors3 == [17, 18, 19, 24, 25, 26, 31, 32, 33] - - @test neighbors4 == [9, 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 23, 24, 25, - 26, 27, 28, 30, 31, 32, 33, 34, 35, 37, 38, 39, 40, 41, 42, 44, 45, 46, 47, - 48, 49] - - @test neighbors5 == [36, 37, 38, 43, 44, 45] - end - - @testset "Rectangular Point Cloud 3D" begin - #### Setup - # Rectangular filled with equidistant spaced particles - # from (x, y, z) = (-0.25, -0.25, -0.25) to (x, y) = (0.35, 0.35, 0.35) - range = -0.25:0.1:0.35 - coordinates1 = hcat(collect.(Iterators.product(range, range, range))...) - nparticles = size(coordinates1, 2) - - particle_position1 = [0.05, 0.05, 0.05] - particle_spacing = 0.1 - radius = particle_spacing - - # Create neighborhood search - nhs1 = GridNeighborhoodSearch{3}(radius, nparticles) - - coords_fun(i) = coordinates1[:, i] - TrixiParticles.initialize!(nhs1, coords_fun) - - # Get each neighbor for `particle_position1` - neighbors1 = sort(collect(TrixiParticles.eachneighbor(particle_position1, nhs1))) - - # Move particles - coordinates2 = coordinates1 .+ [1.4, -3.5, 0.8] - - # Update neighborhood search - coords_fun2(i) = coordinates2[:, i] - TrixiParticles.update!(nhs1, coords_fun2) - - # Get each neighbor for updated NHS - neighbors2 = sort(collect(TrixiParticles.eachneighbor(particle_position1, nhs1))) - - # Change position - particle_position2 = particle_position1 .+ [1.4, -3.5, 0.8] - - # Get each neighbor for `particle_position2` - neighbors3 = sort(collect(TrixiParticles.eachneighbor(particle_position2, nhs1))) - - #### Verification - @test neighbors1 == - [115, 116, 117, 122, 123, 124, 129, 130, 131, 164, 165, 166, 171, 172, - 173, 178, 179, 180, 213, 214, 215, 220, 221, 222, 227, 228, 229] - - @test neighbors2 == Int[] - - @test neighbors3 == - [115, 116, 117, 122, 123, 124, 129, 130, 131, 164, 165, 166, 171, 172, - 173, 178, 179, 180, 213, 214, 215, 220, 221, 222, 227, 228, 229] - end - - @testset verbose=true "Periodicity 2D" begin - @testset "Simple Example" begin - coords = [-0.08 0.0 0.18 0.1 -0.08 - -0.12 -0.05 -0.09 0.15 0.39] - - # 3 x 6 cells - nhs = GridNeighborhoodSearch{2}(0.1, size(coords, 2), - periodic_box_min_corner=[-0.1, -0.2], - periodic_box_max_corner=[0.2, 0.4]) - - TrixiParticles.initialize!(nhs, coords) - - neighbors = [sort(collect(TrixiParticles.eachneighbor(coords[:, i], nhs))) - for i in 1:5] - - # Note that (1, 2) and (2, 3) are not neighbors, but they are in neighboring cells - @test neighbors[1] == [1, 2, 3, 5] - @test neighbors[2] == [1, 2, 3] - @test neighbors[3] == [1, 2, 3] - @test neighbors[4] == [4] - @test neighbors[5] == [1, 5] - - neighbors_loop = [Int[] for _ in axes(coords, 2)] - - TrixiParticles.for_particle_neighbor(nothing, nothing, - coords, coords, nhs, - particles=axes(coords, 2)) do particle, - neighbor, - pos_diff, - distance - append!(neighbors_loop[particle], neighbor) - end - - @test sort(neighbors_loop[1]) == [1, 3, 5] - @test sort(neighbors_loop[2]) == [2] - @test sort(neighbors_loop[3]) == [1, 3] - @test sort(neighbors_loop[4]) == [4] - @test sort(neighbors_loop[5]) == [1, 5] - end - - @testset "Rounding Up Cell Sizes" begin - coords = [-0.08 0.0 0.18 0.1 -0.08 - -0.12 -0.05 -0.09 0.15 0.42] - - # 3 x 6 cells - nhs = GridNeighborhoodSearch{2}(0.1, size(coords, 2), - periodic_box_min_corner=[-0.1, -0.2], - periodic_box_max_corner=[0.205, 0.43]) - - TrixiParticles.initialize!(nhs, coords) - - neighbors = [sort(collect(TrixiParticles.eachneighbor(coords[:, i], nhs))) - for i in 1:5] - - # Note that (1, 2) and (2, 3) are not neighbors, but they are in neighboring cells - @test neighbors[1] == [1, 2, 3, 5] - @test neighbors[2] == [1, 2, 3] - @test neighbors[3] == [1, 2, 3] - @test neighbors[4] == [4] - @test neighbors[5] == [1, 5] - - neighbors_loop = [Int[] for _ in axes(coords, 2)] - - TrixiParticles.for_particle_neighbor(nothing, nothing, - coords, coords, nhs, - particles=axes(coords, 2)) do particle, - neighbor, - pos_diff, - distance - append!(neighbors_loop[particle], neighbor) - end - - @test sort(neighbors_loop[1]) == [1, 3, 5] - @test sort(neighbors_loop[2]) == [2] - @test sort(neighbors_loop[3]) == [1, 3] - @test sort(neighbors_loop[4]) == [4] - @test sort(neighbors_loop[5]) == [1, 5] - end - - @testset "Offset Domain Triggering Split Cells" begin - # This used to trigger a "split cell bug", where the left and right boundary - # cells were only partially contained in the domain. - # The left particle was placed inside a ghost cells, which causes it to not - # see the right particle, even though it is within the search distance. - # The domain size is an integer multiple of the cell size, but the NHS did not - # offset the grid based on the domain position. - # See https://github.com/trixi-framework/TrixiParticles.jl/pull/211 for a more - # detailed explanation. - coords = [-1.4 1.9 - 0.0 0.0] - - # 5 x 1 cells - nhs = GridNeighborhoodSearch{2}(1.0, size(coords, 2), - periodic_box_min_corner=[-1.5, 0.0], - periodic_box_max_corner=[2.5, 3.0]) - - TrixiParticles.initialize!(nhs, coords) - - neighbors = [sort(unique(collect(TrixiParticles.eachneighbor(coords[:, i], nhs)))) - for i in 1:2] - - @test neighbors[1] == [1, 2] - @test neighbors[2] == [1, 2] - end - end - - @testset verbose=true "Periodicity 3D" begin - coords = [-0.08 0.0 0.18 0.1 -0.08 - -0.12 -0.05 -0.09 0.15 0.39 - 0.14 0.34 0.12 0.06 0.13] - - # 3 x 6 x 3 cells - nhs = GridNeighborhoodSearch{3}(0.1, size(coords, 2), - periodic_box_min_corner=[-0.1, -0.2, 0.05], - periodic_box_max_corner=[0.2, 0.4, 0.35]) - - TrixiParticles.initialize!(nhs, coords) - - neighbors = [sort(collect(TrixiParticles.eachneighbor(coords[:, i], nhs))) - for i in 1:5] - - # Note that (1, 2) and (2, 3) are not neighbors, but they are in neighboring cells - @test neighbors[1] == [1, 2, 3, 5] - @test neighbors[2] == [1, 2, 3] - @test neighbors[3] == [1, 2, 3] - @test neighbors[4] == [4] - @test neighbors[5] == [1, 5] - - neighbors_loop = [Int[] for _ in axes(coords, 2)] - - TrixiParticles.for_particle_neighbor(coords, coords, - nhs) do particle, neighbor, pos_diff, distance - append!(neighbors_loop[particle], neighbor) - end - - @test sort(neighbors_loop[1]) == [1, 3, 5] - @test sort(neighbors_loop[2]) == [2] - @test sort(neighbors_loop[3]) == [1, 3] - @test sort(neighbors_loop[4]) == [4] - @test sort(neighbors_loop[5]) == [1, 5] - end -end diff --git a/test/neighborhood_search/neighborhood_search.jl b/test/neighborhood_search/neighborhood_search.jl deleted file mode 100644 index 0c7270a7ed..0000000000 --- a/test/neighborhood_search/neighborhood_search.jl +++ /dev/null @@ -1,2 +0,0 @@ -include("trivial_nhs.jl") -include("grid_nhs.jl") diff --git a/test/neighborhood_search/trivial_nhs.jl b/test/neighborhood_search/trivial_nhs.jl deleted file mode 100644 index aa0b218466..0000000000 --- a/test/neighborhood_search/trivial_nhs.jl +++ /dev/null @@ -1,10 +0,0 @@ -@testset verbose=true "TrivialNeighborhoodSearch" begin - # Setup with 5 particles - nhs = TrixiParticles.TrivialNeighborhoodSearch{2}(1.0, Base.OneTo(5)) - - # Get each neighbor for arbitrary coordinates - neighbors = collect(TrixiParticles.eachneighbor([1.0, 2.0], nhs)) - - #### Verification - @test neighbors == [1, 2, 3, 4, 5] -end diff --git a/test/schemes/boundary/open_boundary/boundary_zone.jl b/test/schemes/boundary/open_boundary/boundary_zone.jl new file mode 100644 index 0000000000..7a34a4bcac --- /dev/null +++ b/test/schemes/boundary/open_boundary/boundary_zone.jl @@ -0,0 +1,221 @@ +@testset verbose=true "Boundary Zone" begin + @testset verbose=true "Boundary Zone 2D" begin + particle_spacing = 0.2 + open_boundary_layers = 4 + + plane_points_1 = [[0.0, 0.0], [0.5, -0.5], [1.0, 0.5]] + plane_points_2 = [[0.0, 1.0], [0.2, 2.0], [2.3, 0.5]] + + @testset verbose=true "Points $i" for i in eachindex(plane_points_1) + point_1 = plane_points_1[i] + point_2 = plane_points_2[i] + + plane_size = point_2 - point_1 + + flow_directions = [ + normalize([-plane_size[2], plane_size[1]]), + -normalize([-plane_size[2], plane_size[1]]), + ] + + @testset verbose=true "Flow Direction $j" for j in eachindex(flow_directions) + inflow = InFlow(; plane=(point_1, point_2), particle_spacing, + flow_direction=flow_directions[j], density=1.0, + open_boundary_layers) + outflow = OutFlow(; plane=(point_1, point_2), particle_spacing, + flow_direction=flow_directions[j], density=1.0, + open_boundary_layers) + + boundary_zones = [ + inflow, + outflow, + ] + + @testset verbose=true "$(nameof(typeof(boundary_zone)))" for boundary_zone in boundary_zones + zone_width = open_boundary_layers * + boundary_zone.initial_condition.particle_spacing + sign_ = (boundary_zone isa InFlow) ? -1 : 1 + + @test plane_points_1[i] == boundary_zone.zone_origin + @test plane_points_2[i] - boundary_zone.zone_origin == + boundary_zone.spanning_set[2] + @test isapprox(sign_ * flow_directions[j], + normalize(boundary_zone.spanning_set[1]), atol=1e-14) + @test isapprox(zone_width, norm(boundary_zone.spanning_set[1]), + atol=1e-14) + end + end + end + end + + @testset verbose=true "Boundary Zone 3D" begin + particle_spacing = 0.05 + open_boundary_layers = 4 + + plane_points_1 = [ + [0.0, 0.0, 0.0], + [0.3113730847835541, 0.19079485535621643, -0.440864622592926], + ] + plane_points_2 = [ + [1.0, 0.0, 0.0], + [-0.10468611121177673, 0.252103328704834, -0.44965094327926636], + ] + plane_points_3 = [ + [0.0, 1.0, 0.0], + [0.3113730847835541, 0.25057315826416016, -0.02374829351902008], + ] + + @testset verbose=true "Points $i" for i in eachindex(plane_points_1) + point_1 = plane_points_1[i] + point_2 = plane_points_2[i] + point_3 = plane_points_3[i] + + edge1 = point_2 - point_1 + edge2 = point_3 - point_1 + + flow_directions = [ + normalize(cross(edge1, edge2)), + -normalize(cross(edge1, edge2)), + ] + + @testset verbose=true "Flow Direction $j" for j in eachindex(flow_directions) + inflow = InFlow(; plane=(point_1, point_2, point_3), particle_spacing, + flow_direction=flow_directions[j], density=1.0, + open_boundary_layers) + outflow = OutFlow(; plane=(point_1, point_2, point_3), particle_spacing, + flow_direction=flow_directions[j], density=1.0, + open_boundary_layers) + + boundary_zones = [ + inflow, + outflow, + ] + + @testset verbose=true "$(nameof(typeof(boundary_zone)))" for boundary_zone in boundary_zones + zone_width = open_boundary_layers * + boundary_zone.initial_condition.particle_spacing + sign_ = (boundary_zone isa InFlow) ? -1 : 1 + + @test plane_points_1[i] == boundary_zone.zone_origin + @test plane_points_2[i] - boundary_zone.zone_origin == + boundary_zone.spanning_set[2] + @test plane_points_3[i] - boundary_zone.zone_origin == + boundary_zone.spanning_set[3] + @test isapprox(sign_ * flow_directions[j], + normalize(boundary_zone.spanning_set[1]), atol=1e-14) + @test isapprox(zone_width, norm(boundary_zone.spanning_set[1]), + atol=1e-14) + end + end + end + end + + @testset verbose=true "Particle In Boundary Zone 2D" begin + plane_points = [[-0.2, -0.5], [0.3, 0.6]] + plane_size = plane_points[2] - plane_points[1] + + flow_direction = normalize([-plane_size[2], plane_size[1]]) + + inflow = InFlow(; plane=plane_points, particle_spacing=0.1, + flow_direction, density=1.0, open_boundary_layers=4) + outflow = OutFlow(; plane=plane_points, particle_spacing=0.1, + flow_direction, density=1.0, open_boundary_layers=4) + + boundary_zones = [ + inflow, + outflow, + ] + + @testset verbose=true "$(nameof(typeof(boundary_zone)))" for boundary_zone in boundary_zones + perturb_ = boundary_zone isa InFlow ? sqrt(eps()) : -sqrt(eps()) + + point1 = plane_points[1] + point2 = plane_points[2] + point3 = boundary_zone.spanning_set[1] + boundary_zone.zone_origin + + query_points = Dict( + "Behind" => ([-1.0, -1.0], false), + "Before" => ([2.0, 2.0], false), + "Closely On Point 1" => (point1 + perturb_ * flow_direction, false), + "Closely On Point 2" => (point2 + perturb_ * flow_direction, false), + "Closely On Point 3" => (point3 - perturb_ * flow_direction, false)) + + @testset verbose=true "$k" for k in keys(query_points) + (particle_position, evaluation) = query_points[k] + + @test evaluation == + TrixiParticles.is_in_boundary_zone(boundary_zone, particle_position) + end + end + end + + @testset verbose=true "Particle In Boundary Zone 3D" begin + point1 = [-0.2, -0.5, 0.0] + point2 = [0.3, 0.5, 0.0] + point3 = [0.13111173850909402, -0.665555869254547, 0.0] + + flow_direction = normalize(cross(point2 - point1, point3 - point1)) + + inflow = InFlow(; plane=[point1, point2, point3], particle_spacing=0.1, + flow_direction, density=1.0, open_boundary_layers=4) + outflow = OutFlow(; plane=[point1, point2, point3], particle_spacing=0.1, + flow_direction, density=1.0, open_boundary_layers=4) + + boundary_zones = [ + inflow, + outflow, + ] + + @testset verbose=true "$(nameof(typeof(boundary_zone)))" for boundary_zone in boundary_zones + perturb_ = boundary_zone isa InFlow ? eps() : -eps() + point4 = boundary_zone.spanning_set[1] + boundary_zone.zone_origin + + query_points = Dict( + "Behind" => ([-1.0, -1.0, 1.2], false), + "Before" => ([2.0, 2.0, -1.2], false), + "Closely On Point 1" => (point1 + perturb_ * flow_direction, false), + "Closely On Point 2" => (point2 + perturb_ * flow_direction, false), + "Closely On Point 3" => (point3 + perturb_ * flow_direction, false), + "Closely On Point 4" => (point4 - perturb_ * flow_direction, false)) + + @testset verbose=true "$k" for k in keys(query_points) + (particle_position, evaluation) = query_points[k] + + @test evaluation == + TrixiParticles.is_in_boundary_zone(boundary_zone, particle_position) + end + end + end + + @testset verbose=true "Illegal Inputs" begin + no_rectangular_plane = [[0.2, 0.3, -0.5], [-1.0, 1.5, 0.2], [-0.2, 2.0, -0.5]] + flow_direction = [0.0, 0.0, 1.0] + + error_str = "the vectors `AB` and `AC` for the provided points `A`, `B`, `C` must be orthogonal" + + @test_throws ArgumentError(error_str) InFlow(; plane=no_rectangular_plane, + particle_spacing=0.1, + flow_direction, density=1.0, + open_boundary_layers=2) + @test_throws ArgumentError(error_str) OutFlow(; plane=no_rectangular_plane, + particle_spacing=0.1, + flow_direction, density=1.0, + open_boundary_layers=2) + + rectangular_plane = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]] + flow_direction = [0.0, 1.0, 0.0] + + error_str = "`flow_direction` is not normal to inflow plane" + + @test_throws ArgumentError(error_str) InFlow(; plane=rectangular_plane, + particle_spacing=0.1, + flow_direction, density=1.0, + open_boundary_layers=2) + + error_str = "`flow_direction` is not normal to outflow plane" + + @test_throws ArgumentError(error_str) OutFlow(; plane=rectangular_plane, + particle_spacing=0.1, + flow_direction, density=1.0, + open_boundary_layers=2) + end +end diff --git a/test/schemes/boundary/open_boundary/characteristic_variables.jl b/test/schemes/boundary/open_boundary/characteristic_variables.jl new file mode 100644 index 0000000000..1fe20722a1 --- /dev/null +++ b/test/schemes/boundary/open_boundary/characteristic_variables.jl @@ -0,0 +1,134 @@ +@testset verbose=true "Characteristic Variables" begin + particle_spacing = 0.1 + + # Number of boundary particles in the influence of fluid particles + influenced_particles = [20, 52, 26] + + open_boundary_layers = 8 + sound_speed = 20.0 + density = 1000.0 + pressure = 5.0 + + smoothing_kernel = SchoenbergCubicSplineKernel{2}() + smoothing_length = 1.2particle_spacing + + # Prescribed quantities + reference_velocity = (pos, t) -> SVector(t, 0.0) + reference_pressure = (pos, t) -> 50_000.0 * t + reference_density = (pos, t) -> 1000.0 * t + + # Plane points of open boundary + plane_points_1 = [[0.0, 0.0], [0.5, -0.5], [1.0, 0.5]] + plane_points_2 = [[0.0, 1.0], [0.2, 2.0], [2.3, 0.5]] + + @testset "Points $i" for i in eachindex(plane_points_1) + n_influenced = influenced_particles[i] + + plane_points = [plane_points_1[i], plane_points_2[i]] + + plane_size = plane_points[2] - plane_points[1] + flow_directions = [ + normalize([-plane_size[2], plane_size[1]]), + -normalize([-plane_size[2], plane_size[1]]), + ] + + @testset "Flow Direction $j" for j in eachindex(flow_directions) + flow_direction = flow_directions[j] + inflow = InFlow(; plane=plane_points, particle_spacing, density, + flow_direction, open_boundary_layers) + outflow = OutFlow(; plane=plane_points, particle_spacing, density, + flow_direction, open_boundary_layers) + + boundary_zones = [ + inflow, + outflow, + ] + + @testset "`$(nameof(typeof(boundary_zone)))`" for boundary_zone in boundary_zones + sign_ = (boundary_zone isa InFlow) ? 1 : -1 + fluid = extrude_geometry(plane_points; particle_spacing, n_extrude=4, + density, pressure, + direction=(sign_ * flow_direction)) + + fluid_system = EntropicallyDampedSPHSystem(fluid, smoothing_kernel, + buffer_size=0, + density_calculator=ContinuityDensity(), + smoothing_length, sound_speed) + + boundary_system = OpenBoundarySPHSystem(boundary_zone; sound_speed, + fluid_system, buffer_size=0, + reference_velocity, + reference_pressure, + reference_density) + + semi = Semidiscretization(fluid_system, boundary_system) + + ode = semidiscretize(semi, (0.0, 5.0)) + + v0_ode, u0_ode = ode.u0.x + v = TrixiParticles.wrap_v(v0_ode, boundary_system, semi) + u = TrixiParticles.wrap_u(u0_ode, boundary_system, semi) + + # ==== Characteristic Variables + # `J1 = -sound_speed^2 * (rho - rho_ref) + (p - p_ref)` + # `J2 = rho * sound_speed * (v - v_ref) + (p - p_ref)` + # `J3 = - rho * sound_speed * (v - v_ref) + (p - p_ref)` + function J1(t) + return -sound_speed^2 * (density - reference_density(0, t)) + + (pressure - reference_pressure(0, t)) + end + function J2(t) + return density * sound_speed * + dot(-reference_velocity(0, t), flow_direction) + + (pressure - reference_pressure(0, t)) + end + function J3(t) + return -density * sound_speed * + dot(-reference_velocity(0, t), flow_direction) + + (pressure - reference_pressure(0, t)) + end + + # First evaluation. + # Particles not influenced by the fluid have zero values. + t1 = 2.0 + TrixiParticles.evaluate_characteristics!(boundary_system, + v, u, v0_ode, u0_ode, semi, t1) + evaluated_vars1 = boundary_system.characteristics + + if boundary_zone isa InFlow + @test all(isapprox.(evaluated_vars1[1, :], 0.0)) + @test all(isapprox.(evaluated_vars1[2, :], 0.0)) + @test all(isapprox.(evaluated_vars1[3, 1:n_influenced], J3(t1))) + @test all(isapprox.(evaluated_vars1[3, (n_influenced + 1):end], 0.0)) + + elseif boundary_zone isa OutFlow + @test all(isapprox.(evaluated_vars1[1, 1:n_influenced], J1(t1))) + @test all(isapprox.(evaluated_vars1[2, 1:n_influenced], J2(t1))) + @test all(isapprox.(evaluated_vars1[1:2, (n_influenced + 1):end], 0.0)) + @test all(isapprox.(evaluated_vars1[3, :], 0.0)) + end + + # Second evaluation. + # Particles not influenced by the fluid have previous values. + t2 = 3.0 + TrixiParticles.evaluate_characteristics!(boundary_system, + v, u, v0_ode, u0_ode, semi, t2) + evaluated_vars2 = boundary_system.characteristics + + if boundary_zone isa InFlow + @test all(isapprox.(evaluated_vars2[1, :], 0.0)) + @test all(isapprox.(evaluated_vars2[2, :], 0.0)) + @test all(isapprox.(evaluated_vars2[3, 1:n_influenced], J3(t2))) + @test all(isapprox.(evaluated_vars2[3, (n_influenced + 1):end], J3(t1))) + + elseif boundary_zone isa OutFlow + @test all(isapprox.(evaluated_vars2[1, 1:n_influenced], J1(t2))) + @test all(isapprox.(evaluated_vars2[2, 1:n_influenced], J2(t2))) + @test all(isapprox.(evaluated_vars2[1, (n_influenced + 1):end], J1(t1))) + @test all(isapprox.(evaluated_vars2[2, (n_influenced + 1):end], J2(t1))) + @test all(isapprox.(evaluated_vars2[3, :], 0.0)) + end + end + end + end +end diff --git a/test/schemes/boundary/open_boundary/open_boundary.jl b/test/schemes/boundary/open_boundary/open_boundary.jl new file mode 100644 index 0000000000..5fe09c7034 --- /dev/null +++ b/test/schemes/boundary/open_boundary/open_boundary.jl @@ -0,0 +1,2 @@ +include("characteristic_variables.jl") +include("boundary_zone.jl") diff --git a/test/schemes/fluid/fluid.jl b/test/schemes/fluid/fluid.jl index bf7c3720c1..639fa64297 100644 --- a/test/schemes/fluid/fluid.jl +++ b/test/schemes/fluid/fluid.jl @@ -1,3 +1,5 @@ include("weakly_compressible_sph/weakly_compressible_sph.jl") include("rhs.jl") include("pressure_acceleration.jl") +include("surface_tension.jl") +include("viscosity.jl") diff --git a/test/schemes/fluid/surface_tension.jl b/test/schemes/fluid/surface_tension.jl new file mode 100644 index 0000000000..9758463f55 --- /dev/null +++ b/test/schemes/fluid/surface_tension.jl @@ -0,0 +1,92 @@ + +@testset verbose=true "Surface Tension" begin + @testset verbose=true "`cohesion_force_akinci`" begin + surface_tension = SurfaceTensionAkinci(surface_tension_coefficient=1.0) + support_radius = 1.0 + m_b = 1.0 + pos_diff = [1.0, 1.0] + + # These values can be extracted from the graphs in the paper by Akinci et al. or by manual calculation. + # Additional digits have been accepted from the actual calculation. + test_distance = 0.1 + val = TrixiParticles.cohesion_force_akinci(surface_tension, support_radius, m_b, + pos_diff, test_distance) * test_distance + @test isapprox(val[1], 0.1443038770421044, atol=6e-15) + @test isapprox(val[2], 0.1443038770421044, atol=6e-15) + + # Maximum repulsion force + test_distance = 0.01 + max = TrixiParticles.cohesion_force_akinci(surface_tension, support_radius, m_b, + pos_diff, test_distance) * test_distance + @test isapprox(max[1], 0.15913517632298307, atol=6e-15) + @test isapprox(max[2], 0.15913517632298307, atol=6e-15) + + # Near 0 + test_distance = 0.2725 + zero = TrixiParticles.cohesion_force_akinci(surface_tension, support_radius, m_b, + pos_diff, test_distance) * test_distance + @test isapprox(zero[1], 0.0004360543645195717, atol=6e-15) + @test isapprox(zero[2], 0.0004360543645195717, atol=6e-15) + + # Maximum attraction force + test_distance = 0.5 + maxa = TrixiParticles.cohesion_force_akinci(surface_tension, support_radius, m_b, + pos_diff, test_distance) * test_distance + @test isapprox(maxa[1], -0.15915494309189535, atol=6e-15) + @test isapprox(maxa[2], -0.15915494309189535, atol=6e-15) + + # Should be 0 + test_distance = 1.0 + zero = TrixiParticles.cohesion_force_akinci(surface_tension, support_radius, m_b, + pos_diff, test_distance) * test_distance + @test isapprox(zero[1], 0.0, atol=6e-15) + @test isapprox(zero[2], 0.0, atol=6e-15) + end + + @testset verbose=true "adhesion_force_akinci" begin + surface_tension = TrixiParticles.SurfaceTensionAkinci(surface_tension_coefficient=1.0) + support_radius = 1.0 + m_b = 1.0 + pos_diff = [1.0, 1.0] + + # These values can be extracted from the graphs in the paper by Akinci et al. or by manual calculation. + # Additional digits have been accepted from the actual calculation. + test_distance = 0.1 + zero = TrixiParticles.adhesion_force_akinci(surface_tension, support_radius, m_b, + pos_diff, test_distance, 1.0) * + test_distance + @test isapprox(zero[1], 0.0, atol=6e-15) + @test isapprox(zero[2], 0.0, atol=6e-15) + + test_distance = 0.5 + zero = TrixiParticles.adhesion_force_akinci(surface_tension, support_radius, m_b, + pos_diff, test_distance, 1.0) * + test_distance + @test isapprox(zero[1], 0.0, atol=6e-15) + @test isapprox(zero[2], 0.0, atol=6e-15) + + # Near 0 + test_distance = 0.51 + zero = TrixiParticles.adhesion_force_akinci(surface_tension, support_radius, m_b, + pos_diff, test_distance, 1.0) * + test_distance + @test isapprox(zero[1], -0.002619160170741761, atol=6e-15) + @test isapprox(zero[2], -0.002619160170741761, atol=6e-15) + + # Maximum adhesion force + test_distance = 0.75 + max = TrixiParticles.adhesion_force_akinci(surface_tension, support_radius, m_b, + pos_diff, test_distance, 1.0) * + test_distance + @test isapprox(max[1], -0.004949747468305833, atol=6e-15) + @test isapprox(max[2], -0.004949747468305833, atol=6e-15) + + # Should be 0 + test_distance = 1.0 + zero = TrixiParticles.adhesion_force_akinci(surface_tension, support_radius, m_b, + pos_diff, test_distance, 1.0) * + test_distance + @test isapprox(zero[1], 0.0, atol=6e-15) + @test isapprox(zero[2], 0.0, atol=6e-15) + end +end diff --git a/test/schemes/fluid/viscosity.jl b/test/schemes/fluid/viscosity.jl new file mode 100644 index 0000000000..a094f9fb90 --- /dev/null +++ b/test/schemes/fluid/viscosity.jl @@ -0,0 +1,65 @@ +@testset verbose=true "Viscosity" begin + particle_spacing = 0.2 + smoothing_length = 1.2 * particle_spacing + smoothing_kernel = SchoenbergCubicSplineKernel{2}() + sound_speed = 10 * sqrt(9.81 * 0.9) + + state_equation = StateEquationCole(; sound_speed, reference_density=1000.0, + exponent=7, clip_negative_pressure=false) + + fluid = rectangular_patch(particle_spacing, (3, 3), seed=1) + + system_wcsph = WeaklyCompressibleSPHSystem(fluid, ContinuityDensity(), + state_equation, smoothing_kernel, + smoothing_length) + + v_diff = [0.1, -0.75] + pos_diff = [-0.5 * smoothing_length, 0.75 * smoothing_length] + distance = norm(pos_diff) + rho_a = rho_b = rho_mean = 1000.0 + + grad_kernel = TrixiParticles.smoothing_kernel_grad(system_wcsph, pos_diff, + distance) + + # We only test here that the values don't change + @testset verbose=true "`ArtificialViscosityMonaghan`" begin + viscosity = ArtificialViscosityMonaghan(alpha=0.02, beta=0.0) + + dv = viscosity(sound_speed, v_diff, pos_diff, distance, + rho_mean, rho_a, rho_b, smoothing_length, + grad_kernel) + + @test isapprox(dv[1], -0.007073849138494646, atol=6e-15) + @test isapprox(dv[2], 0.01061077370774197, atol=6e-15) + end + @testset verbose=true "`ViscosityMorris`" begin + viscosity = ViscosityMorris(nu=7e-3) + + dv = viscosity(sound_speed, v_diff, pos_diff, distance, + rho_mean, rho_a, rho_b, smoothing_length, + grad_kernel) + + @test isapprox(dv[1], -0.00018421886647594437, atol=6e-15) + @test isapprox(dv[2], 0.0013816414985695826, atol=6e-15) + end + @testset verbose=true "`ViscosityAdami`" begin + viscosity = ViscosityAdami(nu=7e-3) + + v = fluid.velocity + + m_a = 0.01 + m_b = 0.01 + + v[1, 1] = v_diff[1] + v[2, 1] = v_diff[2] + v[1, 2] = 0.0 + v[2, 2] = 0.0 + + dv = viscosity(system_wcsph, system_wcsph, + v, v, 1, 2, pos_diff, distance, + sound_speed, m_a, m_b, rho_a, rho_b, grad_kernel) + + @test isapprox(dv[1], -1.8421886647594435e-6, atol=6e-15) + @test isapprox(dv[2], 1.3816414985695826e-5, atol=6e-15) + end +end diff --git a/test/schemes/schemes.jl b/test/schemes/schemes.jl index 2e5e609e5f..5cf7cd5bd2 100644 --- a/test/schemes/schemes.jl +++ b/test/schemes/schemes.jl @@ -1,4 +1,5 @@ include("solid/total_lagrangian_sph/total_lagrangian_sph.jl") include("boundary/dummy_particles/dummy_particles.jl") include("boundary/monaghan_kajtar/monaghan_kajtar.jl") +include("boundary/open_boundary/open_boundary.jl") include("fluid/fluid.jl") diff --git a/test/schemes/solid/total_lagrangian_sph/rhs.jl b/test/schemes/solid/total_lagrangian_sph/rhs.jl index 6a4f6bce32..b64fb6c97c 100644 --- a/test/schemes/solid/total_lagrangian_sph/rhs.jl +++ b/test/schemes/solid/total_lagrangian_sph/rhs.jl @@ -32,7 +32,7 @@ [0.0, 0.0], ] - @testset "Test $i" for i in 1:4 + @testset verbose=true "Test $i" for i in 1:4 #### Setup each_moving_particle = [particle[i]] # Only calculate dv for this one particle eachparticle = [particle[i], neighbor[i]] @@ -53,18 +53,27 @@ kernel_deriv = 1.0 #### Mocking - # Mock the system - system = Val{:mock_system_interact}() - TrixiParticles.ndims(::Val{:mock_system_interact}) = 2 - Base.ntuple(f, ::Symbol) = ntuple(f, 2) # Make `extract_svector` work + # Mock a CPU system to test CPU code + struct MockSystemInteractCPU <: TrixiParticles.System{2, String} end + system = MockSystemInteractCPU() - function TrixiParticles.initial_coordinates(::Val{:mock_system_interact}) + # Mock a GPU system to emulate GPU code on the CPU + struct MockSystemInteractGPU <: TrixiParticles.System{2, Nothing} end + system_gpu = MockSystemInteractGPU() + + function TrixiParticles.KernelAbstractions.get_backend(::MockSystemInteractGPU) + return TrixiParticles.KernelAbstractions.CPU() + end + + MockSystemType = Union{MockSystemInteractCPU, MockSystemInteractGPU} + + function TrixiParticles.initial_coordinates(::MockSystemType) return initial_coordinates end # Unpack calls should return predefined values or # another mock object of the type Val{:mock_property_name}. - function Base.getproperty(::Val{:mock_system_interact}, f::Symbol) + function Base.getproperty(::MockSystemType, f::Symbol) if f === :current_coordinates return current_coordinates elseif f === :material_density @@ -81,11 +90,14 @@ return Val(Symbol("mock_" * string(f))) end - function TrixiParticles.each_moving_particle(::Val{:mock_system_interact}) + function TrixiParticles.each_moving_particle(::MockSystemType) each_moving_particle end - TrixiParticles.eachparticle(::Val{:mock_system_interact}) = eachparticle - TrixiParticles.eachneighbor(_, ::Val{:nhs}) = eachneighbor + TrixiParticles.eachparticle(::MockSystemType) = eachparticle + + # Mock the neighborhood search + nhs = Val{:nhs}() + TrixiParticles.PointNeighbors.eachneighbor(_, ::Val{:nhs}) = eachneighbor function Base.getproperty(::Val{:nhs}, f::Symbol) if f === :search_radius @@ -99,26 +111,25 @@ end TrixiParticles.ndims(::Val{:nhs}) = 2 - function TrixiParticles.pk1_corrected(::Val{:mock_system_dv}, particle_) - if particle_ == particle[i] - return pk1_particle_corrected[i] - end - return pk1_neighbor_corrected[i] - end - - function TrixiParticles.add_acceleration!(_, _, ::Val{:mock_system_interact}) + function TrixiParticles.add_acceleration!(_, _, ::MockSystemType) nothing end TrixiParticles.kernel_deriv(::Val{:mock_smoothing_kernel}, _, _) = kernel_deriv #### Verification - dv = zeros(ndims(system), 10) - dv_expected = copy(dv) - dv_expected[:, particle[i]] = dv_particle_expected[i] + systems = [system, system_gpu] + names = ["CPU code", "Emulate GPU"] + @testset "$(names[j])" for j in eachindex(names) + system_ = systems[j] + + dv = zeros(ndims(system_), 10) + dv_expected = copy(dv) + dv_expected[:, particle[i]] = dv_particle_expected[i] - TrixiParticles.interact_solid_solid!(dv, Val(:nhs), system, system) + TrixiParticles.interact_solid_solid!(dv, nhs, system_, system_) - @test dv ≈ dv_expected + @test dv ≈ dv_expected + end end end @@ -140,7 +151,7 @@ 10 / 1000^2 * 1.5400218087591082 * 324.67072684047224 * 1.224, 0.0, ]) - @testset "Deformation Function: $deformation" for deformation in keys(deformations) + @testset verbose=true "Deformation Function: $deformation" for deformation in keys(deformations) J = deformations[deformation] u = zeros(2, 81) v = zeros(2, 81) @@ -176,22 +187,50 @@ semi = Semidiscretization(system) tspan = (0.0, 1.0) - semidiscretize(semi, tspan) - # Apply the deformation matrix - for particle in axes(u, 2) - # Apply deformation - u[1:2, particle] = deformations[deformation](coordinates[:, particle]) + # To make the code below work + function TrixiParticles.PtrArray{Float64}(::UndefInitializer, length) + TrixiParticles.PtrArray(zeros(length)) end - #### Verification for the particle in the middle - particle = 41 + # We can pass the data type `Array` to convert all systems to `GPUSystem`s + # and emulate the GPU kernels on the GPU. + # But this doesn't test `wrap_v` and `wrap_u` for non-`Array` types. + # In order to test this as well, we need a different data type, so we also + # pass `PtrArray`. + names = ["CPU code", "GPU code with CPU wrapping", "GPU code with GPU wrapping"] + data_types = [nothing, Array, TrixiParticles.PtrArray] + @testset "$(names[i])" for i in eachindex(names) + data_type = data_types[i] + ode = semidiscretize(semi, tspan, data_type=data_type) + + # Apply the deformation matrix + for particle in axes(u, 2) + # Apply deformation + u[1:2, particle] = deformations[deformation](coordinates[:, particle]) + end + + v_ode = ode.u0.x[1] + if isnothing(data_type) + u_ode = vec(u) + else + u_ode = data_type(vec(u)) + end + + @test typeof(v_ode) == typeof(u_ode) + @test length(v_ode) == length(u_ode) + + #### Verification for the particle in the middle + particle = 41 - dv = zeros(ndims(system), 81) - TrixiParticles.kick!(dv, v, u, semi, 0.0) + dv_ode = zero(v_ode) + TrixiParticles.kick!(dv_ode, v_ode, u_ode, ode.p, 0.0) - @test isapprox(dv[:, particle], dv_expected_41[deformation], - rtol=sqrt(eps()), atol=sqrt(eps())) + dv = TrixiParticles.wrap_v(dv_ode, system, semi) + + @test isapprox(dv[:, particle], dv_expected_41[deformation], + rtol=sqrt(eps()), atol=sqrt(eps())) + end end end -end +end; diff --git a/test/setups/extrude_geometry.jl b/test/setups/extrude_geometry.jl new file mode 100644 index 0000000000..7e0c8be4b0 --- /dev/null +++ b/test/setups/extrude_geometry.jl @@ -0,0 +1,110 @@ +# 2D +@testset verbose=true "Extrude Geometry 2D" begin + point1 = [0.0, 0.0] + point2 = [0.5, 1.0] + directions = [ + [-1.0, 2.0], + -[-1.0, 2.0], + [-2.0, 1.0], + -[-2.0, 1.0], + [1.0, 0.0], + [0.0, 1.0], + ] + + @testset verbose=true "Extrude Line-Points" begin + expected_coords = [ + [0.0 0.0625 0.125 0.1875 0.25 0.3125 0.375 0.4375 0.5 -0.0625 0.0 0.0625 0.125 0.1875 0.25 0.3125 0.375 0.4375 -0.125 -0.0625 0.0 0.0625 0.125 0.1875 0.25 0.3125 0.375 -0.1875 -0.125 -0.0625 0.0 0.0625 0.125 0.1875 0.25 0.3125 -0.25 -0.1875 -0.125 -0.0625 0.0 0.0625 0.125 0.1875 0.25; + 0.0 0.125 0.25 0.375 0.5 0.625 0.75 0.875 1.0 0.125 0.25 0.375 0.5 0.625 0.75 0.875 1.0 1.125 0.25 0.375 0.5 0.625 0.75 0.875 1.0 1.125 1.25 0.375 0.5 0.625 0.75 0.875 1.0 1.125 1.25 1.375 0.5 0.625 0.75 0.875 1.0 1.125 1.25 1.375 1.5], + [0.0 0.0625 0.125 0.1875 0.25 0.3125 0.375 0.4375 0.5 0.0625 0.125 0.1875 0.25 0.3125 0.375 0.4375 0.5 0.5625 0.125 0.1875 0.25 0.3125 0.375 0.4375 0.5 0.5625 0.625 0.1875 0.25 0.3125 0.375 0.4375 0.5 0.5625 0.625 0.6875 0.25 0.3125 0.375 0.4375 0.5 0.5625 0.625 0.6875 0.75; + 0.0 0.125 0.25 0.375 0.5 0.625 0.75 0.875 1.0 -0.125 0.0 0.125 0.25 0.375 0.5 0.625 0.75 0.875 -0.25 -0.125 0.0 0.125 0.25 0.375 0.5 0.625 0.75 -0.375 -0.25 -0.125 0.0 0.125 0.25 0.375 0.5 0.625 -0.5 -0.375 -0.25 -0.125 0.0 0.125 0.25 0.375 0.5], + [0.0 0.0625 0.125 0.1875 0.25 0.3125 0.375 0.4375 0.5 -0.125 -0.0625 0.0 0.0625 0.125 0.1875 0.25 0.3125 0.375 -0.25 -0.1875 -0.125 -0.0625 0.0 0.0625 0.125 0.1875 0.25 -0.375 -0.3125 -0.25 -0.1875 -0.125 -0.0625 0.0 0.0625 0.125 -0.5 -0.4375 -0.375 -0.3125 -0.25 -0.1875 -0.125 -0.0625 0.0; + 0.0 0.125 0.25 0.375 0.5 0.625 0.75 0.875 1.0 0.0625 0.1875 0.3125 0.4375 0.5625 0.6875 0.8125 0.9375 1.0625 0.125 0.25 0.375 0.5 0.625 0.75 0.875 1.0 1.125 0.1875 0.3125 0.4375 0.5625 0.6875 0.8125 0.9375 1.0625 1.1875 0.25 0.375 0.5 0.625 0.75 0.875 1.0 1.125 1.25], + [0.0 0.0625 0.125 0.1875 0.25 0.3125 0.375 0.4375 0.5 0.125 0.1875 0.25 0.3125 0.375 0.4375 0.5 0.5625 0.625 0.25 0.3125 0.375 0.4375 0.5 0.5625 0.625 0.6875 0.75 0.375 0.4375 0.5 0.5625 0.625 0.6875 0.75 0.8125 0.875 0.5 0.5625 0.625 0.6875 0.75 0.8125 0.875 0.9375 1.0; + 0.0 0.125 0.25 0.375 0.5 0.625 0.75 0.875 1.0 -0.0625 0.0625 0.1875 0.3125 0.4375 0.5625 0.6875 0.8125 0.9375 -0.125 0.0 0.125 0.25 0.375 0.5 0.625 0.75 0.875 -0.1875 -0.0625 0.0625 0.1875 0.3125 0.4375 0.5625 0.6875 0.8125 -0.25 -0.125 0.0 0.125 0.25 0.375 0.5 0.625 0.75], + [0.0 0.0625 0.125 0.1875 0.25 0.3125 0.375 0.4375 0.5 0.13975424859373686 0.20225424859373686 0.26475424859373686 0.32725424859373686 0.38975424859373686 0.45225424859373686 0.5147542485937369 0.5772542485937369 0.6397542485937369 0.2795084971874737 0.3420084971874737 0.4045084971874737 0.4670084971874737 0.5295084971874737 0.5920084971874737 0.6545084971874737 0.7170084971874737 0.7795084971874737 0.4192627457812106 0.4817627457812106 0.5442627457812106 0.6067627457812106 0.6692627457812106 0.7317627457812106 0.7942627457812106 0.8567627457812106 0.9192627457812106 0.5590169943749475 0.6215169943749475 0.6840169943749475 0.7465169943749475 0.8090169943749475 0.8715169943749475 0.9340169943749475 0.9965169943749475 1.0590169943749475; + 0.0 0.125 0.25 0.375 0.5 0.625 0.75 0.875 1.0 0.0 0.125 0.25 0.375 0.5 0.625 0.75 0.875 1.0 0.0 0.125 0.25 0.375 0.5 0.625 0.75 0.875 1.0 0.0 0.125 0.25 0.375 0.5 0.625 0.75 0.875 1.0 0.0 0.125 0.25 0.375 0.5 0.625 0.75 0.875 1.0], + [0.0 0.0625 0.125 0.1875 0.25 0.3125 0.375 0.4375 0.5 0.0 0.0625 0.125 0.1875 0.25 0.3125 0.375 0.4375 0.5 0.0 0.0625 0.125 0.1875 0.25 0.3125 0.375 0.4375 0.5 0.0 0.0625 0.125 0.1875 0.25 0.3125 0.375 0.4375 0.5 0.0 0.0625 0.125 0.1875 0.25 0.3125 0.375 0.4375 0.5; + 0.0 0.125 0.25 0.375 0.5 0.625 0.75 0.875 1.0 0.13975424859373686 0.26475424859373686 0.38975424859373686 0.5147542485937369 0.6397542485937369 0.7647542485937369 0.8897542485937369 1.0147542485937369 1.1397542485937369 0.2795084971874737 0.4045084971874737 0.5295084971874737 0.6545084971874737 0.7795084971874737 0.9045084971874737 1.0295084971874737 1.1545084971874737 1.2795084971874737 0.4192627457812106 0.5442627457812106 0.6692627457812106 0.7942627457812106 0.9192627457812106 1.0442627457812106 1.1692627457812106 1.2942627457812106 1.4192627457812106 0.5590169943749475 0.6840169943749475 0.8090169943749475 0.9340169943749475 1.0590169943749475 1.1840169943749475 1.3090169943749475 1.4340169943749475 1.5590169943749475], + ] + + @testset "Direction $i" for i in eachindex(directions) + shape = extrude_geometry((point1, point2); direction=directions[i], tlsph=true, + particle_spacing=0.15, n_extrude=5, density=1.0) + + @test shape.coordinates ≈ expected_coords[i] + end + end + + @testset verbose=true "Shifted Particles" begin + point1 = [0.0, 0.0] + point2 = [0.0, 1.0] + + expected_coords = [-0.05 -0.05 -0.05 -0.05 -0.05 -0.05 -0.05 -0.05 -0.05 -0.05 -0.15 -0.15 -0.15 -0.15 -0.15 -0.15 -0.15 -0.15 -0.15 -0.15 -0.25 -0.25 -0.25 -0.25 -0.25 -0.25 -0.25 -0.25 -0.25 -0.25 -0.35 -0.35 -0.35 -0.35 -0.35 -0.35 -0.35 -0.35 -0.35 -0.35 -0.44999999999999996 -0.44999999999999996 -0.44999999999999996 -0.44999999999999996 -0.44999999999999996 -0.44999999999999996 -0.44999999999999996 -0.44999999999999996 -0.44999999999999996 -0.44999999999999996; + 0.05 0.15 0.24999999999999997 0.35 0.44999999999999996 0.55 0.65 0.7499999999999999 0.8499999999999999 0.95 0.05 0.15 0.24999999999999997 0.35 0.44999999999999996 0.55 0.65 0.7499999999999999 0.8499999999999999 0.95 0.05 0.15 0.24999999999999997 0.35 0.44999999999999996 0.55 0.65 0.7499999999999999 0.8499999999999999 0.95 0.05 0.15 0.24999999999999997 0.35 0.44999999999999996 0.55 0.65 0.7499999999999999 0.8499999999999999 0.95 0.05 0.15 0.24999999999999997 0.35 0.44999999999999996 0.55 0.65 0.7499999999999999 0.8499999999999999 0.95] + shape = extrude_geometry((point1, point2); direction=[-1.0, 0.0], + particle_spacing=0.1, n_extrude=5, density=1.0) + + @test shape.coordinates ≈ expected_coords + end +end + +# 3D +@testset verbose=true "Extrude Geometry 3D" begin + @testset verbose=true "Extrude 2D Shape" begin + geometry = SphereShape(0.1, 0.5, (0.2, 0.4), 1000.0, n_layers=3, + sphere_type=RoundSphere(end_angle=pi)) + particle_spacing = geometry.particle_spacing + + directions = [[0.0, 0.0, 1.0], [0.0, -1.0, 1.0]] + + expected_coords = [ + [0.44999999999999996 0.43096988312782164 0.3767766952966369 0.29567085809127247 0.20000000000000004 0.10432914190872761 0.023223304703363173 -0.030969883127821618 -0.04999999999999993 0.55 0.5358225407650741 0.4944387364909134 0.42920125688084976 0.3453952545506602 0.24981019339564986 0.15018980660435027 0.0546047454493398 -0.029201256880849707 -0.09443873649091344 -0.135822540765074 -0.14999999999999997 0.6499999999999999 0.6387175604818206 0.6054359905560887 0.5518241671106134 0.4805704108364301 0.39524768260290116 0.3001344202803415 0.20000000000000004 0.09986557971965856 0.0047523173970988875 -0.08057041083643002 -0.1518241671106133 -0.20543599055608852 -0.23871756048182058 -0.24999999999999994 0.44999999999999996 0.43096988312782164 0.3767766952966369 0.29567085809127247 0.20000000000000004 0.10432914190872761 0.023223304703363173 -0.030969883127821618 -0.04999999999999993 0.55 0.5358225407650741 0.4944387364909134 0.42920125688084976 0.3453952545506602 0.24981019339564986 0.15018980660435027 0.0546047454493398 -0.029201256880849707 -0.09443873649091344 -0.135822540765074 -0.14999999999999997 0.6499999999999999 0.6387175604818206 0.6054359905560887 0.5518241671106134 0.4805704108364301 0.39524768260290116 0.3001344202803415 0.20000000000000004 0.09986557971965856 0.0047523173970988875 -0.08057041083643002 -0.1518241671106133 -0.20543599055608852 -0.23871756048182058 -0.24999999999999994 0.44999999999999996 0.43096988312782164 0.3767766952966369 0.29567085809127247 0.20000000000000004 0.10432914190872761 0.023223304703363173 -0.030969883127821618 -0.04999999999999993 0.55 0.5358225407650741 0.4944387364909134 0.42920125688084976 0.3453952545506602 0.24981019339564986 0.15018980660435027 0.0546047454493398 -0.029201256880849707 -0.09443873649091344 -0.135822540765074 -0.14999999999999997 0.6499999999999999 0.6387175604818206 0.6054359905560887 0.5518241671106134 0.4805704108364301 0.39524768260290116 0.3001344202803415 0.20000000000000004 0.09986557971965856 0.0047523173970988875 -0.08057041083643002 -0.1518241671106133 -0.20543599055608852 -0.23871756048182058 -0.24999999999999994 0.44999999999999996 0.43096988312782164 0.3767766952966369 0.29567085809127247 0.20000000000000004 0.10432914190872761 0.023223304703363173 -0.030969883127821618 -0.04999999999999993 0.55 0.5358225407650741 0.4944387364909134 0.42920125688084976 0.3453952545506602 0.24981019339564986 0.15018980660435027 0.0546047454493398 -0.029201256880849707 -0.09443873649091344 -0.135822540765074 -0.14999999999999997 0.6499999999999999 0.6387175604818206 0.6054359905560887 0.5518241671106134 0.4805704108364301 0.39524768260290116 0.3001344202803415 0.20000000000000004 0.09986557971965856 0.0047523173970988875 -0.08057041083643002 -0.1518241671106133 -0.20543599055608852 -0.23871756048182058 -0.24999999999999994 0.44999999999999996 0.43096988312782164 0.3767766952966369 0.29567085809127247 0.20000000000000004 0.10432914190872761 0.023223304703363173 -0.030969883127821618 -0.04999999999999993 0.55 0.5358225407650741 0.4944387364909134 0.42920125688084976 0.3453952545506602 0.24981019339564986 0.15018980660435027 0.0546047454493398 -0.029201256880849707 -0.09443873649091344 -0.135822540765074 -0.14999999999999997 0.6499999999999999 0.6387175604818206 0.6054359905560887 0.5518241671106134 0.4805704108364301 0.39524768260290116 0.3001344202803415 0.20000000000000004 0.09986557971965856 0.0047523173970988875 -0.08057041083643002 -0.1518241671106133 -0.20543599055608852 -0.23871756048182058 -0.24999999999999994; + 0.4 0.4956708580912724 0.5767766952966369 0.6309698831278217 0.6499999999999999 0.6309698831278217 0.5767766952966369 0.4956708580912725 0.4000000000000001 0.4 0.4986063948945004 0.5892242861094592 0.6645123510239904 0.7183711983740815 0.7464375046583265 0.7464375046583265 0.7183711983740815 0.6645123510239904 0.5892242861094591 0.49860639489450054 0.4000000000000001 0.4 0.5001344202803415 0.5952476826029012 0.6805704108364301 0.7518241671106134 0.8054359905560886 0.8387175604818207 0.85 0.8387175604818207 0.8054359905560886 0.7518241671106134 0.6805704108364301 0.5952476826029012 0.5001344202803415 0.4000000000000001 0.4 0.4956708580912724 0.5767766952966369 0.6309698831278217 0.6499999999999999 0.6309698831278217 0.5767766952966369 0.4956708580912725 0.4000000000000001 0.4 0.4986063948945004 0.5892242861094592 0.6645123510239904 0.7183711983740815 0.7464375046583265 0.7464375046583265 0.7183711983740815 0.6645123510239904 0.5892242861094591 0.49860639489450054 0.4000000000000001 0.4 0.5001344202803415 0.5952476826029012 0.6805704108364301 0.7518241671106134 0.8054359905560886 0.8387175604818207 0.85 0.8387175604818207 0.8054359905560886 0.7518241671106134 0.6805704108364301 0.5952476826029012 0.5001344202803415 0.4000000000000001 0.4 0.4956708580912724 0.5767766952966369 0.6309698831278217 0.6499999999999999 0.6309698831278217 0.5767766952966369 0.4956708580912725 0.4000000000000001 0.4 0.4986063948945004 0.5892242861094592 0.6645123510239904 0.7183711983740815 0.7464375046583265 0.7464375046583265 0.7183711983740815 0.6645123510239904 0.5892242861094591 0.49860639489450054 0.4000000000000001 0.4 0.5001344202803415 0.5952476826029012 0.6805704108364301 0.7518241671106134 0.8054359905560886 0.8387175604818207 0.85 0.8387175604818207 0.8054359905560886 0.7518241671106134 0.6805704108364301 0.5952476826029012 0.5001344202803415 0.4000000000000001 0.4 0.4956708580912724 0.5767766952966369 0.6309698831278217 0.6499999999999999 0.6309698831278217 0.5767766952966369 0.4956708580912725 0.4000000000000001 0.4 0.4986063948945004 0.5892242861094592 0.6645123510239904 0.7183711983740815 0.7464375046583265 0.7464375046583265 0.7183711983740815 0.6645123510239904 0.5892242861094591 0.49860639489450054 0.4000000000000001 0.4 0.5001344202803415 0.5952476826029012 0.6805704108364301 0.7518241671106134 0.8054359905560886 0.8387175604818207 0.85 0.8387175604818207 0.8054359905560886 0.7518241671106134 0.6805704108364301 0.5952476826029012 0.5001344202803415 0.4000000000000001 0.4 0.4956708580912724 0.5767766952966369 0.6309698831278217 0.6499999999999999 0.6309698831278217 0.5767766952966369 0.4956708580912725 0.4000000000000001 0.4 0.4986063948945004 0.5892242861094592 0.6645123510239904 0.7183711983740815 0.7464375046583265 0.7464375046583265 0.7183711983740815 0.6645123510239904 0.5892242861094591 0.49860639489450054 0.4000000000000001 0.4 0.5001344202803415 0.5952476826029012 0.6805704108364301 0.7518241671106134 0.8054359905560886 0.8387175604818207 0.85 0.8387175604818207 0.8054359905560886 0.7518241671106134 0.6805704108364301 0.5952476826029012 0.5001344202803415 0.4000000000000001; + 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.30000000000000004 0.30000000000000004 0.30000000000000004 0.30000000000000004 0.30000000000000004 0.30000000000000004 0.30000000000000004 0.30000000000000004 0.30000000000000004 0.30000000000000004 0.30000000000000004 0.30000000000000004 0.30000000000000004 0.30000000000000004 0.30000000000000004 0.30000000000000004 0.30000000000000004 0.30000000000000004 0.30000000000000004 0.30000000000000004 0.30000000000000004 0.30000000000000004 0.30000000000000004 0.30000000000000004 0.30000000000000004 0.30000000000000004 0.30000000000000004 0.30000000000000004 0.30000000000000004 0.30000000000000004 0.30000000000000004 0.30000000000000004 0.30000000000000004 0.30000000000000004 0.30000000000000004 0.30000000000000004 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4], + [0.44999999999999996 0.43096988312782164 0.3767766952966369 0.29567085809127247 0.20000000000000004 0.10432914190872761 0.023223304703363173 -0.030969883127821618 -0.04999999999999993 0.55 0.5358225407650741 0.4944387364909134 0.42920125688084976 0.3453952545506602 0.24981019339564986 0.15018980660435027 0.0546047454493398 -0.029201256880849707 -0.09443873649091344 -0.135822540765074 -0.14999999999999997 0.6499999999999999 0.6387175604818206 0.6054359905560887 0.5518241671106134 0.4805704108364301 0.39524768260290116 0.3001344202803415 0.20000000000000004 0.09986557971965856 0.0047523173970988875 -0.08057041083643002 -0.1518241671106133 -0.20543599055608852 -0.23871756048182058 -0.24999999999999994 0.44999999999999996 0.43096988312782164 0.3767766952966369 0.29567085809127247 0.20000000000000004 0.10432914190872761 0.023223304703363173 -0.030969883127821618 -0.04999999999999993 0.55 0.5358225407650741 0.4944387364909134 0.42920125688084976 0.3453952545506602 0.24981019339564986 0.15018980660435027 0.0546047454493398 -0.029201256880849707 -0.09443873649091344 -0.135822540765074 -0.14999999999999997 0.6499999999999999 0.6387175604818206 0.6054359905560887 0.5518241671106134 0.4805704108364301 0.39524768260290116 0.3001344202803415 0.20000000000000004 0.09986557971965856 0.0047523173970988875 -0.08057041083643002 -0.1518241671106133 -0.20543599055608852 -0.23871756048182058 -0.24999999999999994 0.44999999999999996 0.43096988312782164 0.3767766952966369 0.29567085809127247 0.20000000000000004 0.10432914190872761 0.023223304703363173 -0.030969883127821618 -0.04999999999999993 0.55 0.5358225407650741 0.4944387364909134 0.42920125688084976 0.3453952545506602 0.24981019339564986 0.15018980660435027 0.0546047454493398 -0.029201256880849707 -0.09443873649091344 -0.135822540765074 -0.14999999999999997 0.6499999999999999 0.6387175604818206 0.6054359905560887 0.5518241671106134 0.4805704108364301 0.39524768260290116 0.3001344202803415 0.20000000000000004 0.09986557971965856 0.0047523173970988875 -0.08057041083643002 -0.1518241671106133 -0.20543599055608852 -0.23871756048182058 -0.24999999999999994 0.44999999999999996 0.43096988312782164 0.3767766952966369 0.29567085809127247 0.20000000000000004 0.10432914190872761 0.023223304703363173 -0.030969883127821618 -0.04999999999999993 0.55 0.5358225407650741 0.4944387364909134 0.42920125688084976 0.3453952545506602 0.24981019339564986 0.15018980660435027 0.0546047454493398 -0.029201256880849707 -0.09443873649091344 -0.135822540765074 -0.14999999999999997 0.6499999999999999 0.6387175604818206 0.6054359905560887 0.5518241671106134 0.4805704108364301 0.39524768260290116 0.3001344202803415 0.20000000000000004 0.09986557971965856 0.0047523173970988875 -0.08057041083643002 -0.1518241671106133 -0.20543599055608852 -0.23871756048182058 -0.24999999999999994 0.44999999999999996 0.43096988312782164 0.3767766952966369 0.29567085809127247 0.20000000000000004 0.10432914190872761 0.023223304703363173 -0.030969883127821618 -0.04999999999999993 0.55 0.5358225407650741 0.4944387364909134 0.42920125688084976 0.3453952545506602 0.24981019339564986 0.15018980660435027 0.0546047454493398 -0.029201256880849707 -0.09443873649091344 -0.135822540765074 -0.14999999999999997 0.6499999999999999 0.6387175604818206 0.6054359905560887 0.5518241671106134 0.4805704108364301 0.39524768260290116 0.3001344202803415 0.20000000000000004 0.09986557971965856 0.0047523173970988875 -0.08057041083643002 -0.1518241671106133 -0.20543599055608852 -0.23871756048182058 -0.24999999999999994; + 0.4 0.4956708580912724 0.5767766952966369 0.6309698831278217 0.6499999999999999 0.6309698831278217 0.5767766952966369 0.4956708580912725 0.4000000000000001 0.4 0.4986063948945004 0.5892242861094592 0.6645123510239904 0.7183711983740815 0.7464375046583265 0.7464375046583265 0.7183711983740815 0.6645123510239904 0.5892242861094591 0.49860639489450054 0.4000000000000001 0.4 0.5001344202803415 0.5952476826029012 0.6805704108364301 0.7518241671106134 0.8054359905560886 0.8387175604818207 0.85 0.8387175604818207 0.8054359905560886 0.7518241671106134 0.6805704108364301 0.5952476826029012 0.5001344202803415 0.4000000000000001 0.3292893218813453 0.4249601799726177 0.5060660171779822 0.5602592050091669 0.5792893218813452 0.5602592050091669 0.5060660171779822 0.4249601799726177 0.3292893218813453 0.3292893218813453 0.4278957167758457 0.5185136079908045 0.5938016729053357 0.6476605202554268 0.6757268265396718 0.6757268265396718 0.6476605202554268 0.5938016729053357 0.5185136079908044 0.4278957167758458 0.3292893218813453 0.3292893218813453 0.4294237421616868 0.5245370044842464 0.6098597327177754 0.6811134889919587 0.7347253124374339 0.7680068823631659 0.7792893218813453 0.7680068823631659 0.7347253124374339 0.6811134889919587 0.6098597327177754 0.5245370044842464 0.4294237421616868 0.3292893218813453 0.2585786437626905 0.3542495018539629 0.4353553390593274 0.48954852689051215 0.5085786437626905 0.48954852689051215 0.4353553390593274 0.354249501853963 0.2585786437626906 0.2585786437626905 0.3571850386571909 0.4478029298721497 0.5230909947866809 0.576949842136772 0.605016148421017 0.605016148421017 0.576949842136772 0.5230909947866809 0.4478029298721496 0.35718503865719103 0.2585786437626906 0.2585786437626905 0.358713064043032 0.45382632636559167 0.5391490545991207 0.610402810873304 0.6640146343187792 0.6972962042445112 0.7085786437626904 0.6972962042445112 0.6640146343187792 0.610402810873304 0.5391490545991207 0.45382632636559167 0.358713064043032 0.2585786437626906 0.18786796564403574 0.28353882373530814 0.3646446609406726 0.41883784877185737 0.43786796564403563 0.41883784877185737 0.3646446609406726 0.2835388237353082 0.1878679656440358 0.18786796564403574 0.28647436053853614 0.3770922517534949 0.45238031666802614 0.5062391640181172 0.5343054703023622 0.5343054703023622 0.5062391640181172 0.45238031666802614 0.3770922517534948 0.28647436053853625 0.1878679656440358 0.18786796564403574 0.2880023859243772 0.3831156482469369 0.4684383764804658 0.5396921327546491 0.5933039562001243 0.6265855261258564 0.6378679656440357 0.6265855261258564 0.5933039562001243 0.5396921327546491 0.4684383764804658 0.3831156482469369 0.2880023859243772 0.1878679656440358 0.11715728752538102 0.21282814561665342 0.2939339828220179 0.34812717065320264 0.3671572875253809 0.34812717065320264 0.2939339828220179 0.21282814561665347 0.11715728752538107 0.11715728752538102 0.21576368241988142 0.3063815736348402 0.3816696385493714 0.43552848589946247 0.46359479218370747 0.46359479218370747 0.43552848589946247 0.3816696385493714 0.3063815736348401 0.21576368241988153 0.11715728752538107 0.11715728752538102 0.2172917078057225 0.31240497012828217 0.3977276983618111 0.4689814546359944 0.5225932780814696 0.5558748480072017 0.567157287525381 0.5558748480072017 0.5225932780814696 0.4689814546359944 0.3977276983618111 0.31240497012828217 0.2172917078057225 0.11715728752538107; + 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.07071067811865475 0.07071067811865475 0.07071067811865475 0.07071067811865475 0.07071067811865475 0.07071067811865475 0.07071067811865475 0.07071067811865475 0.07071067811865475 0.07071067811865475 0.07071067811865475 0.07071067811865475 0.07071067811865475 0.07071067811865475 0.07071067811865475 0.07071067811865475 0.07071067811865475 0.07071067811865475 0.07071067811865475 0.07071067811865475 0.07071067811865475 0.07071067811865475 0.07071067811865475 0.07071067811865475 0.07071067811865475 0.07071067811865475 0.07071067811865475 0.07071067811865475 0.07071067811865475 0.07071067811865475 0.07071067811865475 0.07071067811865475 0.07071067811865475 0.07071067811865475 0.07071067811865475 0.07071067811865475 0.1414213562373095 0.1414213562373095 0.1414213562373095 0.1414213562373095 0.1414213562373095 0.1414213562373095 0.1414213562373095 0.1414213562373095 0.1414213562373095 0.1414213562373095 0.1414213562373095 0.1414213562373095 0.1414213562373095 0.1414213562373095 0.1414213562373095 0.1414213562373095 0.1414213562373095 0.1414213562373095 0.1414213562373095 0.1414213562373095 0.1414213562373095 0.1414213562373095 0.1414213562373095 0.1414213562373095 0.1414213562373095 0.1414213562373095 0.1414213562373095 0.1414213562373095 0.1414213562373095 0.1414213562373095 0.1414213562373095 0.1414213562373095 0.1414213562373095 0.1414213562373095 0.1414213562373095 0.1414213562373095 0.21213203435596428 0.21213203435596428 0.21213203435596428 0.21213203435596428 0.21213203435596428 0.21213203435596428 0.21213203435596428 0.21213203435596428 0.21213203435596428 0.21213203435596428 0.21213203435596428 0.21213203435596428 0.21213203435596428 0.21213203435596428 0.21213203435596428 0.21213203435596428 0.21213203435596428 0.21213203435596428 0.21213203435596428 0.21213203435596428 0.21213203435596428 0.21213203435596428 0.21213203435596428 0.21213203435596428 0.21213203435596428 0.21213203435596428 0.21213203435596428 0.21213203435596428 0.21213203435596428 0.21213203435596428 0.21213203435596428 0.21213203435596428 0.21213203435596428 0.21213203435596428 0.21213203435596428 0.21213203435596428 0.282842712474619 0.282842712474619 0.282842712474619 0.282842712474619 0.282842712474619 0.282842712474619 0.282842712474619 0.282842712474619 0.282842712474619 0.282842712474619 0.282842712474619 0.282842712474619 0.282842712474619 0.282842712474619 0.282842712474619 0.282842712474619 0.282842712474619 0.282842712474619 0.282842712474619 0.282842712474619 0.282842712474619 0.282842712474619 0.282842712474619 0.282842712474619 0.282842712474619 0.282842712474619 0.282842712474619 0.282842712474619 0.282842712474619 0.282842712474619 0.282842712474619 0.282842712474619 0.282842712474619 0.282842712474619 0.282842712474619 0.282842712474619], + ] + + @testset "Direction $i" for i in eachindex(directions) + shape = extrude_geometry(geometry; direction=directions[i], particle_spacing, + n_extrude=5, tlsph=true, density=1.0) + + @test shape.coordinates ≈ expected_coords[i] + end + end + + @testset verbose=true "Extrude 3D Plane" begin + p1 = [0.0, 0.0, 0.0] + p2 = [0.5, 1.0, 1.0] + p3 = [1.0, 0.2, 0.0] + + direction = [0.0, 0.0, 1.0] + + expected_coords = [0.0 0.09090909090909091 0.18181818181818182 0.2727272727272727 0.36363636363636365 0.45454545454545453 0.5454545454545454 0.6363636363636364 0.7272727272727273 0.8181818181818182 0.9090909090909091 1.0 0.03333333333333333 0.12424242424242424 0.21515151515151515 0.30606060606060603 0.396969696969697 0.48787878787878786 0.5787878787878787 0.6696969696969697 0.7606060606060606 0.8515151515151516 0.9424242424242424 1.0333333333333334 0.06666666666666667 0.1575757575757576 0.24848484848484848 0.33939393939393936 0.4303030303030303 0.5212121212121212 0.6121212121212121 0.703030303030303 0.793939393939394 0.8848484848484849 0.9757575757575757 1.0666666666666667 0.1 0.19090909090909092 0.28181818181818186 0.3727272727272727 0.4636363636363636 0.5545454545454546 0.6454545454545454 0.7363636363636363 0.8272727272727273 0.9181818181818182 1.009090909090909 1.1 0.13333333333333333 0.22424242424242424 0.3151515151515152 0.406060606060606 0.49696969696969695 0.5878787878787879 0.6787878787878787 0.7696969696969697 0.8606060606060606 0.9515151515151515 1.0424242424242425 1.1333333333333333 0.16666666666666666 0.25757575757575757 0.3484848484848485 0.43939393939393934 0.5303030303030303 0.6212121212121212 0.712121212121212 0.803030303030303 0.8939393939393939 0.9848484848484849 1.0757575757575757 1.1666666666666667 0.2 0.2909090909090909 0.38181818181818183 0.4727272727272727 0.5636363636363637 0.6545454545454545 0.7454545454545454 0.8363636363636364 0.9272727272727272 1.0181818181818183 1.1090909090909091 1.2 0.23333333333333334 0.3242424242424242 0.41515151515151516 0.5060606060606061 0.5969696969696969 0.6878787878787879 0.7787878787878788 0.8696969696969696 0.9606060606060607 1.0515151515151515 1.1424242424242423 1.2333333333333334 0.26666666666666666 0.35757575757575755 0.4484848484848485 0.5393939393939393 0.6303030303030304 0.7212121212121212 0.812121212121212 0.9030303030303031 0.9939393939393939 1.084848484848485 1.1757575757575758 1.2666666666666666 0.3 0.3909090909090909 0.4818181818181818 0.5727272727272728 0.6636363636363636 0.7545454545454545 0.8454545454545455 0.9363636363636363 1.0272727272727273 1.1181818181818182 1.209090909090909 1.3 0.3333333333333333 0.4242424242424242 0.5151515151515151 0.606060606060606 0.696969696969697 0.7878787878787878 0.8787878787878787 0.9696969696969697 1.0606060606060606 1.1515151515151516 1.2424242424242424 1.3333333333333333 0.36666666666666664 0.4575757575757575 0.5484848484848485 0.6393939393939394 0.7303030303030302 0.8212121212121212 0.9121212121212121 1.003030303030303 1.093939393939394 1.1848484848484848 1.2757575757575756 1.3666666666666667 0.4 0.49090909090909096 0.5818181818181818 0.6727272727272727 0.7636363636363637 0.8545454545454545 0.9454545454545454 1.0363636363636364 1.1272727272727274 1.2181818181818183 1.309090909090909 1.4 0.43333333333333335 0.5242424242424243 0.6151515151515152 0.706060606060606 0.796969696969697 0.8878787878787879 0.9787878787878788 1.0696969696969698 1.1606060606060606 1.2515151515151515 1.3424242424242423 1.4333333333333333 0.4666666666666667 0.5575757575757576 0.6484848484848484 0.7393939393939394 0.8303030303030303 0.9212121212121211 1.0121212121212122 1.103030303030303 1.1939393939393939 1.284848484848485 1.3757575757575757 1.4666666666666668 0.5 0.5909090909090909 0.6818181818181819 0.7727272727272727 0.8636363636363636 0.9545454545454546 1.0454545454545454 1.1363636363636362 1.2272727272727273 1.3181818181818183 1.4090909090909092 1.5 0.0 0.09090909090909091 0.18181818181818182 0.2727272727272727 0.36363636363636365 0.45454545454545453 0.5454545454545454 0.6363636363636364 0.7272727272727273 0.8181818181818182 0.9090909090909091 1.0 0.03333333333333333 0.12424242424242424 0.21515151515151515 0.30606060606060603 0.396969696969697 0.48787878787878786 0.5787878787878787 0.6696969696969697 0.7606060606060606 0.8515151515151516 0.9424242424242424 1.0333333333333334 0.06666666666666667 0.1575757575757576 0.24848484848484848 0.33939393939393936 0.4303030303030303 0.5212121212121212 0.6121212121212121 0.703030303030303 0.793939393939394 0.8848484848484849 0.9757575757575757 1.0666666666666667 0.1 0.19090909090909092 0.28181818181818186 0.3727272727272727 0.4636363636363636 0.5545454545454546 0.6454545454545454 0.7363636363636363 0.8272727272727273 0.9181818181818182 1.009090909090909 1.1 0.13333333333333333 0.22424242424242424 0.3151515151515152 0.406060606060606 0.49696969696969695 0.5878787878787879 0.6787878787878787 0.7696969696969697 0.8606060606060606 0.9515151515151515 1.0424242424242425 1.1333333333333333 0.16666666666666666 0.25757575757575757 0.3484848484848485 0.43939393939393934 0.5303030303030303 0.6212121212121212 0.712121212121212 0.803030303030303 0.8939393939393939 0.9848484848484849 1.0757575757575757 1.1666666666666667 0.2 0.2909090909090909 0.38181818181818183 0.4727272727272727 0.5636363636363637 0.6545454545454545 0.7454545454545454 0.8363636363636364 0.9272727272727272 1.0181818181818183 1.1090909090909091 1.2 0.23333333333333334 0.3242424242424242 0.41515151515151516 0.5060606060606061 0.5969696969696969 0.6878787878787879 0.7787878787878788 0.8696969696969696 0.9606060606060607 1.0515151515151515 1.1424242424242423 1.2333333333333334 0.26666666666666666 0.35757575757575755 0.4484848484848485 0.5393939393939393 0.6303030303030304 0.7212121212121212 0.812121212121212 0.9030303030303031 0.9939393939393939 1.084848484848485 1.1757575757575758 1.2666666666666666 0.3 0.3909090909090909 0.4818181818181818 0.5727272727272728 0.6636363636363636 0.7545454545454545 0.8454545454545455 0.9363636363636363 1.0272727272727273 1.1181818181818182 1.209090909090909 1.3 0.3333333333333333 0.4242424242424242 0.5151515151515151 0.606060606060606 0.696969696969697 0.7878787878787878 0.8787878787878787 0.9696969696969697 1.0606060606060606 1.1515151515151516 1.2424242424242424 1.3333333333333333 0.36666666666666664 0.4575757575757575 0.5484848484848485 0.6393939393939394 0.7303030303030302 0.8212121212121212 0.9121212121212121 1.003030303030303 1.093939393939394 1.1848484848484848 1.2757575757575756 1.3666666666666667 0.4 0.49090909090909096 0.5818181818181818 0.6727272727272727 0.7636363636363637 0.8545454545454545 0.9454545454545454 1.0363636363636364 1.1272727272727274 1.2181818181818183 1.309090909090909 1.4 0.43333333333333335 0.5242424242424243 0.6151515151515152 0.706060606060606 0.796969696969697 0.8878787878787879 0.9787878787878788 1.0696969696969698 1.1606060606060606 1.2515151515151515 1.3424242424242423 1.4333333333333333 0.4666666666666667 0.5575757575757576 0.6484848484848484 0.7393939393939394 0.8303030303030303 0.9212121212121211 1.0121212121212122 1.103030303030303 1.1939393939393939 1.284848484848485 1.3757575757575757 1.4666666666666668 0.5 0.5909090909090909 0.6818181818181819 0.7727272727272727 0.8636363636363636 0.9545454545454546 1.0454545454545454 1.1363636363636362 1.2272727272727273 1.3181818181818183 1.4090909090909092 1.5 0.0 0.09090909090909091 0.18181818181818182 0.2727272727272727 0.36363636363636365 0.45454545454545453 0.5454545454545454 0.6363636363636364 0.7272727272727273 0.8181818181818182 0.9090909090909091 1.0 0.03333333333333333 0.12424242424242424 0.21515151515151515 0.30606060606060603 0.396969696969697 0.48787878787878786 0.5787878787878787 0.6696969696969697 0.7606060606060606 0.8515151515151516 0.9424242424242424 1.0333333333333334 0.06666666666666667 0.1575757575757576 0.24848484848484848 0.33939393939393936 0.4303030303030303 0.5212121212121212 0.6121212121212121 0.703030303030303 0.793939393939394 0.8848484848484849 0.9757575757575757 1.0666666666666667 0.1 0.19090909090909092 0.28181818181818186 0.3727272727272727 0.4636363636363636 0.5545454545454546 0.6454545454545454 0.7363636363636363 0.8272727272727273 0.9181818181818182 1.009090909090909 1.1 0.13333333333333333 0.22424242424242424 0.3151515151515152 0.406060606060606 0.49696969696969695 0.5878787878787879 0.6787878787878787 0.7696969696969697 0.8606060606060606 0.9515151515151515 1.0424242424242425 1.1333333333333333 0.16666666666666666 0.25757575757575757 0.3484848484848485 0.43939393939393934 0.5303030303030303 0.6212121212121212 0.712121212121212 0.803030303030303 0.8939393939393939 0.9848484848484849 1.0757575757575757 1.1666666666666667 0.2 0.2909090909090909 0.38181818181818183 0.4727272727272727 0.5636363636363637 0.6545454545454545 0.7454545454545454 0.8363636363636364 0.9272727272727272 1.0181818181818183 1.1090909090909091 1.2 0.23333333333333334 0.3242424242424242 0.41515151515151516 0.5060606060606061 0.5969696969696969 0.6878787878787879 0.7787878787878788 0.8696969696969696 0.9606060606060607 1.0515151515151515 1.1424242424242423 1.2333333333333334 0.26666666666666666 0.35757575757575755 0.4484848484848485 0.5393939393939393 0.6303030303030304 0.7212121212121212 0.812121212121212 0.9030303030303031 0.9939393939393939 1.084848484848485 1.1757575757575758 1.2666666666666666 0.3 0.3909090909090909 0.4818181818181818 0.5727272727272728 0.6636363636363636 0.7545454545454545 0.8454545454545455 0.9363636363636363 1.0272727272727273 1.1181818181818182 1.209090909090909 1.3 0.3333333333333333 0.4242424242424242 0.5151515151515151 0.606060606060606 0.696969696969697 0.7878787878787878 0.8787878787878787 0.9696969696969697 1.0606060606060606 1.1515151515151516 1.2424242424242424 1.3333333333333333 0.36666666666666664 0.4575757575757575 0.5484848484848485 0.6393939393939394 0.7303030303030302 0.8212121212121212 0.9121212121212121 1.003030303030303 1.093939393939394 1.1848484848484848 1.2757575757575756 1.3666666666666667 0.4 0.49090909090909096 0.5818181818181818 0.6727272727272727 0.7636363636363637 0.8545454545454545 0.9454545454545454 1.0363636363636364 1.1272727272727274 1.2181818181818183 1.309090909090909 1.4 0.43333333333333335 0.5242424242424243 0.6151515151515152 0.706060606060606 0.796969696969697 0.8878787878787879 0.9787878787878788 1.0696969696969698 1.1606060606060606 1.2515151515151515 1.3424242424242423 1.4333333333333333 0.4666666666666667 0.5575757575757576 0.6484848484848484 0.7393939393939394 0.8303030303030303 0.9212121212121211 1.0121212121212122 1.103030303030303 1.1939393939393939 1.284848484848485 1.3757575757575757 1.4666666666666668 0.5 0.5909090909090909 0.6818181818181819 0.7727272727272727 0.8636363636363636 0.9545454545454546 1.0454545454545454 1.1363636363636362 1.2272727272727273 1.3181818181818183 1.4090909090909092 1.5 0.0 0.09090909090909091 0.18181818181818182 0.2727272727272727 0.36363636363636365 0.45454545454545453 0.5454545454545454 0.6363636363636364 0.7272727272727273 0.8181818181818182 0.9090909090909091 1.0 0.03333333333333333 0.12424242424242424 0.21515151515151515 0.30606060606060603 0.396969696969697 0.48787878787878786 0.5787878787878787 0.6696969696969697 0.7606060606060606 0.8515151515151516 0.9424242424242424 1.0333333333333334 0.06666666666666667 0.1575757575757576 0.24848484848484848 0.33939393939393936 0.4303030303030303 0.5212121212121212 0.6121212121212121 0.703030303030303 0.793939393939394 0.8848484848484849 0.9757575757575757 1.0666666666666667 0.1 0.19090909090909092 0.28181818181818186 0.3727272727272727 0.4636363636363636 0.5545454545454546 0.6454545454545454 0.7363636363636363 0.8272727272727273 0.9181818181818182 1.009090909090909 1.1 0.13333333333333333 0.22424242424242424 0.3151515151515152 0.406060606060606 0.49696969696969695 0.5878787878787879 0.6787878787878787 0.7696969696969697 0.8606060606060606 0.9515151515151515 1.0424242424242425 1.1333333333333333 0.16666666666666666 0.25757575757575757 0.3484848484848485 0.43939393939393934 0.5303030303030303 0.6212121212121212 0.712121212121212 0.803030303030303 0.8939393939393939 0.9848484848484849 1.0757575757575757 1.1666666666666667 0.2 0.2909090909090909 0.38181818181818183 0.4727272727272727 0.5636363636363637 0.6545454545454545 0.7454545454545454 0.8363636363636364 0.9272727272727272 1.0181818181818183 1.1090909090909091 1.2 0.23333333333333334 0.3242424242424242 0.41515151515151516 0.5060606060606061 0.5969696969696969 0.6878787878787879 0.7787878787878788 0.8696969696969696 0.9606060606060607 1.0515151515151515 1.1424242424242423 1.2333333333333334 0.26666666666666666 0.35757575757575755 0.4484848484848485 0.5393939393939393 0.6303030303030304 0.7212121212121212 0.812121212121212 0.9030303030303031 0.9939393939393939 1.084848484848485 1.1757575757575758 1.2666666666666666 0.3 0.3909090909090909 0.4818181818181818 0.5727272727272728 0.6636363636363636 0.7545454545454545 0.8454545454545455 0.9363636363636363 1.0272727272727273 1.1181818181818182 1.209090909090909 1.3 0.3333333333333333 0.4242424242424242 0.5151515151515151 0.606060606060606 0.696969696969697 0.7878787878787878 0.8787878787878787 0.9696969696969697 1.0606060606060606 1.1515151515151516 1.2424242424242424 1.3333333333333333 0.36666666666666664 0.4575757575757575 0.5484848484848485 0.6393939393939394 0.7303030303030302 0.8212121212121212 0.9121212121212121 1.003030303030303 1.093939393939394 1.1848484848484848 1.2757575757575756 1.3666666666666667 0.4 0.49090909090909096 0.5818181818181818 0.6727272727272727 0.7636363636363637 0.8545454545454545 0.9454545454545454 1.0363636363636364 1.1272727272727274 1.2181818181818183 1.309090909090909 1.4 0.43333333333333335 0.5242424242424243 0.6151515151515152 0.706060606060606 0.796969696969697 0.8878787878787879 0.9787878787878788 1.0696969696969698 1.1606060606060606 1.2515151515151515 1.3424242424242423 1.4333333333333333 0.4666666666666667 0.5575757575757576 0.6484848484848484 0.7393939393939394 0.8303030303030303 0.9212121212121211 1.0121212121212122 1.103030303030303 1.1939393939393939 1.284848484848485 1.3757575757575757 1.4666666666666668 0.5 0.5909090909090909 0.6818181818181819 0.7727272727272727 0.8636363636363636 0.9545454545454546 1.0454545454545454 1.1363636363636362 1.2272727272727273 1.3181818181818183 1.4090909090909092 1.5; + 0.0 0.018181818181818184 0.03636363636363637 0.05454545454545454 0.07272727272727274 0.09090909090909091 0.10909090909090909 0.1272727272727273 0.14545454545454548 0.16363636363636366 0.18181818181818182 0.2 0.06666666666666667 0.08484848484848485 0.10303030303030303 0.12121212121212122 0.1393939393939394 0.1575757575757576 0.17575757575757575 0.19393939393939397 0.21212121212121215 0.23030303030303034 0.24848484848484848 0.26666666666666666 0.13333333333333333 0.15151515151515152 0.1696969696969697 0.18787878787878787 0.20606060606060606 0.22424242424242424 0.24242424242424243 0.2606060606060606 0.2787878787878788 0.296969696969697 0.3151515151515152 0.33333333333333337 0.2 0.2181818181818182 0.2363636363636364 0.2545454545454546 0.27272727272727276 0.2909090909090909 0.3090909090909091 0.32727272727272727 0.34545454545454546 0.36363636363636365 0.38181818181818183 0.4 0.26666666666666666 0.28484848484848485 0.30303030303030304 0.3212121212121212 0.3393939393939394 0.35757575757575755 0.37575757575757573 0.3939393939393939 0.4121212121212121 0.4303030303030303 0.4484848484848485 0.4666666666666667 0.3333333333333333 0.3515151515151515 0.3696969696969697 0.3878787878787879 0.40606060606060607 0.4242424242424242 0.4424242424242424 0.4606060606060606 0.47878787878787876 0.49696969696969695 0.5151515151515151 0.5333333333333333 0.4 0.4181818181818182 0.4363636363636364 0.4545454545454546 0.4727272727272728 0.49090909090909096 0.5090909090909091 0.5272727272727273 0.5454545454545455 0.5636363636363637 0.5818181818181818 0.6000000000000001 0.4666666666666667 0.48484848484848486 0.503030303030303 0.5212121212121212 0.5393939393939394 0.5575757575757576 0.5757575757575758 0.593939393939394 0.6121212121212122 0.6303030303030304 0.6484848484848484 0.6666666666666667 0.5333333333333333 0.5515151515151515 0.5696969696969697 0.5878787878787879 0.6060606060606061 0.6242424242424243 0.6424242424242425 0.6606060606060606 0.6787878787878788 0.696969696969697 0.7151515151515151 0.7333333333333334 0.6 0.6181818181818182 0.6363636363636364 0.6545454545454545 0.6727272727272727 0.6909090909090909 0.7090909090909091 0.7272727272727273 0.7454545454545455 0.7636363636363637 0.7818181818181817 0.8 0.6666666666666666 0.6848484848484848 0.703030303030303 0.7212121212121212 0.7393939393939394 0.7575757575757576 0.7757575757575758 0.793939393939394 0.8121212121212121 0.8303030303030303 0.8484848484848484 0.8666666666666667 0.7333333333333333 0.7515151515151515 0.7696969696969697 0.7878787878787878 0.806060606060606 0.8242424242424242 0.8424242424242424 0.8606060606060606 0.8787878787878788 0.896969696969697 0.915151515151515 0.9333333333333333 0.8 0.8181818181818182 0.8363636363636364 0.8545454545454546 0.8727272727272728 0.890909090909091 0.9090909090909092 0.9272727272727274 0.9454545454545455 0.9636363636363637 0.9818181818181819 1.0 0.8666666666666667 0.8848484848484849 0.9030303030303031 0.9212121212121213 0.9393939393939394 0.9575757575757576 0.9757575757575758 0.993939393939394 1.0121212121212122 1.0303030303030303 1.0484848484848486 1.0666666666666667 0.9333333333333333 0.9515151515151515 0.9696969696969697 0.9878787878787879 1.006060606060606 1.0242424242424242 1.0424242424242425 1.0606060606060606 1.0787878787878789 1.096969696969697 1.1151515151515152 1.1333333333333333 1.0 1.018181818181818 1.0363636363636364 1.0545454545454545 1.0727272727272728 1.0909090909090908 1.1090909090909091 1.1272727272727272 1.1454545454545455 1.1636363636363636 1.1818181818181819 1.2 0.0 0.018181818181818184 0.03636363636363637 0.05454545454545454 0.07272727272727274 0.09090909090909091 0.10909090909090909 0.1272727272727273 0.14545454545454548 0.16363636363636366 0.18181818181818182 0.2 0.06666666666666667 0.08484848484848485 0.10303030303030303 0.12121212121212122 0.1393939393939394 0.1575757575757576 0.17575757575757575 0.19393939393939397 0.21212121212121215 0.23030303030303034 0.24848484848484848 0.26666666666666666 0.13333333333333333 0.15151515151515152 0.1696969696969697 0.18787878787878787 0.20606060606060606 0.22424242424242424 0.24242424242424243 0.2606060606060606 0.2787878787878788 0.296969696969697 0.3151515151515152 0.33333333333333337 0.2 0.2181818181818182 0.2363636363636364 0.2545454545454546 0.27272727272727276 0.2909090909090909 0.3090909090909091 0.32727272727272727 0.34545454545454546 0.36363636363636365 0.38181818181818183 0.4 0.26666666666666666 0.28484848484848485 0.30303030303030304 0.3212121212121212 0.3393939393939394 0.35757575757575755 0.37575757575757573 0.3939393939393939 0.4121212121212121 0.4303030303030303 0.4484848484848485 0.4666666666666667 0.3333333333333333 0.3515151515151515 0.3696969696969697 0.3878787878787879 0.40606060606060607 0.4242424242424242 0.4424242424242424 0.4606060606060606 0.47878787878787876 0.49696969696969695 0.5151515151515151 0.5333333333333333 0.4 0.4181818181818182 0.4363636363636364 0.4545454545454546 0.4727272727272728 0.49090909090909096 0.5090909090909091 0.5272727272727273 0.5454545454545455 0.5636363636363637 0.5818181818181818 0.6000000000000001 0.4666666666666667 0.48484848484848486 0.503030303030303 0.5212121212121212 0.5393939393939394 0.5575757575757576 0.5757575757575758 0.593939393939394 0.6121212121212122 0.6303030303030304 0.6484848484848484 0.6666666666666667 0.5333333333333333 0.5515151515151515 0.5696969696969697 0.5878787878787879 0.6060606060606061 0.6242424242424243 0.6424242424242425 0.6606060606060606 0.6787878787878788 0.696969696969697 0.7151515151515151 0.7333333333333334 0.6 0.6181818181818182 0.6363636363636364 0.6545454545454545 0.6727272727272727 0.6909090909090909 0.7090909090909091 0.7272727272727273 0.7454545454545455 0.7636363636363637 0.7818181818181817 0.8 0.6666666666666666 0.6848484848484848 0.703030303030303 0.7212121212121212 0.7393939393939394 0.7575757575757576 0.7757575757575758 0.793939393939394 0.8121212121212121 0.8303030303030303 0.8484848484848484 0.8666666666666667 0.7333333333333333 0.7515151515151515 0.7696969696969697 0.7878787878787878 0.806060606060606 0.8242424242424242 0.8424242424242424 0.8606060606060606 0.8787878787878788 0.896969696969697 0.915151515151515 0.9333333333333333 0.8 0.8181818181818182 0.8363636363636364 0.8545454545454546 0.8727272727272728 0.890909090909091 0.9090909090909092 0.9272727272727274 0.9454545454545455 0.9636363636363637 0.9818181818181819 1.0 0.8666666666666667 0.8848484848484849 0.9030303030303031 0.9212121212121213 0.9393939393939394 0.9575757575757576 0.9757575757575758 0.993939393939394 1.0121212121212122 1.0303030303030303 1.0484848484848486 1.0666666666666667 0.9333333333333333 0.9515151515151515 0.9696969696969697 0.9878787878787879 1.006060606060606 1.0242424242424242 1.0424242424242425 1.0606060606060606 1.0787878787878789 1.096969696969697 1.1151515151515152 1.1333333333333333 1.0 1.018181818181818 1.0363636363636364 1.0545454545454545 1.0727272727272728 1.0909090909090908 1.1090909090909091 1.1272727272727272 1.1454545454545455 1.1636363636363636 1.1818181818181819 1.2 0.0 0.018181818181818184 0.03636363636363637 0.05454545454545454 0.07272727272727274 0.09090909090909091 0.10909090909090909 0.1272727272727273 0.14545454545454548 0.16363636363636366 0.18181818181818182 0.2 0.06666666666666667 0.08484848484848485 0.10303030303030303 0.12121212121212122 0.1393939393939394 0.1575757575757576 0.17575757575757575 0.19393939393939397 0.21212121212121215 0.23030303030303034 0.24848484848484848 0.26666666666666666 0.13333333333333333 0.15151515151515152 0.1696969696969697 0.18787878787878787 0.20606060606060606 0.22424242424242424 0.24242424242424243 0.2606060606060606 0.2787878787878788 0.296969696969697 0.3151515151515152 0.33333333333333337 0.2 0.2181818181818182 0.2363636363636364 0.2545454545454546 0.27272727272727276 0.2909090909090909 0.3090909090909091 0.32727272727272727 0.34545454545454546 0.36363636363636365 0.38181818181818183 0.4 0.26666666666666666 0.28484848484848485 0.30303030303030304 0.3212121212121212 0.3393939393939394 0.35757575757575755 0.37575757575757573 0.3939393939393939 0.4121212121212121 0.4303030303030303 0.4484848484848485 0.4666666666666667 0.3333333333333333 0.3515151515151515 0.3696969696969697 0.3878787878787879 0.40606060606060607 0.4242424242424242 0.4424242424242424 0.4606060606060606 0.47878787878787876 0.49696969696969695 0.5151515151515151 0.5333333333333333 0.4 0.4181818181818182 0.4363636363636364 0.4545454545454546 0.4727272727272728 0.49090909090909096 0.5090909090909091 0.5272727272727273 0.5454545454545455 0.5636363636363637 0.5818181818181818 0.6000000000000001 0.4666666666666667 0.48484848484848486 0.503030303030303 0.5212121212121212 0.5393939393939394 0.5575757575757576 0.5757575757575758 0.593939393939394 0.6121212121212122 0.6303030303030304 0.6484848484848484 0.6666666666666667 0.5333333333333333 0.5515151515151515 0.5696969696969697 0.5878787878787879 0.6060606060606061 0.6242424242424243 0.6424242424242425 0.6606060606060606 0.6787878787878788 0.696969696969697 0.7151515151515151 0.7333333333333334 0.6 0.6181818181818182 0.6363636363636364 0.6545454545454545 0.6727272727272727 0.6909090909090909 0.7090909090909091 0.7272727272727273 0.7454545454545455 0.7636363636363637 0.7818181818181817 0.8 0.6666666666666666 0.6848484848484848 0.703030303030303 0.7212121212121212 0.7393939393939394 0.7575757575757576 0.7757575757575758 0.793939393939394 0.8121212121212121 0.8303030303030303 0.8484848484848484 0.8666666666666667 0.7333333333333333 0.7515151515151515 0.7696969696969697 0.7878787878787878 0.806060606060606 0.8242424242424242 0.8424242424242424 0.8606060606060606 0.8787878787878788 0.896969696969697 0.915151515151515 0.9333333333333333 0.8 0.8181818181818182 0.8363636363636364 0.8545454545454546 0.8727272727272728 0.890909090909091 0.9090909090909092 0.9272727272727274 0.9454545454545455 0.9636363636363637 0.9818181818181819 1.0 0.8666666666666667 0.8848484848484849 0.9030303030303031 0.9212121212121213 0.9393939393939394 0.9575757575757576 0.9757575757575758 0.993939393939394 1.0121212121212122 1.0303030303030303 1.0484848484848486 1.0666666666666667 0.9333333333333333 0.9515151515151515 0.9696969696969697 0.9878787878787879 1.006060606060606 1.0242424242424242 1.0424242424242425 1.0606060606060606 1.0787878787878789 1.096969696969697 1.1151515151515152 1.1333333333333333 1.0 1.018181818181818 1.0363636363636364 1.0545454545454545 1.0727272727272728 1.0909090909090908 1.1090909090909091 1.1272727272727272 1.1454545454545455 1.1636363636363636 1.1818181818181819 1.2 0.0 0.018181818181818184 0.03636363636363637 0.05454545454545454 0.07272727272727274 0.09090909090909091 0.10909090909090909 0.1272727272727273 0.14545454545454548 0.16363636363636366 0.18181818181818182 0.2 0.06666666666666667 0.08484848484848485 0.10303030303030303 0.12121212121212122 0.1393939393939394 0.1575757575757576 0.17575757575757575 0.19393939393939397 0.21212121212121215 0.23030303030303034 0.24848484848484848 0.26666666666666666 0.13333333333333333 0.15151515151515152 0.1696969696969697 0.18787878787878787 0.20606060606060606 0.22424242424242424 0.24242424242424243 0.2606060606060606 0.2787878787878788 0.296969696969697 0.3151515151515152 0.33333333333333337 0.2 0.2181818181818182 0.2363636363636364 0.2545454545454546 0.27272727272727276 0.2909090909090909 0.3090909090909091 0.32727272727272727 0.34545454545454546 0.36363636363636365 0.38181818181818183 0.4 0.26666666666666666 0.28484848484848485 0.30303030303030304 0.3212121212121212 0.3393939393939394 0.35757575757575755 0.37575757575757573 0.3939393939393939 0.4121212121212121 0.4303030303030303 0.4484848484848485 0.4666666666666667 0.3333333333333333 0.3515151515151515 0.3696969696969697 0.3878787878787879 0.40606060606060607 0.4242424242424242 0.4424242424242424 0.4606060606060606 0.47878787878787876 0.49696969696969695 0.5151515151515151 0.5333333333333333 0.4 0.4181818181818182 0.4363636363636364 0.4545454545454546 0.4727272727272728 0.49090909090909096 0.5090909090909091 0.5272727272727273 0.5454545454545455 0.5636363636363637 0.5818181818181818 0.6000000000000001 0.4666666666666667 0.48484848484848486 0.503030303030303 0.5212121212121212 0.5393939393939394 0.5575757575757576 0.5757575757575758 0.593939393939394 0.6121212121212122 0.6303030303030304 0.6484848484848484 0.6666666666666667 0.5333333333333333 0.5515151515151515 0.5696969696969697 0.5878787878787879 0.6060606060606061 0.6242424242424243 0.6424242424242425 0.6606060606060606 0.6787878787878788 0.696969696969697 0.7151515151515151 0.7333333333333334 0.6 0.6181818181818182 0.6363636363636364 0.6545454545454545 0.6727272727272727 0.6909090909090909 0.7090909090909091 0.7272727272727273 0.7454545454545455 0.7636363636363637 0.7818181818181817 0.8 0.6666666666666666 0.6848484848484848 0.703030303030303 0.7212121212121212 0.7393939393939394 0.7575757575757576 0.7757575757575758 0.793939393939394 0.8121212121212121 0.8303030303030303 0.8484848484848484 0.8666666666666667 0.7333333333333333 0.7515151515151515 0.7696969696969697 0.7878787878787878 0.806060606060606 0.8242424242424242 0.8424242424242424 0.8606060606060606 0.8787878787878788 0.896969696969697 0.915151515151515 0.9333333333333333 0.8 0.8181818181818182 0.8363636363636364 0.8545454545454546 0.8727272727272728 0.890909090909091 0.9090909090909092 0.9272727272727274 0.9454545454545455 0.9636363636363637 0.9818181818181819 1.0 0.8666666666666667 0.8848484848484849 0.9030303030303031 0.9212121212121213 0.9393939393939394 0.9575757575757576 0.9757575757575758 0.993939393939394 1.0121212121212122 1.0303030303030303 1.0484848484848486 1.0666666666666667 0.9333333333333333 0.9515151515151515 0.9696969696969697 0.9878787878787879 1.006060606060606 1.0242424242424242 1.0424242424242425 1.0606060606060606 1.0787878787878789 1.096969696969697 1.1151515151515152 1.1333333333333333 1.0 1.018181818181818 1.0363636363636364 1.0545454545454545 1.0727272727272728 1.0909090909090908 1.1090909090909091 1.1272727272727272 1.1454545454545455 1.1636363636363636 1.1818181818181819 1.2; + 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.06666666666666667 0.06666666666666667 0.06666666666666667 0.06666666666666667 0.06666666666666667 0.06666666666666667 0.06666666666666667 0.06666666666666667 0.06666666666666667 0.06666666666666667 0.06666666666666667 0.06666666666666667 0.13333333333333333 0.13333333333333333 0.13333333333333333 0.13333333333333333 0.13333333333333333 0.13333333333333333 0.13333333333333333 0.13333333333333333 0.13333333333333333 0.13333333333333333 0.13333333333333333 0.13333333333333333 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.26666666666666666 0.26666666666666666 0.26666666666666666 0.26666666666666666 0.26666666666666666 0.26666666666666666 0.26666666666666666 0.26666666666666666 0.26666666666666666 0.26666666666666666 0.26666666666666666 0.26666666666666666 0.3333333333333333 0.3333333333333333 0.3333333333333333 0.3333333333333333 0.3333333333333333 0.3333333333333333 0.3333333333333333 0.3333333333333333 0.3333333333333333 0.3333333333333333 0.3333333333333333 0.3333333333333333 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4666666666666667 0.4666666666666667 0.4666666666666667 0.4666666666666667 0.4666666666666667 0.4666666666666667 0.4666666666666667 0.4666666666666667 0.4666666666666667 0.4666666666666667 0.4666666666666667 0.4666666666666667 0.5333333333333333 0.5333333333333333 0.5333333333333333 0.5333333333333333 0.5333333333333333 0.5333333333333333 0.5333333333333333 0.5333333333333333 0.5333333333333333 0.5333333333333333 0.5333333333333333 0.5333333333333333 0.6 0.6 0.6 0.6 0.6 0.6 0.6 0.6 0.6 0.6 0.6 0.6 0.6666666666666666 0.6666666666666666 0.6666666666666666 0.6666666666666666 0.6666666666666666 0.6666666666666666 0.6666666666666666 0.6666666666666666 0.6666666666666666 0.6666666666666666 0.6666666666666666 0.6666666666666666 0.7333333333333333 0.7333333333333333 0.7333333333333333 0.7333333333333333 0.7333333333333333 0.7333333333333333 0.7333333333333333 0.7333333333333333 0.7333333333333333 0.7333333333333333 0.7333333333333333 0.7333333333333333 0.8 0.8 0.8 0.8 0.8 0.8 0.8 0.8 0.8 0.8 0.8 0.8 0.8666666666666667 0.8666666666666667 0.8666666666666667 0.8666666666666667 0.8666666666666667 0.8666666666666667 0.8666666666666667 0.8666666666666667 0.8666666666666667 0.8666666666666667 0.8666666666666667 0.8666666666666667 0.9333333333333333 0.9333333333333333 0.9333333333333333 0.9333333333333333 0.9333333333333333 0.9333333333333333 0.9333333333333333 0.9333333333333333 0.9333333333333333 0.9333333333333333 0.9333333333333333 0.9333333333333333 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 0.092709445701687 0.092709445701687 0.092709445701687 0.092709445701687 0.092709445701687 0.092709445701687 0.092709445701687 0.092709445701687 0.092709445701687 0.092709445701687 0.092709445701687 0.092709445701687 0.15937611236835367 0.15937611236835367 0.15937611236835367 0.15937611236835367 0.15937611236835367 0.15937611236835367 0.15937611236835367 0.15937611236835367 0.15937611236835367 0.15937611236835367 0.15937611236835367 0.15937611236835367 0.22604277903502035 0.22604277903502035 0.22604277903502035 0.22604277903502035 0.22604277903502035 0.22604277903502035 0.22604277903502035 0.22604277903502035 0.22604277903502035 0.22604277903502035 0.22604277903502035 0.22604277903502035 0.292709445701687 0.292709445701687 0.292709445701687 0.292709445701687 0.292709445701687 0.292709445701687 0.292709445701687 0.292709445701687 0.292709445701687 0.292709445701687 0.292709445701687 0.292709445701687 0.35937611236835365 0.35937611236835365 0.35937611236835365 0.35937611236835365 0.35937611236835365 0.35937611236835365 0.35937611236835365 0.35937611236835365 0.35937611236835365 0.35937611236835365 0.35937611236835365 0.35937611236835365 0.4260427790350203 0.4260427790350203 0.4260427790350203 0.4260427790350203 0.4260427790350203 0.4260427790350203 0.4260427790350203 0.4260427790350203 0.4260427790350203 0.4260427790350203 0.4260427790350203 0.4260427790350203 0.492709445701687 0.492709445701687 0.492709445701687 0.492709445701687 0.492709445701687 0.492709445701687 0.492709445701687 0.492709445701687 0.492709445701687 0.492709445701687 0.492709445701687 0.492709445701687 0.5593761123683537 0.5593761123683537 0.5593761123683537 0.5593761123683537 0.5593761123683537 0.5593761123683537 0.5593761123683537 0.5593761123683537 0.5593761123683537 0.5593761123683537 0.5593761123683537 0.5593761123683537 0.6260427790350204 0.6260427790350204 0.6260427790350204 0.6260427790350204 0.6260427790350204 0.6260427790350204 0.6260427790350204 0.6260427790350204 0.6260427790350204 0.6260427790350204 0.6260427790350204 0.6260427790350204 0.692709445701687 0.692709445701687 0.692709445701687 0.692709445701687 0.692709445701687 0.692709445701687 0.692709445701687 0.692709445701687 0.692709445701687 0.692709445701687 0.692709445701687 0.692709445701687 0.7593761123683537 0.7593761123683537 0.7593761123683537 0.7593761123683537 0.7593761123683537 0.7593761123683537 0.7593761123683537 0.7593761123683537 0.7593761123683537 0.7593761123683537 0.7593761123683537 0.7593761123683537 0.8260427790350203 0.8260427790350203 0.8260427790350203 0.8260427790350203 0.8260427790350203 0.8260427790350203 0.8260427790350203 0.8260427790350203 0.8260427790350203 0.8260427790350203 0.8260427790350203 0.8260427790350203 0.8927094457016871 0.8927094457016871 0.8927094457016871 0.8927094457016871 0.8927094457016871 0.8927094457016871 0.8927094457016871 0.8927094457016871 0.8927094457016871 0.8927094457016871 0.8927094457016871 0.8927094457016871 0.9593761123683537 0.9593761123683537 0.9593761123683537 0.9593761123683537 0.9593761123683537 0.9593761123683537 0.9593761123683537 0.9593761123683537 0.9593761123683537 0.9593761123683537 0.9593761123683537 0.9593761123683537 1.0260427790350204 1.0260427790350204 1.0260427790350204 1.0260427790350204 1.0260427790350204 1.0260427790350204 1.0260427790350204 1.0260427790350204 1.0260427790350204 1.0260427790350204 1.0260427790350204 1.0260427790350204 1.092709445701687 1.092709445701687 1.092709445701687 1.092709445701687 1.092709445701687 1.092709445701687 1.092709445701687 1.092709445701687 1.092709445701687 1.092709445701687 1.092709445701687 1.092709445701687 0.185418891403374 0.185418891403374 0.185418891403374 0.185418891403374 0.185418891403374 0.185418891403374 0.185418891403374 0.185418891403374 0.185418891403374 0.185418891403374 0.185418891403374 0.185418891403374 0.2520855580700407 0.2520855580700407 0.2520855580700407 0.2520855580700407 0.2520855580700407 0.2520855580700407 0.2520855580700407 0.2520855580700407 0.2520855580700407 0.2520855580700407 0.2520855580700407 0.2520855580700407 0.31875222473670733 0.31875222473670733 0.31875222473670733 0.31875222473670733 0.31875222473670733 0.31875222473670733 0.31875222473670733 0.31875222473670733 0.31875222473670733 0.31875222473670733 0.31875222473670733 0.31875222473670733 0.38541889140337404 0.38541889140337404 0.38541889140337404 0.38541889140337404 0.38541889140337404 0.38541889140337404 0.38541889140337404 0.38541889140337404 0.38541889140337404 0.38541889140337404 0.38541889140337404 0.38541889140337404 0.4520855580700407 0.4520855580700407 0.4520855580700407 0.4520855580700407 0.4520855580700407 0.4520855580700407 0.4520855580700407 0.4520855580700407 0.4520855580700407 0.4520855580700407 0.4520855580700407 0.4520855580700407 0.5187522247367073 0.5187522247367073 0.5187522247367073 0.5187522247367073 0.5187522247367073 0.5187522247367073 0.5187522247367073 0.5187522247367073 0.5187522247367073 0.5187522247367073 0.5187522247367073 0.5187522247367073 0.585418891403374 0.585418891403374 0.585418891403374 0.585418891403374 0.585418891403374 0.585418891403374 0.585418891403374 0.585418891403374 0.585418891403374 0.585418891403374 0.585418891403374 0.585418891403374 0.6520855580700406 0.6520855580700406 0.6520855580700406 0.6520855580700406 0.6520855580700406 0.6520855580700406 0.6520855580700406 0.6520855580700406 0.6520855580700406 0.6520855580700406 0.6520855580700406 0.6520855580700406 0.7187522247367073 0.7187522247367073 0.7187522247367073 0.7187522247367073 0.7187522247367073 0.7187522247367073 0.7187522247367073 0.7187522247367073 0.7187522247367073 0.7187522247367073 0.7187522247367073 0.7187522247367073 0.785418891403374 0.785418891403374 0.785418891403374 0.785418891403374 0.785418891403374 0.785418891403374 0.785418891403374 0.785418891403374 0.785418891403374 0.785418891403374 0.785418891403374 0.785418891403374 0.8520855580700406 0.8520855580700406 0.8520855580700406 0.8520855580700406 0.8520855580700406 0.8520855580700406 0.8520855580700406 0.8520855580700406 0.8520855580700406 0.8520855580700406 0.8520855580700406 0.8520855580700406 0.9187522247367073 0.9187522247367073 0.9187522247367073 0.9187522247367073 0.9187522247367073 0.9187522247367073 0.9187522247367073 0.9187522247367073 0.9187522247367073 0.9187522247367073 0.9187522247367073 0.9187522247367073 0.985418891403374 0.985418891403374 0.985418891403374 0.985418891403374 0.985418891403374 0.985418891403374 0.985418891403374 0.985418891403374 0.985418891403374 0.985418891403374 0.985418891403374 0.985418891403374 1.0520855580700408 1.0520855580700408 1.0520855580700408 1.0520855580700408 1.0520855580700408 1.0520855580700408 1.0520855580700408 1.0520855580700408 1.0520855580700408 1.0520855580700408 1.0520855580700408 1.0520855580700408 1.1187522247367074 1.1187522247367074 1.1187522247367074 1.1187522247367074 1.1187522247367074 1.1187522247367074 1.1187522247367074 1.1187522247367074 1.1187522247367074 1.1187522247367074 1.1187522247367074 1.1187522247367074 1.185418891403374 1.185418891403374 1.185418891403374 1.185418891403374 1.185418891403374 1.185418891403374 1.185418891403374 1.185418891403374 1.185418891403374 1.185418891403374 1.185418891403374 1.185418891403374 0.278128337105061 0.278128337105061 0.278128337105061 0.278128337105061 0.278128337105061 0.278128337105061 0.278128337105061 0.278128337105061 0.278128337105061 0.278128337105061 0.278128337105061 0.278128337105061 0.34479500377172767 0.34479500377172767 0.34479500377172767 0.34479500377172767 0.34479500377172767 0.34479500377172767 0.34479500377172767 0.34479500377172767 0.34479500377172767 0.34479500377172767 0.34479500377172767 0.34479500377172767 0.4114616704383943 0.4114616704383943 0.4114616704383943 0.4114616704383943 0.4114616704383943 0.4114616704383943 0.4114616704383943 0.4114616704383943 0.4114616704383943 0.4114616704383943 0.4114616704383943 0.4114616704383943 0.47812833710506103 0.47812833710506103 0.47812833710506103 0.47812833710506103 0.47812833710506103 0.47812833710506103 0.47812833710506103 0.47812833710506103 0.47812833710506103 0.47812833710506103 0.47812833710506103 0.47812833710506103 0.5447950037717277 0.5447950037717277 0.5447950037717277 0.5447950037717277 0.5447950037717277 0.5447950037717277 0.5447950037717277 0.5447950037717277 0.5447950037717277 0.5447950037717277 0.5447950037717277 0.5447950037717277 0.6114616704383944 0.6114616704383944 0.6114616704383944 0.6114616704383944 0.6114616704383944 0.6114616704383944 0.6114616704383944 0.6114616704383944 0.6114616704383944 0.6114616704383944 0.6114616704383944 0.6114616704383944 0.678128337105061 0.678128337105061 0.678128337105061 0.678128337105061 0.678128337105061 0.678128337105061 0.678128337105061 0.678128337105061 0.678128337105061 0.678128337105061 0.678128337105061 0.678128337105061 0.7447950037717277 0.7447950037717277 0.7447950037717277 0.7447950037717277 0.7447950037717277 0.7447950037717277 0.7447950037717277 0.7447950037717277 0.7447950037717277 0.7447950037717277 0.7447950037717277 0.7447950037717277 0.8114616704383943 0.8114616704383943 0.8114616704383943 0.8114616704383943 0.8114616704383943 0.8114616704383943 0.8114616704383943 0.8114616704383943 0.8114616704383943 0.8114616704383943 0.8114616704383943 0.8114616704383943 0.878128337105061 0.878128337105061 0.878128337105061 0.878128337105061 0.878128337105061 0.878128337105061 0.878128337105061 0.878128337105061 0.878128337105061 0.878128337105061 0.878128337105061 0.878128337105061 0.9447950037717276 0.9447950037717276 0.9447950037717276 0.9447950037717276 0.9447950037717276 0.9447950037717276 0.9447950037717276 0.9447950037717276 0.9447950037717276 0.9447950037717276 0.9447950037717276 0.9447950037717276 1.0114616704383943 1.0114616704383943 1.0114616704383943 1.0114616704383943 1.0114616704383943 1.0114616704383943 1.0114616704383943 1.0114616704383943 1.0114616704383943 1.0114616704383943 1.0114616704383943 1.0114616704383943 1.0781283371050612 1.0781283371050612 1.0781283371050612 1.0781283371050612 1.0781283371050612 1.0781283371050612 1.0781283371050612 1.0781283371050612 1.0781283371050612 1.0781283371050612 1.0781283371050612 1.0781283371050612 1.1447950037717276 1.1447950037717276 1.1447950037717276 1.1447950037717276 1.1447950037717276 1.1447950037717276 1.1447950037717276 1.1447950037717276 1.1447950037717276 1.1447950037717276 1.1447950037717276 1.1447950037717276 1.2114616704383945 1.2114616704383945 1.2114616704383945 1.2114616704383945 1.2114616704383945 1.2114616704383945 1.2114616704383945 1.2114616704383945 1.2114616704383945 1.2114616704383945 1.2114616704383945 1.2114616704383945 1.278128337105061 1.278128337105061 1.278128337105061 1.278128337105061 1.278128337105061 1.278128337105061 1.278128337105061 1.278128337105061 1.278128337105061 1.278128337105061 1.278128337105061 1.278128337105061] + + shape = extrude_geometry((p1, p2, p3); direction, particle_spacing=0.1, n_extrude=4, + density=1000.0, tlsph=true) + + @test shape.coordinates ≈ expected_coords + end + + @testset verbose=true "Shifted Particles" begin + p1 = [0.0, 0.0, 0.0] + p2 = [0.5, 1.0, 0.0] + p3 = [1.0, 0.2, 0.0] + + direction = [0.0, 0.0, 1.0] + + expected_coords = [0.0713897135595439 0.1615839068026347 0.25177810004572554 0.3419722932888163 0.43216648653190715 0.5223606797749979 0.6125548730180886 0.7027490662611794 0.7929432595042704 0.8831374527473611 0.9733316459904519 0.11277868087318066 0.20297287411627146 0.29316706735936227 0.383361260602453 0.47355545384554387 0.5637496470886346 0.6539438403317254 0.7441380335748161 0.8343322268179071 0.9245264200609978 1.0147206133040885 0.15416764818681739 0.2443618414299082 0.334556034672999 0.42475022791608974 0.5149444211591806 0.6051386144022713 0.6953328076453621 0.7855270008884528 0.8757211941315438 0.9659153873746346 1.0561095806177252 0.19555661550045414 0.28575080874354497 0.3759450019866357 0.46613919522972647 0.5563333884728173 0.6465275817159081 0.7367217749589988 0.8269159682020896 0.9171101614451805 1.0073043546882714 1.0974985479313621 0.2369455828140909 0.3271397760571817 0.4173339693002725 0.5075281625433632 0.5977223557864542 0.6879165490295449 0.7781107422726357 0.8683049355157264 0.9584991287588174 1.0486933220019081 1.1388875152449989 0.2783345501277276 0.3685287433708184 0.4587229366139092 0.5489171298569999 0.6391113231000909 0.7293055163431816 0.8194997095862724 0.9096939028293631 0.9998880960724541 1.0900822893155449 1.1802764825586356 0.3197235174413644 0.4099177106844552 0.500111903927546 0.5903060971706368 0.6805002904137276 0.7706944836568184 0.8608886768999091 0.9510828701429999 1.0412770633860908 1.1314712566291816 1.2216654498722723 0.36111248475500113 0.45130667799809193 0.5415008712411827 0.6316950644842735 0.7218892577273643 0.8120834509704551 0.9022776442135458 0.9924718374566366 1.0826660306997276 1.1728602239428183 1.263054417185909 0.40250145206863785 0.49269564531172866 0.5828898385548195 0.6730840317979102 0.7632782250410011 0.8534724182840918 0.9436666115271826 1.0338608047702733 1.1240549980133643 1.214249191256455 1.3044433844995458 0.4438904193822747 0.5340846126253656 0.6242788058684563 0.714472999111547 0.8046671923546379 0.8948613855977287 0.9850555788408194 1.07524977208391 1.1654439653270012 1.255638158570092 1.3458323518131827 0.4852793866959113 0.5754735799390021 0.6656677731820929 0.7558619664251837 0.8460561596682745 0.9362503529113653 1.026444546154456 1.1166387393975468 1.2068329326406377 1.2970271258837285 1.3872213191268192 0.5266683540095481 0.616862547252639 0.7070567404957298 0.7972509337388205 0.8874451269819114 0.9776393202250021 1.0678335134680927 1.1580277067111835 1.2482218999542747 1.3384160931973654 1.4286102864404562 0.0713897135595439 0.1615839068026347 0.25177810004572554 0.3419722932888163 0.43216648653190715 0.5223606797749979 0.6125548730180886 0.7027490662611794 0.7929432595042704 0.8831374527473611 0.9733316459904519 0.11277868087318066 0.20297287411627146 0.29316706735936227 0.383361260602453 0.47355545384554387 0.5637496470886346 0.6539438403317254 0.7441380335748161 0.8343322268179071 0.9245264200609978 1.0147206133040885 0.15416764818681739 0.2443618414299082 0.334556034672999 0.42475022791608974 0.5149444211591806 0.6051386144022713 0.6953328076453621 0.7855270008884528 0.8757211941315438 0.9659153873746346 1.0561095806177252 0.19555661550045414 0.28575080874354497 0.3759450019866357 0.46613919522972647 0.5563333884728173 0.6465275817159081 0.7367217749589988 0.8269159682020896 0.9171101614451805 1.0073043546882714 1.0974985479313621 0.2369455828140909 0.3271397760571817 0.4173339693002725 0.5075281625433632 0.5977223557864542 0.6879165490295449 0.7781107422726357 0.8683049355157264 0.9584991287588174 1.0486933220019081 1.1388875152449989 0.2783345501277276 0.3685287433708184 0.4587229366139092 0.5489171298569999 0.6391113231000909 0.7293055163431816 0.8194997095862724 0.9096939028293631 0.9998880960724541 1.0900822893155449 1.1802764825586356 0.3197235174413644 0.4099177106844552 0.500111903927546 0.5903060971706368 0.6805002904137276 0.7706944836568184 0.8608886768999091 0.9510828701429999 1.0412770633860908 1.1314712566291816 1.2216654498722723 0.36111248475500113 0.45130667799809193 0.5415008712411827 0.6316950644842735 0.7218892577273643 0.8120834509704551 0.9022776442135458 0.9924718374566366 1.0826660306997276 1.1728602239428183 1.263054417185909 0.40250145206863785 0.49269564531172866 0.5828898385548195 0.6730840317979102 0.7632782250410011 0.8534724182840918 0.9436666115271826 1.0338608047702733 1.1240549980133643 1.214249191256455 1.3044433844995458 0.4438904193822747 0.5340846126253656 0.6242788058684563 0.714472999111547 0.8046671923546379 0.8948613855977287 0.9850555788408194 1.07524977208391 1.1654439653270012 1.255638158570092 1.3458323518131827 0.4852793866959113 0.5754735799390021 0.6656677731820929 0.7558619664251837 0.8460561596682745 0.9362503529113653 1.026444546154456 1.1166387393975468 1.2068329326406377 1.2970271258837285 1.3872213191268192 0.5266683540095481 0.616862547252639 0.7070567404957298 0.7972509337388205 0.8874451269819114 0.9776393202250021 1.0678335134680927 1.1580277067111835 1.2482218999542747 1.3384160931973654 1.4286102864404562 0.0713897135595439 0.1615839068026347 0.25177810004572554 0.3419722932888163 0.43216648653190715 0.5223606797749979 0.6125548730180886 0.7027490662611794 0.7929432595042704 0.8831374527473611 0.9733316459904519 0.11277868087318066 0.20297287411627146 0.29316706735936227 0.383361260602453 0.47355545384554387 0.5637496470886346 0.6539438403317254 0.7441380335748161 0.8343322268179071 0.9245264200609978 1.0147206133040885 0.15416764818681739 0.2443618414299082 0.334556034672999 0.42475022791608974 0.5149444211591806 0.6051386144022713 0.6953328076453621 0.7855270008884528 0.8757211941315438 0.9659153873746346 1.0561095806177252 0.19555661550045414 0.28575080874354497 0.3759450019866357 0.46613919522972647 0.5563333884728173 0.6465275817159081 0.7367217749589988 0.8269159682020896 0.9171101614451805 1.0073043546882714 1.0974985479313621 0.2369455828140909 0.3271397760571817 0.4173339693002725 0.5075281625433632 0.5977223557864542 0.6879165490295449 0.7781107422726357 0.8683049355157264 0.9584991287588174 1.0486933220019081 1.1388875152449989 0.2783345501277276 0.3685287433708184 0.4587229366139092 0.5489171298569999 0.6391113231000909 0.7293055163431816 0.8194997095862724 0.9096939028293631 0.9998880960724541 1.0900822893155449 1.1802764825586356 0.3197235174413644 0.4099177106844552 0.500111903927546 0.5903060971706368 0.6805002904137276 0.7706944836568184 0.8608886768999091 0.9510828701429999 1.0412770633860908 1.1314712566291816 1.2216654498722723 0.36111248475500113 0.45130667799809193 0.5415008712411827 0.6316950644842735 0.7218892577273643 0.8120834509704551 0.9022776442135458 0.9924718374566366 1.0826660306997276 1.1728602239428183 1.263054417185909 0.40250145206863785 0.49269564531172866 0.5828898385548195 0.6730840317979102 0.7632782250410011 0.8534724182840918 0.9436666115271826 1.0338608047702733 1.1240549980133643 1.214249191256455 1.3044433844995458 0.4438904193822747 0.5340846126253656 0.6242788058684563 0.714472999111547 0.8046671923546379 0.8948613855977287 0.9850555788408194 1.07524977208391 1.1654439653270012 1.255638158570092 1.3458323518131827 0.4852793866959113 0.5754735799390021 0.6656677731820929 0.7558619664251837 0.8460561596682745 0.9362503529113653 1.026444546154456 1.1166387393975468 1.2068329326406377 1.2970271258837285 1.3872213191268192 0.5266683540095481 0.616862547252639 0.7070567404957298 0.7972509337388205 0.8874451269819114 0.9776393202250021 1.0678335134680927 1.1580277067111835 1.2482218999542747 1.3384160931973654 1.4286102864404562 0.0713897135595439 0.1615839068026347 0.25177810004572554 0.3419722932888163 0.43216648653190715 0.5223606797749979 0.6125548730180886 0.7027490662611794 0.7929432595042704 0.8831374527473611 0.9733316459904519 0.11277868087318066 0.20297287411627146 0.29316706735936227 0.383361260602453 0.47355545384554387 0.5637496470886346 0.6539438403317254 0.7441380335748161 0.8343322268179071 0.9245264200609978 1.0147206133040885 0.15416764818681739 0.2443618414299082 0.334556034672999 0.42475022791608974 0.5149444211591806 0.6051386144022713 0.6953328076453621 0.7855270008884528 0.8757211941315438 0.9659153873746346 1.0561095806177252 0.19555661550045414 0.28575080874354497 0.3759450019866357 0.46613919522972647 0.5563333884728173 0.6465275817159081 0.7367217749589988 0.8269159682020896 0.9171101614451805 1.0073043546882714 1.0974985479313621 0.2369455828140909 0.3271397760571817 0.4173339693002725 0.5075281625433632 0.5977223557864542 0.6879165490295449 0.7781107422726357 0.8683049355157264 0.9584991287588174 1.0486933220019081 1.1388875152449989 0.2783345501277276 0.3685287433708184 0.4587229366139092 0.5489171298569999 0.6391113231000909 0.7293055163431816 0.8194997095862724 0.9096939028293631 0.9998880960724541 1.0900822893155449 1.1802764825586356 0.3197235174413644 0.4099177106844552 0.500111903927546 0.5903060971706368 0.6805002904137276 0.7706944836568184 0.8608886768999091 0.9510828701429999 1.0412770633860908 1.1314712566291816 1.2216654498722723 0.36111248475500113 0.45130667799809193 0.5415008712411827 0.6316950644842735 0.7218892577273643 0.8120834509704551 0.9022776442135458 0.9924718374566366 1.0826660306997276 1.1728602239428183 1.263054417185909 0.40250145206863785 0.49269564531172866 0.5828898385548195 0.6730840317979102 0.7632782250410011 0.8534724182840918 0.9436666115271826 1.0338608047702733 1.1240549980133643 1.214249191256455 1.3044433844995458 0.4438904193822747 0.5340846126253656 0.6242788058684563 0.714472999111547 0.8046671923546379 0.8948613855977287 0.9850555788408194 1.07524977208391 1.1654439653270012 1.255638158570092 1.3458323518131827 0.4852793866959113 0.5754735799390021 0.6656677731820929 0.7558619664251837 0.8460561596682745 0.9362503529113653 1.026444546154456 1.1166387393975468 1.2068329326406377 1.2970271258837285 1.3872213191268192 0.5266683540095481 0.616862547252639 0.7070567404957298 0.7972509337388205 0.8874451269819114 0.9776393202250021 1.0678335134680927 1.1580277067111835 1.2482218999542747 1.3384160931973654 1.4286102864404562; + 0.054527166306904996 0.07256600495552315 0.09060484360414131 0.10864368225275947 0.12668252090137763 0.1447213595499958 0.16276019819861395 0.18079903684723211 0.19883787549585027 0.21687671414446844 0.2349155527930866 0.1373051009341785 0.15534393958279666 0.17338277823141482 0.19142161688003298 0.20946045552865114 0.2274992941772693 0.24553813282588746 0.26357697147450565 0.28161581012312376 0.29965464877174197 0.3176934874203601 0.22008303556145198 0.23812187421007014 0.2561607128586883 0.27419955150730646 0.2922383901559246 0.3102772288045428 0.32831606745316094 0.3463549061017791 0.36439374475039726 0.3824325833990154 0.4004714220476336 0.3028609701887255 0.32089980883734365 0.3389386474859618 0.35697748613457997 0.37501632478319813 0.3930551634318163 0.41109400208043445 0.4291328407290526 0.44717167937767077 0.46521051802628893 0.4832493566749071 0.385638904815999 0.40367774346461716 0.4217165821132353 0.4397554207618535 0.45779425941047164 0.4758330980590898 0.49387193670770796 0.5119107753563261 0.5299496140049442 0.5479884526535624 0.5660272913021807 0.46841683944327245 0.4864556780918906 0.5044945167405088 0.5225333553891269 0.540572194037745 0.5586110326863633 0.5766498713349815 0.5946887099835996 0.6127275486322177 0.6307663872808359 0.6488052259294541 0.551194774070546 0.5692336127191642 0.5872724513677823 0.6053112900164004 0.6233501286650186 0.6413889673136368 0.6594278059622549 0.677466644610873 0.6955054832594912 0.7135443219081095 0.7315831605567276 0.6339727086978194 0.6520115473464376 0.6700503859950557 0.6880892246436738 0.706128063292292 0.7241669019409103 0.7422057405895284 0.7602445792381465 0.7782834178867647 0.7963222565353829 0.814361095184001 0.716750643325093 0.7347894819737111 0.7528283206223293 0.7708671592709475 0.7889059979195656 0.8069448365681837 0.8249836752168019 0.8430225138654202 0.8610613525140383 0.8791001911626564 0.8971390298112746 0.7995285779523665 0.8175674166009848 0.8356062552496029 0.853645093898221 0.8716839325468392 0.8897227711954574 0.9077616098440755 0.9258004484926936 0.9438392871413118 0.96187812578993 0.9799169644385481 0.8823065125796399 0.900345351228258 0.9183841898768762 0.9364230285254944 0.9544618671741125 0.9725007058227306 0.9905395444713488 1.008578383119967 1.0266172217685852 1.0446560604172033 1.0626948990658214 0.9650844472069134 0.9831232858555317 1.0011621245041498 1.0192009631527679 1.0372398018013862 1.0552786404500043 1.0733174790986224 1.0913563177472405 1.1093951563958586 1.127433995044477 1.145472833693095 0.054527166306904996 0.07256600495552315 0.09060484360414131 0.10864368225275947 0.12668252090137763 0.1447213595499958 0.16276019819861395 0.18079903684723211 0.19883787549585027 0.21687671414446844 0.2349155527930866 0.1373051009341785 0.15534393958279666 0.17338277823141482 0.19142161688003298 0.20946045552865114 0.2274992941772693 0.24553813282588746 0.26357697147450565 0.28161581012312376 0.29965464877174197 0.3176934874203601 0.22008303556145198 0.23812187421007014 0.2561607128586883 0.27419955150730646 0.2922383901559246 0.3102772288045428 0.32831606745316094 0.3463549061017791 0.36439374475039726 0.3824325833990154 0.4004714220476336 0.3028609701887255 0.32089980883734365 0.3389386474859618 0.35697748613457997 0.37501632478319813 0.3930551634318163 0.41109400208043445 0.4291328407290526 0.44717167937767077 0.46521051802628893 0.4832493566749071 0.385638904815999 0.40367774346461716 0.4217165821132353 0.4397554207618535 0.45779425941047164 0.4758330980590898 0.49387193670770796 0.5119107753563261 0.5299496140049442 0.5479884526535624 0.5660272913021807 0.46841683944327245 0.4864556780918906 0.5044945167405088 0.5225333553891269 0.540572194037745 0.5586110326863633 0.5766498713349815 0.5946887099835996 0.6127275486322177 0.6307663872808359 0.6488052259294541 0.551194774070546 0.5692336127191642 0.5872724513677823 0.6053112900164004 0.6233501286650186 0.6413889673136368 0.6594278059622549 0.677466644610873 0.6955054832594912 0.7135443219081095 0.7315831605567276 0.6339727086978194 0.6520115473464376 0.6700503859950557 0.6880892246436738 0.706128063292292 0.7241669019409103 0.7422057405895284 0.7602445792381465 0.7782834178867647 0.7963222565353829 0.814361095184001 0.716750643325093 0.7347894819737111 0.7528283206223293 0.7708671592709475 0.7889059979195656 0.8069448365681837 0.8249836752168019 0.8430225138654202 0.8610613525140383 0.8791001911626564 0.8971390298112746 0.7995285779523665 0.8175674166009848 0.8356062552496029 0.853645093898221 0.8716839325468392 0.8897227711954574 0.9077616098440755 0.9258004484926936 0.9438392871413118 0.96187812578993 0.9799169644385481 0.8823065125796399 0.900345351228258 0.9183841898768762 0.9364230285254944 0.9544618671741125 0.9725007058227306 0.9905395444713488 1.008578383119967 1.0266172217685852 1.0446560604172033 1.0626948990658214 0.9650844472069134 0.9831232858555317 1.0011621245041498 1.0192009631527679 1.0372398018013862 1.0552786404500043 1.0733174790986224 1.0913563177472405 1.1093951563958586 1.127433995044477 1.145472833693095 0.054527166306904996 0.07256600495552315 0.09060484360414131 0.10864368225275947 0.12668252090137763 0.1447213595499958 0.16276019819861395 0.18079903684723211 0.19883787549585027 0.21687671414446844 0.2349155527930866 0.1373051009341785 0.15534393958279666 0.17338277823141482 0.19142161688003298 0.20946045552865114 0.2274992941772693 0.24553813282588746 0.26357697147450565 0.28161581012312376 0.29965464877174197 0.3176934874203601 0.22008303556145198 0.23812187421007014 0.2561607128586883 0.27419955150730646 0.2922383901559246 0.3102772288045428 0.32831606745316094 0.3463549061017791 0.36439374475039726 0.3824325833990154 0.4004714220476336 0.3028609701887255 0.32089980883734365 0.3389386474859618 0.35697748613457997 0.37501632478319813 0.3930551634318163 0.41109400208043445 0.4291328407290526 0.44717167937767077 0.46521051802628893 0.4832493566749071 0.385638904815999 0.40367774346461716 0.4217165821132353 0.4397554207618535 0.45779425941047164 0.4758330980590898 0.49387193670770796 0.5119107753563261 0.5299496140049442 0.5479884526535624 0.5660272913021807 0.46841683944327245 0.4864556780918906 0.5044945167405088 0.5225333553891269 0.540572194037745 0.5586110326863633 0.5766498713349815 0.5946887099835996 0.6127275486322177 0.6307663872808359 0.6488052259294541 0.551194774070546 0.5692336127191642 0.5872724513677823 0.6053112900164004 0.6233501286650186 0.6413889673136368 0.6594278059622549 0.677466644610873 0.6955054832594912 0.7135443219081095 0.7315831605567276 0.6339727086978194 0.6520115473464376 0.6700503859950557 0.6880892246436738 0.706128063292292 0.7241669019409103 0.7422057405895284 0.7602445792381465 0.7782834178867647 0.7963222565353829 0.814361095184001 0.716750643325093 0.7347894819737111 0.7528283206223293 0.7708671592709475 0.7889059979195656 0.8069448365681837 0.8249836752168019 0.8430225138654202 0.8610613525140383 0.8791001911626564 0.8971390298112746 0.7995285779523665 0.8175674166009848 0.8356062552496029 0.853645093898221 0.8716839325468392 0.8897227711954574 0.9077616098440755 0.9258004484926936 0.9438392871413118 0.96187812578993 0.9799169644385481 0.8823065125796399 0.900345351228258 0.9183841898768762 0.9364230285254944 0.9544618671741125 0.9725007058227306 0.9905395444713488 1.008578383119967 1.0266172217685852 1.0446560604172033 1.0626948990658214 0.9650844472069134 0.9831232858555317 1.0011621245041498 1.0192009631527679 1.0372398018013862 1.0552786404500043 1.0733174790986224 1.0913563177472405 1.1093951563958586 1.127433995044477 1.145472833693095 0.054527166306904996 0.07256600495552315 0.09060484360414131 0.10864368225275947 0.12668252090137763 0.1447213595499958 0.16276019819861395 0.18079903684723211 0.19883787549585027 0.21687671414446844 0.2349155527930866 0.1373051009341785 0.15534393958279666 0.17338277823141482 0.19142161688003298 0.20946045552865114 0.2274992941772693 0.24553813282588746 0.26357697147450565 0.28161581012312376 0.29965464877174197 0.3176934874203601 0.22008303556145198 0.23812187421007014 0.2561607128586883 0.27419955150730646 0.2922383901559246 0.3102772288045428 0.32831606745316094 0.3463549061017791 0.36439374475039726 0.3824325833990154 0.4004714220476336 0.3028609701887255 0.32089980883734365 0.3389386474859618 0.35697748613457997 0.37501632478319813 0.3930551634318163 0.41109400208043445 0.4291328407290526 0.44717167937767077 0.46521051802628893 0.4832493566749071 0.385638904815999 0.40367774346461716 0.4217165821132353 0.4397554207618535 0.45779425941047164 0.4758330980590898 0.49387193670770796 0.5119107753563261 0.5299496140049442 0.5479884526535624 0.5660272913021807 0.46841683944327245 0.4864556780918906 0.5044945167405088 0.5225333553891269 0.540572194037745 0.5586110326863633 0.5766498713349815 0.5946887099835996 0.6127275486322177 0.6307663872808359 0.6488052259294541 0.551194774070546 0.5692336127191642 0.5872724513677823 0.6053112900164004 0.6233501286650186 0.6413889673136368 0.6594278059622549 0.677466644610873 0.6955054832594912 0.7135443219081095 0.7315831605567276 0.6339727086978194 0.6520115473464376 0.6700503859950557 0.6880892246436738 0.706128063292292 0.7241669019409103 0.7422057405895284 0.7602445792381465 0.7782834178867647 0.7963222565353829 0.814361095184001 0.716750643325093 0.7347894819737111 0.7528283206223293 0.7708671592709475 0.7889059979195656 0.8069448365681837 0.8249836752168019 0.8430225138654202 0.8610613525140383 0.8791001911626564 0.8971390298112746 0.7995285779523665 0.8175674166009848 0.8356062552496029 0.853645093898221 0.8716839325468392 0.8897227711954574 0.9077616098440755 0.9258004484926936 0.9438392871413118 0.96187812578993 0.9799169644385481 0.8823065125796399 0.900345351228258 0.9183841898768762 0.9364230285254944 0.9544618671741125 0.9725007058227306 0.9905395444713488 1.008578383119967 1.0266172217685852 1.0446560604172033 1.0626948990658214 0.9650844472069134 0.9831232858555317 1.0011621245041498 1.0192009631527679 1.0372398018013862 1.0552786404500043 1.0733174790986224 1.0913563177472405 1.1093951563958586 1.127433995044477 1.145472833693095; + 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.14198039027185572 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.2339607805437114 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671 0.3259411708155671] + + shape = extrude_geometry((p1, p2, p3); direction, particle_spacing=0.1, n_extrude=4, + density=1000.0) + + @test shape.coordinates ≈ expected_coords + end +end diff --git a/test/setups/setups.jl b/test/setups/setups.jl index eaffd6761b..0a72c94100 100644 --- a/test/setups/setups.jl +++ b/test/setups/setups.jl @@ -1,3 +1,4 @@ include("rectangular_shape.jl") include("sphere_shape.jl") include("rectangular_tank.jl") +include("extrude_geometry.jl") diff --git a/test/systems/boundary_system.jl b/test/systems/boundary_system.jl index 3876040cd0..2ffa40af48 100644 --- a/test/systems/boundary_system.jl +++ b/test/systems/boundary_system.jl @@ -17,13 +17,14 @@ model = Val(:boundary_model) system = BoundarySPHSystem(initial_condition, model) + TrixiParticles.update_positions!(system, 0, 0, 0, 0, 0, 0.0) @test system isa BoundarySPHSystem @test ndims(system) == NDIMS @test system.coordinates == coordinates @test system.boundary_model == model @test system.movement === nothing - @test system.ismoving == [false] + @test system.ismoving[] == false end end @@ -110,16 +111,17 @@ system = BoundarySPHSystem(initial_condition, model) - show_compact = "BoundarySPHSystem{2}((hydrodynamic_mass = 3,), nothing) with 1 particles" + show_compact = "BoundarySPHSystem{2}((hydrodynamic_mass = 3,), nothing, 0.0) with 2 particles" @test repr(system) == show_compact show_box = """ ┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ │ BoundarySPHSystem{2} │ │ ════════════════════ │ - │ #particles: ………………………………………………… 1 │ + │ #particles: ………………………………………………… 2 │ │ boundary model: ……………………………………… (hydrodynamic_mass = 3,) │ │ movement function: ……………………………… nothing │ + │ adhesion coefficient: ……………………… 0.0 │ └──────────────────────────────────────────────────────────────────────────────────────────────────┘""" @test repr("text/plain", system) == show_box end diff --git a/test/systems/dem_system.jl b/test/systems/dem_system.jl new file mode 100644 index 0000000000..c7ae18bcb4 --- /dev/null +++ b/test/systems/dem_system.jl @@ -0,0 +1,26 @@ +include("../test_util.jl") +@testset verbose=true "DEMSystem" begin + @trixi_testset "show" begin + coordinates = [1.0 2.0 + 1.0 2.0] + mass = [1.25, 1.5] + density = [990.0, 1000.0] + + initial_condition = InitialCondition(; coordinates, mass, density) + system = DEMSystem(initial_condition, 2 * 10^5, 10e9, 0.3, acceleration=(0.0, 10.0)) + + show_compact = "DEMSystem{2}(InitialCondition{Float64}(-1.0, [1.0 2.0; 1.0 2.0], [0.0 0.0; 0.0 0.0], [1.25, 1.5], [990.0, 1000.0], [0.0, 0.0]), 1.0e10, 0.3, 200000.0, 0.0001) with 2 particles" + @test repr(system) == show_compact + show_box = """ + ┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ DEMSystem{2} │ + │ ════════════ │ + │ #particles: ………………………………………………… 2 │ + │ elastic_modulus: …………………………………… 1.0e10 │ + │ poissons_ratio: ……………………………………… 0.3 │ + │ normal_stiffness: ………………………………… 200000.0 │ + │ damping_coefficient: ………………………… 0.0001 │ + └──────────────────────────────────────────────────────────────────────────────────────────────────┘""" + @test repr("text/plain", system) == show_box + end +end diff --git a/test/systems/open_boundary_system.jl b/test/systems/open_boundary_system.jl new file mode 100644 index 0000000000..1158aaff0c --- /dev/null +++ b/test/systems/open_boundary_system.jl @@ -0,0 +1,90 @@ +@testset verbose=true "`OpenBoundarySPHSystem`" begin + @testset verbose=true "Illegal Inputs" begin + plane = ([0.0, 0.0], [0.0, 1.0]) + flow_direction = (1.0, 0.0) + + # Mock fluid system + struct FluidSystemMock2 <: TrixiParticles.FluidSystem{2, Nothing} end + + inflow = InFlow(; plane, particle_spacing=0.1, + flow_direction, density=1.0, open_boundary_layers=2) + + error_str = "`reference_velocity` must be either a function mapping " * + "each particle's coordinates and time to its velocity, " * + "an array where the ``i``-th column holds the velocity of particle ``i`` " * + "or, for a constant fluid velocity, a vector of length 2 for a 2D problem holding this velocity" + + reference_velocity = 1.0 + @test_throws ArgumentError(error_str) OpenBoundarySPHSystem(inflow; sound_speed=1.0, + buffer_size=0, + fluid_system=FluidSystemMock2(), + reference_velocity) + + error_str = "`reference_pressure` must be either a function mapping " * + "each particle's coordinates and time to its pressure, " * + "a vector holding the pressure of each particle, or a scalar" + + reference_pressure = [1.0, 1.0] + @test_throws ArgumentError(error_str) OpenBoundarySPHSystem(inflow; sound_speed=1.0, + buffer_size=0, + fluid_system=FluidSystemMock2(), + reference_pressure) + + error_str = "`reference_density` must be either a function mapping " * + "each particle's coordinates and time to its density, " * + "a vector holding the density of each particle, or a scalar" + + reference_density = [1.0, 1.0] + @test_throws ArgumentError(error_str) OpenBoundarySPHSystem(inflow; sound_speed=1.0, + buffer_size=0, + fluid_system=FluidSystemMock2(), + reference_density) + end + @testset "`show`" begin + inflow = InFlow(; plane=([0.0, 0.0], [0.0, 1.0]), particle_spacing=0.05, + flow_direction=(1.0, 0.0), density=1.0, open_boundary_layers=4) + system = OpenBoundarySPHSystem(inflow; sound_speed=1.0, buffer_size=0, + fluid_system=FluidSystemMock2()) + + show_compact = "OpenBoundarySPHSystem{2}(InFlow) with 80 particles" + @test repr(system) == show_compact + show_box = """ + ┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ OpenBoundarySPHSystem{2} │ + │ ════════════════════════ │ + │ #particles: ………………………………………………… 80 │ + │ #buffer_particles: ……………………………… 0 │ + │ fluid system: …………………………………………… FluidSystemMock2 │ + │ boundary: ……………………………………………………… InFlow │ + │ flow direction: ……………………………………… [1.0, 0.0] │ + │ prescribed velocity: ………………………… constant_vector │ + │ prescribed pressure: ………………………… constant_scalar │ + │ prescribed density: …………………………… constant_scalar │ + │ width: ……………………………………………………………… 0.2 │ + └──────────────────────────────────────────────────────────────────────────────────────────────────┘""" + @test repr("text/plain", system) == show_box + + outflow = OutFlow(; plane=([0.0, 0.0], [0.0, 1.0]), particle_spacing=0.05, + flow_direction=(1.0, 0.0), density=1.0, open_boundary_layers=4) + system = OpenBoundarySPHSystem(outflow; sound_speed=1.0, buffer_size=0, + fluid_system=FluidSystemMock2()) + + show_compact = "OpenBoundarySPHSystem{2}(OutFlow) with 80 particles" + @test repr(system) == show_compact + show_box = """ + ┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ OpenBoundarySPHSystem{2} │ + │ ════════════════════════ │ + │ #particles: ………………………………………………… 80 │ + │ #buffer_particles: ……………………………… 0 │ + │ fluid system: …………………………………………… FluidSystemMock2 │ + │ boundary: ……………………………………………………… OutFlow │ + │ flow direction: ……………………………………… [1.0, 0.0] │ + │ prescribed velocity: ………………………… constant_vector │ + │ prescribed pressure: ………………………… constant_scalar │ + │ prescribed density: …………………………… constant_scalar │ + │ width: ……………………………………………………………… 0.2 │ + └──────────────────────────────────────────────────────────────────────────────────────────────────┘""" + @test repr("text/plain", system) == show_box + end +end diff --git a/test/systems/solid_system.jl b/test/systems/solid_system.jl index 812493fa85..3690222737 100644 --- a/test/systems/solid_system.jl +++ b/test/systems/solid_system.jl @@ -153,7 +153,7 @@ return Val(Symbol("mock_" * string(f))) end - TrixiParticles.eachneighbor(_, ::Val{:nhs}) = neighbors + TrixiParticles.PointNeighbors.eachneighbor(_, ::Val{:nhs}) = neighbors function Base.getproperty(::Val{:nhs}, f::Symbol) if f === :search_radius diff --git a/test/systems/systems.jl b/test/systems/systems.jl index 6fe2ac7fb7..697f575fac 100644 --- a/test/systems/systems.jl +++ b/test/systems/systems.jl @@ -2,3 +2,5 @@ include("wcsph_system.jl") include("edac_system.jl") include("solid_system.jl") include("boundary_system.jl") +include("open_boundary_system.jl") +include("dem_system.jl") diff --git a/test/systems/wcsph_system.jl b/test/systems/wcsph_system.jl index 0749261bd5..68f69aea89 100644 --- a/test/systems/wcsph_system.jl +++ b/test/systems/wcsph_system.jl @@ -193,7 +193,7 @@ smoothing_length, density_diffusion=density_diffusion) - show_compact = "WeaklyCompressibleSPHSystem{2}(SummationDensity(), nothing, Val{:state_equation}(), Val{:smoothing_kernel}(), nothing, Val{:density_diffusion}(), [0.0, 0.0], nothing) with 2 particles" + show_compact = "WeaklyCompressibleSPHSystem{2}(SummationDensity(), nothing, Val{:state_equation}(), Val{:smoothing_kernel}(), nothing, Val{:density_diffusion}(), nothing, [0.0, 0.0], nothing) with 2 particles" @test repr(system) == show_compact show_box = """ ┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ @@ -206,6 +206,7 @@ │ smoothing kernel: ………………………………… Val │ │ viscosity: …………………………………………………… nothing │ │ density diffusion: ……………………………… Val{:density_diffusion}() │ + │ surface tension: …………………………………… nothing │ │ acceleration: …………………………………………… [0.0, 0.0] │ │ source terms: …………………………………………… Nothing │ └──────────────────────────────────────────────────────────────────────────────────────────────────┘""" diff --git a/test/unittest.jl b/test/unittest.jl index 01308bd44d..5c05924e1e 100644 --- a/test/unittest.jl +++ b/test/unittest.jl @@ -3,7 +3,6 @@ @testset verbose=true "Unit Tests" begin include("callbacks/callbacks.jl") include("general/general.jl") - include("neighborhood_search/neighborhood_search.jl") include("setups/setups.jl") include("systems/systems.jl") include("schemes/schemes.jl") diff --git a/test/validation/validation.jl b/test/validation/validation.jl index 95dcfe1bd4..c5aa9bed15 100644 --- a/test/validation/validation.jl +++ b/test/validation/validation.jl @@ -46,16 +46,18 @@ ] @test sol.retcode == ReturnCode.Success @test count_rhs_allocations(sol, semi) == 0 - @test isapprox(error_edac_P1, 0, atol=1e-9) - @test isapprox(error_edac_P2, 0, atol=1e-11) if VERSION >= v"1.10" + @test isapprox(error_edac_P1, 0, atol=eps()) + @test isapprox(error_edac_P2, 0, atol=eps()) @test isapprox(error_wcsph_P1, 0, atol=eps()) @test isapprox(error_wcsph_P2, 0, atol=eps()) else # 1.9 causes a large difference in the solution - @test isapprox(error_wcsph_P1, 0, atol=10) - @test isapprox(error_wcsph_P2, 0, atol=1e-3) + @test isapprox(error_edac_P1, 0, atol=4e-9) + @test isapprox(error_edac_P2, 0, atol=3e-11) + @test isapprox(error_wcsph_P1, 0, atol=5.1) + @test isapprox(error_wcsph_P2, 0, atol=6e-4) end # Ignore method redefinitions from duplicate `include("../validation_util.jl")` diff --git a/validation/dam_break_2d/validation_dam_break_2d.jl b/validation/dam_break_2d/validation_dam_break_2d.jl index baf2c41b4c..bfe216fdea 100644 --- a/validation/dam_break_2d/validation_dam_break_2d.jl +++ b/validation/dam_break_2d/validation_dam_break_2d.jl @@ -65,10 +65,9 @@ function max_x_coord(v, u, t, system) end function interpolated_pressure(coord_top, coord_bottom, v, u, t, system) - sol = (; u=[(; x=(v, u))]) n_interpolation_points = 10 interpolated_values = interpolate_line(coord_top, coord_bottom, - n_interpolation_points, semi, system, sol, + n_interpolation_points, semi, system, v, u, smoothing_length=2.0 * system.smoothing_length, clip_negative_pressure=true) return sum(map(x -> isnan(x) ? 0.0 : x, interpolated_values.pressure)) / diff --git a/validation/dam_break_2d/validation_reference_edac_00075.json b/validation/dam_break_2d/validation_reference_edac_00075.json index bd53415c17..e4855ef6ae 100644 --- a/validation/dam_break_2d/validation_reference_edac_00075.json +++ b/validation/dam_break_2d/validation_reference_edac_00075.json @@ -211,7 +211,7 @@ }, "meta": { "julia_version": "1.10.2", - "solver_version": "98fe4b85-dirty", + "solver_version": "v0.1.0-41-ga05975e-dirty", "solver_name": "TrixiParticles.jl" }, "pressure_P2_fluid_1": { diff --git a/validation/dam_break_2d/validation_reference_edac_0015.json b/validation/dam_break_2d/validation_reference_edac_0015.json index 24757b07f5..fb8ead957c 100644 --- a/validation/dam_break_2d/validation_reference_edac_0015.json +++ b/validation/dam_break_2d/validation_reference_edac_0015.json @@ -139,79 +139,79 @@ 0.0, 0.0, 0.0, - 604.3957582215899, - 854.3064249696633, - 806.7433554643078, - 863.2834525307577, - 1172.7188760376416, - 1185.7354823999622, - 1029.8194599143999, - 1220.8424591423286, - 1129.1130583244467, - 1681.2917027879928, - 1541.8470498978272, - 1941.6817449062, - 1703.3428527373653, - 1820.7752456908524, - 2273.61907013479, - 2059.549527721113, - 2492.4265793163177, - 2431.891436605787, - 2341.4127018992012, - 2380.505662332444, - 2476.6304162505776, - 2377.2152352341263, - 2407.6934010063724, - 2621.6411341900084, - 2604.6982173831625, - 2546.399445810557, - 2689.684321946729, - 2654.6394300204142, - 2815.3615097824277, - 2642.321188486082, - 2862.2882426968963, - 2811.52377372663, - 2800.9092542116946, - 2952.956412783148, - 2844.263845334785, - 2874.303528967565, - 2947.9713667109695, - 2981.7363562084547, - 3022.715296426873, - 3095.8959184057803, - 3223.2834166674697, - 3438.1549404217467, - 3461.6157573942837, - 3889.5328111053327, - 4764.835081898881, - 6691.330625903637, - 5937.573914006671, - 6470.044187005868, - 5114.121150798657, - 3634.979008335772, - 3802.039385890102, - 2751.9598060963713, - 2027.890808792743, - 1718.7065304962896, - 1535.5876413862059, - 1382.3526450110292, - 1479.4637909212727, - 1636.5432932362105, - 1580.8409259586185, - 1362.2791052129037, - 1307.4981915271765, - 1483.094125847357, - 1582.6083889647803, - 3052.2515396988983, - 34536.296819296316, - 37.65056335694383 + 604.3957582210057, + 854.3064249701162, + 806.7433554645238, + 863.2834525323564, + 1172.7188760362817, + 1185.7354823957364, + 1029.8194599051196, + 1220.8424591289627, + 1129.1130583250354, + 1681.2917027821925, + 1541.84704989596, + 1941.681744886013, + 1703.342852837277, + 1820.7752456709127, + 2273.6190702267386, + 2059.549528549632, + 2492.42657872154, + 2431.891435051085, + 2341.412705521953, + 2380.5056651594596, + 2476.6304064185897, + 2377.215248491543, + 2407.6934046918395, + 2621.6411290967458, + 2604.698214284965, + 2546.399443630783, + 2689.6843234734833, + 2654.639435738155, + 2815.3615123943055, + 2642.321187463903, + 2862.2882422981534, + 2811.523774221353, + 2800.9092531867764, + 2952.9564218334053, + 2844.26385035426, + 2874.3035321104517, + 2947.9713669965477, + 2981.736356294694, + 3022.7152925706846, + 3095.8959107824994, + 3223.283421806281, + 3438.1549422956728, + 3461.615757497162, + 3889.532826771071, + 4764.8350876461145, + 6691.330618320588, + 5937.57389697787, + 6470.044184482702, + 5114.121178496523, + 3634.978964640017, + 3802.0393876762696, + 2751.9597721194446, + 2027.8908055573243, + 1718.7065075201758, + 1535.5875986343772, + 1382.352588739524, + 1479.4637838388012, + 1636.543321007025, + 1580.840904760441, + 1362.2790951007676, + 1307.4981559335906, + 1483.0941976925808, + 1582.6084515968898, + 3052.2514672201683, + 34536.29685017654, + 37.65095815197997 ], "datatype": "Float64", "type": "series" }, "meta": { "julia_version": "1.10.2", - "solver_version": "dcd1d2b1-dirty", + "solver_version": "v0.1.0-22-g4d428a23-dirty", "solver_name": "TrixiParticles.jl" }, "pressure_P2_fluid_1": { @@ -360,43 +360,43 @@ 0.0, 0.0, 0.0, - 1.4586145070841525, - 35.23970220674348, - 37.54572391444544, - 13.731385167742658, - 18.846457975737682, - 19.8224841977086, - 16.230770674916435, - 56.876420772446885, - 37.33671454315734, - 78.42964736040615, - 75.04969119915044, - 166.61484088498904, - 162.7352955160456, - 281.7282268014637, - 314.52179459013166, - 297.23412867557687, - 357.6400910860976, - 448.6558207559398, - 465.072910798512, - 612.2401011562348, - 751.9411583776679, - 889.9451285845244, - 1033.4736483837655, - 1058.202206530216, - 1247.2730240894725, - 1414.0754260543076, - 1588.386535262655, - 1665.7826025761042, - 1545.8294662391331, - 1673.9628009744847, - 1683.2228289778777, - 1629.4962992741189, - 1359.6993719827465, - 887.91187263999, - 1629.9983580422795, - 1234.4717693503253, - 2004.3258881084407, + 1.4586145071541898, + 35.23970220674639, + 37.545723914493706, + 13.731385167730085, + 18.84645797568068, + 19.822484197824288, + 16.23077067519315, + 56.87642077158365, + 37.33671454346889, + 78.42964734337639, + 75.04969120367215, + 166.6148408881951, + 162.73529547483014, + 281.72822678093445, + 314.5217962647108, + 297.23412415145486, + 357.6400963258884, + 448.65581898807187, + 465.0729072685026, + 612.2400999708409, + 751.9411576661471, + 889.945132047711, + 1033.4736519596122, + 1058.2021923062105, + 1247.2730353264728, + 1414.0754183693182, + 1588.3865333590663, + 1665.7825902521158, + 1545.8294821241884, + 1673.9628180445059, + 1683.2228111190875, + 1629.4962936654842, + 1359.6993745648126, + 887.9118718296219, + 1629.9983551561381, + 1234.4717618954708, + 2004.325882177016, 0.0, 0.0, 0.0, @@ -538,98 +538,98 @@ 1.302960573952499, 1.3475843440024642, 1.3976361698571822, - 1.4522199811561385, - 1.510520947475266, - 1.57214293886014, - 1.6367982265986314, - 1.7042518338466675, - 1.7742937998709938, - 1.8467352027968718, - 1.9213758913869927, - 1.9980351614993204, - 2.076544579209514, - 2.1566383641391065, - 2.238003134643121, - 2.3206381687170157, - 2.4044919394042736, - 2.489540630902278, - 2.5757273488305, - 2.6627522454913684, - 2.7501950551381626, - 2.837835825711921, - 2.9257131544255777, - 3.013897557214555, - 3.102445768668488, - 3.1914847216832074, - 3.2105882666919, - 3.2106025664774633, - 3.210407075423367, - 3.210274207277424, - 3.210140068534914, - 3.210439644792984, - 3.2112884336091394, - 3.2121208254868923, - 3.2125141820856236, - 3.212599029590728, - 3.2119451344617342, - 3.210893702477008, - 3.2095320951690165, - 3.208866042695388, - 3.20871345061095, - 3.208573760872467, - 3.2083568996870384, - 3.208389043560661, - 3.208031877949919, - 3.2079261547153943, - 3.207855507755997, - 3.207774119588862, - 3.207706002731428, - 3.2076706691657413, + 1.452219981156139, + 1.5105209474752668, + 1.572142938860141, + 1.6367982265986325, + 1.7042518338466686, + 1.774293799870995, + 1.8467352027968729, + 1.9213758913869938, + 1.9980351614993215, + 2.0765445792095147, + 2.1566383641391074, + 2.238003134643122, + 2.3206381687170166, + 2.404491939404275, + 2.4895406309022796, + 2.575727348830502, + 2.662752245491369, + 2.7501950551381618, + 2.837835825711919, + 2.925713154425575, + 3.0138975572145523, + 3.102445768668486, + 3.191484721683206, + 3.2105882666919006, + 3.210602566477464, + 3.2104070754233653, + 3.2102742072774215, + 3.210140068534911, + 3.210439644793008, + 3.211288433609166, + 3.21212082548689, + 3.2125141820856595, + 3.212599029590776, + 3.211945134461789, + 3.210893702477077, + 3.2095320951690947, + 3.2088660426953854, + 3.2087134506109494, + 3.20857376087247, + 3.2083568996870397, + 3.2083890435617195, + 3.2080318779499213, + 3.2079261547153983, + 3.207855507756003, + 3.2077741195888656, + 3.2077060027314293, + 3.207670669165742, 3.2076288978754195, - 3.2075941374177184, + 3.2075941374177193, 3.2075586504257148, - 3.2075368319522997, - 3.2075265213627904, - 3.210218793361137, - 3.21269507520042, - 3.2119664146378195, - 3.2074630303747567, - 3.2074452874588837, - 3.2074252902484437, - 3.2074042323916974, - 3.207389539186995, - 3.207376753579591, - 3.2073732467888747, - 3.207348313206412, - 3.2076648180581375, - 3.2073154618525073, - 3.2078540055088123, - 3.2085800521157415, - 3.2072818774036778, - 3.2072679757668348, - 3.207248958752789, - 3.2072243341066833, - 3.2072195892028104, - 3.2072071351452363, - 3.207191617668054, - 3.207167720999114, - 3.207148223076407, - 3.207125308504274, - 3.2071076627463984, - 3.2070796885516786, - 3.2070631437453265, - 3.207023980424339, - 3.207002203054955, - 3.2069578815542887, - 3.206882240708725, - 3.206818992733726, - 3.2067228411656616, - 3.2066412586740536, - 3.206517043308112, - 3.2063950957148974, - 3.206329455325393, - 3.206229289248847, - 3.2061593054711017 + 3.207536831952301, + 3.2075265213627917, + 3.2102187933610122, + 3.2126950751999495, + 3.21196641463669, + 3.2074630303747598, + 3.207445287458887, + 3.2074252902484464, + 3.2074042323917, + 3.2073895391869978, + 3.207376753579594, + 3.2073732467888774, + 3.2073483132064147, + 3.207664818066874, + 3.20731546185251, + 3.207854005492601, + 3.208580052098399, + 3.2072818774036804, + 3.207267975766838, + 3.2072489587527926, + 3.2072243341066873, + 3.207219589202814, + 3.207207135145238, + 3.207191617668057, + 3.207167720999116, + 3.2071482230764086, + 3.2071253085042755, + 3.2071076627464006, + 3.207079688551678, + 3.2070631437453248, + 3.207023980424338, + 3.2070022030549534, + 3.2069578815542865, + 3.206882240708722, + 3.206818992733722, + 3.206722841165657, + 3.206641258674049, + 3.20651704330811, + 3.2063950957148926, + 3.206329455325389, + 3.206229289248844, + 3.2061593054711004 ], "datatype": "Float64", "type": "series" @@ -792,16 +792,16 @@ 0.0, 0.0, 0.0, - 3.9648485713304686, - 5.688115787444037, - 5.021169068151417, - 3.4690420734565928, - 3.558737480081865, - 3.1571728411890927, - 3.002903034691488, - 4.056831531943184, - 6.550226833801881, - 4.5706557048191865, + 3.9648485713534356, + 5.688115787337868, + 5.021169068061549, + 3.4690420744982595, + 3.5587374771061193, + 3.1571728370072245, + 3.002903045034488, + 4.056831537710453, + 6.550226835881946, + 4.570655703611319, 0.0, 0.0, 0.0, @@ -809,7 +809,7 @@ 0.0, 0.0, 0.0, - 13.988872277177816, + 13.98887227719292, 0.0, 0.0, 0.0, @@ -844,4 +844,4 @@ "datatype": "Float64", "type": "series" } -} \ No newline at end of file +} diff --git a/validation/dam_break_2d/validation_reference_wcsph_0001875.json b/validation/dam_break_2d/validation_reference_wcsph_0001875.json index b65acf6558..70ae2b556a 100644 --- a/validation/dam_break_2d/validation_reference_wcsph_0001875.json +++ b/validation/dam_break_2d/validation_reference_wcsph_0001875.json @@ -136,82 +136,82 @@ 0.0, 0.0, 0.0, - 45.784395004426884, - 29.0963004807273, - 82.09143011178865, - 480.5173247987532, - 973.3483022920233, - 950.6415064448477, - 1205.9391286167588, - 1290.217132972779, - 1493.2505500620105, - 791.1303597083299, - 1621.3332817032026, - 1209.649967879809, - 1450.1769634267432, - 1667.706462908013, - 2021.1368753192794, - 2064.739712897202, - 2257.648079488593, - 2059.094957473553, - 2005.5908178585112, - 2245.7005690725623, - 2144.823341827724, - 2401.6276418260254, - 2511.8101954813187, - 2392.8729062435004, - 2728.389883490009, - 2453.68997501138, - 2574.8103376583067, - 2644.8208050406383, - 2795.560194487943, - 2760.374005463031, - 2665.0826676713305, - 2729.435007141164, - 2781.3157684269163, - 2626.429904341887, - 2825.0779162423023, - 2715.766574533961, - 2850.599606872627, - 2717.4272822426187, - 2855.0549332258984, - 2978.689965745355, - 2976.375589014323, - 2921.329964480744, - 3030.587428212174, - 3142.963554938571, - 3064.474348949782, - 3292.874691549051, - 3592.4250266859026, - 4290.388250924816, - 5103.386394463199, - 6745.91427044437, - 5592.075646090989, - 5198.727685514661, - 5192.428024560253, - 4810.974638781969, - 4057.474362834096, - 4982.9085809799635, - 4326.893681724326, - 2365.579225741909, - 794.454920623942, - 128.518422948347, - 3802.146008868988, - 0.0, - 193.6041251795611, - 155.5982729768126, - 0.0, - 0.0, - 280.0329234725871, - 2691.975566958315, - 587.3786496564874 + 45.795417909753986, + 30.986970334726653, + 88.05948840868432, + 552.7825716933128, + 920.3939791879527, + 843.9803228418943, + 1198.4306383314438, + 1307.4218650259777, + 1551.1614335277905, + 820.9324305229308, + 1618.2777784691862, + 1118.7361526076513, + 1340.3279552181161, + 1819.3970368003422, + 2130.606457954829, + 2169.4926936753154, + 2270.277029561626, + 2182.734498203901, + 2027.1358420234967, + 2204.198718226254, + 1968.7434281115009, + 2492.7425782632013, + 2804.8952931647045, + 2286.915020570262, + 2713.028093827061, + 2557.8946262705217, + 2676.7335881591903, + 2630.692946854259, + 2780.879159625962, + 2566.0554325798157, + 2876.339978484625, + 2687.078292376781, + 2534.9905689398897, + 2805.2451019180644, + 2921.735789046199, + 2609.9054081748445, + 2837.1289059887367, + 2875.3167107168706, + 2833.6939092357015, + 2947.6543723563022, + 2905.713496985916, + 2831.391646895673, + 2942.5289706903586, + 3146.56291085397, + 3186.9261233249576, + 3459.6124426849046, + 3653.2887908885605, + 4297.702961121691, + 5057.085382681299, + 6790.01681537226, + 5714.950602336447, + 5164.43699821667, + 5308.423404900976, + 4797.701901717602, + 4208.150962724796, + 5096.441180151019, + 4805.670702674106, + 3275.206200205639, + 1509.8437133185737, + 8196.388479597907, + 1389.9556140209475, + 48.06627062092718, + 142.2684567691535, + 426.8228505863073, + 0.0, + 706.4392919333849, + 0.0, + 150.25642381312346, + 2277.737279445718 ], "datatype": "Float64", "type": "series" }, "meta": { "julia_version": "1.10.2", - "solver_version": "98fe4b85-dirty", + "solver_version": "v0.1.0-42-gaa74848-dirty", "solver_name": "TrixiParticles.jl" }, "pressure_P2_fluid_1": { @@ -355,58 +355,58 @@ 0.0, 0.0, 0.0, - 8.134978657093146, - 16.116098542012196, - 14.366905888289647, - 16.181410038300935, - 9.851210123836067, - 27.796007277259566, - 11.17503157781834, - 1.0934215179075044, - 12.844439624904116, - 5.940102595559185, - 19.600847118274284, - 38.053279321766254, - 21.295762396264585, - 12.976253972328617, - 81.24650675781987, - 34.479345653339756, - 245.37183177529965, - 306.79960278612987, - 160.13536073045663, - 315.6922797256672, - 296.080651120703, - 333.2708743720141, - 513.4937918935809, - 495.83807208024655, - 520.7690916129709, - 725.2386100241195, - 858.3613537623429, - 898.8101400627029, - 1026.828064254389, - 1190.5527204068717, - 1260.8739368797535, - 1379.7241579310062, - 1441.7980954536122, - 1477.007856603721, - 1547.1434162566334, - 1583.8840399538308, - 1700.7432601788023, - 1785.0753079682509, - 1733.8949904850874, - 1764.7490159275246, - 1432.4338785761856, - 395.27999913305814, - 34.94150836405466, - 24.900856184771346, - 7.679262019233791, - 28.035980801784653, - 8.438531609013937, - 8.387476071262533, - 16.627723954709776, - 16.034203643304302, - 0.0, + 8.137474398356465, + 16.498859955926168, + 15.164345238697965, + 14.497070409731915, + 13.701694162443477, + 17.05819796035522, + 6.862768172486139, + 10.157578690994551, + 14.182020846567593, + 5.092217430219432, + 12.946287418688897, + 19.32316444599005, + 12.839915675024358, + 20.61520320591055, + 132.38172851891431, + 5.79592905224373, + 160.595377745003, + 177.26580283234026, + 243.60522337506455, + 245.64844112230676, + 269.2781916856497, + 421.4220230166705, + 483.7230593494285, + 493.7749362856113, + 502.5221739311165, + 688.2620827693952, + 845.0400854295897, + 994.7161645496323, + 952.950100203993, + 1109.283301027545, + 1256.4344362923243, + 1357.2722316672923, + 1448.776623129004, + 1471.2694024093191, + 1580.8239356843492, + 1641.1726001586471, + 1708.2631397018104, + 1663.758529848884, + 1673.1654103072156, + 1749.8037198896054, + 1348.0922719246669, + 330.25013871710183, + 19.35958286745482, + 3.5368803531079336, + 16.0725928821717, + 16.254801945806612, + 6.1359971374734155, + 8.86353007399404, + 12.474117547442278, + 15.358144333910502, 0.0, + 4.2698150843165505, 0.0, 0.0, 0.0, @@ -531,105 +531,105 @@ "system_name": "fluid", "values": [ 1.1990625, - 1.206291218862206, - 1.2251427920923224, - 1.2533926435243583, - 1.2887327249295564, - 1.3298850673213958, - 1.3762353875886921, - 1.4273098662098027, - 1.4826998207876958, - 1.5420431133536183, - 1.6049044398640613, - 1.6709129699713863, - 1.7398473740262257, - 1.8114203023139126, - 1.8854018187827515, - 1.9616117463234828, - 2.0398817409263104, - 2.1200612626004065, - 2.2020224091181038, - 2.285594356795057, - 2.370625789198274, - 2.4569731709642886, - 2.544597123506079, - 2.633505929520998, - 2.7236864465713833, - 2.8147879428876723, - 2.906985603226265, - 3.000343813149392, - 3.0938721073746325, - 3.1880835992151018, - 3.219445769851805, - 3.2194928505759695, - 3.218972846850055, - 3.218850285198104, - 3.2188789186256095, - 3.218899343489275, - 3.2188060714124114, - 3.218805595407585, - 3.2187922335479557, - 3.2187791224898405, - 3.2188049650925525, - 3.218802965005493, - 3.21874633513394, - 3.218778527620756, - 3.218787800086968, - 3.21877763324876, - 3.2187869503277957, - 3.218801723646658, - 3.2188059646005005, - 3.218802904113761, - 3.2187598378629003, - 3.2187926829108324, - 3.2187830153192003, - 3.2187940663279653, - 3.21887316742644, - 3.2189410001275878, - 3.218960821745065, - 3.218969175054672, - 3.219004759769721, - 3.219034463808112, - 3.2190504796248742, - 3.2190521499490696, - 3.219306855849682, - 3.219525377857718, - 3.2197256432100385, - 3.2199100830560936, - 3.2200888769402294, - 3.220104092135281, - 3.2196277874250585, - 3.2196708582832905, - 3.2192796817127145, - 3.2189086136367777, - 3.218969678322776, - 3.21907942406814, - 3.2190411300367785, - 3.2188676944901733, - 3.2188447959347966, - 3.2189454004639897, - 3.218944669941815, - 3.218860166560762, - 3.2187814287838155, - 3.2188050298515645, - 3.2187352008556602, - 3.2187739282258416, - 3.21912366161124, - 3.2186659672343008, - 3.220265299799684, - 3.2189520703755012, - 3.2207158761914387, - 3.22006342847907, - 3.2216900634986714, - 3.221863805412857, - 3.21992639788017, - 3.22035809693605, - 3.219768897432158, - 3.220162312109093, - 3.2199596621054885, - 3.219830044932556, - 3.219676592514716, - 3.2200324616308778 + 1.2062912188622061, + 1.2251427920923226, + 1.2533926435243588, + 1.288732724929557, + 1.329885067321398, + 1.3762353875886937, + 1.427309866209793, + 1.4826998207878175, + 1.5420431133540629, + 1.6049044398646288, + 1.6709129699718779, + 1.7398473740274438, + 1.8114203023159112, + 1.885401818785752, + 1.9616117463289056, + 2.039881740936874, + 2.120061262605599, + 2.2020224091098526, + 2.285594356802947, + 2.370625789283306, + 2.4569731710870077, + 2.5445971236586034, + 2.6335059297158616, + 2.723686446668211, + 2.8147879426926212, + 2.9069856023019787, + 3.000343813002061, + 3.093872107791237, + 3.188083599333948, + 3.219445770962338, + 3.2194928565875323, + 3.218972833025382, + 3.2188364287840585, + 3.2188952464474996, + 3.2188298138468485, + 3.218820009743553, + 3.2188110597955935, + 3.218828763867503, + 3.2188012953477307, + 3.2188133906357725, + 3.2188211972434915, + 3.2187981546861844, + 3.2188060852462024, + 3.21879698019165, + 3.2188088732280495, + 3.2187737495331152, + 3.218903917738651, + 3.218776459547487, + 3.218803714669472, + 3.2187503464220484, + 3.218795721437317, + 3.218754308375021, + 3.2187822988310626, + 3.2187739284318457, + 3.218770122015697, + 3.2187997574578473, + 3.2188318342355196, + 3.2187869809645915, + 3.2187639760733786, + 3.218824288376943, + 3.2190519021135025, + 3.219306541647642, + 3.2195250599738605, + 3.219725399128985, + 3.219909992321128, + 3.220088762580422, + 3.2201018271885893, + 3.2196238979222653, + 3.2193897456983307, + 3.21911564460507, + 3.2190502512824275, + 3.2189232478583474, + 3.2189093636634465, + 3.2188940148358705, + 3.218767637296241, + 3.21888873879008, + 3.2189397605124817, + 3.2188137685882583, + 3.218731381174506, + 3.2188955184409904, + 3.2190142324249567, + 3.218810411312744, + 3.218722729217857, + 3.2190287832726505, + 3.219089215940388, + 3.220891772767404, + 3.220891947678954, + 3.221120116652444, + 3.2206949160717904, + 3.220227866018156, + 3.2199378138478973, + 3.2203023876287897, + 3.2202986314807363, + 3.219811690478277, + 3.219840248409694, + 3.2199399387860748, + 3.220772411716257, + 3.219926013023057, + 3.221087670404958 ], "datatype": "Float64", "type": "series" @@ -779,50 +779,50 @@ 0.0, 0.0, 0.0, - 30.94820884379941, - 22.90822490454815, - 7.396807867640719, - 7.414100503599705, - 7.607592499293037, - 8.584551268164507, - 15.886168735323801, - 17.583653786794144, - 3.998937810844999, - 26.61922910335046, - 1.143643827251536, - 2.827292135466822, - 1.5729938238346048, - 4.595785914798647, - 2.9661048359859876, - 0.279238435624049, - 0.5856484421451172, - 0.009552267527160834, - 0.0010030857424828412, - 3.720823736828451, - 3.9029906118718665, - 0.034405964785396745, - 3.820213730523163, - 8.931893089299567, - 11.186430353660104, - 4.365343796995128, - 9.257621003118746, - 8.875071770189836, - 5.232857413378655, - 35.57141974974755, - 2.695571516358929, - 0.2777628099152869, - 4.869266502185761, - 10.106394305611726, - 4.6574265770569045, - 4.788598767426367, - 16.250354085392793, - 31.4444062316207, - 8.623685759947092, - 20.213228683789133, - 6.33056812306364, + 30.954596433372068, + 22.918495883508655, + 18.68653875959768, + 6.342328285895448, + 6.010974893248376, + 6.921352268527839, + 12.641287660360078, + 5.324096059467818, + 12.299384992232632, + 5.960799532361503, + 1.692622915372987, + 10.229972006718947, + 17.865220878093922, + 2.140105801547046, + 0.18424376218570795, + 14.127723695934293, + 2.3026452180996864, + 3.316254942849902, + 0.08793564479420095, + 0.07533401695691826, + 4.278889300902068, + 7.602004560117197, + 1.4544293901714205, + 2.772638097843874, + 7.52195746562243, + 4.427164942111693, + 1.3496900702494476, + 8.31428790635449, + 4.5454298608070935, + 5.9366427200282414, + 11.463972179316809, + 11.14697704435911, + 9.130044979184483, + 9.767051289777724, + 27.602126502373164, + 3.778250708541731, + 26.35607071767321, + 6.775108546420978, + 11.369481071601523, + 14.612697591736914, + 20.49504646337744, + 1.1996206222897805, 0.0, 0.0, - 20.32651578506585, 0.0, 0.0, 0.0, @@ -844,4 +844,4 @@ "datatype": "Float64", "type": "series" } -} \ No newline at end of file +} diff --git a/validation/dam_break_2d/validation_reference_wcsph_00075.json b/validation/dam_break_2d/validation_reference_wcsph_00075.json index 2b415c07bd..0763d5b024 100644 --- a/validation/dam_break_2d/validation_reference_wcsph_00075.json +++ b/validation/dam_break_2d/validation_reference_wcsph_00075.json @@ -137,81 +137,81 @@ 0.0, 0.0, 0.0, - 58.34607670780366, - 138.30780730090584, - 422.4439168767831, - 916.8364120975024, - 419.06398575736984, - 1347.417827563866, - 2349.6255616253516, - 1064.5393348074151, - 27.317068325912476, - 101.40177005737137, - 781.1469021132997, - 1397.141799265406, - 1379.759513694514, - 1194.2325124227586, - 577.1087108938541, - 835.985544221772, - 3084.814615378419, - 3315.6976314538865, - 832.4717664353399, - 2897.005749304349, - 3002.164449326629, - 1361.257691488331, - 3857.591385230775, - 1670.062910230366, - 3151.308834774854, - 3129.2187304687695, - 979.7764644576539, - 4547.292898341513, - 2359.9991553519276, - 926.182837379232, - 3935.8020396511865, - 4379.52352100003, - 1722.4646619986313, - 239.70093766679906, - 2089.466442476288, - 4483.323370640237, - 5364.48776205749, - 4475.9885720309485, - 2627.998878339486, - 1181.8674467133405, - 649.6636140991379, - 330.1142981716199, - 345.0782726299165, - 192.3883424307605, - 179.4013719150344, - 1136.3039269688265, - 3826.440939852382, - 7363.986736552375, - 7195.6667823247935, - 7394.382064105072, - 6254.669285089546, - 4209.599045337116, - 6960.72634285375, - 4403.324013800456, - 1981.9578327450497, - 766.6754384823071, - 4585.4210989388175, - 8579.806145402657, - 1303.9255807328104, - 850.3314028904712, - 2149.0574314606965, - 1119.63353117133, - 1025.4664409249024, - 2062.434608152032, - 10.626963343280085, - 2866.5242641333803, - 1210.0747178019974, - 1997.7373282796998 + 58.3449915737484, + 138.30798197205348, + 422.4454327143758, + 916.8379084200394, + 419.0542820835238, + 1347.4169383327599, + 2349.6352553794636, + 1064.518846925364, + 27.284751508188627, + 101.4027110447832, + 781.269929694757, + 1397.375595899754, + 1380.062438593228, + 1194.5290438249299, + 577.3446597874912, + 837.9255914093883, + 3081.1744294335917, + 3318.4600872182405, + 840.9522826551704, + 2892.2815052491255, + 3032.9001716109915, + 1260.6934944658822, + 3817.379408690888, + 1635.6748520209878, + 3229.882576401444, + 3186.2844071491168, + 986.1747562380727, + 4559.229103674002, + 2323.662542906834, + 869.9397283308729, + 3913.8661202543517, + 4345.322714334653, + 1727.691083770075, + 318.1022245457738, + 2137.4034881964117, + 4477.132376971367, + 5280.620479827444, + 4348.915605753067, + 2548.152118233453, + 1166.5958655127329, + 733.7269976054581, + 449.6293773957523, + 528.8475316972172, + 433.4970917379593, + 420.1311022195725, + 1286.81821996594, + 3764.7503687159733, + 7145.96986424888, + 7066.063851401108, + 7353.562854052298, + 6472.864968549782, + 4306.347988855297, + 6918.947963743149, + 3843.2505962682435, + 1933.8221271593907, + 1179.7008451321374, + 4632.996937909627, + 3317.094937208304, + 1656.3251394624062, + 388.08068284496295, + 1879.016655252052, + 1609.0588975679866, + 542.3340923494694, + 2371.0119086155237, + 1156.028036930474, + 3685.0239743888806, + 1009.9810839922333, + 1979.0594313323793 ], "datatype": "Float64", "type": "series" }, "meta": { "julia_version": "1.10.2", - "solver_version": "98fe4b85-dirty", + "solver_version": "v0.1.0-41-ga05975e-dirty", "solver_name": "TrixiParticles.jl" }, "pressure_P2_fluid_1": { @@ -357,52 +357,52 @@ 0.0, 0.0, 0.0, - 20.31323871163639, - 45.50619636076011, - 6.167957730082405, - 23.884709125644008, - 4.43255918517901, - 8.774110436930735, - 8.963620141583254, - 40.731562580934956, - 13.241833317657427, - 7.200450403202941, - 35.88196312189998, - 21.541443478575296, - 3.65452867132935, - 75.4427754124238, - 201.09695963130483, - 49.816202634048224, - 255.57387598753877, - 289.5116797564693, - 267.79845001041633, - 421.33924738849765, - 518.9202572071249, - 443.37050099785904, - 640.6791984196514, - 729.3057886088931, - 708.2134106342477, - 1025.235951022832, - 1026.3999971246153, - 1096.9087688994634, - 1008.4260873388821, - 1269.2640692741431, - 1625.4501394880863, - 1844.8915331962003, - 1937.2541977002384, - 1719.9451731647855, - 1374.1398026925883, - 1600.1725709745563, - 1400.2678834762628, - 1661.0115314501156, - 1371.158183140062, - 646.0660269386094, - 168.90325590861573, - 17.13050116673967, + 20.31323264129541, + 45.50619772878511, + 6.167987914971771, + 23.88731586293732, + 4.430147325868489, + 8.768109090756356, + 8.968266092741535, + 40.8056081128188, + 13.266289074276875, + 7.21976446106391, + 35.837578093058696, + 21.219114696929093, + 3.49673027837686, + 85.22260579192265, + 198.6594657678087, + 84.42527669302487, + 222.28302368858368, + 255.70201681023167, + 287.17352794962125, + 390.1368774908667, + 461.6354219207832, + 479.2956919031473, + 668.131582134366, + 708.6708868363967, + 645.0338221375232, + 1006.9495976769247, + 1022.5991860047172, + 1088.542578329139, + 974.647785952737, + 1302.905562870689, + 1559.2866782248348, + 1896.6993512365123, + 1934.924534902601, + 1663.477452114545, + 1419.3260734938583, + 1576.7967846720799, + 1467.524148572022, + 1574.549493950828, + 1358.3707085870317, + 626.3587223898397, + 170.68459310977235, + 16.588120937902353, 0.0, 0.0, 0.0, - 6.928117091969661, + 6.927951997943524, 0.0, 0.0, 0.0, @@ -540,96 +540,96 @@ 1.415616412333505, 1.470830679860593, 1.5296724290345078, - 1.5918075373727558, - 1.6569645052768505, - 1.7248666483318564, - 1.7952375999021615, - 1.8679490290611642, - 1.942910014856134, - 2.0200484369245837, - 2.0991787194449247, - 2.1799742972516323, - 2.262067264672173, - 2.3452911203047084, - 2.429713872083454, - 2.515361512205054, - 2.602293896796202, - 2.6906214381249502, - 2.780398787100884, - 2.871371035052104, - 2.9631624013020352, - 3.0557396096262015, - 3.148099574918203, + 1.5918075373727552, + 1.6569645052768507, + 1.7248666483318587, + 1.7952375999021644, + 1.8679490290611684, + 1.9429100148561393, + 2.020048436924589, + 2.0991787194449283, + 2.179974297251634, + 2.2620672646721722, + 2.345291120304705, + 2.4297138720834495, + 2.5153615122050486, + 2.602293896796195, + 2.690621438124939, + 2.780398787100872, + 2.8713710350520953, + 2.963162401302029, + 3.055739609626199, + 3.1480995749182057, 3.217883381034535, - 3.2182524076050694, - 3.219035833332324, - 3.2196638215078255, - 3.219258579483189, - 3.2183916128026855, - 3.2182369934034982, - 3.218223208202275, - 3.2182163705155977, - 3.2182383904949226, - 3.2182285980567626, - 3.218238344389985, - 3.2182077455893454, - 3.2182204186323973, - 3.218170605256553, - 3.218151520915598, - 3.2181831831840575, - 3.2182493485634134, - 3.2181785938759218, - 3.218188358844037, - 3.2182324626442353, - 3.2182142215753884, - 3.2182264340719717, - 3.2182064295006336, - 3.218213596467143, - 3.2182008193381617, - 3.2182039410427405, - 3.2182025004491424, - 3.2181926479696, - 3.2182003300356925, - 3.218207646497285, - 3.218209138192204, - 3.218196212105352, - 3.2182009245598135, - 3.2181952111642214, - 3.218212351556307, - 3.2182122930292048, - 3.218334624700235, - 3.218585621943171, - 3.2188865560695596, - 3.2192019897484103, - 3.219574328223231, - 3.219582766932132, - 3.218936057751625, - 3.218203739651142, - 3.218195663545464, - 3.218195596720888, - 3.2182126696027797, - 3.2182138825029765, - 3.2182016914355787, - 3.2182146627037733, - 3.2182120968881103, - 3.2182004332757366, - 3.218473417648065, - 3.2182117733109865, - 3.218198251815128, - 3.218197262453762, - 3.218223980447437, - 3.2227801092805124, - 3.2207859044320903, - 3.218230016750645, - 3.2182184991110523, - 3.218208917822917, - 3.2182016690447384, - 3.221967175753277, - 3.2219266041450023, - 3.221972452578923, - 3.2219639418722528, - 3.221909307915254, - 3.221972546583524 + 3.218252199192597, + 3.2190358988544663, + 3.2196639730256824, + 3.2192588413420857, + 3.218391850339578, + 3.2182383945174027, + 3.2182173493351147, + 3.218213527639729, + 3.2182331098537924, + 3.218212354866577, + 3.2182351451319797, + 3.2181860314410864, + 3.2181826027767317, + 3.2182616091503014, + 3.2182900394924134, + 3.2181013332171955, + 3.218180707465579, + 3.2182413760911537, + 3.2182366042723656, + 3.218190838368207, + 3.218206894438811, + 3.2181922164947707, + 3.2182183047852124, + 3.218209781429125, + 3.2182014695396095, + 3.2181983905210876, + 3.218217781625702, + 3.2182083303459947, + 3.2182141488580243, + 3.2181940144865617, + 3.21819491946819, + 3.218204747678986, + 3.2181938551073994, + 3.2181978426903326, + 3.21820542694873, + 3.2182070153294915, + 3.218334642143193, + 3.218585647166958, + 3.2188866053900216, + 3.21920207104197, + 3.219574419965984, + 3.2195825814614847, + 3.218935747756291, + 3.218199845786071, + 3.21819638894806, + 3.2181940755753295, + 3.2182116677167216, + 3.2182091551294025, + 3.218208689192445, + 3.2182110246884483, + 3.2182138808799365, + 3.218194754804018, + 3.2194851978366095, + 3.218192847601363, + 3.218212526157722, + 3.218208556745474, + 3.2182025076371104, + 3.2181962807514766, + 3.218224806925123, + 3.218198412662153, + 3.2182146801528235, + 3.21819542100968, + 3.2189620377926227, + 3.218968460252789, + 3.2189665397479192, + 3.2189697305224594, + 3.2189438274647815, + 3.218954688518879, + 3.2189532497664337 ], "datatype": "Float64", "type": "series" @@ -782,42 +782,42 @@ 0.0, 0.0, 0.0, - 48.777453004578454, - 10.358152612197825, - 15.049716723438971, - 8.912878348810716, - 3.1038473116339116, - 9.08029215572343, - 11.001309062044069, - 4.389568308532413, - 9.34597656525185, - 8.215815969414672, - 8.921424560673799, - 11.548495006236983, - 8.100275261058105, - 6.463798001100874, - 10.126510322875083, - 3.5656525366383485, - 9.936641893186898, - 8.36706783034966, - 6.531183179219712, - 3.397637976308925, - 4.301526347537932, - 3.1626566693065383, - 13.957194239542332, - 2.355736919532164, - 9.467443984066906, - 15.268611565230216, - 4.803212788857185, - 17.17166030016114, - 16.481158545172544, - 35.126053887486044, + 48.777455403164595, + 10.358164544007067, + 15.04960832545812, + 8.912918411774163, + 3.1038547518232447, + 9.080093791795168, + 11.001861768010636, + 4.389149228051656, + 9.327505518523214, + 8.079456786928928, + 8.873355574751367, + 11.709335633413499, + 9.772263422141894, + 10.115246645209131, + 7.9532840524051505, + 3.9255626813013116, + 7.572786369647373, + 8.644475585297913, + 8.179980736326566, + 4.026316518597092, + 3.9816360448056907, + 3.2242512880165095, + 13.986955872502259, + 2.3506997098606854, + 9.465894897841721, + 15.270226515067424, + 4.803273021482147, + 17.17178281543346, + 16.48196895654314, + 35.117449625443314, 0.0, 0.0, 0.0, 0.0, 0.0, - 6.527472931535234, + 6.527361132717613, 0.0, 0.0, 0.0, diff --git a/validation/dam_break_2d/validation_reference_wcsph_0015.json b/validation/dam_break_2d/validation_reference_wcsph_0015.json index 3a3d2221ed..56662a4596 100644 --- a/validation/dam_break_2d/validation_reference_wcsph_0015.json +++ b/validation/dam_break_2d/validation_reference_wcsph_0015.json @@ -139,79 +139,79 @@ 0.0, 0.0, 0.0, - 741.9939338248116, - 1074.8234704502822, - 780.1667963911615, - 750.2648649048765, - 1867.0876734094586, - 2289.2233790766354, - 1854.778595920126, - 1898.4536968839027, - 1467.7442216200166, - 1634.062582456003, - 1051.0175372038652, - 1792.992475880798, - 2224.4495761272037, - 2072.49744240541, - 1759.7942677800675, - 2213.446522638474, - 2195.8824872685705, - 2296.936590368152, - 2110.2619949482696, - 2199.2839820790064, - 2970.3733860698267, - 2306.9379237499984, - 2708.549145872953, - 3000.178544197365, - 2269.792696086254, - 2084.4626138591966, - 2584.8924415020083, - 2922.9580586157217, - 2700.961322015944, - 2912.960202391661, - 2333.093486494811, - 2272.6997429718494, - 2482.9264961596828, - 2818.1473417252982, - 2995.432486972956, - 2909.3768298891655, - 3100.77021995143, - 3025.9649439094655, - 3235.1990309725757, - 3257.7483119248636, - 2988.7945918465675, - 3115.2094695758724, - 3505.9426755013787, - 3713.8656343932016, - 4586.51617966487, - 4512.604003095575, - 6079.787376393051, - 6651.042779454037, - 5998.169882646548, - 3485.069906443089, - 810.5465351054376, - 1498.1154610674191, - 1280.9451536023262, - 6123.104036824773, - 1207.454673045513, - 1872.428135603674, - 345.5216355544876, - 1190.3488951150066, - 1160.696857170737, - 1552.7750026162616, - 2007.8328589553835, - 1229.3505526363801, - 941.7174210262225, - 1420.6537905276475, - 4080.222555513338, - 774.1134490223096 + 741.9939895034533, + 1074.823477526557, + 780.1667978278533, + 750.2646818625105, + 1867.0876195480973, + 2289.223377185172, + 1854.778592795079, + 1898.4536965337563, + 1467.7442181784463, + 1634.0625814441341, + 1051.0175421397894, + 1792.9924986054696, + 2224.44957136399, + 2072.497448511862, + 1759.7941823838978, + 2213.446718347899, + 2195.8823765248344, + 2296.936996796624, + 2110.26221033083, + 2199.2841631050296, + 2970.3732277673735, + 2306.9379075970605, + 2708.5489780064477, + 3000.1787276966415, + 2269.7922168984032, + 2084.462803522515, + 2584.892959415741, + 2922.9579331489513, + 2700.96048467049, + 2912.959472927148, + 2333.0945761449198, + 2272.699760539699, + 2482.927145390145, + 2818.1481398849137, + 2995.431879484659, + 2909.3787969527793, + 3100.770191742455, + 3025.9650728686365, + 3235.200330338881, + 3257.7536083922405, + 2988.768529824477, + 3115.2219953824097, + 3505.964207981934, + 3713.9020435212456, + 4590.047833488942, + 4356.981088994886, + 6158.5534685202465, + 6758.256849320384, + 5924.747293352886, + 3779.601511593646, + 853.4159363416477, + 1654.7441086189888, + 1292.725609178739, + 5035.3022616347025, + 2519.4325603311504, + 2200.0503609676116, + 677.4791455662695, + 1260.2892839989977, + 632.0785128025252, + 1342.5190787770055, + 2015.993021332793, + 1480.8010982207184, + 1092.6724464916224, + 1240.7416518698235, + 3695.038480349523, + 790.4034873096698 ], "datatype": "Float64", "type": "series" }, "meta": { "julia_version": "1.10.2", - "solver_version": "dcd1d2b1", + "solver_version": "v0.1.0-40-gafc1aaf", "solver_name": "TrixiParticles.jl" }, "pressure_P2_fluid_1": { @@ -360,41 +360,41 @@ 0.0, 0.0, 0.0, - 47.78581694313901, - 44.38574442306553, - 30.473857197402413, - 8.477074724627595, - 33.57951875059658, - 1.8632501617021102, - 22.444845391614827, - 5.229751210510513, - 12.452368822405496, - 113.28031750326882, - 16.872533929605122, - 183.53117184170725, - 124.91979697908702, - 119.72035918066965, - 157.18678733719997, - 274.41224922147677, - 366.928403397206, - 436.7982051917158, - 656.8646154705689, - 693.6804508905344, - 709.8624537628132, - 940.2761580309083, - 713.5961440910972, - 1152.7121152728616, - 1175.0856672128707, - 1117.0740820240865, - 1473.3118337985009, - 1398.3788581542326, - 1426.7186616370407, - 1724.6680215045349, - 1689.582829806212, - 1716.4125370027475, - 2313.2602797513496, - 1548.2978703765393, - 872.8096099036991, + 47.779426335028596, + 44.3755113096053, + 30.47391316492869, + 8.476970547767014, + 33.57949084782525, + 1.86325295891417, + 22.444830543469468, + 5.2297499045038816, + 12.452369473099422, + 113.28030365232425, + 16.872542628935967, + 183.53132403395512, + 124.91982103570506, + 119.72063152487195, + 157.1866385869409, + 274.41221818832616, + 366.9284030230333, + 436.7981563485061, + 656.8644583863837, + 693.680435675162, + 709.8626787029332, + 940.2768525263258, + 713.5965127260125, + 1152.7101065183833, + 1175.085784491902, + 1117.0751123518626, + 1473.3121783676756, + 1398.3778231249435, + 1426.7193716891154, + 1724.669726207463, + 1689.5768339343053, + 1716.4132703616517, + 2313.2808326123604, + 1548.3843452879687, + 872.6651774696463, 0.0, 0.0, 0.0, @@ -531,105 +531,105 @@ "system_name": "fluid", "values": [ 1.1925, - 1.1979096218668643, - 1.2114046382489774, - 1.233642069008528, + 1.1979096218668641, + 1.2114046382489772, + 1.2336420690085277, 1.2644066208331137, - 1.3028362471750408, - 1.347675181367617, - 1.3979265161874306, - 1.4526822092129996, - 1.5111470319630962, - 1.5729272010563125, - 1.6376619970447728, - 1.7050782695517812, - 1.774922235352398, - 1.8470381017706874, - 1.9214486425691797, - 1.9977114877634126, - 2.0756036806364517, - 2.1548845384780377, - 2.235402446374903, - 2.317146330306623, - 2.4000829664557464, - 2.484138084380929, - 2.5693227139746573, - 2.6555845619869327, - 2.7426282187297657, - 2.830107717849038, - 2.9177936457239837, - 3.0055781193133417, - 3.0934656910674185, - 3.1819553106957805, - 3.2108247664460037, - 3.2113869376507562, - 3.2114127786783255, - 3.2114161066075466, - 3.2113893826012587, - 3.211426416730024, - 3.2113609775292216, - 3.21134242815772, - 3.2113732404825783, - 3.2114163514270784, - 3.2115424540866804, - 3.2114240008532415, - 3.21133982171087, - 3.211435764568252, - 3.2114307576517303, - 3.2113884470340004, - 3.211430796900016, - 3.21138905570089, - 3.211385392690142, - 3.211391389367187, - 3.2113867220811616, - 3.211314609628206, - 3.2112229705963546, - 3.211144904724678, - 3.2110067641757443, - 3.210911971600086, - 3.2107740015910333, - 3.2105999166679235, - 3.210409429662026, - 3.2101913618122015, - 3.209950737465752, - 3.2096723630110047, - 3.209388535442989, - 3.209054807974806, - 3.2087189035854937, - 3.2084002150370896, - 3.2092529975678827, - 3.210961614417827, - 3.2087142145674608, - 3.2078771017362766, - 3.2099811740892332, - 3.2099475931241868, - 3.2057948825772176, - 3.2053841685407347, - 3.206091263909073, - 3.205519339413853, - 3.2060202862612424, - 3.208319007154801, - 3.2093695706812637, - 3.2093274365942297, - 3.2077697929876843, - 3.2052776717641263, - 3.204666763480492, - 3.204935394911624, - 3.205533549952792, - 3.206955192729286, - 3.205966890849804, - 3.205813036921348, - 3.2054758271429558, - 3.2056931166230225, - 3.2057049469317778, - 3.205688541125392, - 3.2056979581817227, - 3.205641713595731, - 3.207203044444701, - 3.2078958675858784, - 3.2077053880890443, - 3.2064670429651616, - 3.2020423230291524 + 1.3028362471750405, + 1.3476751813676169, + 1.39792651618743, + 1.452682209212999, + 1.5111470319630955, + 1.5729272010563116, + 1.6376619970447719, + 1.7050782695517805, + 1.7749222353523975, + 1.847038101770687, + 1.9214486425691792, + 1.997711487763412, + 2.075603680636451, + 2.154884538478037, + 2.235402446374902, + 2.317146330306622, + 2.400082966455745, + 2.4841380843809278, + 2.569322713974655, + 2.6555845619869305, + 2.742628218729764, + 2.8301077178490366, + 2.917793645723982, + 3.00557811931334, + 3.093465691067416, + 3.181955310695781, + 3.210824766446009, + 3.2113869376507593, + 3.2114127336130904, + 3.211416086243699, + 3.211389363109841, + 3.2114263696509733, + 3.211360939063853, + 3.2113423890613726, + 3.2113730636581423, + 3.211416879533602, + 3.2115410232660544, + 3.211420449020959, + 3.2113404149551292, + 3.2114367116116433, + 3.2114314964286073, + 3.2113887143331348, + 3.211430070547805, + 3.21138921418565, + 3.2113859601123305, + 3.2113923720866837, + 3.211392764090055, + 3.2113232302753256, + 3.2112382778331168, + 3.2111709478503747, + 3.2110448136299907, + 3.210961931912992, + 3.2108357753696595, + 3.210676328658437, + 3.2104947086715394, + 3.2102932553041774, + 3.2100651346283873, + 3.2097946508137327, + 3.2095209162980893, + 3.2091908718225577, + 3.2088580733865246, + 3.208540190797431, + 3.209252986094329, + 3.2109615902106654, + 3.2087142100000996, + 3.207877151637441, + 3.209981078282075, + 3.2099475145370917, + 3.2059177379649135, + 3.2055004729811776, + 3.206091143678486, + 3.2055193197002003, + 3.2060219413836486, + 3.208390543508341, + 3.209639183492156, + 3.2099090780661865, + 3.2091035797982252, + 3.2060994879120677, + 3.204667302726753, + 3.2049293269676054, + 3.2055243749588707, + 3.205489593713636, + 3.2070488215164983, + 3.2059831350068317, + 3.2056723103164897, + 3.2056894526500916, + 3.2056003904638524, + 3.2054279890859227, + 3.2056363154541985, + 3.2081676171369784, + 3.2106976756102723, + 3.210581134988433, + 3.209557542221741, + 3.208689628218864, + 3.2042633909252842 ], "datatype": "Float64", "type": "series" @@ -789,29 +789,29 @@ 0.0, 0.0, 0.0, - 7.496013435689593, - 4.405923582489511, - 3.863763140478915, - 5.608197870347884, - 5.873367887336063, - 8.085513458105599, - 5.478876868208552, - 4.599394355614402, - 4.4993063713207775, - 4.593781791458133, - 4.183019826563516, - 1.4104621904133992, - 3.2046660479543077, - 11.008713759804175, - 5.743853671430627, - 4.599785421280336, + 7.495670452200194, + 4.405996201079288, + 3.863768751307385, + 5.608251391833488, + 5.873393414199472, + 8.085514751124725, + 5.478880909811544, + 4.599391660688403, + 4.499303678841139, + 4.5937747885098, + 4.183039155161291, + 1.4104749893139292, + 3.204735219197912, + 11.008687038401128, + 5.743991393979669, + 4.599470537356818, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, - 6.279733295239109, + 6.936334864959468, 0.0, 0.0, 0.0,