Skip to content

Commit 418c9c2

Browse files
LasNikasLasNikas
andauthored
Automatic ordering of clamped particles (#931)
* add automatic reordering * fix typo * revise docs * indentation * add tests * add vector variant * force unique indices * add depwarning * fix regex * fix again * implement suggestions * apply formatter * fix examples * fix * implement suggestions * rm datatype restriction * implement suggestions * adapt example files * fix --------- Co-authored-by: LasNikas <[email protected]>
1 parent f314552 commit 418c9c2

File tree

7 files changed

+199
-28
lines changed

7 files changed

+199
-28
lines changed

examples/fsi/dam_break_gate_2d.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ clamped_particles = RectangularShape(structure_particle_spacing,
9696
(n_particles_x, 1), (plate_position, 0.0),
9797
density=structure_density, place_on_shell=true)
9898

99-
structure = union(plate, clamped_particles)
99+
structure = union(clamped_particles, plate)
100100

101101
# ==========================================================================================
102102
# ==== Fluid
@@ -147,7 +147,7 @@ structure_system = TotalLagrangianSPHSystem(structure,
147147
structure_smoothing_kernel,
148148
structure_smoothing_length,
149149
E, nu, boundary_model=boundary_model_structure,
150-
n_clamped_particles=n_particles_x,
150+
clamped_particles=1:nparticles(clamped_particles),
151151
acceleration=(0.0, -gravity))
152152

153153
# ==========================================================================================

examples/fsi/dam_break_plate_2d.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ clamped_particles = RectangularShape(structure_particle_spacing,
7070
(n_particles_x, 1), plate_position,
7171
density=structure_density, place_on_shell=true)
7272

73-
structure = union(plate, clamped_particles)
73+
structure = union(clamped_particles, plate)
7474

7575
# ==========================================================================================
7676
# ==== Fluid
@@ -127,7 +127,7 @@ structure_system = TotalLagrangianSPHSystem(structure,
127127
structure_smoothing_kernel,
128128
structure_smoothing_length,
129129
E, nu, boundary_model=boundary_model_structure,
130-
n_clamped_particles=n_particles_x,
130+
clamped_particles=1:nparticles(clamped_particles),
131131
acceleration=(0.0, -gravity),
132132
penalty_force=PenaltyForceGanzenmueller(alpha=0.01))
133133

examples/fsi/falling_water_column_2d.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ structure_system = TotalLagrangianSPHSystem(structure,
6868
smoothing_kernel, smoothing_length,
6969
material.E, material.nu,
7070
boundary_model=boundary_model,
71-
n_clamped_particles=nparticles(clamped_particles),
71+
clamped_particles=1:nparticles(clamped_particles),
7272
acceleration=(0.0, -gravity))
7373

7474
# ==========================================================================================

examples/structure/oscillating_beam_2d.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ n_particles_per_dimension = (round(Int, elastic_beam.length / particle_spacing)
5252
beam = RectangularShape(particle_spacing, n_particles_per_dimension,
5353
(0.0, 0.0), density=material.density, place_on_shell=true)
5454

55-
structure = union(beam, clamped_particles)
55+
structure = union(clamped_particles, beam)
5656

5757
# ==========================================================================================
5858
# ==== Structure
@@ -61,7 +61,7 @@ smoothing_kernel = WendlandC2Kernel{2}()
6161

6262
structure_system = TotalLagrangianSPHSystem(structure, smoothing_kernel, smoothing_length,
6363
material.E, material.nu,
64-
n_clamped_particles=nparticles(clamped_particles),
64+
clamped_particles=1:nparticles(clamped_particles),
6565
acceleration=(0.0, -gravity),
6666
penalty_force=nothing, viscosity=nothing,
6767
clamped_particles_motion=nothing)

src/general/initial_condition.jl

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,3 +415,35 @@ function find_too_close_particles(coords, min_distance)
415415

416416
return result
417417
end
418+
419+
function move_particles_to_end!(ic::InitialCondition, particle_ids_to_move)
420+
invalid_id = findfirst(i -> i <= 0 || i > nparticles(ic), particle_ids_to_move)
421+
isnothing(invalid_id) || throw(BoundsError(ic, invalid_id))
422+
423+
sort_key = [i in particle_ids_to_move ? 1 : 0 for i in eachparticle(ic)]
424+
# Determine a permutation that sorts 'sort_key' in ascending order
425+
permutation = sortperm(sort_key)
426+
427+
ic.coordinates .= ic.coordinates[:, permutation]
428+
ic.velocity .= ic.velocity[:, permutation]
429+
ic.mass .= ic.mass[permutation]
430+
ic.density .= ic.density[permutation]
431+
ic.pressure .= ic.pressure[permutation]
432+
433+
return ic
434+
end
435+
436+
function move_particles_to_end!(a::AbstractVector, particle_ids_to_move)
437+
invalid_id = findfirst(i -> i <= 0 || i > length(a), particle_ids_to_move)
438+
isnothing(invalid_id) || throw(BoundsError(a, invalid_id))
439+
440+
sort_key = [i in particle_ids_to_move ? 1 : 0 for i in eachindex(a)]
441+
# determine a permutation that sorts 'sort_key' in ascending order
442+
permutation = sortperm(sort_key)
443+
444+
a .= a[permutation]
445+
446+
return a
447+
end
448+
449+
move_particles_to_end!(a::Real, particle_ids_to_move) = a

src/schemes/structure/total_lagrangian_sph/system.jl

Lines changed: 62 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
@doc raw"""
22
TotalLagrangianSPHSystem(initial_condition, smoothing_kernel, smoothing_length,
33
young_modulus, poisson_ratio;
4-
n_clamped_particles=0, clamped_particles_motion=nothing,
5-
boundary_model=nothing,
4+
n_clamped_particles=0,
5+
clamped_particles=Int[],
6+
clamped_particles_motion=nothing,
67
acceleration=ntuple(_ -> 0.0, NDIMS),
7-
penalty_force=nothing, source_terms=nothing)
8+
penalty_force=nothing, viscosity=nothing,
9+
source_terms=nothing, boundary_model=nothing)
810
911
System for particles of an elastic structure.
1012
@@ -23,9 +25,13 @@ See [Total Lagrangian SPH](@ref tlsph) for more details on the method.
2325
See [Smoothing Kernels](@ref smoothing_kernel).
2426
2527
# Keywords
26-
- `n_clamped_particles`: Number of clamped particles which are fixed and not integrated
27-
to clamp the structure. Note that the clamped particles must be the **last**
28-
particles in the `InitialCondition`. See the info box below.
28+
- `n_clamped_particles` (deprecated): Number of clamped particles that are fixed and not integrated
29+
to clamp the structure. Note that the clamped particles must be the **last**
30+
particles in the `InitialCondition`. See the info box below.
31+
This keyword is deprecated and will be removed in a future release.
32+
Instead pass `clamped_particles` with the explicit particle indices to be clamped.
33+
- `clamped_particles`: Indices specifying the clamped particles that are fixed
34+
and not integrated to clamp the structure.
2935
- `clamped_particles_motion`: Prescribed motion of the clamped particles.
3036
If `nothing` (default), the clamped particles are fixed.
3137
See [`PrescribedMotion`](@ref) for details.
@@ -43,7 +49,8 @@ See [Total Lagrangian SPH](@ref tlsph) for more details on the method.
4349
See, for example, [`SourceTermDamping`](@ref).
4450
4551
!!! note
46-
The clamped particles must be the **last** particles in the `InitialCondition`.
52+
If specifying the clamped particles manually (via `n_clamped_particles`),
53+
the clamped particles must be the **last** particles in the `InitialCondition`.
4754
To do so, e.g. use the `union` function:
4855
```jldoctest; output = false, setup = :(clamped_particles = RectangularShape(0.1, (1, 4), (0.0, 0.0), density=1.0); beam = RectangularShape(0.1, (3, 4), (0.1, 0.0), density=1.0))
4956
structure = union(beam, clamped_particles)
@@ -90,12 +97,13 @@ end
9097

9198
function TotalLagrangianSPHSystem(initial_condition, smoothing_kernel, smoothing_length,
9299
young_modulus, poisson_ratio;
93-
n_clamped_particles=0, clamped_particles_motion=nothing,
94-
boundary_model=nothing,
100+
n_clamped_particles=0,
101+
clamped_particles=Int[],
102+
clamped_particles_motion=nothing,
95103
acceleration=ntuple(_ -> 0.0,
96104
ndims(smoothing_kernel)),
97105
penalty_force=nothing, viscosity=nothing,
98-
source_terms=nothing)
106+
source_terms=nothing, boundary_model=nothing)
99107
NDIMS = ndims(initial_condition)
100108
ELTYPE = eltype(initial_condition)
101109
n_particles = nparticles(initial_condition)
@@ -110,30 +118,63 @@ function TotalLagrangianSPHSystem(initial_condition, smoothing_kernel, smoothing
110118
throw(ArgumentError("`acceleration` must be of length $NDIMS for a $(NDIMS)D problem"))
111119
end
112120

113-
initial_coordinates = copy(initial_condition.coordinates)
114-
current_coordinates = copy(initial_condition.coordinates)
115-
mass = copy(initial_condition.mass)
116-
material_density = copy(initial_condition.density)
121+
# Backwards compatibility: `n_clamped_particles` is deprecated.
122+
# Emit a deprecation warning and (if the user didn't supply explicit indices)
123+
# convert the old `n_clamped_particles` convention to `clamped_particles`.
124+
if n_clamped_particles != 0
125+
Base.depwarn("keyword `n_clamped_particles` is deprecated and will be removed in a future release; " *
126+
"pass `clamped_particles` (Vector{Int} of indices) instead.",
127+
:n_clamped_particles)
128+
if isempty(clamped_particles)
129+
clamped_particles = collect((n_particles - n_clamped_particles + 1):n_particles)
130+
else
131+
throw(ArgumentError("Either `n_clamped_particles` or `clamped_particles` can be specified, not both."))
132+
end
133+
end
134+
135+
# Handle clamped particles
136+
if !isempty(clamped_particles)
137+
@assert allunique(clamped_particles) "`clamped_particles` contains duplicate particle indices"
138+
139+
n_clamped_particles = length(clamped_particles)
140+
initial_condition_sorted = deepcopy(initial_condition)
141+
young_modulus_sorted = copy(young_modulus)
142+
poisson_ratio_sorted = copy(poisson_ratio)
143+
move_particles_to_end!(initial_condition_sorted, clamped_particles)
144+
move_particles_to_end!(young_modulus_sorted, clamped_particles)
145+
move_particles_to_end!(poisson_ratio_sorted, clamped_particles)
146+
else
147+
initial_condition_sorted = initial_condition
148+
young_modulus_sorted = young_modulus
149+
poisson_ratio_sorted = poisson_ratio
150+
end
151+
152+
initial_coordinates = copy(initial_condition_sorted.coordinates)
153+
current_coordinates = copy(initial_condition_sorted.coordinates)
154+
mass = copy(initial_condition_sorted.mass)
155+
material_density = copy(initial_condition_sorted.density)
117156
correction_matrix = Array{ELTYPE, 3}(undef, NDIMS, NDIMS, n_particles)
118157
pk1_corrected = Array{ELTYPE, 3}(undef, NDIMS, NDIMS, n_particles)
119158
deformation_grad = Array{ELTYPE, 3}(undef, NDIMS, NDIMS, n_particles)
120159

121160
n_integrated_particles = n_particles - n_clamped_particles
122161

123-
lame_lambda = @. young_modulus * poisson_ratio /
124-
((1 + poisson_ratio) * (1 - 2 * poisson_ratio))
125-
lame_mu = @. (young_modulus / 2) / (1 + poisson_ratio)
162+
lame_lambda = @. young_modulus_sorted * poisson_ratio_sorted /
163+
((1 + poisson_ratio_sorted) *
164+
(1 - 2 * poisson_ratio_sorted))
165+
lame_mu = @. (young_modulus_sorted / 2) / (1 + poisson_ratio_sorted)
126166

127167
ismoving = Ref(!isnothing(clamped_particles_motion))
128-
initialize_prescribed_motion!(clamped_particles_motion, initial_condition,
168+
initialize_prescribed_motion!(clamped_particles_motion, initial_condition_sorted,
129169
n_clamped_particles)
130170

131-
cache = create_cache_tlsph(clamped_particles_motion, initial_condition)
171+
cache = create_cache_tlsph(clamped_particles_motion, initial_condition_sorted)
132172

133-
return TotalLagrangianSPHSystem(initial_condition, initial_coordinates,
173+
return TotalLagrangianSPHSystem(initial_condition_sorted, initial_coordinates,
134174
current_coordinates, mass, correction_matrix,
135175
pk1_corrected, deformation_grad, material_density,
136-
n_integrated_particles, young_modulus, poisson_ratio,
176+
n_integrated_particles, young_modulus_sorted,
177+
poisson_ratio_sorted,
137178
lame_lambda, lame_mu, smoothing_kernel,
138179
smoothing_length, acceleration_, boundary_model,
139180
penalty_force, viscosity, source_terms,

test/general/initial_condition.jl

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,4 +333,102 @@
333333
@test initial_condition.coordinates expected_coords
334334
end
335335
end
336+
@testset verbose=true "Move Particles to End" begin
337+
@testset verbose=true "InitialCondition Variant" begin
338+
@testset verbose=true "Valid Particle Movement" begin
339+
# Create a simple initial condition with known particle arrangement
340+
coordinates = [1.0 2.0 3.0 4.0 5.0;
341+
1.0 2.0 3.0 4.0 5.0]
342+
velocity = [0.1 0.2 0.3 0.4 0.5;
343+
0.1 0.2 0.3 0.4 0.5]
344+
mass = [1.1, 2.2, 3.3, 4.4, 5.5]
345+
density = [10.0, 20.0, 30.0, 40.0, 50.0]
346+
pressure = [100.0, 200.0, 300.0, 400.0, 500.0]
347+
348+
ic = InitialCondition(coordinates=coordinates, velocity=velocity,
349+
mass=mass, density=density, pressure=pressure)
350+
351+
# Move particles 2 and 4 to the end
352+
particle_ids_to_move = [2, 4]
353+
TrixiParticles.move_particles_to_end!(ic, particle_ids_to_move)
354+
355+
# Expected order after moving: [1, 3, 5, 2, 4]
356+
expected_coordinates = [1.0 3.0 5.0 2.0 4.0;
357+
1.0 3.0 5.0 2.0 4.0]
358+
expected_velocity = [0.1 0.3 0.5 0.2 0.4;
359+
0.1 0.3 0.5 0.2 0.4]
360+
expected_mass = [1.1, 3.3, 5.5, 2.2, 4.4]
361+
expected_density = [10.0, 30.0, 50.0, 20.0, 40.0]
362+
expected_pressure = [100.0, 300.0, 500.0, 200.0, 400.0]
363+
364+
@test ic.coordinates == expected_coordinates
365+
@test ic.velocity == expected_velocity
366+
@test ic.mass == expected_mass
367+
@test ic.density == expected_density
368+
@test ic.pressure == expected_pressure
369+
end
370+
371+
@testset verbose=true "Duplicate Particle IDs" begin
372+
coordinates = [1.0 2.0 3.0 4.0; 1.0 2.0 3.0 4.0]
373+
velocity = [0.1 0.2 0.3 0.4; 0.1 0.2 0.3 0.4]
374+
mass = [1.1, 2.2, 3.3, 4.4]
375+
density = [10.0, 20.0, 30.0, 40.0]
376+
377+
ic = InitialCondition(coordinates=coordinates, velocity=velocity,
378+
mass=mass, density=density)
379+
380+
# Move particle 2 multiple times (should only move once)
381+
TrixiParticles.move_particles_to_end!(ic, [2, 2, 3])
382+
383+
# Expected order: [1, 4, 2, 3] (2 and 3 moved to end)
384+
expected_coordinates = [1.0 4.0 2.0 3.0; 1.0 4.0 2.0 3.0]
385+
expected_mass = [1.1, 4.4, 2.2, 3.3]
386+
387+
@test ic.coordinates == expected_coordinates
388+
@test ic.mass == expected_mass
389+
end
390+
391+
@testset verbose=true "Out of Bounds Particle IDs" begin
392+
ic = rectangular_patch(0.1, (2, 4))
393+
394+
# Test with invalid particle ID
395+
@test_throws BoundsError TrixiParticles.move_particles_to_end!(ic, [9])
396+
@test_throws BoundsError TrixiParticles.move_particles_to_end!(ic, [0])
397+
@test_throws BoundsError TrixiParticles.move_particles_to_end!(ic, [-1])
398+
end
399+
end
400+
401+
@testset verbose=true "Vector Variant" begin
402+
@testset verbose=true "Valid Particle Movement" begin
403+
a = [1.0, 2.0, 3.0, 4.0, 5.0]
404+
returned = TrixiParticles.move_particles_to_end!(a, [2, 4])
405+
406+
expected = [1.0, 3.0, 5.0, 2.0, 4.0]
407+
@test a == expected
408+
@test returned == expected
409+
end
410+
411+
@testset verbose=true "Duplicate Particle IDs" begin
412+
a = [1, 2, 3, 4]
413+
TrixiParticles.move_particles_to_end!(a, [2, 2, 3])
414+
415+
expected = [1, 4, 2, 3]
416+
@test a == expected
417+
end
418+
419+
@testset verbose=true "Out of Bounds Particle IDs" begin
420+
a = collect(1:8)
421+
422+
@test_throws BoundsError TrixiParticles.move_particles_to_end!(a, [9])
423+
@test_throws BoundsError TrixiParticles.move_particles_to_end!(a, [0])
424+
@test_throws BoundsError TrixiParticles.move_particles_to_end!(a, [-1])
425+
end
426+
427+
@testset verbose=true "Empty IDs" begin
428+
a = [10, 20, 30]
429+
TrixiParticles.move_particles_to_end!(a, Int[])
430+
@test a == [10, 20, 30]
431+
end
432+
end
433+
end
336434
end

0 commit comments

Comments
 (0)