Skip to content

Commit 4099559

Browse files
authored
Fix open boundary shifting (#1195)
* Fix open boundary shifting * Add docs and test * Update NEWS.md * Reformat * Implement suggestions * Implement suggestions * Fix * Add check for boundary zone width
1 parent 1da2843 commit 4099559

5 files changed

Lines changed: 146 additions & 7 deletions

File tree

NEWS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ used in the Julia ecosystem. Notable changes will be documented in this file for
1919
- Fixed interpolation with `cut_off_bnd=false` and `interpolate_wall_velocity=true` (#1185).
2020
- Fixed interpolation from a `PostprocessCallback` on GPUs (#1192).
2121
- Fixed interpolation with different smoothing length on the GPU (#952).
22+
- Disabled shifting in the free-surface region of `OpenBoundarySystem` with
23+
`BoundaryModelDynamicalPressureZhang` and ramp it only between one and two compact
24+
supports away from the free surface (#1195).
25+
2226
## Version 0.5
2327

2428
### API Changes

docs/src/systems/boundary.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,14 @@ To further handle incomplete kernel support, for example in the viscous term of
344344
the updated velocity of particles within the [`BoundaryZone`](@ref) is projected onto the face normal,
345345
so that only the component in flow direction is kept.
346346

347+
When a shifting technique or transport velocity formulation is used with this boundary model,
348+
the shifting velocity is modified near the free surface of the [`BoundaryZone`](@ref).
349+
The free-surface region is defined as the layer within one compact support of the free surface;
350+
inside this region, shifting is set to zero because the kernel support is incomplete.
351+
From one to two compact supports away from the free surface, the shifting velocity is ramped
352+
from zero to the unmodified value using a kernel-weighted transition.
353+
Particles farther away from the free surface use the unmodified shifting velocity.
354+
347355
# Pressure Models
348356
```@autodocs
349357
Modules = [TrixiParticles]

src/schemes/boundary/open_boundary/system.jl

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,16 @@ function OpenBoundarySystem(boundary_zones::Union{BoundaryZone, Nothing}...;
8181
density_diffusion(fluid_system) : nothing)
8282
boundary_zones_ = filter(bz -> !isnothing(bz), boundary_zones)
8383

84+
if boundary_model isa BoundaryModelDynamicalPressureZhang &&
85+
!isnothing(shifting_technique)
86+
# When dynamical pressure is used with shifting, the shifting is ramped up until
87+
# it reaches the full value at two compact supports away from the free surface
88+
# of the boundary zone.
89+
# In order to prevent discontinuities in the shifting velocity, the boundary zone
90+
# must be wide enough to accommodate this ramping region.
91+
check_boundary_zone_widths(boundary_zones_, fluid_system)
92+
end
93+
8494
initial_conditions = union((bz.initial_condition for bz in boundary_zones_)...)
8595

8696
buffer = SystemBuffer(nparticles(initial_conditions), buffer_size)
@@ -605,15 +615,30 @@ end
605615
-boundary_zone.face_normal)
606616
dist_free_surface = boundary_zone.zone_width - dist_to_transition
607617

608-
if dist_free_surface < compact_support(fluid_system, fluid_system)
609-
# Ramp shifting velocity near the free surface using a kernel-weighted transition.
618+
# Width of the free-surface region where shifting is disabled.
619+
free_surface_region_width = compact_support(fluid_system, fluid_system)
620+
# Width of the ramp region behind the free-surface region where shifting is ramped
621+
# from zero to the unmodified value.
622+
ramp_width = free_surface_region_width
623+
624+
if dist_free_surface <= free_surface_region_width
625+
# The free-surface region is the layer within one compact support of the
626+
# free surface. Shifting is disabled there to avoid shifting particles with
627+
# incomplete support.
628+
for dim in 1:ndims(system)
629+
cache.delta_v[dim, particle] = zero(eltype(system))
630+
end
631+
elseif dist_free_surface < free_surface_region_width + ramp_width
632+
# Between one and two compact supports from the free surface, shifting is
633+
# ramped from zero to the unmodified shifting velocity with a kernel-weighted
634+
# transition.
610635
# According to our experiments, the proposed alternative approaches lead to particle disorder:
611636
# - Sun et al. 2017: only use surface-tangential component
612637
# - Zhang et al. 2025: disable shifting entirely
613638
kernel_max = smoothing_kernel(system, 0, particle)
614-
dist_from_cutoff = compact_support(fluid_system, fluid_system) -
615-
dist_free_surface
616-
shifting_weight = smoothing_kernel(system, dist_from_cutoff, particle) /
639+
# Distance from the area where full shifting is applied.
640+
dist_from_ramp_end = free_surface_region_width + ramp_width - dist_free_surface
641+
shifting_weight = smoothing_kernel(system, dist_from_ramp_end, particle) /
617642
kernel_max
618643
delta_v_ramped = delta_v(system, particle) * shifting_weight
619644
for dim in 1:ndims(system)
@@ -689,8 +714,7 @@ end
689714

690715
@inline use_open_boundary_interpolation_neighbor(system) = false
691716

692-
function check_configuration(system::OpenBoundarySystem, systems,
693-
neighborhood_search::PointNeighbors.AbstractNeighborhoodSearch)
717+
function check_configuration(system::OpenBoundarySystem, systems, neighborhood_search)
694718
(; boundary_model, boundary_zones) = system
695719

696720
# Store index of the fluid system. This is necessary for re-linking
@@ -710,3 +734,19 @@ function check_configuration(system::OpenBoundarySystem, systems,
710734
"See the PointNeighbors.jl documentation for more details."))
711735
end
712736
end
737+
738+
function check_boundary_zone_widths(boundary_zones, fluid_system)
739+
support = compact_support(system_smoothing_kernel(fluid_system),
740+
initial_smoothing_length(fluid_system))
741+
minimum_width = 2 * support
742+
743+
for (i, zone) in enumerate(boundary_zones)
744+
if zone.zone_width < minimum_width
745+
throw(ArgumentError("boundary zone $i has width $(zone.zone_width), " *
746+
"but must be at least two compact supports " *
747+
"($(minimum_width))"))
748+
end
749+
end
750+
751+
return nothing
752+
end

test/schemes/boundary/open_boundary/open_boundary.jl

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,56 @@ include("mirroring.jl")
33
include("boundary_zone.jl")
44
include("pressure_model.jl")
55

6+
@testset verbose=true "Free-Surface Shifting Ramp" begin
7+
particle_spacing = 0.1
8+
smoothing_kernel = SchoenbergCubicSplineKernel{2}()
9+
smoothing_length = 0.5
10+
compact_support = TrixiParticles.compact_support(smoothing_kernel, smoothing_length)
11+
12+
zone_width = 3 * compact_support
13+
distances_from_free_surface = compact_support .* [0.5, 1.0, 1.5, 2.0]
14+
coordinates = stack(SVector(-zone_width + distance, 0.5)
15+
for distance in distances_from_free_surface)
16+
initial_condition = InitialCondition(; coordinates, density=1.0,
17+
particle_spacing)
18+
19+
fluid = RectangularShape(particle_spacing, (2, 2), (0.0, 0.0), density=1.0)
20+
fluid_system = EntropicallyDampedSPHSystem(fluid; smoothing_kernel,
21+
smoothing_length, sound_speed=1.0,
22+
shifting_technique=ParticleShiftingTechnique())
23+
24+
boundary_zone = BoundaryZone(; boundary_face=([0.0, 0.0], [0.0, 1.0]),
25+
face_normal=[1.0, 0.0], boundary_type=InFlow(),
26+
open_boundary_layers=round(Int,
27+
zone_width / particle_spacing),
28+
initial_condition, density=1.0, particle_spacing)
29+
open_boundary = OpenBoundarySystem(boundary_zone; fluid_system, buffer_size=0,
30+
boundary_model=BoundaryModelDynamicalPressureZhang())
31+
open_boundary.boundary_zone_indices .= 1
32+
33+
unmodified_delta_v = [1.0, -2.0]
34+
for particle in axes(open_boundary.cache.delta_v, 2)
35+
open_boundary.cache.delta_v[:, particle] .= unmodified_delta_v
36+
end
37+
38+
TrixiParticles.modify_shifting_at_free_surfaces!(open_boundary,
39+
initial_condition.coordinates,
40+
DummySemidiscretization())
41+
42+
kernel_max = TrixiParticles.smoothing_kernel(open_boundary, 0, 1)
43+
44+
@test iszero(open_boundary.cache.delta_v[:, 1])
45+
@test iszero(open_boundary.cache.delta_v[:, 2])
46+
47+
dist_from_cutoff = 2 * compact_support - distances_from_free_surface[3]
48+
shifting_weight = TrixiParticles.smoothing_kernel(open_boundary, dist_from_cutoff,
49+
3) / kernel_max
50+
@test isapprox(open_boundary.cache.delta_v[:, 3],
51+
unmodified_delta_v * shifting_weight)
52+
53+
@test open_boundary.cache.delta_v[:, 4] == unmodified_delta_v
54+
end
55+
656
@testset verbose=true "Calculate Flow Rate" begin
757
particle_spacing = 0.01
858

test/systems/open_boundary_system.jl

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,41 @@
9797

9898
@test repr("text/plain", system) == show_box
9999
end
100+
101+
@testset "boundary zone width" begin
102+
particle_spacing = 0.05
103+
smoothing_kernel = WendlandC2Kernel{2}()
104+
smoothing_length = 0.1
105+
106+
fluid = RectangularShape(particle_spacing, (2, 2), (0.0, 0.0), density=1.0)
107+
fluid_system = EntropicallyDampedSPHSystem(fluid; smoothing_kernel,
108+
smoothing_length, sound_speed=1.0)
109+
110+
boundary_zone = BoundaryZone(; boundary_face=([0.0, 0.0], [0.0, 1.0]),
111+
particle_spacing, face_normal=(1.0, 0.0),
112+
density=1.0, open_boundary_layers=7,
113+
boundary_type=InFlow())
114+
115+
compact_support = TrixiParticles.compact_support(smoothing_kernel, smoothing_length)
116+
error_str = "boundary zone 1 has width $(boundary_zone.zone_width), " *
117+
"but must be at least two compact supports ($(2 * compact_support))"
118+
119+
@test_nowarn OpenBoundarySystem(boundary_zone; buffer_size=0,
120+
boundary_model=BoundaryModelCharacteristicsLastiwka(),
121+
fluid_system)
122+
123+
@test_nowarn OpenBoundarySystem(boundary_zone; buffer_size=0,
124+
boundary_model=BoundaryModelDynamicalPressureZhang(),
125+
fluid_system)
126+
127+
fluid_system_with_shifting = EntropicallyDampedSPHSystem(fluid; smoothing_kernel,
128+
smoothing_length,
129+
sound_speed=1.0,
130+
shifting_technique=ParticleShiftingTechnique())
131+
132+
@test_throws ArgumentError(error_str) OpenBoundarySystem(boundary_zone;
133+
buffer_size=0,
134+
boundary_model=BoundaryModelDynamicalPressureZhang(),
135+
fluid_system=fluid_system_with_shifting)
136+
end
100137
end

0 commit comments

Comments
 (0)