MFC
Exascale flow solver
Loading...
Searching...
No Matches
m_perturbation.fpp.f90
Go to the documentation of this file.
1# 1 "/home/runner/work/MFC/MFC/src/pre_process/m_perturbation.fpp"
2!>
3!! @file
4!! @brief Contains module m_perturbation
5
6!> @brief Perturbs initial mean flow fields with random noise, mixing-layer instabilities, or simplex noise
8
11 use m_mpi_proxy
13 use m_helper
15 use ieee_arithmetic
16
17 implicit none
18
19 real(wp), allocatable, dimension(:,:,:,:) :: q_prim_temp
20
21contains
22
23 !> Allocate the temporary primitive variable array used by elliptic smoothing.
25
26 if (elliptic_smoothing) then
27 allocate (q_prim_temp(0:m,0:n,0:p,1:sys_size))
28 end if
29
31
32 !> Randomly perturb partial density fields at the interface of a spherical volume fraction region.
33 impure subroutine s_perturb_sphere(q_prim_vf)
34
35 type(scalar_field), dimension(sys_size), intent(inout) :: q_prim_vf
36 integer :: i, j, k, l
37 real(wp) :: perturb_alpha
38 real(wp) :: rand_real
39
40 call random_seed()
41
42 do k = 0, p
43 do j = 0, n
44 do i = 0, m
45 call random_number(rand_real)
46
47 perturb_alpha = q_prim_vf(eqn_idx%E + perturb_sph_fluid)%sf(i, j, k)
48
49 ! Perturb partial density fields to match perturbed volume fraction fields when the volume fraction is not near
50 ! 0 or 1
51 if ((.not. f_approx_equal(perturb_alpha, 0._wp)) .and. (.not. f_approx_equal(perturb_alpha, 1._wp))) then
52 do l = 1, num_fluids
53 q_prim_vf(l)%sf(i, j, k) = q_prim_vf(eqn_idx%E + l)%sf(i, j, k)*fluid_rho(l)
54 end do
55 end if
56 end do
57 end do
58 end do
59
60 end subroutine s_perturb_sphere
61
62 !> Add random noise to the velocity and void fraction of the surrounding flow field.
63 impure subroutine s_perturb_surrounding_flow(q_prim_vf)
64
65 type(scalar_field), dimension(sys_size), intent(inout) :: q_prim_vf
66 integer :: i, j, k
67 real(wp) :: rand_real, v_beg
68
69 call random_seed()
70
71 do k = 0, p
72 do j = 0, n
73 do i = 0, m
74 call random_number(rand_real)
75 rand_real = rand_real*perturb_flow_mag
76 v_beg = q_prim_vf(eqn_idx%mom%beg)%sf(i, j, k)
77 q_prim_vf(eqn_idx%mom%beg)%sf(i, j, k) = (1._wp + rand_real)*v_beg
78 if (num_vels > 1) q_prim_vf(eqn_idx%mom%end)%sf(i, j, k) = rand_real*v_beg
79 if (bubbles_euler) then
80 q_prim_vf(eqn_idx%alf)%sf(i, j, k) = (1._wp + rand_real)*q_prim_vf(eqn_idx%alf)%sf(i, j, k)
81 end if
82 end do
83 end do
84 end do
85
86 end subroutine s_perturb_surrounding_flow
87
88 !> Iteratively smooth all primitive variable fields using a discrete elliptic (Laplacian) filter.
89 impure subroutine s_elliptic_smoothing(q_prim_vf, bc_type, q_T_sf)
90
91 type(scalar_field), dimension(sys_size), intent(inout) :: q_prim_vf
92 type(integer_field), dimension(1:num_dims,1:2), intent(in) :: bc_type
93 type(scalar_field), optional, intent(inout) :: q_t_sf
94 integer :: i, j, k, l, q
95
97 ! Communication of buffer regions and apply boundary conditions
98 call s_populate_variables_buffers(bc_type, q_prim_vf, pb%sf, mv%sf, q_t_sf)
99
100 ! Perform smoothing and store in temp array
101 if (n == 0) then
102 do j = 0, m
103 do i = 1, sys_size
104 q_prim_temp(j, 0, 0, i) = (1._wp/4._wp)*(q_prim_vf(i)%sf(j + 1, 0, 0) + q_prim_vf(i)%sf(j - 1, 0, &
105 & 0) + 2._wp*q_prim_vf(i)%sf(j, 0, 0))
106 end do
107 end do
108 else if (p == 0) then
109 do k = 0, n
110 do j = 0, m
111 do i = 1, sys_size
112 q_prim_temp(j, k, 0, i) = (1._wp/8._wp)*(q_prim_vf(i)%sf(j + 1, k, 0) + q_prim_vf(i)%sf(j - 1, k, &
113 & 0) + q_prim_vf(i)%sf(j, k + 1, 0) + q_prim_vf(i)%sf(j, k - 1, &
114 & 0) + 4._wp*q_prim_vf(i)%sf(j, k, 0))
115 end do
116 end do
117 end do
118 else
119 do l = 0, p
120 do k = 0, n
121 do j = 0, m
122 do i = 1, sys_size
123 q_prim_temp(j, k, l, i) = (1._wp/12._wp)*(q_prim_vf(i)%sf(j + 1, k, l) + q_prim_vf(i)%sf(j - 1, &
124 & k, l) + q_prim_vf(i)%sf(j, k + 1, l) + q_prim_vf(i)%sf(j, k - 1, &
125 & l) + q_prim_vf(i)%sf(j, k, l + 1) + q_prim_vf(i)%sf(j, k, &
126 & l - 1) + 6._wp*q_prim_vf(i)%sf(j, k, l))
127 end do
128 end do
129 end do
130 end do
131 end if
132
133 ! Copy smoothed data back to array of scalar fields
134 do l = 0, p
135 do k = 0, n
136 do j = 0, m
137 do i = 1, sys_size
138 q_prim_vf(i)%sf(j, k, l) = q_prim_temp(j, k, l, i)
139 end do
140 end do
141 end do
142 end do
143 end do
144
145 end subroutine s_elliptic_smoothing
146
147 !> Perturb velocity and volume fraction fields using multi-octave simplex noise.
148 subroutine s_perturb_simplex(q_prim_vf)
149
150 type(scalar_field), dimension(sys_size), intent(inout) :: q_prim_vf
151 real(wp) :: mag, freq, scale, vel_rsm
152 real(wp), dimension(:,:), allocatable :: ofs
153 integer :: nOffsets
154 real(wp) :: xl, yl, zl
155 integer :: i, j, k, l, q
156
157 noffsets = max(num_dims, num_fluids)
158
159 allocate (ofs(noffsets, num_dims))
160
161 ! Store offsets
162 do i = 1, num_dims
163 do j = 1, num_dims
164 ofs(j, i) = simplex_params%perturb_vel_offset(j, i)
165 end do
166 end do
167
168 ! Perturb velocities
169 do i = 1, num_dims
170 if (simplex_params%perturb_vel(i)) then
171 freq = simplex_params%perturb_vel_freq(i)
172 scale = simplex_params%perturb_vel_scale(i)
173 do l = 0, p
174 do k = 0, n
175 do j = 0, m
176 xl = freq*(x_cc(j) + ofs(i, 1))
177 yl = freq*(y_cc(k) + ofs(i, 2))
178 if (num_dims == 2) then
179 mag = f_simplex2d(xl, yl)
180 else if (num_dims == 3) then
181 zl = freq*(z_cc(l) + ofs(i, 3))
182 mag = f_simplex3d(xl, yl, zl)
183 end if
184
185 vel_rsm = 0._wp
186 do q = 1, num_dims
187 vel_rsm = vel_rsm + q_prim_vf(eqn_idx%mom%beg + q - 1)%sf(j, k, l)**2._wp
188 end do
189 vel_rsm = sqrt(vel_rsm)
190
191 q_prim_vf(eqn_idx%mom%beg + i - 1)%sf(j, k, l) = q_prim_vf(eqn_idx%mom%beg + i - 1)%sf(j, k, &
192 & l) + vel_rsm*scale*mag
193 end do
194 end do
195 end do
196 end if
197 end do
198
199 ! Store offsets
200 do i = 1, num_dims
201 do j = 1, num_fluids
202 ofs(j, i) = simplex_params%perturb_dens_offset(j, i)
203 end do
204 end do
205
206 ! Perturb densities
207 do i = 1, num_fluids
208 if (simplex_params%perturb_dens(i)) then
209 freq = simplex_params%perturb_dens_freq(i)
210 scale = simplex_params%perturb_dens_scale(i)
211 do l = 0, p
212 do k = 0, n
213 do j = 0, m
214 xl = freq*(x_cc(j) + ofs(i, 1))
215 yl = freq*(y_cc(k) + ofs(i, 2))
216 if (num_dims == 2) then
217 mag = f_simplex2d(xl, yl)
218 else if (num_dims == 3) then
219 zl = freq*(z_cc(l) + ofs(i, 3))
220 mag = f_simplex3d(xl, yl, zl)
221 end if
222 q_prim_vf(eqn_idx%cont%beg + i - 1)%sf(j, k, l) = q_prim_vf(eqn_idx%cont%beg + i - 1)%sf(j, k, &
223 & l) + q_prim_vf(eqn_idx%cont%beg + i - 1)%sf(j, k, l)*scale*mag
224 end do
225 end do
226 end do
227 end if
228 end do
229
230 deallocate (ofs)
231
232 end subroutine s_perturb_simplex
233
234 !> Compute velocity perturbations for a temporal mixing layer with a hyperbolic tangent mean streamwise velocity profile, using
235 !! an inverted version of the spectrum-based synthetic turbulence generation method proposed by Guo et al. (2023, JFM).
236 subroutine s_perturb_mixlayer(q_prim_vf)
237
238 type(scalar_field), dimension(sys_size), intent(inout) :: q_prim_vf
239 real(wp), dimension(mixlayer_perturb_nk) :: k, Ek
240 real(wp), dimension(3, 3) :: Rij, Lmat
241 real(wp), dimension(3) :: velfluc, sig_tmp, sig, khat, xi
242 real(wp) :: dk, alpha, Eksum, q, uu0, phi
243 integer :: i, j, l, r, ierr
244
245 dk = 1._wp/mixlayer_perturb_nk
246
247 ! Compute prescribed energy spectra
248 eksum = 0._wp
249 do i = 1, mixlayer_perturb_nk
250 k(i) = dk*i
251 ek(i) = (k(i)/mixlayer_perturb_k0)**4._wp*exp(-2._wp*(k(i)/mixlayer_perturb_k0)**2._wp)
252 eksum = eksum + ek(i)
253 end do
254
255 ! Main loop
256 do r = 0, n
257 ! Compute prescribed Reynolds stress tensor with about half magnitude of its self-similar value
258 rij(:,:) = 0._wp
259 uu0 = patch_icpp(1)%vel(1)**2._wp*(1._wp - tanh(y_cc(r)*mixlayer_vel_coef)**2._wp)
260 rij(1, 1) = 0.05_wp*uu0
261 rij(2, 2) = 0.03_wp*uu0
262 rij(3, 3) = 0.03_wp*uu0
263 rij(1, 2) = -0.02_wp*uu0
264 rij(2, 1) = rij(1, 2)
265
266 ! Cholesky decomposition for inhomogeneity and anisotropy
267 lmat = 0._wp
268 lmat(1, 1) = sqrt(rij(1, 1))
269 if (abs(lmat(1, 1)) < sgm_eps) lmat(1, 1) = sgm_eps
270 lmat(2, 1) = rij(2, 1)/lmat(1, 1)
271 lmat(2, 2) = sqrt(rij(2, 2) - lmat(2, 1)**2._wp)
272 if (abs(lmat(2, 2)) < sgm_eps) lmat(2, 2) = sgm_eps
273 lmat(3, 1) = rij(3, 1)/lmat(1, 1)
274 lmat(3, 2) = (rij(3, 2) - lmat(3, 1)*lmat(2, 1))/lmat(2, 2)
275 lmat(3, 3) = sqrt(rij(3, 3) - lmat(3, 1)**2._wp - lmat(3, 2)**2._wp)
276
277 ! Compute perturbation for each Fourier component
278 do i = 1, mixlayer_perturb_nk
279 ! Generate random numbers for unit wavevector khat, random unit vector xi, and random mode phase phi
280 if (proc_rank == 0) then
281 call s_generate_random_perturbation(khat, xi, phi, i, y_cc(r))
282 end if
283
284#ifdef MFC_MPI
285 call mpi_bcast(khat, 3, mpi_p, 0, mpi_comm_world, ierr)
286 call mpi_bcast(xi, 3, mpi_p, 0, mpi_comm_world, ierr)
287 call mpi_bcast(phi, 1, mpi_p, 0, mpi_comm_world, ierr)
288#endif
289
290 ! Compute mode direction by two-time cross product
291 sig_tmp = f_cross(xi, khat)
292 sig_tmp = sig_tmp/sqrt(sum(sig_tmp**2._wp))
293 sig = f_cross(khat, sig_tmp)
294
295 ! Compute perturbation for each grid
296 do l = 0, p
297 do j = 0, m
298 q = sqrt(ek(i)/eksum)
299 alpha = k(i)*(khat(1)*x_cc(j) + khat(2)*y_cc(r) + khat(3)*z_cc(l)) + 2._wp*pi*phi
300 velfluc = 2._wp*q*sig*cos(alpha)
301 velfluc = matmul(lmat, velfluc)
302 q_prim_vf(eqn_idx%mom%beg)%sf(j, r, l) = q_prim_vf(eqn_idx%mom%beg)%sf(j, r, l) + velfluc(1)
303 q_prim_vf(eqn_idx%mom%beg + 1)%sf(j, r, l) = q_prim_vf(eqn_idx%mom%beg + 1)%sf(j, r, l) + velfluc(2)
304 q_prim_vf(eqn_idx%mom%beg + 2)%sf(j, r, l) = q_prim_vf(eqn_idx%mom%beg + 2)%sf(j, r, l) + velfluc(3)
305 end do
306 end do
307 end do
308 end do
309
310 end subroutine s_perturb_mixlayer
311
312 !> Generate deterministic pseudo-random wave vector, polarization, and phase for a perturbation mode.
313 subroutine s_generate_random_perturbation(khat, xi, phi, ik, yloc)
314
315 integer, intent(in) :: ik
316 real(wp), intent(in) :: yloc
317 real(wp), dimension(3), intent(out) :: khat, xi
318 real(wp), intent(out) :: phi
319 real(wp) :: theta, eta
320 integer :: seed, kfac, yfac
321
322 kfac = ik*amplifier
323 yfac = nint((sin(yloc) + 1._wp)*amplifier)
324 seed = nint(0.5_wp*modmul(kfac) + 0.5_wp*modmul(yfac))
325
326 call s_prng(theta, seed)
327 call s_prng(eta, seed)
328 khat = f_unit_vector(theta, eta)
329
330 call s_prng(theta, seed)
331 call s_prng(eta, seed)
332 xi = f_unit_vector(theta, eta)
333
334 call s_prng(phi, seed)
335
336 end subroutine s_generate_random_perturbation
337
338 !> Generate a unit vector uniformly distributed on the sphere from two random parameters.
339 function f_unit_vector(theta, eta) result(vec)
340
341 real(wp), intent(in) :: theta, eta
342 real(wp) :: zeta, xi
343 real(wp), dimension(3) :: vec
344
345 xi = 2._wp*pi*theta
346 zeta = acos(2._wp*eta - 1._wp)
347 vec(1) = sin(zeta)*cos(xi)
348 vec(2) = sin(zeta)*sin(xi)
349 vec(3) = cos(zeta)
350
351 end function f_unit_vector
352
353 !> Generate a pseudo-random number between 0 and 1 using a linear congruential generator.
354 subroutine s_prng(var, seed)
355
356 integer, intent(inout) :: seed
357 real(wp), intent(out) :: var
358
359 seed = mod(modmul(seed), modulus)
360 var = seed/real(modulus, wp)
361
362 end subroutine s_prng
363
364 !> Compute a modular multiplication step for the linear congruential pseudo-random number generator.
365 function modmul(a) result(val)
366
367 integer, intent(in) :: a
368 integer :: val
369 real(wp) :: x, y
370
371 x = (multiplier/real(modulus, wp))*a + (increment/real(modulus, wp))
372 y = nint((x - floor(x))*decimal_trim)/decimal_trim
373 val = nint(y*modulus)
374
375 end function modmul
376
377 !> Deallocate the temporary primitive variable array used by elliptic smoothing.
379
380 if (elliptic_smoothing) then
381 deallocate (q_prim_temp)
382 end if
383
384 end subroutine s_finalize_perturbation_module
385
386end module m_perturbation
integer, intent(in) k
integer, intent(in) j
integer, intent(in) l
Noncharacteristic and processor boundary condition application for ghost cells and buffer regions.
impure subroutine, public s_populate_variables_buffers(bc_type, q_prim_vf, pb_in, mv_in, q_t_sf)
Populate the buffers of the primitive variables based on the selected boundary conditions.
Shared derived types for field data, patch geometry, bubble dynamics, and MPI I/O structures.
Defines global parameters for the computational domain, simulation algorithm, and initial conditions.
real(wp) perturb_flow_mag
Magnitude of perturbation with perturb_flow flag.
real(wp) mixlayer_perturb_k0
Peak wavenumber for mixlayer perturbation (default: most unstable mode).
integer num_fluids
Number of different fluids present in the flow.
real(wp), dimension(:), allocatable y_cc
integer proc_rank
Rank of the local processor Number of cells in the x-, y- and z-coordinate directions.
integer sys_size
Number of unknowns in the system of equations.
type(simplex_noise_params) simplex_params
integer num_dims
Number of spatial dimensions.
real(wp), dimension(:), allocatable x_cc
Locations of cell-centers (cc) in x-, y- and z-directions, respectively.
integer mixlayer_perturb_nk
Number of Fourier modes for perturbation with mixlayer_perturb flag.
integer num_vels
Number of velocity components (different from num_dims for mhd).
integer perturb_sph_fluid
Fluid to be perturbed with perturb_sph flag.
real(wp), dimension(num_fluids_max) fluid_rho
real(wp), dimension(:), allocatable z_cc
type(ic_patch_parameters), dimension(num_patches_max) patch_icpp
IC patch parameters (max: num_patches_max).
real(wp) mixlayer_vel_coef
Coefficient for the hyperbolic tangent streamwise velocity profile.
type(eqn_idx_info) eqn_idx
All conserved-variable equation index ranges and scalars.
Utility routines for bubble model setup, coordinate transforms, array sampling, and special functions...
pure real(wp) function, dimension(3), public f_cross(a, b)
Compute the cross product of two vectors.
Broadcasts user inputs and decomposes the domain across MPI ranks for pre-processing.
Perturbs initial mean flow fields with random noise, mixing-layer instabilities, or simplex noise.
integer function modmul(a)
Compute a modular multiplication step for the linear congruential pseudo-random number generator.
impure subroutine s_perturb_sphere(q_prim_vf)
Randomly perturb partial density fields at the interface of a spherical volume fraction region.
impure subroutine s_perturb_surrounding_flow(q_prim_vf)
Add random noise to the velocity and void fraction of the surrounding flow field.
real(wp) function, dimension(3) f_unit_vector(theta, eta)
Generate a unit vector uniformly distributed on the sphere from two random parameters.
subroutine s_generate_random_perturbation(khat, xi, phi, ik, yloc)
Generate deterministic pseudo-random wave vector, polarization, and phase for a perturbation mode.
subroutine s_prng(var, seed)
Generate a pseudo-random number between 0 and 1 using a linear congruential generator.
real(wp), dimension(:,:,:,:), allocatable q_prim_temp
impure subroutine s_finalize_perturbation_module()
Deallocate the temporary primitive variable array used by elliptic smoothing.
impure subroutine s_elliptic_smoothing(q_prim_vf, bc_type, q_t_sf)
Iteratively smooth all primitive variable fields using a discrete elliptic (Laplacian) filter.
impure subroutine s_initialize_perturbation_module()
Allocate the temporary primitive variable array used by elliptic smoothing.
subroutine s_perturb_simplex(q_prim_vf)
Perturb velocity and volume fraction fields using multi-octave simplex noise.
subroutine s_perturb_mixlayer(q_prim_vf)
Compute velocity perturbations for a temporal mixing layer with a hyperbolic tangent mean streamwise ...
2D and 3D simplex noise generation for procedural initial condition perturbations
real(wp) function, public f_simplex2d(xin, yin)
Evaluate 2D simplex noise at the given coordinates and return a value in [-1, 1].
real(wp) function, public f_simplex3d(xin, yin, zin)
Evaluate 3D simplex noise at the given coordinates and return a value in [-1, 1].
Derived type annexing an integer scalar field (SF).
Derived type annexing a scalar field (SF).