Report Typos and Errors    
Semi-Lagrangian Library
Modular library for kinetic and gyrokinetic simulations of plasmas in fusion energy devices.
sll_m_maxwell_1d_fem.F90
Go to the documentation of this file.
1 
11  !+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
12 #include "sll_memory.h"
13 #include "sll_working_precision.h"
14 
15  use sll_m_constants, only: &
16  sll_p_pi, &
18 
19  use sll_m_fft, only: &
20  sll_t_fft, &
26 
29 
32 
33  use sll_m_linear_solver_mgmres, only : &
35 
36  use sll_m_low_level_bsplines, only: &
37  sll_s_uniform_bsplines_eval_basis
38 
39  use sll_m_matrix_csr, only: &
41 
42  use sll_m_maxwell_1d_base, only: &
45 
46  use sll_m_spline_fem_utilities, only: &
52 
56 
57 
58  implicit none
59 
60  public :: &
62 
63  private
64  !+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
65 
68 
69  type( sll_t_linear_operator_schur_eb_1d ) :: linear_op_schur_eb
70  type( sll_t_linear_solver_mgmres ) :: linear_solver_schur_eb
71 
72  type(sll_t_matrix_csr) :: mass0
73  type(sll_t_matrix_csr) :: mass1
74  type(sll_t_matrix_csr) :: mixed_mass
75 
76  sll_real64, allocatable :: mass_0(:)
77  sll_real64, allocatable :: mass_1(:)
78  sll_real64, allocatable :: eig_mass0(:)
79  sll_real64, allocatable :: eig_mass1(:)
80  sll_real64, allocatable :: eig_weak_ampere(:)
81  sll_real64, allocatable :: eig_weak_poisson(:)
82  sll_real64, allocatable :: eig_strong_poisson(:)
83  sll_real64, allocatable :: eig_splines0(:)
84  sll_real64, allocatable :: eig_splines1(:)
85  sll_real64, allocatable :: eig_isplines0(:)
86  sll_real64, allocatable :: eig_itsplines0(:)
87  sll_real64, allocatable :: eig_kit_mass_0(:)
88  sll_real64, allocatable :: eig_kit_mass_1(:)
89 
90  sll_real64, allocatable :: eig_eb(:)
91  sll_real64, allocatable :: eig_mass0_inv(:)
92  sll_real64, allocatable :: eig_mass1_inv(:)
93 
94  type(sll_t_fft) :: plan_fw
95  type(sll_t_fft) :: plan_bw
96  sll_real64, allocatable :: wsave(:)
97  sll_real64, allocatable :: work(:)
98 
99  sll_real64 :: force_sign = 1._f64
100  logical :: adiabatic_electrons
101 
102 
103  contains
104  procedure :: &
105  compute_e_from_b => sll_s_compute_e_from_b_1d_fem
106  procedure :: &
107  compute_b_from_e => sll_s_compute_b_from_e_1d_fem
108  procedure :: &
109  compute_curl_part => sll_s_compute_curl_part_1d_fem
110  procedure :: &
111  compute_e_from_rho => sll_s_compute_e_from_rho_1d_fem
112  procedure :: &
113  compute_rho_from_e => compute_rho_from_e_1d_fem
114  procedure :: &
115  compute_e_from_j => choose_interpolation
116  procedure :: &
117  compute_phi_from_rho => compute_phi_from_rho_1d_fem
118  procedure :: &
119  compute_phi_from_j => compute_phi_from_j_1d_fem
120  procedure :: &
121  compute_rhs_from_function => sll_s_compute_fem_rhs
122  procedure :: &
123  l2projection => l2projection_1d_fem
124  procedure :: &
125  l2norm_squared => l2norm_squared_1d_fem
126  procedure :: &
127  inner_product => inner_product_1d_fem
128  procedure :: &
129  init => init_1d_fem
130  procedure :: &
131  free => free_1d_fem
132  procedure :: &
133  multiply_g
134  procedure :: &
136  procedure :: &
137  invert_mass => invert_mass_1d_fem
138  procedure :: &
139  multiply_mass => multiply_mass_1d_fem
140  procedure :: &
141  transform_dofs => transform_dofs_1d_fem
142  procedure :: &
144  procedure :: &
145  solve_e_b => solve_e_b_1d_fem
146 
147 
148  end type sll_t_maxwell_1d_fem
149 
150 contains
151 
153  subroutine sll_s_compute_e_from_b_1d_fem(self, delta_t, field_in, field_out)
154  class(sll_t_maxwell_1d_fem) :: self
155  sll_real64, intent(in) :: delta_t
156  sll_real64, intent(in) :: field_in(:)
157  sll_real64, intent(inout) :: field_out(:)
158 
159 
160  if(self%strong_ampere .eqv. .true.) then
161  call strong_curl(self, delta_t, field_in, field_out)
162  else
163  call weak_curl(self, delta_t, field_in, field_out)
164  end if
165 
166  end subroutine sll_s_compute_e_from_b_1d_fem
167 
168 
169  subroutine sll_s_compute_b_from_e_1d_fem(self, delta_t, field_in, field_out)
170  class(sll_t_maxwell_1d_fem) :: self
171  sll_real64, intent(in) :: delta_t
172  sll_real64, intent(in) :: field_in(:)
173  sll_real64, intent(inout) :: field_out(:)
174 
175  if(self%strong_ampere .eqv. .true.) then
176  call weak_curl(self, delta_t, field_in, field_out)
177  else
178  call strong_curl(self, delta_t, field_in, field_out)
179  end if
180 
181  end subroutine sll_s_compute_b_from_e_1d_fem
182 
183 
184  subroutine sll_s_compute_curl_part_1d_fem( self, delta_t, efield, bfield, betar )
185  class(sll_t_maxwell_1d_fem) :: self
186  sll_real64, intent(in) :: delta_t
187  sll_real64, intent(inout) :: efield(:)
188  sll_real64, intent(inout) :: bfield(:)
189  sll_real64, optional :: betar
190  !local variables
191  sll_real64 :: factor
192 
193  if( present(betar) ) then
194  factor = betar
195  else
196  factor = 1._f64
197  end if
198 
199  self%work = 0._f64
200  self%wsave = 0._f64
201 
202  ! Compute D^T M2 b
203  call self%multiply_mass( bfield, self%work, self%s_deg_1 )
204  call self%multiply_gt( self%work, self%wsave )
205 
206  self%linear_op_schur_eb%sign = -delta_t**2*0.25_f64*factor
207  call self%linear_op_schur_eb%dot( efield, self%work )
208  self%work = self%work + delta_t*factor*self%wsave
209 
210  ! Save efield dofs from previous time step for B field update
211  self%wsave = efield
212 
213  ! Invert Schur complement matrix
214  self%linear_op_schur_eb%sign = delta_t**2*0.25_f64*factor
215  call self%linear_solver_schur_eb%set_guess( efield )
216  call self%linear_solver_schur_eb%solve( self%work, efield )
217 
218  ! Update B field
219  self%wsave = self%wsave + efield
220  call self%compute_B_from_E( delta_t*0.5_f64, self%wsave, bfield )
221 
222  end subroutine sll_s_compute_curl_part_1d_fem
223 
224 
226  subroutine weak_curl(self, delta_t, field_in, field_out)
227  class(sll_t_maxwell_1d_fem) :: self
228  sll_real64, intent(in) :: delta_t
229  sll_real64, intent(in) :: field_in(:)
230  sll_real64, intent(inout) :: field_out(:)
231  ! local variables
232  sll_real64 :: coef
233 
234  ! Compute potential weak curl of Bz using eigenvalue of circulant inverse matrix
235  call solve_circulant(self, self%eig_weak_ampere, field_in, self%work)
236  ! Update bz from self value
237  coef = delta_t/self%delta_x
238  field_out = field_out + coef*self%work
239 
240  end subroutine weak_curl
241 
242 
245  !subroutine sll_s_compute_b_from_e_1d_fem
246  subroutine strong_curl(self, delta_t, field_in, field_out)
247  class(sll_t_maxwell_1d_fem) :: self
248  sll_real64, intent(in) :: delta_t
249  sll_real64, intent(in) :: field_in(:)
250  sll_real64, intent(inout) :: field_out(:)
251  ! local variables
252  sll_real64 :: coef
253  sll_int32 :: i
254 
255  coef = delta_t/self%delta_x
256  ! relation betwen spline coefficients for strong Ampere
257  do i=2,self%n_cells
258  field_out(i) = field_out(i) + coef * ( field_in(i-1) - field_in(i) )
259  end do
260  ! treat Periodic point
261  field_out(1) = field_out(1) + coef * ( field_in(self%n_cells) - field_in(1) )
262  end subroutine strong_curl
263 
264 
265  subroutine sll_s_compute_e_from_rho_1d_fem(self, field_in, field_out )
266  class(sll_t_maxwell_1d_fem) :: self
267  sll_real64, intent(in) :: field_in(:)
268  sll_real64, intent(out) :: field_out(:)
269  ! local variables
270  sll_int32 :: i
271 
272  self%wsave = self%force_sign * field_in
273 
274  if ( self%strong_ampere .eqv. .false. ) then
275  ! Compute potential phi from rho, using eigenvalue of circulant inverse matrix
276  call solve_circulant(self, self%eig_weak_poisson, self%wsave, self%work)
277  ! Compute spline coefficients of Ex from those of phi
278  do i=2,self%n_cells
279  field_out(i) = (self%work(i-1) - self%work(i)) !* (self%delta_x)
280  end do
281  ! treat Periodic point
282  field_out(1) = (self%work(self%n_cells) - self%work(1)) !* (self%delta_x)
283  else
284  call solve_circulant( self, self%eig_isplines0, self%wsave, self%work )
285  call solve_circulant( self, self%eig_strong_poisson, self%work, field_out )
286 
287  end if
288 
289 
290  end subroutine sll_s_compute_e_from_rho_1d_fem
291 
292 
294  subroutine compute_rho_from_e_1d_fem(self, field_in, field_out)
295  class(sll_t_maxwell_1d_fem) :: self
296  sll_real64, intent(in) :: field_in(:)
297  sll_real64, intent(out) :: field_out(:)
298 
299  sll_int32 :: i
300 
301  if ( self%strong_ampere .eqv. .true. ) then
302 
303  ! The differentiation matrix
304  do i=2,self%n_cells
305  self%work(i) = self%force_sign * ( field_in(i) - field_in(i-1) )/ self%delta_x
306  end do
307  ! treat Periodic point
308  self%work(1) = self%force_sign * ( field_in(1) - field_in(self%n_cells) )/ self%delta_x
309 
310  call solve_circulant( self, self%eig_splines0 , self%work, field_out )
311  else
312  call solve_circulant(self, self%eig_mass1, field_in, self%work)
313 
314  ! relation betwen spline coefficients for strong Ampere
315  do i=1,self%n_cells-1
316  field_out(i) = self%force_sign * ( self%work(i+1) - self%work(i) )
317  end do
318  ! treat Periodic point
319  field_out(self%n_cells) = self%force_sign * ( self%work(1) - self%work(self%n_cells) )
320  end if
321 
322  end subroutine compute_rho_from_e_1d_fem
323 
324 
326  subroutine choose_interpolation(self, current, component, E)
327  class(sll_t_maxwell_1d_fem) :: self
328  sll_real64,dimension(:),intent(in) :: current
329  sll_int32, intent(in) :: component
330  sll_real64,dimension(:),intent(inout) :: e
331 
332  if(self%strong_ampere .eqv. .true.) then
333  call compute_e_from_j_1d_fem_shape(self, current, component, e)
334  else
335  call compute_e_from_j_1d_fem(self, current, component, e)
336  end if
337 
338  end subroutine choose_interpolation
339 
340 
342  subroutine compute_e_from_j_1d_fem(self, current, component, E)
343  class(sll_t_maxwell_1d_fem) :: self
344  sll_real64,dimension(:),intent(in) :: current
345  sll_int32, intent(in) :: component
346  sll_real64,dimension(:),intent(inout) :: e
347 
348  ! Multiply by inverse mass matrix using the eigenvalues of the circulant inverse matrix
349  if (component == 1) then
350  call solve_circulant(self, self%eig_mass1_inv, current, self%work)
351  elseif (component == 2) then
352  call solve_circulant(self, self%eig_mass0_inv, current, self%work)
353  else
354  print*, 'Component ', component, 'not implemented in compute_E_from_j_1d_fem.'
355  end if
356 
357 
358  ! Update the electric field and scale
359  e = e - self%force_sign * self%work/self%delta_x
360 
361  end subroutine compute_e_from_j_1d_fem
362 
363  subroutine compute_e_from_j_1d_fem_shape(self, current, component, E)
364  class(sll_t_maxwell_1d_fem) :: self
365  sll_real64,dimension(:),intent(in) :: current
366  sll_int32, intent(in) :: component
367  sll_real64,dimension(:),intent(inout) :: e
368 
369  ! Multiply by inverse mass matrix using the eigenvalues of the circulant inverse matrix
370  call solve_circulant(self, self%eig_isplines0, current, self%work)
371 
372  ! Update the electric field and scale
373  e = e - self%work!/self%delta_x
374 
375  end subroutine compute_e_from_j_1d_fem_shape
376 
377 
379  class(sll_t_maxwell_1d_fem) :: self
380  sll_real64,dimension(:),intent(in) :: in
381  sll_real64,dimension(:),intent(inout) :: out
382 
383  ! Multiply by inverse mass matrix using the eigenvalues of the circulant inverse matrix
384  call solve_circulant(self, self%eig_itsplines0, in, out)
385 
387 
388 
390  subroutine solve_e_b_1d_fem(self, delta_t, bfield, efield)
391  class(sll_t_maxwell_1d_fem) :: self
392  sll_real64, intent(in) :: delta_t
393  sll_real64,intent(in) :: bfield(:)
394  sll_real64,intent(inout) :: efield(:)
395 
396 
397  call self%compute_e_from_b( delta_t, bfield, efield )
398 
399  call solve_circulant( self, self%eig_eb, efield, self%work )
400  efield = self%work
401 
402  end subroutine solve_e_b_1d_fem
403 
404 
405  subroutine solve_circulant(self, eigvals, rhs, res)
406  class(sll_t_maxwell_1d_fem) :: self
407  sll_real64, intent(in) :: eigvals(:)
408  sll_real64, intent(in) :: rhs(:)
409  sll_real64, intent(out) :: res(:)
410  ! local variables
411  sll_int32 :: k
412  sll_real64 :: re, im
413 
414  ! Compute res from rhs, using eigenvalue of circulant matrix
415  res = rhs
416  ! Forward FFT
417  call sll_s_fft_exec_r2r_1d ( self%plan_fw, res, self%wsave )
418  self%wsave(1) = self%wsave(1) * eigvals(1)
419  do k=2, (self%n_cells+1)/2
420  re = self%wsave(k) * eigvals(k) - &
421  self%wsave(self%n_cells-k+2) * eigvals(self%n_cells-k+2)
422  im = self%wsave(k) * eigvals(self%n_cells-k+2) + &
423  self%wsave(self%n_cells-k+2) * eigvals(k)
424  self%wsave(k) = re
425  self%wsave(self%n_cells-k+2) = im
426  end do
427  if (modulo(self%n_cells,2) == 0 ) then
428  self%wsave(self%n_cells/2+1) = self%wsave(self%n_cells/2+1)*eigvals(self%n_cells/2+1)
429  end if
430  ! Backward FFT
431  call sll_s_fft_exec_r2r_1d( self%plan_bw, self%wsave, res )
432  ! normalize
433  res = res / real(self%n_cells, f64)
434  end subroutine solve_circulant
435 
436 
438  subroutine compute_phi_from_rho_1d_fem( self, in, phi, efield )
439  class(sll_t_maxwell_1d_fem) :: self
440  sll_real64, intent(in) :: in(:)
441  sll_real64, intent(out) :: phi(:)
442  sll_real64, intent(out) :: efield(:)
443 
444  ! Compute phi by inverting the mass matrix
445  call self%invert_mass( in, phi, self%s_deg_0 )
446 
447  ! Compute the degrees of freedom of the electric field as -G phi
448  call multiply_g( self, phi, efield )
449  efield = -efield
450 
451  end subroutine compute_phi_from_rho_1d_fem
452 
453 
455  subroutine compute_phi_from_j_1d_fem( self, in, phi, efield )
456  class(sll_t_maxwell_1d_fem) :: self
457  sll_real64, intent(in) :: in(:)
458  sll_real64, intent(out) :: phi(:)
459  sll_real64, intent(out) :: efield(:)
460 
461  ! Compute divergence of the current (G^T current) (assuming a 1v model)
462  call multiply_gt( self, in, self%work )
463 
464  ! Compute phi by inverting the mass matrix
465  call self%invert_mass( self%work, self%wsave, self%s_deg_0 )
466  phi = phi + self%wsave
467 
468  ! Compute the degrees of freedom of the electric field as -G phi
469  call multiply_g( self, phi, efield )
470  efield = -efield
471 
472  end subroutine compute_phi_from_j_1d_fem
473 
474 
477  subroutine sll_s_compute_fem_rhs(self, func, degree, coefs_dofs)
478  class(sll_t_maxwell_1d_fem) :: self
479  procedure(sll_i_function_1d_real64) :: func
480  sll_int32, intent(in) :: degree
481  sll_real64, intent(out) :: coefs_dofs(:) ! Finite Element right-hand-side
482  ! local variables
483  sll_int32 :: i,j,k
484  sll_real64 :: coef
485  sll_real64, dimension(2,degree+1) :: xw_gauss
486  sll_real64, dimension(degree+1,degree+1) :: bspl
487 
488  ! take enough Gauss points so that projection is exact for splines of degree deg
489  ! rescale on [0,1] for compatibility with B-splines
490  xw_gauss = sll_f_gauss_legendre_points_and_weights(degree+1, 0.0_f64, 1.0_f64)
491  ! Compute bsplines at gauss_points
492  do k=1,degree+1
493  call sll_s_uniform_bsplines_eval_basis(degree,xw_gauss(1,k), bspl(:,k))
494  !print*, 'bs', bspl(k,:)
495  end do
496 
497  ! Compute coefs_dofs = int f(x)N_i(x)
498  do i = 1, self%n_cells
499  coef=0.0_f64
500  ! loop over support of B spline
501  do j = 1, degree+1
502  ! loop over Gauss points
503  do k=1, degree+1
504  coef = coef + xw_gauss(2,k)*func(self%delta_x * (xw_gauss(1,k) + real(i + j - 2 - ((i + j - 2)/self%n_cells) * self%n_cells,f64))) * bspl(degree+2-j,k)
505  !print*, i,j,k, xw_gauss(2,k), xw_gauss(1,k),f(self%delta_x*(xw_gauss(1,k) + i + j - 2))
506  enddo
507  enddo
508  ! rescale by cell size
509  coefs_dofs(i) = coef*self%delta_x
510  enddo
511 
512  end subroutine sll_s_compute_fem_rhs
513 
514 
516  function l2norm_squared_1d_fem(self, coefs_dofs, degree) result (r)
517  class(sll_t_maxwell_1d_fem) :: self
518  sll_real64 :: coefs_dofs(:)
519  sll_int32 :: degree
520  sll_real64 :: r
521 
522  ! Multiply coefficients by mass matrix (use diagonalization FFT and mass matrix eigenvalues)
523  if (degree == self%s_deg_0 ) then
524 
525  call solve_circulant(self, self%eig_mass0, coefs_dofs, self%work)
526 
527  elseif (degree == self%s_deg_1) then
528 
529  call solve_circulant(self, self%eig_mass1, coefs_dofs, self%work)
530 
531  end if
532  ! Multiply by the coefficients from the left (inner product)
533  r = sum(coefs_dofs*self%work)
534  ! Scale by delt_x
535  r = r*self%delta_x
536 
537  end function l2norm_squared_1d_fem
538 
539 
540 
541  function inner_product_1d_fem( self, coefs1_dofs, coefs2_dofs, degree, degree2 ) result (r)
542  class(sll_t_maxwell_1d_fem) :: self
543  sll_real64 :: coefs1_dofs(:)
544  sll_real64 :: coefs2_dofs(:)
545  sll_int32 :: degree
546  sll_int32, optional :: degree2
547  sll_real64 :: r
548 
549  ! Multiply coefficients by mass matrix (use diagonalization FFT and mass matrix eigenvalues)
550  if (present(degree2)) then
551  if (degree == degree2) then
552  if (degree == self%s_deg_0 ) then
553  call solve_circulant(self, self%eig_mass0, coefs2_dofs, self%work)
554  elseif (degree == self%s_deg_1) then
555  call solve_circulant(self, self%eig_mass1, coefs2_dofs, self%work)
556  end if
557  ! Multiply by the coefficients from the left (inner product)
558  r = sum(coefs1_dofs*self%work)
559  ! Scale by delt_x
560  r = r*self%delta_x
561  else
562  if (degree == self%s_deg_0) then
563  call self%mixed_mass%dot( coefs2_dofs, self%work )
564 
565  ! Multiply by the coefficients from the left (inner product)
566  r = sum(coefs1_dofs*self%work)
567  else
568  call self%mixed_mass%dot( coefs1_dofs, self%work )
569 
570  ! Multiply by the coefficients from the left (inner product)
571  r = sum(coefs2_dofs*self%work)
572  end if
573  end if
574  else
575  if (degree == self%s_deg_0 ) then
576  call solve_circulant(self, self%eig_mass0, coefs2_dofs, self%work)
577  elseif (degree == self%s_deg_1) then
578  call solve_circulant(self, self%eig_mass1, coefs2_dofs, self%work)
579  end if
580  ! Multiply by the coefficients from the left (inner product)
581  r = sum(coefs1_dofs*self%work)
582  ! Scale by delt_x
583  r = r*self%delta_x
584  end if
585 
586  end function inner_product_1d_fem
587 
588 
590  subroutine l2projection_1d_fem(self, func, degree, coefs_dofs)
591  class(sll_t_maxwell_1d_fem) :: self
592  procedure(sll_i_function_1d_real64) :: func
593  sll_int32, intent(in) :: degree
594  sll_real64, intent(out) :: coefs_dofs(:) ! spline coefficients of projection
595 
596  ! Compute right-hand-side
597  call sll_s_compute_fem_rhs(self, func, degree, self%work)
598 
599  ! Multiply by inverse mass matrix (! complex numbers stored in real array with fftpack ordering)
600 
601  if (degree == self%s_deg_0) then
602  call solve_circulant(self, self%eig_mass0_inv, self%work, coefs_dofs)
603  elseif (degree == self%s_deg_0-1) then
604  call solve_circulant( self, self%eig_mass1_inv, self%work, coefs_dofs)
605  else
606  print*, 'degree ', degree, 'not availlable in maxwell_1d_fem object'
607  endif
608 
609  ! Account for scaling in the mass matrix by dx
610  coefs_dofs = coefs_dofs/self%delta_x
611 
612  end subroutine l2projection_1d_fem
613 
614 
615  subroutine init_1d_fem( self, domain, n_dofs, s_deg_0, delta_t, strong_ampere, solver_tolerance, force_sign, adiabatic_electrons )
616  class(sll_t_maxwell_1d_fem), intent(out) :: self
617  sll_real64, intent(in) :: domain(2)
618  sll_int32, intent(in) :: n_dofs
619  sll_int32, intent(in) :: s_deg_0
620  sll_real64, intent(in) :: delta_t
621  logical, intent(in),optional :: strong_ampere
622  sll_real64, intent(in), optional :: solver_tolerance
623  sll_real64, intent(in), optional :: force_sign
624  logical, intent(in), optional :: adiabatic_electrons
625  ! local variables
626  sll_int32 :: ierr
627  sll_real64 :: mass_line_0(s_deg_0+1), mass_line_1(s_deg_0), mass_line_mixed(s_deg_0*2)
628  sll_int32 :: j, k ! loop variables
629  sll_real64 :: coef0, coef1, sin_mode, cos_mode
630  sll_real64 :: ni !1/n_dofs in double precision
631  sll_real64 :: dt
632  sll_real64 :: t_e = 1._f64!10000._f64
633 
634  ni=1.0_f64/real(n_dofs,f64)
635 
636  self%n_dofs0 = n_dofs
637  self%n_dofs1 = n_dofs
638  self%n_cells = n_dofs
639  self%Lx = domain(2) - domain(1)
640  self%delta_x = self%Lx *ni
641  !print*, 'dx', self%delta_x
642  self%s_deg_0 = s_deg_0
643  self%s_deg_1 = s_deg_0 - 1
644 
645  if (present(strong_ampere)) then
646  self%strong_ampere=strong_ampere
647  else
648  self%strong_ampere=.false.
649  endif
650 
651  dt = delta_t
652 
653  if (present( solver_tolerance) ) then
654  self%solver_tolerance = solver_tolerance
655  else
656  self%solver_tolerance = 1d-12
657  end if
658 
659  if (present( force_sign) ) then
660  self%force_sign = force_sign
661  end if
662 
663  if (present( adiabatic_electrons) ) then
664  self%adiabatic_electrons = adiabatic_electrons
665  end if
666 
667  sll_allocate(self%mass_0(s_deg_0+1), ierr)
668  sll_allocate(self%mass_1(s_deg_0), ierr)
669 
670  select case(s_deg_0)
671  case(1) ! linear and constant splines
672  ! Upper diagonal coeficients of linear spline mass matrix (from Eulerian numbers)
673  self%mass_0(1) = 4.0_f64/6.0_f64
674  self%mass_0(2) = 1.0_f64/6.0_f64
675  ! Upper diagonal coeficients of constant spline mass matrix
676  self%mass_1(1) = 1.0_f64
677  case(2) ! quadratic and linear splines
678  ! Upper diagonal coeficients of quadratic spline mass matrix (from Eulerian numbers)
679  self%mass_0(1) = 66.0_f64/120.0_f64
680  self%mass_0(2) = 26.0_f64/120.0_f64
681  self%mass_0(3) = 1.0_f64/120.0_f64
682  ! Upper diagonal coeficients of linear spline mass matrix (from Eulerian numbers)
683  self%mass_1(1) = 4.0_f64/6.0_f64
684  self%mass_1(2) = 1.0_f64/6.0_f64
685  case(3)
686  ! Upper diagonal coeficients of cubic spline mass matrix (from Eulerian numbers)
687  self%mass_0(1) = 2416.0_f64/5040.0_f64
688  self%mass_0(2) = 1191.0_f64/5040.0_f64
689  self%mass_0(3) = 120.0_f64/5040.0_f64
690  self%mass_0(4) = 1.0_f64/5040.0_f64
691  ! Upper diagonal coeficients of quadratic spline mass matrix (from Eulerian numbers)
692  self%mass_1(1) = 66.0_f64/120.0_f64
693  self%mass_1(2) = 26.0_f64/120.0_f64
694  self%mass_1(3) = 1.0_f64/120.0_f64
695 
696  case default
697  call sll_s_spline_fem_mass_line ( self%s_deg_0, self%mass_0 )
698  call sll_s_spline_fem_mass_line ( self%s_deg_1, self%mass_1 )
699  end select
700 
701  if(self%adiabatic_electrons) then
702  mass_line_0=self%mass_0*self%delta_x/t_e
703  else
704  mass_line_0= self%mass_0*self%delta_x
705  end if
706  mass_line_1= self%mass_1*self%delta_x
707  call sll_s_spline_fem_mass1d( n_dofs, self%s_deg_0, mass_line_0, self%mass0 )
708  call sll_s_spline_fem_mass1d( n_dofs, self%s_deg_1, mass_line_1, self%mass1 )
709 
710  if(self%adiabatic_electrons) then
711  self%mass_0= self%mass_0/t_e
712  end if
713 
714  sll_allocate(self%eig_mass0(n_dofs), ierr)
715  sll_allocate(self%eig_mass1(n_dofs), ierr)
716  sll_allocate(self%eig_mass0_inv(n_dofs), ierr)
717  sll_allocate(self%eig_mass1_inv(n_dofs), ierr)
718  sll_allocate(self%eig_weak_ampere(n_dofs), ierr)
719  sll_allocate(self%eig_weak_poisson(n_dofs), ierr)
720  sll_allocate(self%eig_strong_poisson(n_dofs), ierr)
721  ! for interpolation matrix
722  sll_allocate(self%eig_splines0(n_dofs), ierr)
723  sll_allocate(self%eig_splines1(n_dofs), ierr)
724  sll_allocate(self%eig_isplines0(n_dofs), ierr)
725  sll_allocate(self%eig_itsplines0(n_dofs), ierr)
726  sll_allocate(self%eig_kit_mass_0(n_dofs), ierr)
727  sll_allocate(self%eig_kit_mass_1(n_dofs), ierr)
728 
729  self%eig_mass0 = 0.0_f64
730  self%eig_mass1 = 0.0_f64
731  self%eig_mass0_inv = 0.0_f64
732  self%eig_mass1_inv = 0.0_f64
733  self%eig_weak_ampere = 0.0_f64
734  self%eig_weak_poisson = 0.0_f64
735  ! for the e, b solver
736  sll_allocate(self%eig_eb(n_dofs), ierr )
737  self%eig_eb = 0.0_f64
738 
739  ! Initialise FFT
740  sll_allocate(self%work(n_dofs),ierr)
741  sll_allocate(self%wsave(n_dofs),ierr)
742  call sll_s_fft_init_r2r_1d( self%plan_fw, n_dofs, self%work, self%wsave, sll_p_fft_forward, normalized=.false. )
743  call sll_s_fft_init_r2r_1d( self%plan_bw, n_dofs, self%work, self%work, sll_p_fft_backward, normalized=.false. )
744 
745  ! Compute eigenvalues of circulant Ampere update matrix M_0^{-1} D^T M_1
746  ! and circulant Poisson Matrix (D^T M_1 D)^{-1}
747  ! zero mode vanishes due to derivative matrix D^T
748  self%eig_weak_ampere(1) = 0.0_f64
749  self%eig_weak_poisson(1) = 0.0_f64 ! Matrix is not invertible: 0-mode is set to 0
750  self%eig_strong_poisson(1) = 0.0_f64 ! Matrix is not invertible: 0-mode is set to 0
751  self%eig_mass0(1) = 1.0_f64 ! sum of coefficents is one
752  self%eig_mass1(1) = 1.0_f64 ! sum of coefficents is one
753 
754  call sll_s_spline_fem_interpolation_eigenvalues( self%s_deg_0, self%n_cells, self%eig_splines0 )
755  self%eig_isplines0(1) = 1.0_f64/ self%eig_splines0(1)
756  self%eig_eb(1) = 1.0_f64
757 
758  do k=1, (n_dofs+1)/2 - 1
759  coef0 = self%mass_0(1)
760  coef1 = self%mass_1(1)
761  do j=1,s_deg_0 - 1
762  cos_mode = cos(sll_p_twopi*ni*real(j*k, f64))
763  coef0 = coef0 + 2* self%mass_0(j+1)*cos_mode
764  coef1 = coef1 + 2* self%mass_1(j+1)*cos_mode
765  enddo
766  ! add last term for larger matrix
767  j = s_deg_0
768 
769  coef0 = coef0 + 2* self%mass_0(j+1)*cos(sll_p_twopi*ni*real(j*k, f64))
770 
771  ! compute eigenvalues
772  self%eig_mass0(k+1) = coef0 ! real part
773  self%eig_mass0(n_dofs-k+1) = 0.0_f64 ! imaginary part
774  self%eig_mass1(k+1) = coef1 ! real part
775  self%eig_mass1(n_dofs-k+1) = 0.0_f64 ! imaginary part
776 
777  cos_mode = cos(sll_p_twopi*ni*real(k, f64))
778  sin_mode = sin(sll_p_twopi*ni*real(k, f64))
779 
780  self%eig_weak_ampere(k+1) = (coef1 / coef0) * (1-cos_mode) ! real part
781  self%eig_weak_ampere(n_dofs-k+1) = -(coef1 / coef0) * sin_mode ! imaginary part
782  self%eig_weak_poisson(k+1) = 1.0_f64 / (coef1 * ((1.0_f64-cos_mode)**2 + &
783  sin_mode**2)) ! real part
784  self%eig_weak_poisson(n_dofs-k+1) = 0.0_f64 ! imaginary part
785 
786  coef0 = (1.0_f64 - cos_mode) * self%eig_splines0(k+1) - &
787  sin_mode * self%eig_splines0(n_dofs-k+1)
788  coef1 = (1.0_f64 - cos_mode) * self%eig_splines0(n_dofs-k+1) + &
789  sin_mode * self%eig_splines0(k+1)
790 
791  self%eig_strong_poisson(k+1) = (1.0_f64 - cos_mode)/((1.0_f64-cos_mode)**2 + sin_mode**2)!coef0 /(coef0**2 + coef1**2)
792  self%eig_strong_poisson(n_dofs-k+1) = -sin_mode/((1.0_f64-cos_mode)**2 + sin_mode**2)!-coef1 /(coef0**2 + coef1**2)
793 
794  self%eig_isplines0(k+1) = self%eig_splines0(k+1)/(self%eig_splines0(k+1)**2 + self%eig_splines0(n_dofs-k+1)**2)
795  self%eig_isplines0(n_dofs-k+1) = -self%eig_splines0(n_dofs-k+1)/(self%eig_splines0(k+1)**2 + self%eig_splines0(n_dofs-k+1)**2)
796 
797 
798  self%eig_eb(k+1) = 1.0_f64/ ( 1.0_f64 + (dt/self%delta_x)**2*self%eig_mass1(k+1)/self%eig_mass0(k+1)*2.0_f64 *(1.0_f64 -cos_mode) )
799  self%eig_eb(n_dofs-k+1) = 0.0_f64
800 
801  enddo
802 
803  if ( modulo(n_dofs,2) == 0 ) then
804  ! N/2 mode
805  coef0 = self%mass_0(1)
806  coef1 = self%mass_1(1)
807  do j=1, s_deg_0 - 1
808  coef0 = coef0 + 2 * self%mass_0(j+1)*cos(sll_p_pi*real(j,f64))
809  coef1 = coef1 + 2 * self%mass_1(j+1)*cos(sll_p_pi*real(j,f64))
810  enddo
811 
812  ! add last term for larger matrix
813  j = s_deg_0
814  coef0 = coef0 + 2 * self%mass_0(j+1)*cos(sll_p_pi*real(j, f64))
815 
816  ! compute eigenvalues
817  self%eig_mass0(n_dofs/2+1) = coef0
818  self%eig_mass1(n_dofs/2+1) = coef1
819  self%eig_weak_ampere(n_dofs/2+1) = 2.0_f64 * (coef1 / coef0)
820  self%eig_weak_poisson(n_dofs/2+1) = 1.0_f64 / (coef1 *4.0_f64)
821  self%eig_strong_poisson(n_dofs/2+1) = 0.5_f64!0.25_f64!self%eig_splines1(n_dofs/2+1)/ 4.0_f64
822  self%eig_isplines0(n_dofs/2+1) = 1.0_f64 / self%eig_splines0(n_dofs/2+1)
823  self%eig_eb(n_dofs/2+1) = 1.0_f64/ ( 1.0_f64 + (dt/self%delta_x)**2*self%eig_mass1(n_dofs/2+1)/self%eig_mass0(n_dofs/2+1)*4.0_f64 )
824  end if
825 
826  self%eig_strong_poisson = self%eig_strong_poisson* self%delta_x
827 
828  !print*, n_dofs/2+1, size(self%eig_kit_mass_0), size(
829  do k=1,n_dofs/2+1
830  self%eig_kit_mass_0(k) = self%eig_isplines0(k) * self%eig_mass0(k) * self%delta_x
831  self%eig_kit_mass_1(k) = self%eig_isplines0(k) * self%eig_mass1(k) * self%delta_x
832  self%eig_itsplines0(k) = self%eig_isplines0(k)
833  end do
834  do k=1, (n_dofs-1)/2
835  self%eig_kit_mass_0(n_dofs-k+1) = -self%eig_isplines0(n_dofs-k+1) * self%eig_mass0(k+1)*self%delta_x
836  self%eig_kit_mass_1(n_dofs-k+1) = -self%eig_isplines0(n_dofs-k+1) * self%eig_mass1(k+1)*self%delta_x
837  self%eig_itsplines0(n_dofs-k+1) = -self%eig_isplines0(n_dofs-k+1)
838  end do
839 
840  do j=1,(self%n_cells+1)/2!+1 !2
841  self%eig_mass0_inv(j) = 1.0_f64 / self%eig_mass0(j)
842  self%eig_mass1_inv(j) = 1.0_f64 / self%eig_mass1(j)
843  end do
844  if ( modulo(n_dofs,2) == 0 ) then
845  self%eig_mass0_inv(self%n_cells/2+1) = 1.0_f64 / self%eig_mass0(self%n_cells/2+1)
846  self%eig_mass1_inv(self%n_cells/2+1) = 1.0_f64 / self%eig_mass1(self%n_cells/2+1)
847  end if
848 
849  call sll_s_spline_fem_mixedmass_line( self%s_deg_0, mass_line_mixed)
850  mass_line_mixed=mass_line_mixed*self%delta_x
851  call sll_s_spline_fem_mixedmass1d( n_dofs, self%s_deg_0, mass_line_mixed, self%mixed_mass )
852 
853  ! Only for Schur complement eb solver
854  call self%linear_op_schur_eb%create( self%mass0, self%mass1, self%n_cells, self%delta_x )
855  call self%linear_solver_schur_eb%create( self%linear_op_schur_eb )
856  self%linear_solver_schur_eb%atol = self%solver_tolerance
857  self%linear_solver_schur_eb%rtol = self%solver_tolerance
858  !self%linear_solver_schur_eb%verbose = .true.
859 
860  end subroutine init_1d_fem
861 
862 
863  subroutine free_1d_fem(self)
864  class(sll_t_maxwell_1d_fem) :: self
865 
866  call sll_s_fft_free( self%plan_fw )
867  call sll_s_fft_free( self%plan_bw )
868  deallocate(self%mass_0)
869  deallocate(self%mass_1)
870  deallocate(self%eig_mass0)
871  deallocate(self%eig_mass1)
872  deallocate(self%eig_mass0_inv)
873  deallocate(self%eig_mass1_inv)
874  deallocate(self%eig_weak_ampere)
875  deallocate(self%eig_weak_poisson)
876  deallocate(self%wsave)
877  deallocate(self%work)
878  deallocate(self%eig_splines0)
879  end subroutine free_1d_fem
880 
881  subroutine multiply_g( self, in, out)
882  class(sll_t_maxwell_1d_fem), intent(in) :: self
883  sll_real64, intent(in) :: in(:)
884  sll_real64, intent(out) :: out(:)
885 
886  call sll_s_multiply_g_1d( self%n_cells, self%delta_x, in, out )
887 
888  end subroutine multiply_g
889 
890 
891  subroutine multiply_gt( self, in, out)
892  class(sll_t_maxwell_1d_fem), intent(in) :: self
893  sll_real64, intent(in) :: in(:)
894  sll_real64, intent(out) :: out(:)
895 
896  call sll_s_multiply_gt_1d( self%n_cells, self%delta_x, in, out )
897 
898  end subroutine multiply_gt
899 
900 
901  subroutine multiply_mass_1d_fem( self, in, out, degree)
902  class(sll_t_maxwell_1d_fem), intent(inout) :: self
903  sll_real64, intent(in) :: in(:)
904  sll_real64, intent(out) :: out(:)
905  sll_int32, intent(in) :: degree
906 
907  ! Multiply coefficients by mass matrix (use diagonalization FFT and mass matrix eigenvalues)
908  if (degree == self%s_deg_0 ) then
909  call solve_circulant(self, self%eig_mass0, in, out)
910  out = out*self%delta_x
911  elseif (degree == self%s_deg_1) then
912  call solve_circulant(self, self%eig_mass1, in, out)
913  out = out*self%delta_x
914  elseif(degree == 10) then
915  call self%mixed_mass%dot( in, out )
916  else
917  print*, 'maxwell_solver_1d_fem: multiply mass for other form not yet implemented'
918  stop
919  end if
920 
921 
922 
923 
924  end subroutine multiply_mass_1d_fem
925 
926 
928  subroutine invert_mass_1d_fem(self, in, out, degree)
929  class(sll_t_maxwell_1d_fem), intent(inout) :: self
930  sll_int32, intent(in) :: degree
931  sll_real64, intent(in) :: in(:)
932  sll_real64, intent(out) :: out(:)
933 
934  ! Multiply by inverse mass matrix (! complex numbers stored in real array with fftpack ordering)
935  if (degree == self%s_deg_0) then
936  call solve_circulant( self, self%eig_mass0_inv, in, out )
937  elseif (degree == self%s_deg_0-1) then
938  call solve_circulant( self, self%eig_mass1_inv, in, out )
939  else
940  print*, 'degree ', degree, 'not availlable in maxwell_1d_fem object'
941  endif
942 
943  ! Account for scaling in the mass matrix by dx
944  out = out/self%delta_x
945 
946  end subroutine invert_mass_1d_fem
947 
948 
950  subroutine transform_dofs_1d_fem(self, in, out, degree)
951  class(sll_t_maxwell_1d_fem), intent(inout) :: self
952  sll_int32, intent(in) :: degree
953  sll_real64, intent(in) :: in(:)
954  sll_real64, intent(out) :: out(:)
955  ! local variables
956  sll_int32 :: i
957 
958  if(self%strong_ampere .eqv. .true.) then
959  if (degree == 0) then
960  call solve_circulant(self, self%eig_kit_mass_0, in, out)
961  elseif (degree == 1) then
962  call solve_circulant(self, self%eig_kit_mass_1, in, out)
963  elseif (degree == 2 ) then
964  call self%multiply_mass( in , self%work, self%s_deg_0 )
965  do i=1,self%n_cells-1
966  self%wsave(i+1) = 0.5_f64 * ( self%work(i) + self%work(i+1) )
967  end do
968  self%wsave(1) = 0.5_f64 * ( self%work(1) + self%work(self%n_cells) )
969  call self%multiply_interpolation_inverse_transpose( self%wsave, out )
970  elseif (degree == 3) then
971  call self%multiply_mass( in , self%work, self%s_deg_0-1 )
972  do i=1,self%n_cells-1
973  self%wsave(i+1) = 0.5_f64 * ( self%work(i) + self%work(i+1) )
974  end do
975  self%wsave(1) = 0.5_f64 * ( self%work(1) + self%work(self%n_cells) )
976  call self%multiply_interpolation_inverse_transpose( self%wsave, out )
977  else
978  print*, 'degree ', degree, 'not availlable in maxwell_1d_fem object'
979  endif
980  else
981  out = in
982  end if
983 
984  end subroutine transform_dofs_1d_fem
985 
986 
987 end module sll_m_maxwell_1d_fem
Fortran module where set some physical and mathematical constants.
real(kind=f64), parameter, public sll_p_pi
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_init_r2r_1d(plan, nx, array_in, array_out, direction, normalized, aligned, optimization)
Create new 1d real to real plan.
integer, parameter, public sll_p_fft_backward
Flag to specify backward FFT (positive sign) [value 1].
subroutine, public sll_s_fft_exec_r2r_1d(plan, array_in, array_out)
Compute fast Fourier transform in real to real mode.
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 [...
module for a sequential gmres
Low level arbitrary degree splines.
module for Compressed Sparse Row Matrix (CSR)
Module interface to solve Maxwell's equations in 1D.
Solve Maxwell's equations in 1D.
subroutine solve_circulant(self, eigvals, rhs, res)
subroutine invert_mass_1d_fem(self, in, out, degree)
Invert the mass matrix.
subroutine compute_phi_from_j_1d_fem(self, in, phi, efield)
For model with adiabatic electrons.
subroutine solve_e_b_1d_fem(self, delta_t, bfield, efield)
Solves for the efield part in a implicit curl part solve.
subroutine sll_s_compute_curl_part_1d_fem(self, delta_t, efield, bfield, betar)
subroutine sll_s_compute_e_from_rho_1d_fem(self, field_in, field_out)
subroutine compute_e_from_j_1d_fem(self, current, component, E)
Compute E_i from j_i integrated over the time interval using weak Ampere formulation
subroutine strong_curl(self, delta_t, field_in, field_out)
Compute Bz from Ey using strong 1D Faraday equation for spline coefficients $B_z^{new}(x_j) = B_z^{ol...
subroutine choose_interpolation(self, current, component, E)
Choose between compute_E_from_j_1d_fem and compute_E_from_j_1d_fem_shape.
subroutine sll_s_compute_e_from_b_1d_fem(self, delta_t, field_in, field_out)
compute Ey from Bz using weak Ampere formulation
subroutine transform_dofs_1d_fem(self, in, out, degree)
Invert the mass matrix.
subroutine init_1d_fem(self, domain, n_dofs, s_deg_0, delta_t, strong_ampere, solver_tolerance, force_sign, adiabatic_electrons)
subroutine sll_s_compute_b_from_e_1d_fem(self, delta_t, field_in, field_out)
subroutine compute_phi_from_rho_1d_fem(self, in, phi, efield)
For model with adiabatic electrons.
subroutine sll_s_compute_fem_rhs(self, func, degree, coefs_dofs)
Compute the FEM right-hand-side for a given function f and periodic splines of given degree Its compo...
subroutine compute_rho_from_e_1d_fem(self, field_in, field_out)
Compute rho from Gauss law for given efield.
subroutine weak_curl(self, delta_t, field_in, field_out)
compute Ey from Bz using weak Ampere formulation
real(kind=f64) function inner_product_1d_fem(self, coefs1_dofs, coefs2_dofs, degree, degree2)
subroutine multiply_gt(self, in, out)
real(kind=f64) function l2norm_squared_1d_fem(self, coefs_dofs, degree)
Compute square of the L2norm.
subroutine multiply_mass_1d_fem(self, in, out, degree)
subroutine multiply_interpolation_inverse_transpose(self, in, out)
subroutine multiply_g(self, in, out)
subroutine compute_e_from_j_1d_fem_shape(self, current, component, E)
subroutine l2projection_1d_fem(self, func, degree, coefs_dofs)
Compute the L2 projection of a given function f on periodic splines of given degree.
Utilites for Maxwell solver's with spline finite elements using sparse matrix linear solvers.
subroutine, public sll_s_spline_fem_mass1d(n_cells, s_deg, mass_line, matrix)
Set up 1d mass matrix (sparsity pattern and values)
subroutine, public sll_s_spline_fem_mixedmass1d(n_cells, s_deg, mass_line, matrix)
Utilites for Maxwell solver's with spline finite elements.
subroutine, public sll_s_multiply_g_1d(n_dofs, delta_x, in, out)
Multiplication of the input vector in by the derivative matrix G.
subroutine, public sll_s_multiply_gt_1d(n_dofs, delta_x, in, out)
Multiplication of the input vector in by the transposed derivative matrix G^T
subroutine, public sll_s_spline_fem_interpolation_eigenvalues(degree, ndofs, eig)
Compute the eigenvalues of the interpolation matrix for splines of degree degree (with first grid poi...
subroutine, public sll_s_spline_fem_mixedmass_line(deg, mass_line)
Helper function to find line of mass matrix ( N_i^p N_j^{p-1}))
subroutine, public sll_s_spline_fem_mass_line(degree, mass_line)
Computes the mass line for a mass matrix with degree splines.
Type for FFT plan in SeLaLib.
    Report Typos and Errors