Report Typos and Errors    
Semi-Lagrangian Library
Modular library for kinetic and gyrokinetic simulations of plasmas in fusion energy devices.
sll_m_poisson_3d_fem_fft.F90
Go to the documentation of this file.
2 
3 !+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 #include "sll_working_precision.h"
5 
6  use sll_m_low_level_bsplines, only: &
7  sll_s_uniform_bsplines_eval_basis
8 
9  use sll_m_constants, only : &
11 
12  use sll_m_fft
13 
16 
18 
19  implicit none
20 
21  public :: &
23 
24  private
25 !+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
26 
27 
28 
29 
31 
32  sll_int32 :: n_dofs(3)
33  sll_int32 :: degree(3)
34  sll_real64 :: delta_x(3)
35 
36  sll_real64, allocatable :: eig_values_dtm1d_1(:)
37  sll_real64, allocatable :: eig_values_dtm1d_2(:)
38  sll_real64, allocatable :: eig_values_dtm1d_3(:)
39  sll_comp64, allocatable :: eig_values_d1(:)
40  sll_comp64, allocatable :: eig_values_d2(:)
41  sll_comp64, allocatable :: eig_values_d3(:)
42  sll_real64, allocatable :: eig_values_mass_0_1(:)
43  sll_real64, allocatable :: eig_values_mass_0_2(:)
44  sll_real64, allocatable :: eig_values_mass_0_3(:)
45 
46  type(sll_t_fft) :: fft1
47  type(sll_t_fft) :: fft2
48  type(sll_t_fft) :: fft3
49  type(sll_t_fft) :: ifft1
50  type(sll_t_fft) :: ifft2
51  type(sll_t_fft) :: ifft3
52 
53  ! Scratch data
54  sll_comp64, allocatable :: array1d_x(:)
55  sll_comp64, allocatable :: array1d_y(:)
56  sll_comp64, allocatable :: array1d_z(:)
57  sll_comp64, allocatable :: scratch(:,:,:)
58  sll_comp64, allocatable :: scratchx(:,:,:)
59  sll_comp64, allocatable :: scratchy(:,:,:)
60 
61  contains
62  procedure :: compute_phi_from_rho => compute_phi_from_rho_fft
63  procedure :: compute_e_from_rho => compute_e_from_rho_fft
64  procedure :: free => free_fft
65  procedure :: init => init_fft
66  procedure :: compute_rhs_from_function => compute_rhs_from_function_fft
67 
69 
70  abstract interface
71 
73  use sll_m_working_precision ! can't pass a header file because the
74  ! preprocessor prevents double inclusion.
75  ! It is very rare.
76  sll_real64 :: sll_i_function_3d_real64
77  sll_real64, intent(in) :: x(3)
78  end function sll_i_function_3d_real64
79  end interface
80 
81 contains
82 
83  subroutine compute_e_from_rho_fft( self, rho, efield )
84  class(sll_t_poisson_3d_fem_fft), intent( inout ) :: self
85  sll_real64, intent(in) :: rho(:)
86  sll_real64, intent(out) :: efield(:)
87 
88  sll_int32 :: n_dofs(3), ntotal
89  sll_int32 :: i,j,k, ind
90  sll_real64 :: factor1jk, factor2jk, eig_val
91 
92  n_dofs = self%n_dofs
93  ntotal = product(n_dofs)
94 
95  ! Compute Fourier transform
96  ind=0
97  do k=1,n_dofs(3)
98  do j=1,n_dofs(2)
99  do i=1,n_dofs(1)
100  ind = ind+1
101  self%array1d_x(i) = cmplx( rho(ind), 0_f64, kind=f64)
102  end do
103  call sll_s_fft_exec_c2c_1d( self%fft1, self%array1d_x, self%array1d_x)
104  do i=1,n_dofs(1)
105  self%scratch(i,j,k) = self%array1d_x(i)
106  end do
107  end do
108  end do
109  do k=1,n_dofs(3)
110  do i=1,n_dofs(1)
111  do j=1,n_dofs(2)
112  self%array1d_y(j) = self%scratch(i,j,k)
113  end do
114  call sll_s_fft_exec_c2c_1d( self%fft2, self%array1d_y, self%array1d_y)
115  do j=1,n_dofs(2)
116  self%scratch(i,j,k) = self%array1d_y(j)
117  end do
118  end do
119  end do
120  do j=1,n_dofs(2)
121  do i=1,n_dofs(1)
122  do k=1,n_dofs(3)
123  self%array1d_z(k) = self%scratch(i,j,k)
124  end do
125  call sll_s_fft_exec_c2c_1d( self%fft3, self%array1d_z, self%array1d_z)
126  do k=1,n_dofs(3)
127  self%scratch(i,j,k) = self%array1d_z(k)
128  end do
129  end do
130  end do
131 
132 
133  ! Apply inverse matrix of eigenvalues on mode
134  do k=1,n_dofs(3)
135  do j=1,n_dofs(2)
136  factor1jk = self%eig_values_dtm1d_2(j)*self%eig_values_mass_0_3(k) + &
137  self%eig_values_dtm1d_3(k)*self%eig_values_mass_0_2(j)
138  factor2jk = self%eig_values_mass_0_2(j) * self%eig_values_mass_0_3(k)
139  do i=1,n_dofs(1)
140  if ( i == 1 .and. j==1 .and. k==1 ) then
141  self%scratch(i,j,k) = cmplx(0.0_f64, 0.0_f64, f64)
142  else
143  eig_val = factor2jk * self%eig_values_dtm1d_1(i) + &
144  factor1jk * self%eig_values_mass_0_1(i)
145  self%scratch(i,j,k) = self%scratch(i,j,k) / cmplx(eig_val, 0.0_f64 , f64)
146  end if
147 
148  self%scratchx(i,j,k) = -self%scratch(i,j,k)* self%eig_values_d1(i)
149  self%scratchy(i,j,k) = -self%scratch(i,j,k)* self%eig_values_d2(j)
150  self%scratch(i,j,k) = -self%scratch(i,j,k)* self%eig_values_d3(k)
151 
152  end do
153  end do
154  end do
155 
156 
157  ! Compute inverse Fourier transfrom
158 
159 
160  call ifft3d( self, self%scratchx, efield(1:ntotal) )
161  call ifft3d( self, self%scratchy, efield(1+ntotal:2*ntotal) )
162  call ifft3d( self, self%scratch, efield(1+2*ntotal:3*ntotal) )
163 
164 
165  end subroutine compute_e_from_rho_fft
166 
167  subroutine compute_phi_from_rho_fft( self, rho, phi )
168  class(sll_t_poisson_3d_fem_fft), intent( inout ) :: self
169  sll_real64, intent(in) :: rho(:)
170  sll_real64, intent(out) :: phi(:)
171 
172  sll_int32 :: n_dofs(3)
173  sll_int32 :: i,j,k, ind
174  sll_real64 :: factor1jk, factor2jk, eig_val
175 
176 
177  n_dofs = self%n_dofs
178 
179  ! Compute Fourier transform
180  ind=0
181  do k=1,n_dofs(3)
182  do j=1,n_dofs(2)
183  do i=1,n_dofs(1)
184  ind = ind+1
185  self%array1d_x(i) = cmplx( rho(ind), 0_f64, kind=f64)
186  end do
187  call sll_s_fft_exec_c2c_1d( self%fft1, self%array1d_x, self%array1d_x)
188  do i=1,n_dofs(1)
189  self%scratch(i,j,k) = self%array1d_x(i)
190  end do
191  end do
192  end do
193  do k=1,n_dofs(3)
194  do i=1,n_dofs(1)
195  do j=1,n_dofs(2)
196  self%array1d_y(j) = self%scratch(i,j,k)
197  end do
198  call sll_s_fft_exec_c2c_1d( self%fft2, self%array1d_y, self%array1d_y)
199  do j=1,n_dofs(2)
200  self%scratch(i,j,k) = self%array1d_y(j)
201  end do
202  end do
203  end do
204  do j=1,n_dofs(2)
205  do i=1,n_dofs(1)
206  do k=1,n_dofs(3)
207  self%array1d_z(k) = self%scratch(i,j,k)
208  end do
209  call sll_s_fft_exec_c2c_1d( self%fft3, self%array1d_z, self%array1d_z)
210  do k=1,n_dofs(3)
211  self%scratch(i,j,k) = self%array1d_z(k)
212  end do
213  end do
214  end do
215 
216 
217  ! Apply inverse matrix of eigenvalues on mode
218  do k=1,n_dofs(3)
219  do j=1,n_dofs(2)
220  factor1jk = self%eig_values_dtm1d_2(j)*self%eig_values_mass_0_3(k) + &
221  self%eig_values_dtm1d_3(k)*self%eig_values_mass_0_2(j)
222  factor2jk = self%eig_values_mass_0_2(j) * self%eig_values_mass_0_3(k)
223  do i=1,n_dofs(1)
224  if ( i == 1 .and. j==1 .and. k==1 ) then
225  self%scratch(i,j,k) = cmplx(0.0_f64, 0.0_f64, f64)
226  else
227  eig_val = factor2jk * self%eig_values_dtm1d_1(i) + &
228  factor1jk * self%eig_values_mass_0_1(i)
229  self%scratch(i,j,k) = self%scratch(i,j,k) / cmplx(eig_val, 0.0_f64, f64)
230  end if
231  end do
232  end do
233  end do
234 
235  call ifft3d( self, self%scratch, phi )
236 
237 
238  end subroutine compute_phi_from_rho_fft
239 
240 
241 
242  subroutine free_fft( self )
243  class(sll_t_poisson_3d_fem_fft), intent( inout ) :: self
244 
245  call sll_s_fft_free( self%fft1 )
246 
247 
248  end subroutine free_fft
249 
250  subroutine init_fft( self, n_dofs, degree, delta_x)
251  class(sll_t_poisson_3d_fem_fft), intent( out ) :: self
252  sll_int32, intent( in ) :: n_dofs(3)
253  sll_int32, intent( in ) :: degree(3)
254  sll_real64, intent( in ) :: delta_x(3)
255 
256 
257  sll_real64 :: mass_line0_1(degree(1)+1)
258  sll_real64 :: mass_line1_1(degree(1))
259  sll_real64 :: mass_line0_2(degree(2)+1)
260  sll_real64 :: mass_line1_2(degree(2))
261  sll_real64 :: mass_line0_3(degree(3)+1)
262  sll_real64 :: mass_line1_3(degree(3))
263  sll_real64 :: eig_values_mass_1_1(n_dofs(1))
264  sll_real64 :: eig_values_mass_1_2(n_dofs(2))
265  sll_real64 :: eig_values_mass_1_3(n_dofs(3))
266  sll_real64 :: angle
267  sll_int32 :: j
268 
269  self%n_dofs = n_dofs
270 
271  allocate(self%array1d_x(n_dofs(1)))
272  allocate(self%array1d_y(n_dofs(2)))
273  allocate(self%array1d_z(n_dofs(3)))
274  allocate(self%scratch(n_dofs(1), n_dofs(2), n_dofs(3)))
275  allocate(self%scratchx(n_dofs(1), n_dofs(2), n_dofs(3)))
276  allocate(self%scratchy(n_dofs(1), n_dofs(2), n_dofs(3)))
277 
278 
279  call sll_s_fft_init_c2c_1d( self%fft1, n_dofs(1), self%array1d_x, self%array1d_x, sll_p_fft_forward)
280  call sll_s_fft_init_c2c_1d( self%fft2, n_dofs(2), self%array1d_y, self%array1d_y, sll_p_fft_forward)
281  call sll_s_fft_init_c2c_1d( self%fft3, n_dofs(3), self%array1d_z, self%array1d_z, sll_p_fft_forward)
282  call sll_s_fft_init_c2c_1d( self%ifft1, n_dofs(1), self%array1d_x, self%array1d_x, &
283  sll_p_fft_backward, normalized=.true.)
284  call sll_s_fft_init_c2c_1d( self%ifft2, n_dofs(2), self%array1d_y, self%array1d_y, &
285  sll_p_fft_backward, normalized=.true.)
286  call sll_s_fft_init_c2c_1d( self%ifft3, n_dofs(3), self%array1d_z, self%array1d_z, &
287  sll_p_fft_backward, normalized=.true.)
288 
289  ! Eigenvalues of mass matrices
290  call sll_s_spline_fem_mass_line( degree(1), mass_line0_1 )
291  call sll_s_spline_fem_mass_line( degree(2), mass_line0_2 )
292  call sll_s_spline_fem_mass_line( degree(3), mass_line0_3 )
293  call sll_s_spline_fem_mass_line( degree(1)-1, mass_line1_1 )
294  call sll_s_spline_fem_mass_line( degree(2)-1, mass_line1_2 )
295  call sll_s_spline_fem_mass_line( degree(3)-1, mass_line1_3 )
296 
297 
298  allocate( self%eig_values_mass_0_1(n_dofs(1)) )
299  call sll_s_spline_fem_compute_mass_eig( n_dofs(1), degree(1), mass_line0_1*delta_x(1), self%eig_values_mass_0_1 )
300  call sll_s_spline_fem_compute_mass_eig( n_dofs(1), degree(1)-1, mass_line1_1*delta_x(1), eig_values_mass_1_1 )
301 
302  allocate( self%eig_values_mass_0_2(n_dofs(2)) )
303  call sll_s_spline_fem_compute_mass_eig( n_dofs(2), degree(2), mass_line0_2*delta_x(2), self%eig_values_mass_0_2 )
304  call sll_s_spline_fem_compute_mass_eig( n_dofs(2), degree(2)-1, mass_line1_2*delta_x(2), eig_values_mass_1_2 )
305 
306  allocate( self%eig_values_mass_0_3(n_dofs(3)) )
307  call sll_s_spline_fem_compute_mass_eig( n_dofs(3), degree(3), mass_line0_3*delta_x(3), self%eig_values_mass_0_3 )
308  call sll_s_spline_fem_compute_mass_eig( n_dofs(3), degree(3)-1, mass_line1_3*delta_x(3), eig_values_mass_1_3 )
309 
310 
311  allocate( self%eig_values_d1(n_dofs(1)) )
312  allocate( self%eig_values_dtm1d_1(n_dofs(1)) )
313 
314  self%eig_values_d1(1) = cmplx(0.0_f64, 0.0_f64, f64)
315  self%eig_values_dtm1d_1(1) = 0.0_f64
316  do j=2,n_dofs(1)
317  angle = sll_p_twopi*real(j-1,f64)/real(n_dofs(1), f64)
318 
319  self%eig_values_d1(j) = cmplx((1.0_f64 - cos(angle))/delta_x(1),sin(angle)/delta_x(1), f64 )
320  self%eig_values_dtm1d_1(j) = 2.0_f64/delta_x(1)**2*(1.0_f64-cos(angle))* eig_values_mass_1_1(j)
321 
322  end do
323 
324  allocate( self%eig_values_d2(n_dofs(2)) )
325  allocate( self%eig_values_dtm1d_2(n_dofs(2)) )
326 
327  self%eig_values_d2(1) = cmplx(0.0_f64, 0.0_f64, f64)
328  self%eig_values_dtm1d_2(1) = 0.0_f64
329  do j=2,n_dofs(2)
330  angle = sll_p_twopi*real(j-1,f64)/real(n_dofs(2), f64)
331 
332  self%eig_values_d2(j) = cmplx((1.0_f64 - cos(angle))/delta_x(2),sin(angle)/delta_x(2), f64 )
333  self%eig_values_dtm1d_2(j) = 2.0_f64/delta_x(2)**2*(1.0_f64-cos(angle))* eig_values_mass_1_2(j)
334 
335  end do
336 
337  allocate( self%eig_values_d3(n_dofs(3)) )
338  allocate( self%eig_values_dtm1d_3(n_dofs(3)) )
339 
340 
341  self%eig_values_d3(1) = cmplx(0.0_f64, 0.0_f64, f64)
342  self%eig_values_dtm1d_3(1) = 0.0_f64
343  do j=2,n_dofs(3)
344  angle = sll_p_twopi*real(j-1,f64)/real(n_dofs(3), f64)
345 
346  self%eig_values_d3(j) = cmplx((1.0_f64 - cos(angle))/delta_x(3),sin(angle)/delta_x(3), f64 )
347  self%eig_values_dtm1d_3(j) = 2.0_f64/delta_x(3)**2*(1.0_f64-cos(angle))* eig_values_mass_1_3(j)
348 
349  end do
350 
351  self%degree = degree
352  self%delta_x = delta_x
353 
354  end subroutine init_fft
355 
356 
357 
360  subroutine compute_rhs_from_function_fft(self, func, coefs_dofs)
361  class(sll_t_poisson_3d_fem_fft) :: self
362  procedure(sll_i_function_3d_real64) :: func
363  sll_real64, intent(out) :: coefs_dofs(:) ! Finite Element right-hand-side
364 
365  ! local variables
366  sll_int32 :: i1, i2, i3,j1, j2, j3, k1, k2, k3, k, counter
367  sll_real64 :: coef
368  sll_real64 :: xw_gauss_d1(2,self%degree(1)+1)
369  sll_real64 :: xw_gauss_d2(2,self%degree(2)+1)
370  sll_real64 :: xw_gauss_d3(2,self%degree(3)+1)
371  sll_real64 :: bspl_d1(self%degree(1)+1, self%degree(1)+1)
372  sll_real64 :: bspl_d2(self%degree(2)+1, self%degree(2)+1)
373  sll_real64 :: bspl_d3(self%degree(3)+1, self%degree(3)+1)
374  sll_real64 :: volume
375 
376  volume = product(self%delta_x)
377 
378  ! take enough Gauss points so that projection is exact for splines of degree deg
379  ! rescale on [0,1] for compatibility with B-splines
380  xw_gauss_d1 = sll_f_gauss_legendre_points_and_weights(self%degree(1)+1, 0.0_f64, 1.0_f64)
381  ! Compute bsplines at gauss_points
382  do k=1,self%degree(1)+1
383  call sll_s_uniform_bsplines_eval_basis(self%degree(1),xw_gauss_d1(1,k), bspl_d1(k,:))
384  end do
385 
386  xw_gauss_d2 = sll_f_gauss_legendre_points_and_weights(self%degree(2)+1, 0.0_f64, 1.0_f64)
387  ! Compute bsplines at gauss_points
388  do k=1,self%degree(2)+1
389  call sll_s_uniform_bsplines_eval_basis(self%degree(2),xw_gauss_d2(1,k), bspl_d2(k,:))
390  end do
391 
392  xw_gauss_d3 = sll_f_gauss_legendre_points_and_weights(self%degree(3)+1, 0.0_f64, 1.0_f64)
393  ! Compute bsplines at gauss_points
394  do k=1,self%degree(3)+1
395  call sll_s_uniform_bsplines_eval_basis(self%degree(3),xw_gauss_d3(1,k), bspl_d3(k,:))
396  end do
397 
398 
399 
400  counter = 1
401  ! Compute coefs_dofs = int f(x)N_i(x)
402  do i3 = 1, self%n_dofs(3)
403  do i2 = 1, self%n_dofs(2)
404  do i1 = 1, self%n_dofs(1)
405  coef=0.0_f64
406  ! loop over support of B spline
407  do j1 = 1, self%degree(1)+1
408  do j2 = 1, self%degree(2)+1
409  do j3 = 1, self%degree(3)+1
410  ! loop over Gauss points
411  do k1=1, self%degree(1)+1
412  do k2=1, self%degree(2)+1
413  do k3=1, self%degree(3)+1
414  coef = coef + xw_gauss_d1(2,k1)* xw_gauss_d2(2,k2)* &
415  xw_gauss_d3(2,k3) *&
416  func([self%delta_x(1)*(xw_gauss_d1(1,k1) + real(i1 + j1 - 2,f64)), self%delta_x(2)*(xw_gauss_d2(1,k2) + real(i2 + j2 - 2,f64)), self%delta_x(3)*(xw_gauss_d3(1,k3) + real(i3 + j3 - 2,f64))] ) * &
417  bspl_d1(k1,self%degree(1)+2-j1)*&
418  bspl_d2(k2,self%degree(2)+2-j2)*&
419  bspl_d3(k3,self%degree(3)+2-j3)
420 
421  enddo
422  enddo
423  end do
424  end do
425  end do
426  end do
427  ! rescale by cell size
428  coefs_dofs(counter) = coef*volume
429  counter = counter+1
430  enddo
431  end do
432  end do
433 
434  end subroutine compute_rhs_from_function_fft
435 
436 
438 
439  subroutine ifft3d( self, inval, outval )
440  class(sll_t_poisson_3d_fem_fft),intent( inout) :: self
441  sll_comp64, intent( inout ) :: inval(:,:,:)
442  sll_real64, intent( out ) :: outval(:)
443 
444  sll_int32 :: i,j,k,ind
445  sll_int32 :: n_dofs(3)
446 
447  n_dofs = self%n_dofs
448 
449  ! Compute inverse Fourier transfrom
450  do j=1,n_dofs(2)
451  do i=1,n_dofs(1)
452  do k=1,n_dofs(3)
453  self%array1d_z(k) = inval(i,j,k)
454  end do
455  call sll_s_fft_exec_c2c_1d( self%ifft3, self%array1d_z, self%array1d_z)
456  do k=1,n_dofs(3)
457  inval(i,j,k) = self%array1d_z(k)
458  end do
459  end do
460  end do
461 
462  do k=1,n_dofs(3)
463  do i=1,n_dofs(1)
464  do j=1,n_dofs(2)
465  self%array1d_y(j) = inval(i,j,k)
466  end do
467  call sll_s_fft_exec_c2c_1d( self%ifft2, self%array1d_y, self%array1d_y)
468  do j=1,n_dofs(2)
469  inval(i,j,k) = self%array1d_y(j)
470  end do
471  end do
472  end do
473 
474  ind=0
475  do k=1,n_dofs(3)
476  do j=1,n_dofs(2)
477  do i=1,n_dofs(1)
478  self%array1d_x(i) = inval(i,j,k)
479  end do
480  call sll_s_fft_exec_c2c_1d( self%ifft1, self%array1d_x, self%array1d_x)
481 
482  do i=1,n_dofs(1)
483  ind = ind+1
484  outval(ind) = real( self%array1d_x(i), kind=f64 )
485  end do
486  end do
487  end do
488 
489  end subroutine ifft3d
490 
491 
492 end module sll_m_poisson_3d_fem_fft
Fortran module where set some physical and mathematical constants.
real(kind=f64), parameter, public sll_p_twopi
FFT interface for FFTW.
subroutine, public sll_s_fft_free(plan)
Delete a plan.
integer, parameter, public sll_p_fft_forward
Flag to specify forward FFT (negative sign) [value -1].
subroutine, public sll_s_fft_exec_c2c_1d(plan, array_in, array_out)
Compute fast Fourier transform in complex to complex mode.
subroutine, public sll_s_fft_init_c2c_1d(plan, nx, array_in, array_out, direction, normalized, aligned, optimization)
Create new 1d complex to complex plan.
integer, parameter, public sll_p_fft_backward
Flag to specify backward FFT (positive sign) [value 1].
real(kind=f64) function, dimension(2, 1:npoints), public sll_f_gauss_legendre_points_and_weights(npoints, a, b)
Returns a 2d array of size (2,npoints) containing gauss-legendre points and weights in the interval [...
Low level arbitrary degree splines.
subroutine compute_phi_from_rho_fft(self, rho, phi)
subroutine init_fft(self, n_dofs, degree, delta_x)
subroutine compute_rhs_from_function_fft(self, func, coefs_dofs)
Compute the FEM right-hand-side for a given function f and periodic splines of given degree Its compo...
subroutine ifft3d(self, inval, outval)
Helper function for 3d fft.
subroutine compute_e_from_rho_fft(self, rho, efield)
Utilites for Maxwell solver's with spline finite elements.
subroutine, public sll_s_spline_fem_mass_line(degree, mass_line)
Computes the mass line for a mass matrix with degree splines.
subroutine, public sll_s_spline_fem_compute_mass_eig(n_cells, degree, mass_line, eig_values_mass)
Compute eigenvalues of mass matrix with given line mass_line.
Module to select the kind parameter.
Type for FFT plan in SeLaLib.
    Report Typos and Errors