Skip to content

Commit 4725885

Browse files
efaulhaberLasNikas
andauthored
Make DEM run on GPUs and with Float32 (#979)
* Make DEM run on GPUs * Add test * Fix Metal tests * Fix DEM coefficients --------- Co-authored-by: Niklas Neher <73897120+LasNikas@users.noreply.github.com>
1 parent 7f6ec8a commit 4725885

File tree

7 files changed

+92
-47
lines changed

7 files changed

+92
-47
lines changed

examples/dem/rectangular_tank_2d.jl

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@ tank = RectangularTank(particle_spacing, (rock_width, rock_height),
3535

3636
# Move the rock particles up to let them fall
3737
tank.fluid.coordinates[2, :] .+= 0.5
38-
# small perturbation
39-
tank.fluid.coordinates .+= 0.01 .* (2 .* rand(size(tank.fluid.coordinates)) .- 1)
38+
# Small perturbation
39+
for i in 1:size(tank.fluid.coordinates, 2)
40+
tank.fluid.coordinates[:, i] .+= 0.01 .* (2 .* rand(2) .- 1)
41+
end
4042

4143
# Create a contact model.
4244
# Option 1: Hertzian contact model (uses elastic modulus and Poisson's ratio)
@@ -54,8 +56,9 @@ boundary_system = BoundaryDEMSystem(tank.boundary, 10e7)
5456
# ==========================================================================================
5557
# ==== Simulation
5658
# ==========================================================================================
57-
58-
semi = Semidiscretization(rock_system, boundary_system)
59+
semi = Semidiscretization(rock_system, boundary_system;
60+
neighborhood_search=GridNeighborhoodSearch{2}(),
61+
parallelization_backend=PolyesterBackend())
5962

6063
tspan = (0.0, 4.0)
6164
ode = semidiscretize(semi, tspan)

src/general/gpu.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ Adapt.@adapt_structure TotalLagrangianSPHSystem
2020
Adapt.@adapt_structure BoundaryZone
2121
Adapt.@adapt_structure SystemBuffer
2222
Adapt.@adapt_structure OpenBoundarySystem
23+
Adapt.@adapt_structure DEMSystem
24+
Adapt.@adapt_structure BoundaryDEMSystem
2325
Adapt.@adapt_structure RCRWindkesselModel
2426

2527
KernelAbstractions.get_backend(::PtrArray) = KernelAbstractions.CPU()

src/schemes/boundary/dem_boundary/system.jl

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,29 @@ struct BoundaryDEMSystem{NDIMS, ELTYPE <: Real, IC,
1616
normal_stiffness :: ELTYPE
1717
buffer :: Nothing
1818

19-
function BoundaryDEMSystem(initial_condition, normal_stiffness)
20-
coordinates = initial_condition.coordinates
21-
radius = 0.5 * initial_condition.particle_spacing *
22-
ones(length(initial_condition.mass))
23-
NDIMS = size(coordinates, 1)
19+
function BoundaryDEMSystem(initial_condition, coordinates, radius,
20+
normal_stiffness, buffer)
21+
NDIMS = ndims(initial_condition)
2422

25-
return new{NDIMS, eltype(coordinates), typeof(initial_condition), typeof(radius),
23+
return new{NDIMS, eltype(initial_condition), typeof(initial_condition),
24+
typeof(radius),
2625
typeof(coordinates)}(initial_condition, coordinates, radius,
27-
normal_stiffness, nothing)
26+
normal_stiffness, buffer)
2827
end
2928
end
3029

30+
# The default constructor needs to be accessible for Adapt.jl to work with this struct.
31+
# See the comments in general/gpu.jl for more details.
32+
function BoundaryDEMSystem(initial_condition, normal_stiffness)
33+
ELTYPE = eltype(initial_condition)
34+
coordinates = initial_condition.coordinates
35+
radius = initial_condition.particle_spacing *
36+
ones(ELTYPE, length(initial_condition.mass)) / 2
37+
38+
return BoundaryDEMSystem(initial_condition, coordinates, radius,
39+
normal_stiffness, nothing)
40+
end
41+
3142
@inline function Base.eltype(system::BoundaryDEMSystem)
3243
eltype(system.coordinates)
3344
end

src/schemes/structure/discrete_element_method/contact_models.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ end
8888
r_star = (r_a * r_b) / (r_a + r_b)
8989

9090
# Non-linear stiffness for Hertzian contact
91-
elastic_force_per_overlap = (4 / 3) * E_star * sqrt(r_star * overlap)
91+
elastic_force_per_overlap = 4 * E_star * sqrt(r_star * overlap) / 3
9292

9393
# Compute effective mass for damping
9494
if neighbor_system isa DEMSystem

src/schemes/structure/discrete_element_method/rhs.jl

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ function interact!(dv, v_particle_system, u_particle_system, v_neighbor_system,
1212
neighbor,
1313
pos_diff,
1414
distance
15-
# See `src/general/smoothing_kernels.jl` for more details.
15+
# See `src/general/smoothing_kernels.jl` for more details
1616
distance^2 < eps(first(particle_system.radius)^2) && return
1717

1818
# Retrieve particle properties
@@ -26,7 +26,7 @@ function interact!(dv, v_particle_system, u_particle_system, v_neighbor_system,
2626
# Compute the unit normal vector (from neighbor to particle)
2727
normal = pos_diff / distance
2828

29-
# Compute Normal Force by Dispatching on the Contact Model.
29+
# Compute Normal Force by Dispatching on the Contact Model
3030
F_normal = collision_force_normal(particle_system.contact_model,
3131
particle_system, neighbor_system,
3232
overlap, normal,
@@ -46,7 +46,7 @@ function interact!(dv, v_particle_system, u_particle_system, v_neighbor_system,
4646
end
4747

4848
# Apply a simple position correction to mitigate overlap.
49-
# TODO: use update callback
49+
# TODO: use update callback, changing `u` is not allowed here.
5050
position_correction!(neighbor_system, u_particle_system, overlap, normal,
5151
particle)
5252
end
@@ -63,10 +63,14 @@ end
6363
overlap, normal,
6464
v_particle_system, v_neighbor_system,
6565
particle, neighbor, F_normal)
66-
# Tangential force parameters
67-
friction_coefficient = 0.5 # Coulomb friction coefficient [Cundall and Strack, 1979]
68-
tangential_stiffness = 1e3 # Tangential spring constant
69-
tangential_damping = 0.001 # Damping coefficient for tangential force
66+
# Tangential force parameters. Avoid hardcoding double values.
67+
ELTYPE = eltype(particle_system)
68+
# Coulomb friction coefficient [Cundall and Strack, 1979]
69+
friction_coefficient = convert(ELTYPE, 0.5)
70+
# Tangential spring constant
71+
tangential_stiffness = 1000
72+
# Damping coefficient for tangential force
73+
tangential_damping = convert(ELTYPE, 0.001)
7074

7175
# Compute relative velocity and extract the tangential component.
7276
v_a = current_velocity(v_particle_system, particle_system, particle)
@@ -79,7 +83,7 @@ end
7983

8084
# Coulomb friction: limit the tangential force to μ * |F_normal|
8185
max_tangent = friction_coefficient * norm(F_normal)
82-
if norm(F_t) > max_tangent && norm(F_t) > 0.0
86+
if norm(F_t) > max_tangent && norm(F_t) > 0
8387
F_t = F_t * (max_tangent / norm(F_t))
8488
end
8589

@@ -94,6 +98,6 @@ end
9498
@inline function position_correction!(neighbor_system::BoundaryDEMSystem,
9599
u_particle_system, overlap, normal, particle)
96100
for i in 1:ndims(neighbor_system)
97-
u_particle_system[i, particle] -= 0.5 * overlap * normal[i]
101+
u_particle_system[i, particle] -= overlap * normal[i] / 2
98102
end
99103
end

src/schemes/structure/discrete_element_method/system.jl

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -38,35 +38,35 @@ struct DEMSystem{NDIMS, ELTYPE <: Real, IC, ARRAY1D, ST,
3838
acceleration :: SVector{NDIMS, ELTYPE}
3939
source_terms :: ST
4040
contact_model :: CM
41+
end
4142

42-
function DEMSystem(initial_condition, contact_model; damping_coefficient=0.0001,
43-
acceleration=ntuple(_ -> 0.0,
44-
ndims(initial_condition)), source_terms=nothing,
45-
radius=nothing)
46-
NDIMS = ndims(initial_condition)
47-
ELTYPE = eltype(initial_condition)
48-
49-
mass = copy(initial_condition.mass)
43+
# The default constructor needs to be accessible for Adapt.jl to work with this struct.
44+
# See the comments in general/gpu.jl for more details.
45+
function DEMSystem(initial_condition, contact_model; damping_coefficient=0.0001,
46+
acceleration=ntuple(_ -> 0.0,
47+
ndims(initial_condition)), source_terms=nothing,
48+
radius=nothing)
49+
NDIMS = ndims(initial_condition)
50+
ELTYPE = eltype(initial_condition)
5051

51-
if isnothing(radius)
52-
radius = 0.5 * initial_condition.particle_spacing * ones(length(mass))
53-
else
54-
mass = (radius / (0.5 * initial_condition.particle_spacing))^3 * mass
55-
radius = radius * ones(length(mass))
56-
end
52+
mass = copy(initial_condition.mass)
5753

58-
# Make acceleration an SVector
59-
acceleration_ = SVector(acceleration...)
60-
if length(acceleration_) != NDIMS
61-
throw(ArgumentError("`acceleration` must be of length $NDIMS for a $(NDIMS)D problem"))
62-
end
54+
if isnothing(radius)
55+
radius = initial_condition.particle_spacing * ones(ELTYPE, length(mass)) / 2
56+
else
57+
mass = (radius / (initial_condition.particle_spacing / 2))^3 * mass
58+
radius = radius * ones(ELTYPE, length(mass))
59+
end
6360

64-
return new{NDIMS, ELTYPE, typeof(initial_condition),
65-
typeof(mass), typeof(source_terms),
66-
typeof(contact_model)}(initial_condition, mass, radius,
67-
damping_coefficient, acceleration_, source_terms,
68-
contact_model)
61+
# Make acceleration an SVector
62+
acceleration_ = SVector(acceleration...)
63+
if length(acceleration_) != NDIMS
64+
throw(ArgumentError("`acceleration` must be of length $NDIMS for a $(NDIMS)D problem"))
6965
end
66+
67+
return DEMSystem(initial_condition, mass, radius,
68+
damping_coefficient, acceleration_, source_terms,
69+
contact_model)
7070
end
7171

7272
function Base.show(io::IO, system::DEMSystem)
@@ -118,12 +118,12 @@ end
118118
timer_name(::DEMSystem) = "solid"
119119

120120
function TrixiParticles.write_u0!(u0, system::DEMSystem)
121-
u0 .= system.initial_condition.coordinates
121+
copyto!(u0, system.initial_condition.coordinates)
122122
return u0
123123
end
124124

125125
function TrixiParticles.write_v0!(v0, system::DEMSystem)
126-
v0 .= system.initial_condition.velocity
126+
copyto!(v0, system.initial_condition.velocity)
127127
return v0
128128
end
129129

test/examples/gpu.jl

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,31 @@ end
482482
end
483483
end
484484

485+
@testset verbose=true "DEM" begin
486+
@trixi_testset "dem/rectangular_tank_2d.jl" begin
487+
@trixi_test_nowarn trixi_include_changeprecision(Float32, @__MODULE__,
488+
joinpath(examples_dir(), "dem",
489+
"rectangular_tank_2d.jl"),
490+
ode=nothing, sol=nothing)
491+
# Neighborhood search with `FullGridCellList` for GPU compatibility
492+
min_corner = minimum(tank.boundary.coordinates, dims=2)
493+
max_corner = maximum(tank.boundary.coordinates, dims=2)
494+
cell_list = FullGridCellList(; min_corner, max_corner)
495+
neighborhood_search = GridNeighborhoodSearch{2}(; cell_list,
496+
update_strategy=ParallelUpdate())
497+
498+
@trixi_test_nowarn trixi_include_changeprecision(Float32, @__MODULE__,
499+
joinpath(examples_dir(), "dem",
500+
"rectangular_tank_2d.jl"),
501+
tspan=(0.0f0, 0.05f0),
502+
neighborhood_search=neighborhood_search,
503+
parallelization_backend=Main.parallelization_backend)
504+
@test sol.retcode == ReturnCode.Success
505+
backend = TrixiParticles.KernelAbstractions.get_backend(sol.u[end].x[1])
506+
@test backend == Main.parallelization_backend
507+
end
508+
end
509+
485510
@testset verbose=true "Postprocessing $TRIXIPARTICLES_TEST_" begin
486511
@testset verbose=true "Interpolation" begin
487512
# Run the dam break example to get a solution

0 commit comments

Comments
 (0)