@@ -118,3 +118,124 @@ plot!(p5, Plots.Shape, geometry, linestyle=:dash, label=nothing, axis=false, gri
118118# As shown in the plot, the interface of the geometry surface is not well resolved yet.
119119# In other words, there is no body-fitted configuration.
120120# This is where the particle packing will come into play.
121+ # If we want to assign the mass of each sampled partcle consistently with its density,
122+ # we can adjust it as follows:
123+ shape_sampled. mass .= density * TrixiParticles. volume (geometry) / nparticles (shape_sampled)
124+
125+ # # Particle packing
126+
127+ # In the following, we will essentially follow the same steps described in
128+ # "Setting up your simulation from scratch" (TODO : ref).
129+ # That means we will generate systems that are then passed to the [`Semidiscretization`](@ref Semidiscretization).
130+ # The difference from a typical physical simulation is that we use [`ParticlePackingSystem`](@ref ParticlePackingSystem),
131+ # which does not represent any physical law. Instead, we only use the simulation framework to time-integrate
132+ # the packing process.
133+
134+ # We first need to import [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl).
135+ using OrdinaryDiffEq
136+
137+ # Next, we set a background pressure. This can be chosen arbitrarily.
138+ # A higher value results in smaller time steps, but the final packed state
139+ # will remain the same after running the same number of steps.
140+ background_pressure = 1.0
141+
142+ # For particle interaction, we select a [smoothing kernel](@ref smoothing_kernel)
143+ # with a suitable smoothing length. Empirically, a factor of `0.8` times the
144+ # particle spacing gives good results [neher2025robustefficientpreprocessingtechniques](@cite).
145+ smoothing_kernel = SchoenbergQuinticSplineKernel {ndims(geometry)} ()
146+ smoothing_length = 0.8 * particle_spacing
147+
148+ # Now we can create the packing system. For learning purposes, let’s first try
149+ # passing no signed distance field (SDF) and see what happens.
150+ packing_system = ParticlePackingSystem (shape_sampled;
151+ smoothing_kernel= smoothing_kernel,
152+ smoothing_length= smoothing_length,
153+ signed_distance_field= nothing , background_pressure)
154+
155+ # We now proceed with the familiar steps
156+ # "Semidiscretization" and "Time integration" from the tutorial (TODO : ref)
157+ semi = Semidiscretization (packing_system)
158+
159+ # # Use a high `tspan` to guarantee that the simulation runs for at least `maxiters`
160+ tspan = (0 , 10000.0 )
161+ ode = semidiscretize (semi, tspan)
162+
163+ maxiters = 100
164+ callbacks = CallbackSet (UpdateCallback ())
165+ time_integrator = RDPK3SpFSAL35 ()
166+
167+ sol = solve (ode, time_integrator;
168+ abstol= 1e-7 , reltol= 1e-4 , save_everystep= false , maxiters= maxiters,
169+ callback= callbacks)
170+
171+ packed_ic = InitialCondition (sol, packing_system, semi)
172+
173+ p_not_constrained = plot (packed_ic, xlims= my_xlims, ylims= my_ylims)
174+ plot! (p_not_constrained, Plots. Shape, geometry, xlims= my_xlims, ylims= my_ylims)
175+
176+ # As we can see in the plot, the particles are not constrained to the
177+ # geometric surface.
178+
179+ # We therefore add an SDF for the geometry and repeat the same procedure.
180+ packing_system = ParticlePackingSystem (shape_sampled;
181+ smoothing_kernel= smoothing_kernel,
182+ smoothing_length= smoothing_length,
183+ signed_distance_field,
184+ background_pressure)
185+
186+ # Again, we follow the same steps for semidiscretization and time integration.
187+ semi = Semidiscretization (packing_system)
188+
189+ tspan = (0 , 10000.0 )
190+ ode = semidiscretize (semi, tspan)
191+
192+ maxiters = 1000
193+ callbacks = CallbackSet (UpdateCallback ())
194+ time_integrator = RDPK3SpFSAL35 ()
195+
196+ sol = solve (ode, time_integrator;
197+ abstol= 1e-7 , reltol= 1e-4 , save_everystep= false , maxiters= maxiters,
198+ callback= callbacks)
199+
200+ packed_ic = InitialCondition (sol, packing_system, semi)
201+
202+ p = plot (packed_ic, xlims= my_xlims, ylims= my_ylims)
203+ plot! (p, Plots. Shape, geometry, xlims= my_xlims, ylims= my_ylims)
204+
205+ # We can see that the particles now stay inside the geometry,
206+ # but their distribution on the boundary is still not optimal.
207+ # Therefore, we set up a dedicated boundary packing system.
208+ # To do this, we set `is_boundary = true`.
209+ # For highly convex geometries, it is useful to slightly compress the
210+ # boundary layer thickness. The background for this is that the true
211+ # geometric volume is often larger than what was assumed when sampling
212+ # the boundary particles, because the mass was computed assuming an
213+ # interior particle volume.
214+ # A `boundary_compress_factor` of `0.8` or `0.9` works well for most shapes.
215+ # Since we have a relatively large particle spacing compared to the
216+ # geometry size in this example, we will choose `0.7`.
217+ boundary_system = ParticlePackingSystem (boundary_sampled;
218+ is_boundary= true ,
219+ smoothing_kernel= smoothing_kernel,
220+ smoothing_length= smoothing_length,
221+ boundary_compress_factor= 0.7 ,
222+ signed_distance_field, background_pressure)
223+
224+ # We can now couple the boundary system with the interior system:
225+ semi = Semidiscretization (packing_system, boundary_system)
226+
227+ tspan = (0 , 10000.0 )
228+ ode = semidiscretize (semi, tspan)
229+
230+ maxiters = 1000
231+ callbacks = CallbackSet (UpdateCallback ())
232+ time_integrator = RDPK3SpFSAL35 ()
233+
234+ sol = solve (ode, time_integrator;
235+ abstol= 1e-7 , reltol= 1e-4 , save_everystep= false , maxiters= maxiters,
236+ callback= callbacks)
237+
238+ packed_ic = InitialCondition (sol, packing_system, semi)
239+ packed_boundary_ic = InitialCondition (sol, boundary_system, semi)
240+ p_final = plot (packed_ic, packed_boundary_ic, xlims= my_xlims, ylims= my_ylims)
241+ plot (p_final, Plots. Shape, geometry, xlims= my_xlims, ylims= my_ylims, linestyle= :dash )
0 commit comments