MFC
Exascale flow solver
Loading...
Searching...
No Matches
m_particle_cloud.fpp.f90
Go to the documentation of this file.
1# 1 "/home/runner/work/MFC/MFC/src/simulation/m_particle_cloud.fpp"
2!>
3!! @file m_particle_cloud.fpp
4!! @brief Generates particle beds: converts particle_cloud specifications into
5!! individual sphere/circle particle_cloud_ibs entries before reduction.
6
7!> @brief Generates particle beds by converting particle_cloud patch specifications into individual immersed boundary patches before
8!! domain reduction. Each rank runs the same deterministic placement so no MPI broadcast of particle positions is needed.
10
12 use m_constants
13 use m_mpi_common
14
15 implicit none
16
17 private
18
20
21contains
22
23 !> Generate all particle beds and fill particle_cloud_ibs. Called on all ranks before s_reduce_ib_patch_array.
24 impure subroutine s_generate_particle_clouds(particle_cloud_ibs)
25
26 type(ib_patch_parameters), allocatable, intent(out), dimension(:) :: particle_cloud_ibs
27 integer :: cloud_idx, ib_idx, n_total_particles
28 real(wp) :: t_start, t_end
29
30 if (num_particle_clouds == 0) then
31 allocate (particle_cloud_ibs(0))
32 return
33 end if
34
35 call cpu_time(t_start)
36
37 ! Pre-count total particles across all beds so particle_cloud_ibs can be allocated exactly once.
38 n_total_particles = 0
39 do cloud_idx = 1, num_particle_clouds
40 n_total_particles = n_total_particles + particle_cloud(cloud_idx)%num_particles
41 end do
42 allocate (particle_cloud_ibs(n_total_particles))
43
44 ib_idx = 0 ! index into particle_cloud_ibs
45
46 do cloud_idx = 1, num_particle_clouds
47 select case (particle_cloud(cloud_idx)%packing_method)
48 case (1) ! random box packing method
49 call s_particle_cloud_random_box(cloud_idx, ib_idx, particle_cloud_ibs)
50 end select
51 end do
52
53 call cpu_time(t_end)
54 if (proc_rank == 0) print '(a,i0,a,f0.3,a)', 'Particle beds placed ', ib_idx, ' particles in ', t_end - t_start, ' seconds.'
55
56 end subroutine s_generate_particle_clouds
57
58 !> Generates a random distributions of particles in a box with a minimum spacing
59 subroutine s_particle_cloud_random_box(cloud_idx, ib_idx, particle_cloud_ibs)
60
61 integer, intent(in) :: cloud_idx
62 integer, intent(inout) :: ib_idx
63 type(ib_patch_parameters), intent(inout), dimension(:) :: particle_cloud_ibs
64 integer :: n_placed, geom, seed
65 integer(8) :: n_attempts, max_attempts
66 real(wp) :: xmin, xmax, ymin, ymax, zmin, zmax, min_dist
67 real(wp) :: rx, ry, rz, dist
68 logical :: overlaps
69 real(wp), allocatable :: placed(:,:)
70 integer :: hash_size, slot
71 integer :: bx, by, bz, nbx, nby, nbz
72 integer :: dx_b, dy_b, dz_b, dz_lo, dz_hi, j
73 integer, allocatable :: hash_head(:), chain_next(:)
74
75 xmin = particle_cloud(cloud_idx)%x_centroid - 0.5_wp*particle_cloud(cloud_idx)%length_x
76 xmax = particle_cloud(cloud_idx)%x_centroid + 0.5_wp*particle_cloud(cloud_idx)%length_x
77 ymin = particle_cloud(cloud_idx)%y_centroid - 0.5_wp*particle_cloud(cloud_idx)%length_y
78 ymax = particle_cloud(cloud_idx)%y_centroid + 0.5_wp*particle_cloud(cloud_idx)%length_y
79 zmin = particle_cloud(cloud_idx)%z_centroid - 0.5_wp*particle_cloud(cloud_idx)%length_z
80 zmax = particle_cloud(cloud_idx)%z_centroid + 0.5_wp*particle_cloud(cloud_idx)%length_z
81
82 min_dist = 2._wp*particle_cloud(cloud_idx)%radius + particle_cloud(cloud_idx)%min_spacing
83
84 if (p == 0) then
85 geom = 2 ! circle for 2D
86 dz_lo = 0
87 dz_hi = 0
88 else
89 geom = 8 ! sphere for 3D
90 dz_lo = -1
91 dz_hi = 1
92 end if
93
94 max_attempts = int(particle_cloud(cloud_idx)%num_particles, 8)*1000_8
95 n_placed = 0
96 n_attempts = 0
97 seed = particle_cloud(cloud_idx)%seed
98 if (seed == 0) seed = 1 + cloud_idx*1013904223
99
100 allocate (placed(3, particle_cloud(cloud_idx)%num_particles))
101
102 ! Hash table: 4x overprovisioned for ~25% load factor, minimum 16 buckets. chain_next(i) links placed particle i to the
103 ! previous occupant of its bucket.
104 hash_size = max(16, 4*particle_cloud(cloud_idx)%num_particles)
105 allocate (hash_head(hash_size))
106 allocate (chain_next(particle_cloud(cloud_idx)%num_particles))
107 hash_head = -1
108 chain_next = -1
109
110 do while (n_placed < particle_cloud(cloud_idx)%num_particles .and. n_attempts < max_attempts)
111 n_attempts = n_attempts + 1
112
113 rx = xmin + f_xorshift(seed)*(xmax - xmin)
114 ry = ymin + f_xorshift(seed)*(ymax - ymin)
115 if (p == 0) then
116 rz = particle_cloud(cloud_idx)%z_centroid
117 else
118 rz = zmin + f_xorshift(seed)*(zmax - zmin)
119 end if
120
121 bx = int(floor(rx/min_dist))
122 by = int(floor(ry/min_dist))
123 bz = 0
124 if (p /= 0) bz = int(floor(rz/min_dist))
125
126 ! Check 3x3(x3) neighboring bins - O(1) average via hash lookup
127 overlaps = .false.
128 outer: do dx_b = -1, 1
129 do dy_b = -1, 1
130 do dz_b = dz_lo, dz_hi
131 nbx = bx + dx_b
132 nby = by + dy_b
133 nbz = bz + dz_b
134 slot = f_bin_hash(nbx, nby, nbz, hash_size)
135 j = hash_head(slot)
136 do while (j > 0)
137 if (p == 0) then
138 dist = sqrt((rx - placed(1, j))**2 + (ry - placed(2, j))**2)
139 else
140 dist = sqrt((rx - placed(1, j))**2 + (ry - placed(2, j))**2 + (rz - placed(3, j))**2)
141 end if
142 if (dist < min_dist) then
143 overlaps = .true.
144 exit outer
145 end if
146 j = chain_next(j)
147 end do
148 end do
149 end do
150 end do outer
151
152 if (.not. overlaps) then
153 n_placed = n_placed + 1
154 placed(1, n_placed) = rx
155 placed(2, n_placed) = ry
156 placed(3, n_placed) = rz
157
158 ! Insert into hash grid as head of bucket chain
159 slot = f_bin_hash(bx, by, bz, hash_size)
160 chain_next(n_placed) = hash_head(slot)
161 hash_head(slot) = n_placed
162
163 ib_idx = ib_idx + 1
164
165 ! gbl_patch_id is relative within particle_cloud_ibs here; s_reduce_ib_patch_array adjusts to global indexing.
166 particle_cloud_ibs(ib_idx)%gbl_patch_id = ib_idx
167 particle_cloud_ibs(ib_idx)%geometry = geom
168 particle_cloud_ibs(ib_idx)%x_centroid = rx
169 particle_cloud_ibs(ib_idx)%y_centroid = ry
170 particle_cloud_ibs(ib_idx)%z_centroid = rz
171 particle_cloud_ibs(ib_idx)%step_x_centroid = rx
172 particle_cloud_ibs(ib_idx)%step_y_centroid = ry
173 particle_cloud_ibs(ib_idx)%step_z_centroid = rz
174 particle_cloud_ibs(ib_idx)%angles(:) = 0._wp
175 particle_cloud_ibs(ib_idx)%step_angles(:) = 0._wp
176 particle_cloud_ibs(ib_idx)%vel(:) = 0._wp
177 particle_cloud_ibs(ib_idx)%step_vel(:) = 0._wp
178 particle_cloud_ibs(ib_idx)%angular_vel(:) = 0._wp
179 particle_cloud_ibs(ib_idx)%step_angular_vel(:) = 0._wp
180 particle_cloud_ibs(ib_idx)%force(:) = 0._wp
181 particle_cloud_ibs(ib_idx)%torque(:) = 0._wp
182 particle_cloud_ibs(ib_idx)%centroid_offset(:) = 0._wp
183 particle_cloud_ibs(ib_idx)%rotation_matrix = 0._wp
184 particle_cloud_ibs(ib_idx)%rotation_matrix(1, 1) = 1._wp
185 particle_cloud_ibs(ib_idx)%rotation_matrix(2, 2) = 1._wp
186 particle_cloud_ibs(ib_idx)%rotation_matrix(3, 3) = 1._wp
187 particle_cloud_ibs(ib_idx)%rotation_matrix_inverse = particle_cloud_ibs(ib_idx)%rotation_matrix
188 particle_cloud_ibs(ib_idx)%radius = particle_cloud(cloud_idx)%radius
189 particle_cloud_ibs(ib_idx)%mass = particle_cloud(cloud_idx)%mass
190 particle_cloud_ibs(ib_idx)%moment = dflt_real
191 particle_cloud_ibs(ib_idx)%moving_ibm = particle_cloud(cloud_idx)%moving_ibm
192 particle_cloud_ibs(ib_idx)%slip = .false.
193 end if
194 end do
195
196 if (n_placed < particle_cloud(cloud_idx)%num_particles) then
197 call s_mpi_abort("Error :: Failed to place all particles in particle bed")
198 end if
199
200 deallocate (placed, hash_head, chain_next)
201
202 end subroutine s_particle_cloud_random_box
203
204 !> Xorshift PRNG. Advances seed in-place and returns a value in [0, 1).
205 function f_xorshift(seed) result(rval)
206
207 integer, intent(inout) :: seed
208 real(wp) :: rval
209
210 seed = ieor(seed, ishft(seed, 13))
211 seed = ieor(seed, ishft(seed, -17))
212 seed = ieor(seed, ishft(seed, 5))
213
214 rval = abs(real(seed, wp))/real(huge(seed), wp)
215
216 end function f_xorshift
217
218 !> Hash bin coordinates to a 1-indexed slot in [1, hash_size]. Uses large prime multipliers to spread bins across buckets. Hash
219 !! collisions are benign: the distance check catches false neighbours.
220 function f_bin_hash(bx, by, bz, hash_size) result(slot)
221
222 integer, intent(in) :: bx, by, bz, hash_size
223 integer :: slot
224 integer(8) :: key
225
226 key = ieor(ieor(int(bx, 8)*73856093_8, int(by, 8)*19349663_8), int(bz, 8)*83492791_8)
227 slot = int(mod(abs(key), int(hash_size, 8))) + 1
228
229 end function f_bin_hash
230
231end module m_particle_cloud
Compile-time constant parameters: default values, tolerances, and physical constants.
real(wp), parameter dflt_real
Default real value.
Global parameters for the computational domain, fluid properties, and simulation algorithm configurat...
integer proc_rank
Rank of the local processor.
type(particle_cloud_parameters), dimension(num_particle_clouds_max) particle_cloud
Particle bed specifications.
MPI communication layer: domain decomposition, halo exchange, reductions, and parallel I/O setup.
impure subroutine s_mpi_abort(prnt, code)
The subroutine terminates the MPI execution environment.
Generates particle beds by converting particle_cloud patch specifications into individual immersed bo...
impure subroutine, public s_generate_particle_clouds(particle_cloud_ibs)
Generate all particle beds and fill particle_cloud_ibs. Called on all ranks before s_reduce_ib_patch_...
real(wp) function f_xorshift(seed)
Xorshift PRNG. Advances seed in-place and returns a value in [0, 1).
integer function f_bin_hash(bx, by, bz, hash_size)
Hash bin coordinates to a 1-indexed slot in [1, hash_size]. Uses large prime multipliers to spread bi...
subroutine s_particle_cloud_random_box(cloud_idx, ib_idx, particle_cloud_ibs)
Generates a random distributions of particles in a box with a minimum spacing.