Repository: trixi-framework/TrixiParticles.jl
Pull request #769: Implement Tensile Instability Control (TIC)
Author: login: efaulhaber
Labels: name: enhancement
description: New feature or request
Created: 2025-04-14T11:59:53Z | Merged: 2025-05-18T18:35:49Z

This is an undocumented feature until we figure out when and if it is useful.
So far, it is not useful alone:
<img width="1290" alt="grafik" src="https://github.com/user-attachments/assets/e4b26726-9f49-4839-b80f-b90b826b6044" />

But it might be useful in addition to PST.

Note that, just like PST, this does not work at free surfaces.


Diff:
diff --git a/docs/src/refs.bib b/docs/src/refs.bib
index 67f75b6925..9fd7c80f91 100644
--- a/docs/src/refs.bib
+++ b/docs/src/refs.bib
@@ -648,6 +648,17 @@ @Article{Sun2017
   publisher = {Elsevier BV},
 }
 
+@article{Sun2018,
+	author    = {Sun, P. N. and Colagrossi, A. and Marrone, S. and Antuono, M. and Zhang, A. M.},
+	title     = {Multi-resolution Delta-plus-{SPH} with tensile instability control: Towards high Reynolds number flows},
+	journal   = {Computer Physics Communications},
+	volume    = {224},
+  year      = {2018},
+	issn      = {0010-4655},
+	pages     = {63--80},
+	doi       = {10.1016/j.cpc.2017.11.016},
+}
+
 @Article{Tafuni2018,
   author    = {A. Tafuni and J.M. Dom{\'{\i}}nguez and R. Vacondio and A.J.C. Crespo},
   journal   = {Computer Methods in Applied Mechanics and Engineering},
@@ -723,4 +734,4 @@ @book{Poling2001
   year = {2001},
   publisher = {McGraw-Hill},
   address = {New York}
-}
\ No newline at end of file
+}
diff --git a/docs/src/systems/fluid.md b/docs/src/systems/fluid.md
index 5fa876dde6..1697d8b4fb 100644
--- a/docs/src/systems/fluid.md
+++ b/docs/src/systems/fluid.md
@@ -1,8 +1,8 @@
 # [Fluid Models](@id fluid_models)
 
 Currently available fluid methods are the [weakly compressible SPH method](@ref wcsph) and the
-[entropically damped artificial compressibility for SPH](@ref edac).  
-This page lists models and techniques that apply to both of these methods.  
+[entropically damped artificial compressibility for SPH](@ref edac).
+This page lists models and techniques that apply to both of these methods.
 
 ## [Viscosity](@id viscosity_sph)
 
@@ -58,21 +58,19 @@ by Balsara ([Balsara1995](@cite)) or Morris ([Morris1997](@cite)).
 The force exerted by particle `b` on particle `a` due to artificial viscosity is given by:
 
 ```math
-\mathbf{F}_{ab}^{\text{AV}} = - m_a m_b \Pi_{ab} \nabla W_{ab}
+F_{ab}^{\text{AV}} = - m_a m_b \Pi_{ab} \nabla W_{ab}
 ```
 
 where:
 
-- `\Pi_{ab}` is the artificial viscosity term defined as:
-
-    ```math
-    \Pi_{ab} =
-    \begin{cases}
-        -\frac{\alpha c \mu_{ab} + \beta \mu_{ab}^2}{\bar{\rho}_{ab}} & \text{if } \mathbf{v}_{ab} \cdot \mathbf{r}_{ab} < 0, \\
-        0 & \text{otherwise}
-    \end{cases}
-    ```
-
+- ``\Pi_{ab}`` is the artificial viscosity term defined as:
+  ```math
+  \Pi_{ab} =
+  \begin{cases}
+      -\frac{\alpha c \mu_{ab} + \beta \mu_{ab}^2}{\bar{\rho}_{ab}} & \text{if } \mathbf{v}_{ab} \cdot \mathbf{r}_{ab} < 0, \\
+      0 & \text{otherwise}
+  \end{cases}
+  ```
 - ``\alpha`` and ``\beta`` are viscosity parameters,
 - ``c`` is the local speed of sound,
 - ``\bar{\rho}_{ab}`` is the arithmetic mean of the densities of particles ``a`` and ``b``.
@@ -361,7 +359,7 @@ with:
 This divergence can be computed numerically in the SPH framework as
 
 ```math
-\sum_b \frac{m_b}{\rho_a \rho_b} (S_a + S_b) \nabla W_{ab} 
+\sum_b \frac{m_b}{\rho_a \rho_b} (S_a + S_b) \nabla W_{ab}
 ```
 
 #### Advantages and limitations
diff --git a/docs/src/systems/weakly_compressible_sph.md b/docs/src/systems/weakly_compressible_sph.md
index 8848dfeff1..51355961f6 100644
--- a/docs/src/systems/weakly_compressible_sph.md
+++ b/docs/src/systems/weakly_compressible_sph.md
@@ -54,8 +54,8 @@ pressure field. It is highly recommended to use density diffusion when using WCS
 All density diffusion terms extend the continuity equation (see [`ContinuityDensity`](@ref))
 by an additional term
 ```math
-\frac{\mathrm{d}\rho_a}{\mathrm{d}t} = \sum_{b} m_b v_{ab} \cdot \nabla_{r_a} W(\Vert r_{ab} \Vert, h)
-    + \delta h c \sum_{b} V_b \psi_{ab} \cdot \nabla_{r_a} W(\Vert r_{ab} \Vert, h),
+\frac{\mathrm{d}\rho_a}{\mathrm{d}t} = \sum_{b} m_b v_{ab} \cdot \nabla W_{ab}
+    + \delta h c \sum_{b} V_b \psi_{ab} \cdot \nabla W_{ab},
 ```
 where ``V_b = m_b / \rho_b`` is the volume of particle ``b`` and ``\psi_{ab}`` depends on
 the density diffusion method (see [`DensityDiffusion`](@ref) for available terms).
@@ -109,3 +109,88 @@ term is very expensive and adds about 40--50% of computational cost.
 Modules = [TrixiParticles]
 Pages = [joinpath("schemes", "fluid", "weakly_compressible_sph", "density_diffusion.jl")]
 ```
+
+## [Particle Shifting Technique](@id shifting)
+
+The Particle Shifting Technique (PST) is used to correct tensile instability
+in regions of low pressure, as observed in viscous flow around an object.
+Without PST, tensile instability causes non-physical separation of the fluid
+from the object, introducing void regions behind the object.
+
+At lower resolutions, PST alone can be effective to correct a viscous flow
+around a cylinder, as shown in this figure.
+![particle_shifting](https://github.com/user-attachments/assets/70892b0b-af36-4531-b328-f73e63a5e33c)
+At higher resolutions, PST alone is not effective anymore; see the figure in
+[Tensile Instability Control](@ref tic).
+We recommend using PST and Tensile Instability Control together
+in such simulations.
+
+### Mathematical formulation
+
+We use the following formulation by [Sun et al. (2018)](@cite Sun2018).
+After each time step, a correction term ``\delta r_a`` is added to the position ``r_a``
+of particle ``a``, which is given by
+```math
+\delta r_a = -4 \Delta t \, v_\text{max} h
+    \sum_b \left( 1 + R \left( \frac{W_{ab}}{W(\Delta x_a)} \right)^n \right) \nabla W_{ab}
+    \frac{m_b}{\rho_a + \rho_b},
+```
+where:
+- ``\Delta t`` is the time step,
+- ``v_\text{max}`` is the maximum velocity over all particles,
+- ``h`` is the smoothing length,
+- ``R`` and ``n`` are constants, which are set to ``0.2`` and ``4`` respectively,
+- ``W(\Delta x_a)`` is the smoothing kernel of the particle size of particle ``a``,
+  which can be interpreted as the target particle spacing that we want to achieve.
+- ``\nabla W_{ab}`` is the gradient of the smoothing kernel,
+- ``m_b`` is the mass of particle ``b``,
+- ``\rho_a, \rho_b`` is the density of particles ``a`` and ``b``, respectively.
+
+Note that we replaced ``\text{CFL} \cdot \text{Ma}`` by ``\Delta t \cdot v_\text{max} / h``,
+as explained in [Sun2018](@cite Sun2018) on page 29, right above Equation 9.
+
+The ``\delta``-SPH method (WCSPH with density diffusion) together with this formulation
+of PST is commonly referred to as ``\delta^+``-SPH.
+
+The Particle Shifting Technique can be applied in form
+of the [`ParticleShiftingCallback`](@ref).
+
+## [Tensile Instability Control](@id tic)
+
+Tensile Instability Control (TIC) is a pressure acceleration formulation to correct tensile
+instability in regions of low pressure, as observed in viscous flow around an object.
+The technique was introduced by [Sun et al. (2018)](@cite Sun2018).
+The formulation is described in Section 2.1 of this paper.
+It can be used in combination with the [Particle Shifting Technique (PST)](@ref shifting)
+to effectively prevent non-physical separation of the fluid from the object.
+
+As can be seen in the following figure, TIC alone can cause instabilities
+and does not improve the simulation.
+PST alone can mostly prevent separation at lower resolutions.
+A small void region is still visible, but quickly disappears.
+The combination of PST and TIC prevents separation effectively.
+![low_res](https://github.com/user-attachments/assets/5b30d440-8ca5-4c13-94d0-d110de2eb7cc)
+
+At higher resolutions, PST alone is not effective to prevent separation, as can be seen
+in the next figure.
+Only the combination of PST and TIC is able to produce physical results.
+![high_res](https://github.com/user-attachments/assets/674aec76-33e6-4ee3-bcd7-ba8c381a2775)
+
+### Mathematical formulation
+
+The force that particle ``a`` experiences from particle ``b`` due to pressure is given by
+```math
+f_{ab} = -m_a m_b \frac{p_a + p_b}{\rho_a \rho_b} \nabla W_{ab}
+```
+for the WCSPH method with [`ContinuityDensity`](@ref).
+
+The TIC formulation changes this force to
+```math
+f_{ab} = -m_a m_b \frac{|p_a| + p_b}{\rho_a \rho_b} \nabla W_{ab}.
+```
+Note that this formulation is asymmetric and sacrifices conservation of linear and angular
+momentum.
+
+```@docs
+tensile_instability_control
+```
diff --git a/examples/fluid/periodic_channel_2d.jl b/examples/fluid/periodic_channel_2d.jl
index 6735c759d4..efc7e9eb37 100644
--- a/examples/fluid/periodic_channel_2d.jl
+++ b/examples/fluid/periodic_channel_2d.jl
@@ -35,9 +35,11 @@ smoothing_kernel = SchoenbergCubicSplineKernel{2}()
 fluid_density_calculator = ContinuityDensity()
 viscosity = ArtificialViscosityMonaghan(alpha=0.02, beta=0.0)
 
+# `pressure_acceleration=nothing` is the default and can be overwritten with `trixi_include`
 fluid_system = WeaklyCompressibleSPHSystem(tank.fluid, fluid_density_calculator,
                                            state_equation, smoothing_kernel,
-                                           smoothing_length, viscosity=viscosity)
+                                           smoothing_length, viscosity=viscosity,
+                                           pressure_acceleration=nothing)
 
 # ==========================================================================================
 # ==== Boundary
@@ -65,8 +67,10 @@ ode = semidiscretize(semi, tspan)
 
 info_callback = InfoCallback(interval=100)
 saving_callback = SolutionSavingCallback(dt=0.02, prefix="")
+# This can be overwritten with `trixi_include`
+extra_callback = nothing
 
-callbacks = CallbackSet(info_callback, saving_callback)
+callbacks = CallbackSet(info_callback, saving_callback, extra_callback)
 
 # Use a Runge-Kutta method with automatic (error based) time step size control.
 # Limiting of the maximum stepsize is necessary to prevent crashing.
diff --git a/src/TrixiParticles.jl b/src/TrixiParticles.jl
index aaf299b5fb..bab7e7a813 100644
--- a/src/TrixiParticles.jl
+++ b/src/TrixiParticles.jl
@@ -76,6 +76,7 @@ export StateEquationCole, StateEquationIdealGas
 export ArtificialViscosityMonaghan, ViscosityAdami, ViscosityMorris
 export DensityDiffusion, DensityDiffusionMolteniColagrossi, DensityDiffusionFerrari,
        DensityDiffusionAntuono
+export tensile_instability_control
 export BoundaryModelMonaghanKajtar, BoundaryModelDummyParticles, AdamiPressureExtrapolation,
        PressureMirroring, PressureZeroing, BoundaryModelLastiwka, BoundaryModelTafuni,
        BernoulliPressureExtrapolation
diff --git a/src/callbacks/particle_shifting.jl b/src/callbacks/particle_shifting.jl
index b5e357aa2e..145c9c1a1f 100644
--- a/src/callbacks/particle_shifting.jl
+++ b/src/callbacks/particle_shifting.jl
@@ -6,8 +6,13 @@ Following the original paper, the callback is applied in every time step and not
 in every stage of a multi-stage time integration method to reduce the computational
 cost and improve the stability of the scheme.
 
-## References
-[Sun2017](@cite)
+See [Callbacks](@ref Callbacks) for more information on how to use this callback.
+See [Particle Shifting Technique](@ref shifting) for more information on the method itself.
+
+!!! warning
+    The Particle Shifting Technique needs to be disabled close to the free surface
+    and therefore requires a free surface detection method. This is not yet implemented.
+    **This callback cannot be used in a free surface simulation.**
 """
 function ParticleShiftingCallback()
     # The first one is the `condition`, the second the `affect!`
diff --git a/src/schemes/fluid/pressure_acceleration.jl b/src/schemes/fluid/pressure_acceleration.jl
index 62105cf6c7..a20b25ad83 100644
--- a/src/schemes/fluid/pressure_acceleration.jl
+++ b/src/schemes/fluid/pressure_acceleration.jl
@@ -36,6 +36,27 @@ end
     return -m_b / (rho_a * rho_b) * (p_a * W_a - p_b * W_b)
 end
 
+@doc raw"""
+    tensile_instability_control
+
+Pressure acceleration formulation to prevent tensile instability
+by [Sun et al. (2018)](@cite Sun2018).
+
+This formulation can be passed as keyword argument `pressure_acceleration` to
+the [`WeaklyCompressibleSPHSystem`](@ref) constructor.
+See [Tensile Instability Control](@ref tic) for more information on this technique.
+
+!!! warning
+    Tensile Instability Control needs to be disabled close to the free surface
+    and therefore requires a free surface detection method. This is not yet implemented.
+    **This technique cannot be used in a free surface simulation.**
+"""
+@inline function tensile_instability_control(m_a, m_b, rho_a, rho_b, p_a, p_b, W_a)
+    # Same as `pressure_acceleration_continuity_density`, but using the minus formulation
+    # when pressures are negative to avoid tensile instability.
+    return -m_b * (abs(p_a) + p_b) / (rho_a * rho_b) * W_a
+end
+
 # 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 da31baf92f..60d1967442 100644
--- a/src/schemes/fluid/weakly_compressible_sph/density_diffusion.jl
+++ b/src/schemes/fluid/weakly_compressible_sph/density_diffusion.jl
@@ -97,11 +97,11 @@ where ``\rho_a`` and ``\rho_b`` denote the densities of particles ``a`` and ``b`
 and ``r_{ab} = r_a - r_b`` is the difference of the coordinates of particles ``a`` and ``b``.
 The symbol ``\nabla\rho^L_a`` denotes the renormalized density gradient defined as
 ```math
-\nabla\rho^L_a = -\sum_b (\rho_a - \rho_b) V_b L_a \nabla_{r_a} W(\Vert r_{ab} \Vert, h)
+\nabla\rho^L_a = -\sum_b (\rho_a - \rho_b) V_b L_a \nabla W_{ab}
 ```
 with
 ```math
-L_a := \left( -\sum_{b} V_b r_{ab} \otimes \nabla_{r_a} W(\Vert r_{ab} \Vert, h) \right)^{-1} \in \R^{d \times d},
+L_a := \left( -\sum_{b} V_b r_{ab} \otimes \nabla W_{ab} \right)^{-1} \in \R^{d \times d},
 ```
 where ``d`` is the number of dimensions.
 
diff --git a/src/schemes/fluid/weakly_compressible_sph/system.jl b/src/schemes/fluid/weakly_compressible_sph/system.jl
index 69cc4598a9..74306d8445 100644
--- a/src/schemes/fluid/weakly_compressible_sph/system.jl
+++ b/src/schemes/fluid/weakly_compressible_sph/system.jl
@@ -2,9 +2,10 @@
     WeaklyCompressibleSPHSystem(initial_condition,
                                 density_calculator, state_equation,
                                 smoothing_kernel, smoothing_length;
+                                acceleration=ntuple(_ -> 0.0, NDIMS),
                                 viscosity=nothing, density_diffusion=nothing,
+                                pressure_acceleration=nothing,
                                 transport_velocity=nothing,
-                                acceleration=ntuple(_ -> 0.0, NDIMS),
                                 buffer_size=nothing,
                                 correction=nothing, source_terms=nothing,
                                 surface_tension=nothing, surface_normal_method=nothing,
@@ -26,11 +27,17 @@ See [Weakly Compressible SPH](@ref wcsph) for more details on the method.
                         See [Smoothing Kernels](@ref smoothing_kernel).
 
 # Keyword Arguments
+- `acceleration`:               Acceleration vector for the system. (default: zero vector)
 - `viscosity`:                  Viscosity model for this system (default: no viscosity).
                                 See [`ArtificialViscosityMonaghan`](@ref) or [`ViscosityAdami`](@ref).
-- `transport_velocity`:         [Transport Velocity Formulation (TVF)](@ref transport_velocity_formulation). Default is no TVF.
 - `density_diffusion`:          Density diffusion terms for this system. See [`DensityDiffusion`](@ref).
-- `acceleration`:               Acceleration vector for the system. (default: zero vector)
+- `pressure_acceleration`:      Pressure acceleration formulation for this system.
+                                By default, the correct formulation is chosen based on the
+                                density calculator and the correction method.
+                                To use [Tensile Instability Control](@ref tic), pass
+                                [`tensile_instability_control`](@ref) here.
+- `transport_velocity`:         [Transport Velocity Formulation (TVF)](@ref transport_velocity_formulation).
+                                Default is no TVF.
 - `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))
@@ -50,7 +57,6 @@ See [Weakly Compressible SPH](@ref wcsph) for more details on the method.
 - `reference_particle_spacing`: The reference particle spacing used for weighting values at the boundary,
                                 which currently is only needed when using surface tension.
 - `color_value`:                The value used to initialize the color of particles in the system.
-
 """
 struct WeaklyCompressibleSPHSystem{NDIMS, ELTYPE <: Real, IC, MA, P, DC, SE, K, V, DD, COR,
                                    PF, TV, ST, B, SRFT, SRFN, PR, C} <: FluidSystem{NDIMS}
@@ -79,12 +85,12 @@ end
 function WeaklyCompressibleSPHSystem(initial_condition,
                                      density_calculator, state_equation,
                                      smoothing_kernel, smoothing_length;
+                                     acceleration=ntuple(_ -> zero(eltype(initial_condition)),
+                                                         ndims(smoothing_kernel)),
+                                     viscosity=nothing, density_diffusion=nothing,
                                      pressure_acceleration=nothing,
                                      transport_velocity=nothing,
                                      buffer_size=nothing,
-                                     viscosity=nothing, density_diffusion=nothing,
-                                     acceleration=ntuple(_ -> zero(eltype(initial_condition)),
-                                                         ndims(smoothing_kernel)),
                                      correction=nothing, source_terms=nothing,
                                      surface_tension=nothing, surface_normal_method=nothing,
                                      reference_particle_spacing=0, color_value=1)
diff --git a/test/examples/examples_fluid.jl b/test/examples/examples_fluid.jl
index 32b2ccbde1..a9633217a6 100644
--- a/test/examples/examples_fluid.jl
+++ b/test/examples/examples_fluid.jl
@@ -268,6 +268,27 @@
         @test count_rhs_allocations(sol, semi) == 0
     end
 
+    @trixi_testset "fluid/periodic_channel_2d.jl with PST" begin
+        @trixi_test_nowarn trixi_include(@__MODULE__,
+                                         joinpath(examples_dir(), "fluid",
+                                                  "periodic_channel_2d.jl"),
+                                         tspan=(0.0, 0.2),
+                                         extra_callback=ParticleShiftingCallback())
+        @test sol.retcode == ReturnCode.Success
+        @test count_rhs_allocations(sol, semi) == 0
+    end
+
+    @trixi_testset "fluid/periodic_channel_2d.jl with PST and TIC" begin
+        @trixi_test_nowarn trixi_include(@__MODULE__,
+                                         joinpath(examples_dir(), "fluid",
+                                                  "periodic_channel_2d.jl"),
+                                         tspan=(0.0, 0.2),
+                                         extra_callback=ParticleShiftingCallback(),
+                                         pressure_acceleration=tensile_instability_control)
+        @test sol.retcode == ReturnCode.Success
+        @test count_rhs_allocations(sol, semi) == 0
+    end
+
     @trixi_testset "fluid/pipe_flow_2d.jl - BoundaryModelLastiwka (WCSPH)" begin
         @trixi_test_nowarn trixi_include(@__MODULE__, tspan=(0.0, 0.5),
                                          joinpath(examples_dir(), "fluid",


Reviews:
user: login: svchb
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-04-15T11:20:20Z
user: login: efaulhaber
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-15T11:22:18Z
user: login: svchb
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-04-15T13:55:00Z
user: login: efaulhaber
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-12T16:10:38Z
user: login: LasNikas
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-15T16:32:32Z
user: login: efaulhaber
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-15T18:09:12Z
user: login: LasNikas
state: DISMISSED
author_association: COLLABORATOR
submitted_at: 2025-05-17T08:29:59Z
user: login: svchb
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-05-18T00:13:16Z
user: login: efaulhaber
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-18T08:48:35Z
user: login: svchb
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-18T08:53:04Z
user: login: svchb
state: DISMISSED
author_association: COLLABORATOR
submitted_at: 2025-05-18T08:54:12Z
user: login: efaulhaber
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-18T08:55:02Z
user: login: efaulhaber
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-18T08:55:14Z
user: login: svchb
body: /run-gpu-tests 
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-18T13:23:16Z

Review comments:
diff_hunk: @@ -58,6 +58,14 @@ end
     return -volume_term * pressure_tilde * W_a
 end
 
+# Same as `pressure_acceleration_continuity_density`, but using the minus formulation
+# when pressures are negative to avoid tensile instability.
+# See Lyu et al. (2021). https://doi.org/10.1016/j.apor.2021.102938
+@inline function tensile_instability_correction(m_a, m_b, rho_a, rho_b, p_a, p_b, W_a)
+    p = ifelse(p_a >= 0, p_a + p_b, p_b - p_a)
+    return -m_b * p / (rho_a * rho_b) * W_a
path: src/schemes/fluid/pressure_acceleration.jl
user: login: svchb
body: ```suggestion
    p_sum_effective = abs(p_a) + abs(p_b)
    return -m_b * p_sum_effective / (rho_a * rho_b) * W_a
```

Your formulation is not correct I think since p_b can be negative as well.
created_at: 2025-04-15T11:20:12Z
author_association: COLLABORATOR
diff_hunk: @@ -58,6 +58,14 @@ end
     return -volume_term * pressure_tilde * W_a
 end
 
+# Same as `pressure_acceleration_continuity_density`, but using the minus formulation
+# when pressures are negative to avoid tensile instability.
+# See Lyu et al. (2021). https://doi.org/10.1016/j.apor.2021.102938
+@inline function tensile_instability_correction(m_a, m_b, rho_a, rho_b, p_a, p_b, W_a)
+    p = ifelse(p_a >= 0, p_a + p_b, p_b - p_a)
+    return -m_b * p / (rho_a * rho_b) * W_a
path: src/schemes/fluid/pressure_acceleration.jl
user: login: efaulhaber
body: The reference paper:
![grafik](https://github.com/user-attachments/assets/d377fb95-e900-4aa5-8870-716b03fc2b4c)

![grafik](https://github.com/user-attachments/assets/96f3c6e1-364d-45f1-acda-5af847963ad9)

created_at: 2025-04-15T11:22:17Z
author_association: MEMBER
diff_hunk: @@ -58,6 +58,14 @@ end
     return -volume_term * pressure_tilde * W_a
 end
 
+# Same as `pressure_acceleration_continuity_density`, but using the minus formulation
+# when pressures are negative to avoid tensile instability.
+# See Lyu et al. (2021). https://doi.org/10.1016/j.apor.2021.102938
path: src/schemes/fluid/pressure_acceleration.jl
user: login: svchb
body: ```suggestion
# See Lyu et al. (2021). https://doi.org/10.1016/j.apor.2021.102938
# See Sun et al. (2018). https://doi.org/10.1016/j.cpc.2017.11.016
# Note: This violates the momentum conservation of a fluid system due to the loss of the symmetry of particle # interactions.
# Note: This should only be applied for the fluid-fluid interaction for non-free surface flows!
```
created_at: 2025-04-15T13:51:29Z
author_association: COLLABORATOR
diff_hunk: @@ -58,6 +58,14 @@ end
     return -volume_term * pressure_tilde * W_a
 end
 
+# Same as `pressure_acceleration_continuity_density`, but using the minus formulation
+# when pressures are negative to avoid tensile instability.
+# See Lyu et al. (2021). https://doi.org/10.1016/j.apor.2021.102938
+@inline function tensile_instability_correction(m_a, m_b, rho_a, rho_b, p_a, p_b, W_a)
+    p = p_b + abs(p_a)
+    return -m_b * p / (rho_a * rho_b) * W_a
+end
+
path: src/schemes/fluid/pressure_acceleration.jl
user: login: svchb
body: ```suggestion
# Symmetric version of the concept introduced by Sun et al. (2018). https://doi.org/10.1016/j.cpc.2017.11.016 
# Note: This should only be applied for the fluid-fluid interaction for non-free surface flows!
@inline function tensile_instability_correction_sym(m_a, m_b, rho_a, rho_b, p_a, p_b, W_a)
    # Use absolute values of pressure so that negative pressures (tension)
    # also contribute repulsively, preventing unphysical clustering.
    p_abs = abs(p_a) + abs(p_b)

    return -m_b * p_abs / (rho_a * rho_b) * W_a
end
```
created_at: 2025-04-15T13:53:24Z
author_association: COLLABORATOR
diff_hunk: @@ -58,6 +58,14 @@ end
     return -volume_term * pressure_tilde * W_a
 end
 
+# Same as `pressure_acceleration_continuity_density`, but using the minus formulation
+# when pressures are negative to avoid tensile instability.
+# See Lyu et al. (2021). https://doi.org/10.1016/j.apor.2021.102938
+@inline function tensile_instability_correction(m_a, m_b, rho_a, rho_b, p_a, p_b, W_a)
+    p = p_b + abs(p_a)
+    return -m_b * p / (rho_a * rho_b) * W_a
+end
+
path: src/schemes/fluid/pressure_acceleration.jl
user: login: efaulhaber
body: Unstable (see comment below).
created_at: 2025-05-12T16:10:38Z
author_association: MEMBER
diff_hunk: @@ -109,3 +109,88 @@ term is very expensive and adds about 40--50% of computational cost.
 Modules = [TrixiParticles]
 Pages = [joinpath("schemes", "fluid", "weakly_compressible_sph", "density_diffusion.jl")]
 ```
+
+## [Particle Shifting Technique](@id shifting)
+
+The Particle Shifting Technique (PST) is a technique to correct tensile instability
+in regions of low pressure, as observed in viscous flow around an object.
+Without PST, tensile instability causes non-physical separation of the fluid
+from the object, introducing void regions behind the object.
+
+At lower resolutions, PST alone can be effective to correct a viscous flow
+around a cylinder, as shown in this figure.
+![particle_shifting](https://github.com/user-attachments/assets/70892b0b-af36-4531-b328-f73e63a5e33c)
+At higher resolutions, PST alone is not effective anymore; see the figure in
+[Tensile Instability Control](@ref tic).
+We generally recommend using PST and Tensile Instability Control together
+in such a simulation.
+
+### Mathematical formulation
+
+We use the following formulation by [Sun et al. (2018)](@cite Sun2018).
+After each time step, a correction term ``\delta r_a`` is added to the position ``r_a``
+of particle ``a``, which is given by
+```math
+\delta r_a = -4 \Delta t \, v_\text{max} h
+    \sum_b \left( 1 + R \left( \frac{W_{ab}}{W(\Delta x_a)} \right)^n \right) \nabla W_{ab}
+    \frac{m_b}{\rho_a + \rho_b},
+```
+where:
+- ``\Delta t`` is the time step,
+- ``v_\text{max}`` is the maximum velocity over all particles,
+- ``h`` is the smoothing length,
+- ``R`` and ``n`` are constants, which are set to ``0.2`` and ``4`` respectively,
+- ``W(\Delta x_a)`` is the smoothing kernel of the particle size of particle ``a``,
+  which can be interpreted as the target particle spacing that we want to achieve.
+- ``\nabla W_{ab}`` is the gradient of the smoothing kernel,
+- ``m_b`` is the mass of particle ``b``,
+- ``\rho_a, \rho_b`` is the density of particles ``a`` and ``b``, respectively.
+
+Note that we replaced ``\text{CFL} \cdot \text{Ma}`` by ``\Delta t \cdot v_\text{max} / h``,
+as explained on page 29, right above Equation 9.
path: docs/src/systems/weakly_compressible_sph.md
user: login: LasNikas
body: ```suggestion
as explained in [Sun2018](@cite Sun2018) on page 29, right above Equation 9.
```
created_at: 2025-05-15T16:20:28Z
author_association: COLLABORATOR
diff_hunk: @@ -109,3 +109,88 @@ term is very expensive and adds about 40--50% of computational cost.
 Modules = [TrixiParticles]
 Pages = [joinpath("schemes", "fluid", "weakly_compressible_sph", "density_diffusion.jl")]
 ```
+
+## [Particle Shifting Technique](@id shifting)
+
+The Particle Shifting Technique (PST) is a technique to correct tensile instability
+in regions of low pressure, as observed in viscous flow around an object.
+Without PST, tensile instability causes non-physical separation of the fluid
+from the object, introducing void regions behind the object.
+
+At lower resolutions, PST alone can be effective to correct a viscous flow
+around a cylinder, as shown in this figure.
+![particle_shifting](https://github.com/user-attachments/assets/70892b0b-af36-4531-b328-f73e63a5e33c)
+At higher resolutions, PST alone is not effective anymore; see the figure in
+[Tensile Instability Control](@ref tic).
path: docs/src/systems/weakly_compressible_sph.md
user: login: LasNikas
body: I’m not sure I fully understand the reason for this behavior. Is it related to a smaller compact support at higher resolution?
We also observe detachment from the boundary at coarser resolutions, which creates a free surface that PST cannot handle—correct?
Why does the problem actually get worse as the resolution increases?
Is the same void region also present at coarser resolution, but "compensated" for by the larger compact support?
created_at: 2025-05-15T16:23:44Z
author_association: COLLABORATOR
diff_hunk: @@ -30,6 +30,11 @@ See [Weakly Compressible SPH](@ref wcsph) for more details on the method.
                                 See [`ArtificialViscosityMonaghan`](@ref) or [`ViscosityAdami`](@ref).
 - `transport_velocity`:         [Transport Velocity Formulation (TVF)](@ref transport_velocity_formulation). Default is no TVF.
 - `density_diffusion`:          Density diffusion terms for this system. See [`DensityDiffusion`](@ref).
+- `pressure_acceleration`:      Pressure acceleration formulation for this system.
path: src/schemes/fluid/weakly_compressible_sph/system.jl
user: login: LasNikas
body: Please add also to the constructor above
created_at: 2025-05-15T16:31:29Z
author_association: COLLABORATOR
diff_hunk: @@ -109,3 +109,88 @@ term is very expensive and adds about 40--50% of computational cost.
 Modules = [TrixiParticles]
 Pages = [joinpath("schemes", "fluid", "weakly_compressible_sph", "density_diffusion.jl")]
 ```
+
+## [Particle Shifting Technique](@id shifting)
+
+The Particle Shifting Technique (PST) is a technique to correct tensile instability
+in regions of low pressure, as observed in viscous flow around an object.
+Without PST, tensile instability causes non-physical separation of the fluid
+from the object, introducing void regions behind the object.
+
+At lower resolutions, PST alone can be effective to correct a viscous flow
+around a cylinder, as shown in this figure.
+![particle_shifting](https://github.com/user-attachments/assets/70892b0b-af36-4531-b328-f73e63a5e33c)
+At higher resolutions, PST alone is not effective anymore; see the figure in
+[Tensile Instability Control](@ref tic).
path: docs/src/systems/weakly_compressible_sph.md
user: login: efaulhaber
body: The initial detachment is about the same size for both the lower and the higher resolution simulation. But for the lower resolution, the void region is much smaller compared to the particle size, so the shifting can close the hole again, whereas in the higher resolution it is too large to close. 

Another way to think about it is that in the high resolution simulation we have more particles where the velocity is essentially orthogonal to the boundary. 
created_at: 2025-05-15T18:09:12Z
author_association: MEMBER
diff_hunk: @@ -109,3 +109,88 @@ term is very expensive and adds about 40--50% of computational cost.
 Modules = [TrixiParticles]
 Pages = [joinpath("schemes", "fluid", "weakly_compressible_sph", "density_diffusion.jl")]
 ```
+
+## [Particle Shifting Technique](@id shifting)
+
+The Particle Shifting Technique (PST) is a technique to correct tensile instability
path: docs/src/systems/weakly_compressible_sph.md
user: login: svchb
body: ```suggestion
The Particle Shifting Technique (PST) is a method to correct tensile instability
```
created_at: 2025-05-18T00:07:36Z
author_association: COLLABORATOR
diff_hunk: @@ -109,3 +109,88 @@ term is very expensive and adds about 40--50% of computational cost.
 Modules = [TrixiParticles]
 Pages = [joinpath("schemes", "fluid", "weakly_compressible_sph", "density_diffusion.jl")]
 ```
+
+## [Particle Shifting Technique](@id shifting)
+
+The Particle Shifting Technique (PST) is a technique to correct tensile instability
+in regions of low pressure, as observed in viscous flow around an object.
+Without PST, tensile instability causes non-physical separation of the fluid
+from the object, introducing void regions behind the object.
+
+At lower resolutions, PST alone can be effective to correct a viscous flow
+around a cylinder, as shown in this figure.
+![particle_shifting](https://github.com/user-attachments/assets/70892b0b-af36-4531-b328-f73e63a5e33c)
+At higher resolutions, PST alone is not effective anymore; see the figure in
+[Tensile Instability Control](@ref tic).
+We generally recommend using PST and Tensile Instability Control together
+in such a simulation.
path: docs/src/systems/weakly_compressible_sph.md
user: login: svchb
body: ```suggestion
We recommend using PST and Tensile Instability Control together
in such simulations.
```
created_at: 2025-05-18T00:09:34Z
author_association: COLLABORATOR
diff_hunk: @@ -109,3 +109,88 @@ term is very expensive and adds about 40--50% of computational cost.
 Modules = [TrixiParticles]
 Pages = [joinpath("schemes", "fluid", "weakly_compressible_sph", "density_diffusion.jl")]
 ```
+
+## [Particle Shifting Technique](@id shifting)
+
+The Particle Shifting Technique (PST) is a technique to correct tensile instability
path: docs/src/systems/weakly_compressible_sph.md
user: login: efaulhaber
body: I tried to avoid the word method, as this is what we usually use for the solver itself, so it would be the WCSPH method with the PST and TIC techniques.
created_at: 2025-05-18T08:48:35Z
author_association: MEMBER
diff_hunk: @@ -109,3 +109,88 @@ term is very expensive and adds about 40--50% of computational cost.
 Modules = [TrixiParticles]
 Pages = [joinpath("schemes", "fluid", "weakly_compressible_sph", "density_diffusion.jl")]
 ```
+
+## [Particle Shifting Technique](@id shifting)
+
+The Particle Shifting Technique (PST) is a technique to correct tensile instability
path: docs/src/systems/weakly_compressible_sph.md
user: login: svchb
body: Just sounds weird to have technique twice so close repeated.
created_at: 2025-05-18T08:53:04Z
author_association: COLLABORATOR
diff_hunk: @@ -109,3 +109,88 @@ term is very expensive and adds about 40--50% of computational cost.
 Modules = [TrixiParticles]
 Pages = [joinpath("schemes", "fluid", "weakly_compressible_sph", "density_diffusion.jl")]
 ```
+
+## [Particle Shifting Technique](@id shifting)
+
+The Particle Shifting Technique (PST) is a technique to correct tensile instability
path: docs/src/systems/weakly_compressible_sph.md
user: login: efaulhaber
body: I asked ChatGPT.
created_at: 2025-05-18T08:55:01Z
author_association: MEMBER
diff_hunk: @@ -109,3 +109,88 @@ term is very expensive and adds about 40--50% of computational cost.
 Modules = [TrixiParticles]
 Pages = [joinpath("schemes", "fluid", "weakly_compressible_sph", "density_diffusion.jl")]
 ```
+
+## [Particle Shifting Technique](@id shifting)
+
+The Particle Shifting Technique (PST) is a technique to correct tensile instability
path: docs/src/systems/weakly_compressible_sph.md
user: login: efaulhaber
body: ```suggestion
The Particle Shifting Technique (PST) is used to correct tensile instability
```
created_at: 2025-05-18T08:55:14Z
author_association: MEMBER

PR comments:
user: login: codecov[bot]
created_at: 2025-04-14T12:13:25Z
body: ## [Codecov](https://app.codecov.io/gh/trixi-framework/TrixiParticles.jl/pull/769?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=trixi-framework) Report
Attention: Patch coverage is `33.33333%` with `2 lines` in your changes missing coverage. Please review.
> Project coverage is 70.68%. Comparing base [(`13694fc`)](https://app.codecov.io/gh/trixi-framework/TrixiParticles.jl/commit/13694fc0a661a3a8fd0f4f7341fe683d532a397a?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=trixi-framework) to head [(`087b500`)](https://app.codecov.io/gh/trixi-framework/TrixiParticles.jl/commit/087b5004ee8378cd1acd1e72550ae894d9c208e7?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=trixi-framework).

| [Files with missing lines](https://app.codecov.io/gh/trixi-framework/TrixiParticles.jl/pull/769?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=trixi-framework) | Patch % | Lines |
|---|---|---|
| [src/schemes/fluid/pressure\_acceleration.jl](https://app.codecov.io/gh/trixi-framework/TrixiParticles.jl/pull/769?src=pr&el=tree&filepath=src%2Fschemes%2Ffluid%2Fpressure_acceleration.jl&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=trixi-framework#diff-c3JjL3NjaGVtZXMvZmx1aWQvcHJlc3N1cmVfYWNjZWxlcmF0aW9uLmps) | 0.00% | [2 Missing :warning: ](https://app.codecov.io/gh/trixi-framework/TrixiParticles.jl/pull/769?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=trixi-framework) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #769      +/-   ##
==========================================
- Coverage   70.70%   70.68%   -0.03%     
==========================================
  Files         106      106              
  Lines        6701     6703       +2     
==========================================
  Hits         4738     4738              
- Misses       1963     1965       +2     
```

| [Flag](https://app.codecov.io/gh/trixi-framework/TrixiParticles.jl/pull/769/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=trixi-framework) | Coverage Δ | |
|---|---|---|
| [unit](https://app.codecov.io/gh/trixi-framework/TrixiParticles.jl/pull/769/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=trixi-framework) | `70.68% <33.33%> (-0.03%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=trixi-framework#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/trixi-framework/TrixiParticles.jl/pull/769?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=trixi-framework).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=trixi-framework).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: svchb
created_at: 2025-04-15T09:22:22Z
body: @efaulhaber Is there any point in adding that then? The function is not used anywhere.
author_association: COLLABORATOR
user: login: efaulhaber
created_at: 2025-04-15T11:08:08Z
body: The point is that we can experiment with it. For example, Niklas can try using PST + TIC for the high-res vortex street he's having problems with.
author_association: MEMBER
user: login: svchb
created_at: 2025-04-15T11:12:35Z
body: But do we need to merge it than without documentation? He can try this like it is.
author_association: COLLABORATOR
user: login: efaulhaber
created_at: 2025-04-15T11:14:24Z
body: It's easier to try it if you don't need an extra branch for it. I don't really see a downside of merging this, as it's a very small and non-invasive change.
author_association: MEMBER
user: login: efaulhaber
created_at: 2025-05-12T15:58:46Z
body: > The point is that we can experiment with it. For example, Niklas can try using PST + TIC for the high-res vortex street he's having problems with.

I experimented with this and it is indeed solving the high resolution issues. Top is PST and bottom PST + TIC, left and right are the same setup, just a different resolution.

https://github.com/user-attachments/assets/ffcde256-e7b6-4f24-8031-416bfeb01a3c

I will try @svchb's symmetric version, and then I'll make this public API and add docs and tests.
author_association: MEMBER
user: login: efaulhaber
created_at: 2025-05-12T16:10:25Z
body: Update: The symmetric version (top panel) is not stable.

<img width="498" alt="grafik" src="https://github.com/user-attachments/assets/707a39ac-ca26-4d01-b864-e089034ae51e" />

This is shortly before the symmetric version is crashing:
<img width="589" alt="grafik" src="https://github.com/user-attachments/assets/71b70991-7d38-4c8f-9f36-d9ae63a4eac2" />

Here is the code again, so it doesn't get lost in the conversations:
```julia
@inline function tensile_instability_correction_sym(m_a, m_b, rho_a, rho_b, p_a, p_b, W_a)
    # Use absolute values of pressure so that negative pressures (tension)
    # also contribute repulsively, preventing unphysical clustering.
    p_abs = abs(p_a) + abs(p_b)
    return -m_b * p_abs / (rho_a * rho_b) * W_a
end
```
author_association: MEMBER
user: login: svchb
created_at: 2025-05-12T16:13:18Z
body: Yes that was the likely result probably if its conservative its not allowing some loss in energy necessary for it to work.
author_association: COLLABORATOR
user: login: efaulhaber
created_at: 2025-05-12T16:30:21Z
body: > `# Note: This should only be applied for the fluid-fluid interaction for non-free surface flows!`

@svchb where did you get the information from that this is not supposed to be used for fluid-boundary interaction? I don't find any mention of this in any of the papers.
author_association: MEMBER
user: login: efaulhaber
created_at: 2025-05-12T16:46:29Z
body: I just tried TIC only for fluid-fluid interaction and the regular (p_a + p_b) formulation for fluid-boundary interaction, and I got this. Top is no TIC for fluid-boundary, bottom is TIC for everything.
I would say that is clearly showing that we need TIC for both.

https://github.com/user-attachments/assets/20e494d6-9118-4f5d-9276-c0d21e4b2e8b

author_association: MEMBER
user: login: svchb
created_at: 2025-05-12T18:38:41Z
body: So TIC should only be applied for dummy boundaries and MK bnd need to be excluded.
author_association: COLLABORATOR
user: login: efaulhaber
created_at: 2025-05-12T19:35:42Z
body: MK boundaries use a special "pressure acceleration" anyway, so we don't need to change anything. 
author_association: MEMBER
user: login: efaulhaber
created_at: 2025-05-15T15:33:25Z
body: /run-gpu-tests
author_association: MEMBER
user: login: svchb
created_at: 2025-05-18T08:53:53Z
body: /run-gpu-tests 
author_association: COLLABORATOR
user: login: LasNikas
created_at: 2025-05-18T11:16:05Z
body: \run-gpu-tests
author_association: COLLABORATOR
user: login: svchb
created_at: 2025-05-18T18:18:54Z
body: /run-gpu-tests
author_association: COLLABORATOR