• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

gyrokinetics / gs2 / 1970319704

06 Aug 2025 03:50PM UTC coverage: 8.18% (+0.002%) from 8.178%
1970319704

push

gitlab-ci

David Dickinson
Merged in bugfix/fix_issue_88_multiple_ncheck (pull request #1109)

3667 of 44831 relevant lines covered (8.18%)

124503.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/diagnostics/diagnostics_velocity_space.fpp
1
!> A module which writes out quantities which writes out 
2
!! diagnostic quantities which assess whether the velocity 
3
!! space resolution is sufficient.
4
module diagnostics_velocity_space
5
  implicit none
6
  private
7

8
  public :: init_diagnostics_velocity_space, write_velocity_space_checks, get_gtran
9
  public :: write_collision_error, finish_diagnostics_velocity_space, get_verr
10
  public :: collision_error, finish_legendre_transform, setup_legendre_transform
11
  public :: init_weights, finish_weights
12

13
  ! GTRAN
14
  real, dimension (:,:), allocatable :: lpl
15
  real, dimension (:,:,:), allocatable :: lpe
16
  real, dimension (:,:,:,:), allocatable :: lpt
17
  ! VERR
18
  real, dimension (:,:,:), allocatable :: wlmod, wtmod, wmod
19

20
contains
21

22
  !> FIXME : Add documentation
23
  subroutine init_diagnostics_velocity_space(gnostics)
×
24
    use diagnostics_config, only: diagnostics_type
25
    use collisions, only: collision_model_switch, collision_model_lorentz
26
    use collisions, only: init_lorentz_error, collision_model_lorentz_test
27
    implicit none
28
    type(diagnostics_type), intent(inout) :: gnostics 
29
    
30
    if (.not. gnostics%write_verr) return
×
31
    
32
    ! initialize weights for less accurate integrals used
33
    ! to provide an error estimate for v-space integrals (energy and untrapped)
34
    call init_weights
×
35
    call setup_legendre_transform
×
36
    
37
    if (gnostics%write_cerr) then
×
38
       if (collision_model_switch == collision_model_lorentz .or. &
×
39
            collision_model_switch == collision_model_lorentz_test) then
40
          call init_lorentz_error
×
41
       else
42
          gnostics%write_cerr = .false.
×
43
       end if
44
    end if
45
  end subroutine init_diagnostics_velocity_space
46

47
  !> FIXME : Add documentation  
48
  subroutine finish_diagnostics_velocity_space()
×
49
    implicit none
50
    call finish_weights
×
51
    call finish_legendre_transform
×
52
  end subroutine finish_diagnostics_velocity_space
×
53

54
  !> FIXME : Add documentation  
55
  subroutine write_velocity_space_checks(gnostics, vary_vnew_only)
×
56
    use mp, only: proc0
57
    use le_grids, only: grid_has_trapped_particles
58
    use fields_arrays, only: phinew, bparnew
59
    use gs2_time, only: user_time
60
    use collisions, only: vnmult
61
    use species, only: spec
62
    use diagnostics_config, only: diagnostics_type
63
#ifdef NETCDF
64
    use gs2_io, only: starts, time_dim, dim_2, dim_3, dim_5
65
    use neasyf, only: neasyf_write
66
#endif
67
    implicit none
68
    type(diagnostics_type), intent(in) :: gnostics
69
    logical, intent(in) :: vary_vnew_only
70
    real, dimension (5,2) :: errest
71
    integer, dimension (5,3) :: erridx
72
    real :: geavg, glavg, gtavg
73

74
    errest = 0.0; erridx = 0
×
75
    geavg = 0.0 ; glavg = 0.0 ; gtavg = 0.0
×
76

77
    ! error estimate obtained by comparing standard integral with less-accurate integral
78
    call get_verr (errest, erridx, phinew, bparnew)
×
79

80
    if (vary_vnew_only) return
×
81

82
    ! error estimate based on monitoring amplitudes of legendre polynomial coefficients
83
    call get_gtran (geavg, glavg, gtavg, phinew, bparnew)
×
84

85
#ifdef NETCDF
86
    if (gnostics%writing) then
×
87
       call neasyf_write(gnostics%file_id, "vspace_lpcfrac", [geavg, glavg, gtavg], &
88
            dim_names=[dim_3, time_dim], start=starts(2, gnostics%nout), &
89
            long_name="Fraction of free energy contained in the high order coefficients of &
90
            & the Legendre polynomial transform of (1) energy space, (2) untrapped &
91
            & pitch angles and (3) trapped pitch angles (each should ideally be < 0.1).  &
92
            & Note that there are no trapped pitch angles for certain geometries")
×
93
       call neasyf_write(gnostics%file_id, "vspace_err", errest, &
94
            dim_names=[dim_5, dim_2, time_dim], start=starts(3, gnostics%nout), &
95
            long_name="Estimate of the (1) absolute and (2) relative errors resulting from &
96
            & velocity space integrals in the calculation of the following quantities &
97
            & in the given dimensions: (1) k phi, energy (2) k phi, untrapped pitch angles &
98
            & (3) k phi, trapped pitch angles, (4) k apar, energy, (5) k apar, untrapped &
99
            & angles. Relative errors should be < 0.1. ")
×
100
       call neasyf_write(gnostics%file_id, "vspace_vnewk", &
101
            [vnmult(1)*spec(1)%vnewk, vnmult(2)*spec(1)%vnewk], &
×
102
            dim_names=[dim_2, time_dim], start=starts(2, gnostics%nout), &
103
            long_name="If the simulation is set to vary the collisionality in order to keep &
104
            & error in velocity integrals to acceptable levels, contains species 1 &
105
            & collisionality in (1) pitch angle and (2) energy  ")
×
106

107
       if (gnostics%write_max_verr) then
×
108
          call neasyf_write(gnostics%file_id, "vspace_err_maxindex", erridx, &
109
               dim_names=[dim_5, dim_3, time_dim], start=starts(3, gnostics%nout), &
110
               long_name="Gives the (1) theta index, (2) ky index and (3) kx index of the maximum &
111
               & error resulting from the &
112
               & velocity space integrals in the calculation of the following quantities &
113
               & in the given dimensions: (1) k phi, energy (2) k phi, untrapped pitch angles &
114
               & (3) k phi, trapped pitch angles, (4) k apar, energy, (5) k apar, untrapped &
115
               & angles. Relative errors should be < 0.1. ")
×
116
       end if
117
    end if
118
#endif
119
    if (proc0 .and. gnostics%ascii_files%write_to_vres .and. (.not. gnostics%create)) call write_ascii
×
120
  contains
121
    !> FIXME : Add documentation
122
    subroutine write_ascii
×
123
      if (grid_has_trapped_particles()) then
×
124
         write(gnostics%ascii_files%lpc,"(4(1x,e13.6))") user_time, geavg, glavg, gtavg
×
125
      else
126
         write(gnostics%ascii_files%lpc,"(3(1x,e13.6))") user_time, geavg, glavg
×
127
      end if
128
      write(gnostics%ascii_files%vres,"(8(1x,e13.6))") user_time, errest(1,2), errest(2,2), errest(3,2), &
×
129
           errest(4,2), errest(5,2), vnmult(1)*spec(1)%vnewk, vnmult(2)*spec(1)%vnewk
×
130
      if (gnostics%write_max_verr) then
×
131
         write(gnostics%ascii_files%vres2,"(3(i8),(1x,e13.6),3(i8),(1x,e13.6),3(i8),(1x,e13.6),3(i8),(1x,e13.6),3(i8),(1x,e13.6))") &
132
              erridx(1,1), erridx(1,2), erridx(1,3), errest(1,1), &
×
133
              erridx(2,1), erridx(2,2), erridx(2,3), errest(2,1), &
×
134
              erridx(3,1), erridx(3,2), erridx(3,3), errest(3,1), &
×
135
              erridx(4,1), erridx(4,2), erridx(4,3), errest(4,1), &
×
136
              erridx(5,1), erridx(5,2), erridx(5,3), errest(5,1)
×
137
      end if
138
    end subroutine write_ascii
×
139
  end subroutine write_velocity_space_checks
140

141
  !> Calculate and write the collision error
142
  subroutine write_collision_error (gnostics)
×
143
    use diagnostics_config, only: diagnostics_type
144
    use fields_arrays, only: phinew, bparnew
145
    type(diagnostics_type), intent(in) :: gnostics
146
    if (gnostics%write_ascii) call collision_error(gnostics%ascii_files%cres, phinew, bparnew)
×
147
  end subroutine write_collision_error
×
148

149
  !> Calculate the collision error and write to text file `<runname>.cres`
150
  subroutine collision_error(unit, phi, bpar)
×
151
    use mp, only: proc0, iproc, send, receive
152
    use le_grids, only: ng2, jend, nlambda, lambda_map
153
    use theta_grid, only: ntgrid
154
    use dist_fn_arrays, only: gnew, g_adjust, g_work, to_g_gs2, from_g_gs2
155
    use gs2_layouts, only: lz_lo, ig_idx, idx_local, proc_id
156
    use gs2_layouts, only: ik_idx, ie_idx, is_idx, it_idx, il_idx
157
    use collisions, only: dtot, fdf, fdb
158
    use redistribute, only: gather
159
    use gs2_time, only: user_time
160
    use array_utils, only: zero_array
161
    implicit none
162
    integer, intent(in) :: unit
163
    complex, dimension(-ntgrid:, :, :), intent(in) :: phi, bpar
164
    integer :: je, te, ig, il, ip, ilz, ie, is, ik, it
165
    integer :: igmax, ikmax, itmax, iemax, ilmax, ismax, proc_idx
166
    complex, dimension (:), allocatable :: ltmp, ftmp
×
167
    complex, dimension (:,:), allocatable :: lcoll, fdcoll, glze
×
168
    real :: etmp, emax, etot, eavg, edenom, ltmax
169

170
    allocate (ltmp(2*nlambda), ftmp(2*nlambda))
×
171
    allocate (lcoll(2*nlambda,lz_lo%llim_proc:lz_lo%ulim_alloc))
×
172
    allocate (fdcoll(2*nlambda,lz_lo%llim_proc:lz_lo%ulim_alloc))
×
173
    allocate (glze(max(2*nlambda,2*ng2+1),lz_lo%llim_proc:lz_lo%ulim_alloc))
×
174

175
    call zero_array(lcoll); call zero_array(fdcoll); call zero_array(glze)
×
176
    ltmp = 0.0; ftmp = 0.0
×
177
    etmp = 0.0; emax = 0.0; etot = 0.0; eavg = 0.0; edenom = 1.0; ltmax = 1.0
×
178

179
    ! convert gnew from g to h
180
    call g_adjust(gnew, phi, bpar, direction = from_g_gs2)
×
181
    g_work = gnew
×
182
    ! convert gnew from h back to g
183
    call g_adjust(gnew, phi, bpar, direction = to_g_gs2)
×
184

185
    ! map from g_work(ig,isgn,iglo) to glze(il,ilz)
186
    ! Note lambda_map is only defined if use_le_layout = F (not the default).
187
    ! We force write_cerr = F if use_le_layout = T
188
    call gather (lambda_map, g_work, glze)
×
189

190
    ! loop over ig, isign, ik, it, ie, is
191
    !$OMP PARALLEL DO DEFAULT(none) &
192
    !$OMP PRIVATE(ilz, ig, je, te, il, ip) &
193
    !$OMP SHARED(lz_lo, jend, ng2, dtot, glze, fdf, fdb, fdcoll) &
194
    !$OMP REDUCTION(+: lcoll) &
195
    !$OMP SCHEDULE(static)
196
    do ilz = lz_lo%llim_proc, lz_lo%ulim_proc
×
197
       ig = ig_idx(lz_lo,ilz)
×
198

199
       if (jend(ig) == 0) then      ! no trapped particles
×
200
          ! je = number of + vpa grid pts
201
          ! te = number of + and - vpa grid pts
202
          je = ng2
×
203
          te = 2*ng2
×
204

205
          ! find d/d(xi) ((1+xi**2)( d g(xi)/ d(xi) )) at each xi
206
          ! using lagrange (lcoll) and finite difference (fdcoll)
207
          il = 1
×
208
          do ip = il, il+2
×
209
             lcoll(il,ilz) = lcoll(il,ilz) + dtot(ig,il,ip)*glze(ip,ilz)
×
210
          end do
211

212
          il = 2
×
213
          do ip = il-1, il+1
×
214
             lcoll(il,ilz) = lcoll(il,ilz) + dtot(ig,il,ip-il+2)*glze(ip,ilz)
×
215
          end do
216

217
          do il=3,ng2
×
218
             do ip=il-2,il+2
×
219
                lcoll(il,ilz) = lcoll(il,ilz) + dtot(ig,il,ip-il+3)*glze(ip,ilz)
×
220
             end do
221
          end do
222

223
          do il=ng2+1, 2*ng2-2
×
224
             do ip = il-2,il+2
×
225
                lcoll(il,ilz) = lcoll(il,ilz) + dtot(ig,2*ng2-il+1,il-ip+3)*glze(ip,ilz)
×
226
             end do
227
          end do
228

229
          il = 2*ng2-1
×
230
          do ip = il-1, il+1
×
231
             lcoll(il,ilz) = lcoll(il,ilz) + dtot(ig,2,il-ip+2)*glze(ip,ilz)
×
232
          end do
233

234
          il = 2*ng2
×
235
          do ip = il-2, il
×
236
             lcoll(il,ilz) = lcoll(il,ilz) + dtot(ig,1,il-ip+1)*glze(ip,ilz)
×
237
          end do
238

239
          ! deal with xi from 1-eps -> eps
240
          do il=2,ng2
×
241
             fdcoll(il,ilz) = fdf(ig,il)*(glze(il+1,ilz) - glze(il,ilz)) - fdb(ig,il)*(glze(il,ilz) - glze(il-1,ilz))
×
242
          end do
243
          ! deal with xi from -eps -> -1+eps
244
          do il=ng2+1, 2*ng2-1
×
245
             fdcoll(il,ilz) = fdb(ig,2*ng2-il+1)*(glze(il+1,ilz) - glze(il,ilz)) - fdf(ig,2*ng2-il+1)*(glze(il,ilz) - glze(il-1,ilz))
×
246
          end do
247

248
          fdcoll(1,ilz) = fdf(ig,1)*(glze(2,ilz) - glze(1,ilz))
×
249
          fdcoll(2*ng2,ilz) = -fdf(ig,1)*(glze(2*ng2,ilz) - glze(2*ng2-1,ilz))
×
250

251
       else       ! trapped particle runs          
252
          je = jend(ig)
×
253
          te = 2*je - 1
×
254

255
          il = 1
×
256
          do ip = il, il+2
×
257
             lcoll(il,ilz) = lcoll(il,ilz) + dtot(ig,il,ip)*glze(ip,ilz)
×
258
          end do
259

260
          il = 2
×
261
          do ip = il-1, il+1
×
262
             lcoll(il,ilz) = lcoll(il,ilz) + dtot(ig,il,ip-il+2)*glze(ip,ilz)
×
263
          end do
264

265
          do il=3,je
×
266
             do ip=il-2,il+2
×
267
                lcoll(il,ilz) = lcoll(il,ilz) + dtot(ig,il,ip-il+3)*glze(ip,ilz)
×
268
             end do
269
          end do
270

271
          do il=je+1, te-2
×
272
             do ip = il-2,il+2
×
273
                lcoll(il,ilz) = lcoll(il,ilz) + dtot(ig,te-il+1,il-ip+3)*glze(ip,ilz)
×
274
             end do
275
          end do
276

277
          il = te-1
×
278
          do ip = il-1, il+1
×
279
             lcoll(il,ilz) = lcoll(il,ilz) + dtot(ig,2,il-ip+2)*glze(ip,ilz)
×
280
          end do
281

282
          il = te
×
283
          do ip = il-2, il
×
284
             lcoll(il,ilz) = lcoll(il,ilz) + dtot(ig,1,il-ip+1)*glze(ip,ilz)
×
285
          end do
286

287
          ! is il=je handled correctly here?
288
          do il=2,je
×
289
             fdcoll(il,ilz) = fdf(ig,il)*(glze(il+1,ilz) - glze(il,ilz)) - fdb(ig,il)*(glze(il,ilz) - glze(il-1,ilz))
×
290
          end do
291
          do il=je+1,te-1
×
292
             fdcoll(il,ilz) = fdb(ig,te-il+1)*(glze(il+1,ilz) - glze(il,ilz)) - fdf(ig,te-il+1)*(glze(il,ilz) - glze(il-1,ilz))
×
293
          end do
294

295
          fdcoll(1,ilz) = fdf(ig,1)*(glze(2,ilz) - glze(1,ilz))
×
296
          fdcoll(te,ilz) = -fdf(ig,1)*(glze(te,ilz) - glze(te-1,ilz))
×
297

298
       end if
299
    end do
300
    !$OMP END PARALLEL DO
301

302
    do ilz=lz_lo%llim_world, lz_lo%ulim_world
×
303
       ig = ig_idx(lz_lo, ilz)
×
304
       ik = ik_idx(lz_lo, ilz)
×
305
       it = it_idx(lz_lo, ilz)
×
306
       ie = ie_idx(lz_lo, ilz)
×
307
       is = is_idx(lz_lo, ilz)
×
308
       je = jend(ig)
×
309

310
       if (je == 0) then
×
311
          te = 2 * ng2
×
312
       else
313
          te = 2 * je - 1
×
314
       end if
315

316
       proc_idx = proc_id(lz_lo, ilz)
×
317
       if (proc_idx == iproc) then
×
318
          if (proc0) then 
×
319
             ltmp = lcoll(:,ilz)
×
320
             ftmp = fdcoll(:,ilz)
×
321
          else
322
             call send (lcoll(:,ilz), 0)
×
323
             call send (fdcoll(:,ilz), 0)
×
324
          endif
325
       else if (proc0) then
×
326
          call receive (ltmp, proc_idx)
×
327
          call receive (ftmp, proc_idx)
×
328
       endif
329

330
       ! Only proc0 has ltmp and ftmp set so on that proc calculate the error
331
       ! (difference) and look for maximum
332
       if (proc0) then
×
333
          do il = 1, te
×
334
             etmp = abs(ltmp(il) - ftmp(il))
×
335

336
             if (etmp > emax) then
×
337
                emax = etmp
×
338
                ltmax = abs(ltmp(il))
×
339
                ikmax = ik
×
340
                itmax = it
×
341
                iemax = ie
×
342
                ismax = is
×
343
                ilmax = il
×
344
                igmax = ig
×
345
             end if
346

347
             etot = etot + etmp
×
348
             edenom = edenom + abs(ltmp(il))
×
349
          end do
350
       end if
351
    end do
352

353
    if (proc0) then
×
354
       eavg = etot / edenom
×
355
       emax = emax / ltmax
×
356
       write(unit,"((1x,e13.6),6(i8),2(1x,e13.6))") user_time, &
×
357
            igmax, ikmax, itmax, iemax, ilmax, ismax, emax, eavg
×
358
    end if
359

360
    deallocate (lcoll, fdcoll, glze, ltmp, ftmp)
×
361

362
  end subroutine collision_error
×
363

364
  !> Error estimate obtained by comparing standard integral with less-accurate integral
365
  !>
366
  !> Estimate of the (1) absolute and (2) relative errors resulting from velocity space
367
  !> integrals in the calculation of the following quantities in the given dimensions: (1)
368
  !> k phi, energy (2) k phi, untrapped pitch angles (3) k phi, trapped pitch angles, (4)
369
  !> k apar, energy, (5) k apar, untrapped angles. Relative errors should be < 0.1.
370
  !>
371
  !> @note Should this be extended to include error estimates on bpar as well?
372
  subroutine get_verr (errest, erridx, phi, bpar)
×
373
    use le_grids, only: integrate_species, nesub, new_trap_int, grid_has_trapped_particles
374
    use le_grids, only: ng2, nlambda
375
    use theta_grid, only: ntgrid
376
    use kt_grids, only: ntheta0, naky, aky, akx
377
    use species, only: nspec, spec
378
    use dist_fn_arrays, only: gnew, aj0, vpa, g_adjust, g_work, to_g_gs2, from_g_gs2
379
    use run_parameters, only: has_phi, has_apar, beta
380
    use gs2_layouts, only: g_lo
381
    use collisions, only: adjust_vnmult
382
    use array_utils, only: zero_array
383
    implicit none
384
    !> Indices of maximum error.
385
    integer, dimension (5,3), intent (out) :: erridx
386
    !> Estimated error.
387
    real, dimension (5,2), intent (out) :: errest
388
    !> Electrostatic potential and parallel magnetic field
389
    complex, dimension (-ntgrid:,:,:), intent (in) :: phi, bpar
390
    integer :: ig, it, ik, iglo, isgn, ntrap
391
    complex, dimension (:,:,:), allocatable :: phi_app, apar_app
×
392
    complex, dimension (:,:,:,:), allocatable :: phi_e, phi_l, phi_t, apar_e, apar_l, apar_t
×
393
    real, dimension (:,:), allocatable :: kmax
×
394
    real, dimension (:), allocatable :: wgt
×
395
    real, dimension(2) :: errtmp
396
    integer, dimension(3) :: idxtmp
397
    logical, parameter :: trap_flag = .true.
398
    logical :: compute_trapped_error
399

400
    allocate(wgt(nspec))
×
401

402
    if (has_phi) then
×
403
       allocate(phi_app(-ntgrid:ntgrid,ntheta0,naky))
×
404
       allocate(phi_e(-ntgrid:ntgrid,ntheta0,naky,nesub))
×
405
       allocate(phi_l(-ntgrid:ntgrid,ntheta0,naky,ng2))
×
406
    end if
407

408
    if (has_apar) then
×
409
       allocate(apar_app(-ntgrid:ntgrid,ntheta0,naky))
×
410
       allocate(apar_e(-ntgrid:ntgrid,ntheta0,naky,nesub))
×
411
       allocate(apar_l(-ntgrid:ntgrid,ntheta0,naky,ng2))
×
412
    end if
413

414
    compute_trapped_error = grid_has_trapped_particles() .and. new_trap_int
×
415

416
    ! first call to g_adjust converts gyro-averaged dist. fn. (g)
417
    ! into nonadiabatic part of dist. fn. (h)
418
    call g_adjust (gnew, phi, bpar, direction = from_g_gs2)
×
419

420
    ! These next if statements and loops are essentially repeating some of getan
421
    ! and adding in error estimate calls, which are themselves essentially integrate_species
422
    ! but done multiple times for different v-space weights.
423

424
    ! take gyro-average of h at fixed total position (not g.c. position)
425
    ! Note here phi_app and apar_app are effectively antot and antota that
426
    ! would be returned from a call to getan_from_dnf(gnew, antot, antota,...)
427
    if (has_phi) then
×
428
       !$OMP PARALLEL DO DEFAULT(none) &
429
       !$OMP PRIVATE(iglo, isgn, ig) &
430
       !$OMP SHARED(g_lo, ntgrid, g_work, aj0, gnew) &
431
       !$OMP COLLAPSE(3) &
432
       !$OMP SCHEDULE(static)
433
       do iglo = g_lo%llim_proc, g_lo%ulim_proc
×
434
          do isgn = 1, 2
×
435
             do ig = -ntgrid, ntgrid
×
436
                g_work(ig, isgn, iglo) = aj0(ig, iglo) * gnew(ig, isgn, iglo)
×
437
             end do
438
          end do
439
       end do
440
       !$OMP END PARALLEL DO
441

442
       wgt = spec%z*spec%dens
×
443
       call integrate_species (g_work, wgt, phi_app)
×
444

445
       ! integrates dist fn of each species over v-space
446
       ! after dropping an energy grid point and returns
447
       ! phi_e, which contains the integral approximations
448
       ! to phi for each point dropped
449
       call eint_error (g_work, wgt, phi_e)
×
450

451
       ! integrates dist fn of each species over v-space
452
       ! after dropping an untrapped lambda grid point and returns phi_l.
453
       ! phi_l contains ng2 approximations for the integral over lambda that
454
       ! come from dropping different pts from the gaussian quadrature grid
455
       call lint_error (g_work, wgt, phi_l)
×
456

457
       ! next loop gets error estimate for trapped particles, if there are any
458
       if (compute_trapped_error) then
×
459
          ntrap = nlambda - ng2
×
460
          allocate(phi_t(-ntgrid:ntgrid, ntheta0, naky, ntrap))
×
461
          call zero_array(phi_t)
×
462
          call trap_error (g_work, wgt, phi_t)
×
463
       end if
464

465
    end if
466

467
    if (has_apar) then
×
468
       !$OMP PARALLEL DO DEFAULT(none) &
469
       !$OMP PRIVATE(iglo, isgn, ig) &
470
       !$OMP SHARED(g_lo, ntgrid, g_work, aj0, vpa, gnew) &
471
       !$OMP COLLAPSE(3) &
472
       !$OMP SCHEDULE(static)
473
       do iglo = g_lo%llim_proc, g_lo%ulim_proc
×
474
          do isgn = 1, 2
×
475
             do ig = -ntgrid, ntgrid
×
476
                g_work(ig, isgn, iglo) = aj0(ig, iglo) * vpa(ig, isgn, iglo) * &
×
477
                     gnew(ig, isgn, iglo)
×
478
             end do
479
          end do
480
       end do
481
       !$OMP END PARALLEL DO
482

483
       wgt = 2.0 *beta * spec%z * spec%dens * sqrt(spec%temp / spec%mass)
×
484
       call integrate_species (g_work, wgt, apar_app)
×
485

486
       call eint_error (g_work, wgt, apar_e)
×
487
       call lint_error (g_work, wgt, apar_l)
×
488
       if (compute_trapped_error) then
×
489
          ntrap = nlambda - ng2
×
490
          allocate(apar_t(-ntgrid:ntgrid, ntheta0, naky, ntrap))
×
491
          call zero_array(apar_t)
×
492
          call trap_error (g_work, wgt, apar_t)
×
493
       end if
494
    end if
495
    deallocate (wgt)
×
496

497
    ! second call to g_adjust converts from h back to g
498
    call g_adjust (gnew, phi, bpar, direction = to_g_gs2)
×
499

500
    allocate (kmax(ntheta0, naky))
×
501
    do ik = 1, naky
×
502
       do it = 1, ntheta0
×
503
          kmax(it,ik) = max(akx(it),aky(ik))
×
504
       end do
505
    end do
506

507
    errest = 0.0
×
508
    erridx = 0
×
509

510
    if (has_phi) then
×
511

512
       call estimate_error (phi_app, phi_e, kmax, errtmp, idxtmp)
×
513
       errest(1,:) = errtmp
×
514
       erridx(1,:) = idxtmp
×
515

516
       call estimate_error (phi_app, phi_l, kmax, errtmp, idxtmp)
×
517
       errest(2,:) = errtmp
×
518
       erridx(2,:) = idxtmp
×
519

520
       if (compute_trapped_error) then
×
521
          call estimate_error (phi_app, phi_t, kmax, errtmp, idxtmp, trap_flag)
×
522
          errest(3,:) = errtmp
×
523
          erridx(3,:) = idxtmp
×
524
          deallocate (phi_t)
×
525
       end if
526
       deallocate(phi_app, phi_e, phi_l)
×
527
    end if
528

529
    ! No trapped errors for apar?
530
    if (has_apar) then
×
531

532
       call estimate_error (apar_app, apar_e, kmax, errtmp, idxtmp)
×
533
       errest(4,:) = errtmp
×
534
       erridx(4,:) = idxtmp
×
535

536
       call estimate_error (apar_app, apar_l, kmax, errtmp, idxtmp)
×
537
       errest(5,:) = errtmp
×
538
       erridx(5,:) = idxtmp
×
539

540
       deallocate(apar_app, apar_e, apar_l)
×
541
    end if
542

543
    call adjust_vnmult(errest, compute_trapped_error)
×
544
  end subroutine get_verr
×
545

546
  !> FIXME : Add documentation
547
  subroutine estimate_error (app1, app2, kmax_local, errest, erridx, trap)
×
548
    use kt_grids, only: naky, ntheta0
549
    use theta_grid, only: ntgrid
550
    use le_grids, only: ng2, jend, il_is_trapped
551
    use optionals, only: get_option_with_default
552
    implicit none
553
    complex, dimension (-ntgrid:,:,:), intent (in) :: app1
554
    complex, dimension(-ntgrid:,:,:,:), intent (in) :: app2
555
    real, dimension(:, :), intent (in) :: kmax_local
556
    logical, intent (in), optional :: trap
557
    real, dimension(2), intent (out) :: errest
558
    integer, dimension(3), intent (out) :: erridx
559
    real, dimension(-ntgrid:ntgrid, ntheta0, naky) :: gpavg_arr, gs_arr
×
560
    integer, dimension(3) :: max_locs
561
    integer :: ik, it, ig, end_idx, igmax, ikmax, itmax
562
    real :: gdsum, gdmax, gnsum
563
    logical :: do_trap
564
    do_trap = get_option_with_default(trap, .false.)
×
565
    gpavg_arr = 0.0 ; gs_arr = 0.0
×
566

567
    !$OMP PARALLEL DO DEFAULT(none) &
568
    !$OMP PRIVATE(ik, it, ig, end_idx) &
569
    !$OMP SHARED(naky, ntheta0, ntgrid, do_trap, jend, ng2, app2, &
570
    !$OMP app1, kmax_local, gpavg_arr, gs_arr) &
571
    !$OMP COLLAPSE(3) &
572
    !$OMP SCHEDULE(static)
573
    do ik = 1, naky
×
574
       do it = 1, ntheta0
×
575
          do ig = -ntgrid, ntgrid
×
576
             if (.not. do_trap .or. il_is_trapped(jend(ig))) then
×
577
                if (do_trap) then
×
578
                   end_idx = jend(ig) - ng2
×
579
                else
580
                   end_idx = size(app2, 4)
×
581
                end if
582

583
                gpavg_arr(ig, it, ik) = sum(abs(app1(ig, it, ik) - app2(ig, it, ik, 1:end_idx))) * kmax_local(it, ik) / end_idx
×
584
                gs_arr(ig, it, ik) = kmax_local(it, ik) * abs(app1(ig, it, ik))
×
585
             end if
586
          end do
587
       end do
588
    end do
589
    !$OMP END PARALLEL DO
590

591
    max_locs = maxloc(gpavg_arr)
×
592
    igmax = max_locs(1) - (ntgrid + 1) !Adjust for non-1 lbound
×
593
    itmax = max_locs(2)
×
594
    ikmax = max_locs(3)
×
595

596
    if (abs(gs_arr(igmax, itmax, ikmax)) < epsilon(0.0)) then
×
597
       gdmax = 0.0
×
598
    else
599
       gdmax = gpavg_arr(igmax, itmax, ikmax) / gs_arr(igmax, itmax, ikmax)
×
600
    end if
601

602
    erridx(1) = igmax
×
603
    erridx(2) = ikmax
×
604
    erridx(3) = itmax
×
605
    errest(1) = gdmax
×
606

607
    gnsum = sum(gpavg_arr)
×
608
    gdsum = sum(gs_arr)
×
609

610
    ! Guard against divide by zero. This can commonly occur on the first time step
611
    ! in runs with fapar /= 0 due to our initial conditions typically ensuring that
612
    ! the initial apar is identically zero.
613
    if (abs(gdsum) < epsilon(0.0)) then
×
614
       errest(2) = 0.0
×
615
    else
616
       errest(2) = gnsum / gdsum
×
617
    end if
618
  end subroutine estimate_error
×
619

620
  !> Calculates energy grid and (untrapped) pitch angle weights for
621
  !> integrals which drop a point from the energy/lambda
622
  !> dimensions. These are only used for estimating the velocity space
623
  !> error in [[dist_fn::get_verr]] as a part of the adaptive
624
  !> collisionality algorithm.
625
  subroutine init_weights
×
626
    use file_utils, only: open_output_file, close_output_file
627
    use constants, only: twopi => dtwopi
628
    use species, only: nspec, f0_values
629
    use le_grids, only: get_weights, speed, nesub, ng2, vcut, w, energy, xx, negrid, nmax
630
    use le_grids, only: grid_has_trapped_particles, wl, nlambda, al, new_trap_int
631
    use le_grids, only: setup_trapped_lambda_grids_new_trap_int
632
    use theta_grid, only: bmag, bmax, ntgrid
633
    implicit none
634
    real, dimension (:), allocatable :: modzeroes, werrtmp  ! (negrid-2)
×
635
    real, dimension (:), allocatable :: lmodzeroes, wlerrtmp ! (ng2-1)
×
636
    real, dimension(:,:), allocatable :: dummy, wlerr
×
637
    real, dimension(:,:,:), allocatable :: wlterr, werr
×
638
    integer :: ipt, ndiv, divmax, is, ntrap, il
639
    logical :: eflag
640

641
    allocate(lmodzeroes(ng2-1), wlerrtmp(ng2-1))
×
642
    allocate(wlerr(ng2,ng2))
×
643
    wlerr = 0.0; lmodzeroes = 0.0; wlerrtmp = 0.0
×
644
    allocate(modzeroes(nesub-1), werrtmp(nesub-1))
×
645
    allocate(werr(negrid-1,nesub,nspec))
×
646
    werr = 0.0 ; modzeroes = 0.0 ; werrtmp = 0.0
×
647

648
    ! loop to obtain weights for energy grid points.  negrid-1 sets
649
    ! of weights are needed because we want to compute integrals
650
    ! for negrid-1 sets of energy points (corresponding to negrid-1
651
    ! points that we can choose to drop from the guassian quadrature)
652
    do ipt = 1, nesub
×
653
       do is = 1, nspec
×
654

655
         ! drops the point corresponding to ipt from the energy grid
656
         if (ipt /= 1) modzeroes(:ipt-1) = speed(:ipt-1,is)
×
657
         if (ipt /= nesub) modzeroes(ipt:nesub-1) = speed(ipt+1:nesub,is)
×
658

659
         ! get weights for energy grid points
660
         call get_weights (nmax,0.0,vcut,modzeroes,werrtmp,ndiv,divmax,eflag)
×
661

662
         ! a zero is left in the position corresponding to the dropped point
663
         if (ipt /= 1) werr(:ipt-1,ipt,is) = werrtmp(:ipt-1)
×
664
         if (ipt /= nesub) werr(ipt+1:nesub,ipt,is) = werrtmp(ipt:nesub-1)
×
665
         werr(nesub+1:,ipt,is) = w(nesub+1:negrid-1,is)
×
666

667
         ! absorbing volume element into weights
668
         werr(:nesub,ipt,is) = werr(:nesub,ipt,is)*energy(:nesub,is)*twopi*f0_values(:nesub,is)
×
669
       end do
670
    end do
671

672
    ! same thing done here for lamdba as was
673
    ! done earlier for energy space
674
    do ipt=1,ng2
×
675

676
       if (ipt /= 1) lmodzeroes(:ipt-1) = xx(:ipt-1)
×
677
       if (ipt /= ng2) lmodzeroes(ipt:ng2-1) = xx(ipt+1:)
×
678

679
       call get_weights (nmax,1.0,0.0,lmodzeroes,wlerrtmp,ndiv,divmax,eflag)
×
680

681
       if (ipt /= 1) wlerr(:ipt-1,ipt) = wlerrtmp(:ipt-1)
×
682
       if (ipt /= ng2) wlerr(ipt+1:,ipt) = wlerrtmp(ipt:)
×
683
    end do
684
    deallocate(modzeroes,werrtmp,lmodzeroes,wlerrtmp)
×
685
    eflag = .false.
×
686

687
    !lint_error
688
    allocate (wlmod(-ntgrid:ntgrid,nlambda,ng2))
×
689

690
    do ipt = 1, ng2
×
691
       do il = 1, ng2
×
692
          wlmod(:,il,ipt) = wlerr(il,ipt)*2.0*sqrt((bmag(:)/bmax) &
×
693
               *((1.0/bmax-al(il))/(1.0/bmag(:)-al(il))))
×
694
       end do
695
       !If we have trapped particles use the precalculated weights
696
       !in wlmod as above is only for passing particles
697
       if (grid_has_trapped_particles()) wlmod(:,ng2+1:,ipt) = wl(:,ng2+1:)
×
698
    end do
699

700
    !How many trapped pitch angles are there?
701
    if (grid_has_trapped_particles() .and. new_trap_int) then
×
702
       ntrap = nlambda - ng2
×
703
       allocate(wlterr(-ntgrid:ntgrid,nlambda,ntrap))
×
704
       allocate(dummy(-ntgrid:ntgrid,nlambda))
×
705
       wlterr = 0
×
706
       call setup_trapped_lambda_grids_new_trap_int(al, dummy, wlterr)
×
707

708
       ! trap_error
709
       allocate (wtmod(-ntgrid:ntgrid,nlambda,ntrap))
×
710

711
       do ipt=1,ntrap
×
712
          wtmod(:,:ng2,ipt) = wl(:,:ng2)
×
713
       end do
714

715
       wtmod(:,ng2+1:,:) = wlterr(:,ng2+1:,:)
×
716
    end if
717

718
    ! eint_error
719
    allocate (wmod(negrid, nesub, nspec))
×
720

721
    wmod(:negrid-1,:,:) = werr(:,:,:)
×
722
    do is = 1,nspec
×
723
       wmod(negrid,:,is) = w(negrid,is)
×
724
    end do
725
  end subroutine init_weights
×
726

727
  !> Deallocate the weights used to provide error estimates
728
  subroutine finish_weights
×
729
    if (allocated(wlmod)) deallocate (wlmod)
×
730
    if (allocated(wmod)) deallocate (wmod)
×
731
    if (allocated(wtmod)) deallocate (wtmod)
×
732
  end subroutine finish_weights
×
733

734
  subroutine int_error_helper(g, weights, w_e, w_l, total)
×
735
    use theta_grid, only: ntgrid
736
    use gs2_layouts, only: g_lo, is_idx, ik_idx, it_idx, ie_idx, il_idx
737
    use array_utils, only: zero_array
738
    complex, dimension (-ntgrid:,:,g_lo%llim_proc:), intent (in) :: g
739
    real, dimension(:), intent (in) :: weights
740
    real, dimension(:, :), intent(in) :: w_e
741
    real, dimension(-ntgrid:, :), intent(in) :: w_l
742
    complex, dimension (-ntgrid:,:,:), intent (out) :: total
743
    integer :: is, il, ie, ik, it, iglo
744
    call zero_array(total)
×
745
    do iglo = g_lo%llim_proc, g_lo%ulim_proc
×
746
       ik = ik_idx(g_lo,iglo)
×
747
       it = it_idx(g_lo,iglo)
×
748
       ie = ie_idx(g_lo,iglo)
×
749
       is = is_idx(g_lo,iglo)
×
750
       il = il_idx(g_lo,iglo)
×
751

752
       total(:, it, ik) = total(:, it, ik) + weights(is) * w_e(ie, is) * w_l(:,il) * &
×
753
            (g(:,1,iglo)+g(:,2,iglo))
×
754
    end do
755
  end subroutine int_error_helper
×
756

757
  !> FIXME : Add documentation
758
  subroutine lint_error (g, weights, total)
×
759
    use le_grids, only: ng2, w
760
    use mp, only: sum_allreduce
761
    implicit none
762
    complex, dimension(:,:,:), intent (in) :: g
763
    real, dimension(:), intent (in) :: weights
764
    complex, dimension(:,:,:,:), intent (out) :: total
765
    integer :: ipt
766
    !For each (passing) lambda point do velocity space integral
767
    !$OMP PARALLEL DO DEFAULT(none) &
768
    !$OMP PRIVATE(ipt) &
769
    !$OMP SHARED(ng2, g, weights, w, wlmod, total) &
770
    !$OMP SCHEDULE(static)
771
    do ipt = 1, ng2
×
772
       call int_error_helper(g, weights, w, wlmod(:, :, ipt), total(:, :, :, ipt))
×
773
    end do
774
    !$OMP END PARALLEL DO
775
    call sum_allreduce (total)
×
776
  end subroutine lint_error
×
777

778
  !> FIXME : Add documentation
779
  subroutine trap_error (g, weights, total)
×
780
    use le_grids, only: nlambda, ng2, w
781
    use mp, only: sum_allreduce
782
    implicit none
783
    complex, dimension(:,:,:), intent (in) :: g
784
    real, dimension(:), intent (in) :: weights
785
    complex, dimension(:,:,:,:), intent (out) :: total
786
    integer :: ipt
787
    !Loop over number of trapped points
788
    !$OMP PARALLEL DO DEFAULT(none) &
789
    !$OMP PRIVATE(ipt) &
790
    !$OMP SHARED(nlambda, ng2, g, weights, w, wtmod, total) &
791
    !$OMP SCHEDULE(static)
792
    do ipt = 1, nlambda - ng2
×
793
       call int_error_helper(g, weights, w, wtmod(:, :, ipt), total(:, :, :, ipt))
×
794
    end do
795
    !$OMP END PARALLEL DO
796
    call sum_allreduce (total)
×
797
  end subroutine trap_error
×
798

799
  !> FIXME : Add documentation
800
  subroutine eint_error (g, weights, total)
×
801
    use le_grids, only: nesub, wl
802
    use mp, only: sum_allreduce
803
    implicit none
804
    complex, dimension(:,:,:), intent (in) :: g
805
    real, dimension(:), intent (in) :: weights
806
    complex, dimension(:,:,:,:), intent (out) :: total
807
    integer :: ipt
808
    !Do velocity space integral for each ipt (for all energy grid points)
809
    !$OMP PARALLEL DO DEFAULT(none) &
810
    !$OMP PRIVATE(ipt) &
811
    !$OMP SHARED(nesub, g, weights, wmod, wl, total) &
812
    !$OMP SCHEDULE(static)
813
    do ipt = 1, nesub
×
814
       call int_error_helper(g, weights, wmod(:, ipt, :), wl, total(:, :, :, ipt))
×
815
    end do
816
    !$OMP END PARALLEL DO
817
    call sum_allreduce (total)
×
818
  end subroutine eint_error
×
819

820
  !> Error estimate based on monitoring amplitudes of legendre polynomial coefficients.
821
  !>
822
  !> FIXME: finish documentation
823
  subroutine get_gtran (geavg, glavg, gtavg, phi, bpar)
×
824
    use le_grids, only: nlambda, ng2, nesub, jend, grid_has_trapped_particles, il_is_trapped
825
    use theta_grid, only: ntgrid
826
    use kt_grids, only: ntheta0, naky
827
    use species, only: nspec
828
    use dist_fn_arrays, only: gnew, aj0, g_adjust, g_work, to_g_gs2, from_g_gs2
829
    use gs2_layouts, only: g_lo
830
    use array_utils, only: zero_array
831
    implicit none
832
    complex, dimension (-ntgrid:,:,:), intent (in) :: phi, bpar
833
    real, intent (out) :: geavg, glavg, gtavg
834
    real, dimension (:), allocatable :: gne2, gnl2, gnt2
×
835
    complex, dimension (:,:,:,:,:), allocatable :: getran, gltran, gttran
×
836
    real :: genorm, gemax, genum, gedenom
837
    real :: glnorm, glmax, glnum, gldenom
838
    real :: gtnorm, gtmax, gtnum, gtdenom
839
    integer :: ig, it, ik, is, ie, il, iglo, isgn
840
    allocate(gne2(0:nesub-1))
×
841
    allocate(gnl2(0:ng2-1))
×
842

843
    genorm = 0.0 ; glnorm = 0.0
×
844
    gne2  = 0.0 ; gnl2 = 0.0
×
845
    gemax = 0.0 ; glmax = 0.0
×
846
    genum = 0.0 ; gedenom = 0.0
×
847
    glnum = 0.0 ; gldenom = 0.0
×
848
    gtnum = 0.0 ; gtdenom = 0.0
×
849

850
    allocate(getran(0:nesub-1, -ntgrid:ntgrid, ntheta0, naky, nspec))
×
851
    allocate(gltran(0:ng2-1, -ntgrid:ntgrid, ntheta0, naky, nspec))
×
852

853
    call zero_array(getran); call zero_array(gltran)
×
854

855
    ! Only needed if we have trapped particles, but always allocate to support
856
    ! omp reduction later etc.
857
    allocate(gnt2(0:2*(nlambda-ng2-1)))
×
858
    allocate(gttran(0:2*(nlambda-ng2-1), -ntgrid:ntgrid, ntheta0, naky, nspec))
×
859
    gtnorm = 0.0 ; gnt2 = 0.0 ; gtmax = 0.0 ; call zero_array(gttran)
×
860

861
    ! transform from g to h
862
    call g_adjust (gnew, phi, bpar, direction = from_g_gs2)
×
863

864
    !$OMP PARALLEL DO DEFAULT(none) &
865
    !$OMP PRIVATE(iglo, isgn, ig) &
866
    !$OMP SHARED(g_lo, ntgrid, g_work, aj0, gnew) &
867
    !$OMP COLLAPSE(3) &
868
    !$OMP SCHEDULE(static)
869
    do iglo = g_lo%llim_proc, g_lo%ulim_proc
×
870
       do isgn = 1, 2
×
871
          do ig = -ntgrid, ntgrid
×
872
             g_work(ig, isgn, iglo) = aj0(ig, iglo) * gnew(ig, isgn, iglo)
×
873
          end do
874
       end do
875
    end do
876
    !$OMP END PARALLEL DO
877

878
    ! transform from h back to g
879
    call g_adjust (gnew, phi, bpar, direction = to_g_gs2)
×
880

881
    ! perform legendre transform on dist. fn. to obtain coefficients
882
    ! used when expanding dist. fn. in legendre polynomials
883
    if (grid_has_trapped_particles()) then
×
884
       call legendre_transform (g_work, getran, gltran, gttran)
×
885
    else
886
       call legendre_transform (g_work, getran, gltran)
×
887
    end if
888

889
    !$OMP PARALLEL DO DEFAULT(none) &
890
    !$OMP PRIVATE(is, ik, it, ig, ie, il, gne2, gnl2, genorm, gemax, glnorm, glmax, &
891
    !$OMP gnt2, gtnorm, gtmax) &
892
    !$OMP SHARED(nspec, naky, ntheta0, ntgrid, getran, gltran, gttran, nesub, ng2, jend) &
893
    !$OMP REDUCTION(+: genum, gedenom, glnum, gldenom, gtnum, gtdenom) &
894
    !$OMP SCHEDULE(static)
895
    do is = 1, nspec
×
896
       do ik = 1, naky
×
897
          do it = 1, ntheta0
×
898
             do ig = -ntgrid, ntgrid
×
899
                do ie = 0, nesub-1
×
900
                   gne2(ie) = real(getran(ie,ig,it,ik,is)*conjg(getran(ie,ig,it,ik,is)))
×
901
                end do
902

903
                do il=0,ng2-1
×
904
                   gnl2(il) = real(gltran(il,ig,it,ik,is)*conjg(gltran(il,ig,it,ik,is)))
×
905
                end do
906

907
                genorm = maxval(gne2)
×
908
                if (nesub < 3) then
×
909
                   gemax = gne2(size(gne2)-1)
×
910
                else
911
                   gemax = maxval(gne2(nesub-3:nesub-1))
×
912
                end if
913
                glnorm = maxval(gnl2)
×
914
                glmax = maxval(gnl2(ng2-3:ng2-1))
×
915

916
                genum = genum + gemax
×
917
                gedenom = gedenom + genorm
×
918
                glnum = glnum + glmax
×
919
                gldenom = gldenom + glnorm
×
920

921
                if (grid_has_trapped_particles()) then
×
922
                   do il=0, 2*(jend(ig)-ng2-1)
×
923
                      gnt2(il) = real(gttran(il,ig,it,ik,is)*conjg(gttran(il,ig,it,ik,is)))
×
924
                   end do
925
                   gtnorm = maxval(gnt2(0:2*(jend(ig)-ng2-1)))
×
926
                   if (il_is_trapped(jend(ig))) then
×
927
                      gtmax = maxval(gnt2(2*(jend(ig)-ng2-1)-2:2*(jend(ig)-ng2-1)))
×
928
                   else
929
                      gtmax = gnt2(0)
×
930
                   end if
931

932
                   gtnum = gtnum + gtmax
×
933
                   gtdenom = gtdenom + gtnorm
×
934
                end if
935
             end do
936
          end do
937
       end do
938
    end do
939
    !$OMP END PARALLEL DO
940

941
    geavg = genum / gedenom
×
942
    glavg = glnum / gldenom
×
943
    if (grid_has_trapped_particles()) gtavg = gtnum / gtdenom
×
944

945
    deallocate(gne2, gnl2, getran, gltran, gnt2, gttran)
×
946
  end subroutine get_gtran
×
947

948
  subroutine setup_legendre_transform
×
949
    use le_grids, only: nesub, nlambda, ng2, vcut, speed, jend, al, negrid, xx
950
    use le_grids, only: grid_has_trapped_particles
951
    use theta_grid, only: bmag, bmax, ntgrid
952
    use species, only: nspec
953
    real, dimension (:), allocatable :: nodes
×
954
    real, dimension (:,:), allocatable :: lpltmp, lpttmp
×
955
    real :: ulim
956
    integer :: is, il, ig, ntrap, lpesize
957
    lpesize = nesub
×
958

959
    if (allocated(lpl)) return
×
960
    allocate(lpltmp(ng2,0:ng2-1))
×
961
    allocate(lpl(nlambda,0:ng2-1))
×
962
    allocate(lpe(negrid,0:lpesize-1,nspec)) ; lpe = 0.0
×
963

964
    ! get value of first nesub legendre polynomials
965
    ! at each of the grid points on (0,vcut)
966
    do is = 1, nspec
×
967
       call legendre_polynomials (0.0,vcut,speed(:lpesize,is),lpe(:lpesize,:,is))
×
968
    end do
969

970
    ! get value of first ng2 legendre polynomials
971
    ! at each of the grid points on (0,1)
972
    call legendre_polynomials (0.0,1.0,xx,lpltmp)
×
973

974
    lpl = 0.0
×
975
    lpl(1:ng2,:) = lpltmp
×
976

977
    if (grid_has_trapped_particles()) then
×
978
       allocate (lpt(nlambda,0:2*(nlambda-ng2-1),-ntgrid:ntgrid,2))
×
979
       lpt = 0.0
×
980
       do ig = -ntgrid, ntgrid
×
981
          ntrap = 1
×
982
          if (jend(ig) > ng2+1) then
×
983
             ntrap = jend(ig)-ng2
×
984
             allocate (nodes(2*ntrap-1))
×
985
             allocate (lpttmp(2*ntrap-1,0:2*(ntrap-1)))
×
986
             do il = 1, ntrap
×
987
                nodes(il) = -sqrt(max(0.0,1.0-al(ng2+il)*bmag(ig)))
×
988
             end do
989
             nodes(ntrap+1:) = -nodes(ntrap-1:1:-1)
×
990

991
             ulim = sqrt(1.0-bmag(ig)/bmax)
×
992
             call legendre_polynomials (-ulim,ulim,nodes,lpttmp)
×
993
             lpt(ng2+1:jend(ig),0:2*(ntrap-1),ig,2) = lpttmp(1:ntrap,:)
×
994
             lpt(ng2+1:jend(ig)-1,0:2*(ntrap-1),ig,1) = lpttmp(2*ntrap-1:ntrap+1:-1,:)
×
995
             deallocate (nodes, lpttmp)
×
996
          end if
997
       end do
998
    end if
999

1000
    deallocate (lpltmp)
×
1001

1002
  end subroutine setup_legendre_transform
×
1003

1004
  subroutine finish_legendre_transform
×
1005
    if (allocated(lpl)) deallocate(lpl, lpe, lpt)
×
1006
  end subroutine finish_legendre_transform
×
1007
  
1008
  !> FIXME : Add documentation
1009
  !>
1010
  !> @note This routine depends on the values stored in [[xx]] as input
1011
  !> to the legendre_polynomials method. However, with Radau grids the
1012
  !> values in [[xx]] are not Legendre roots so this routine is probably
1013
  !> incorrect when [[le_grids_knobs::radau_gauss_grid]] is `.true.`.
1014
  subroutine legendre_transform (g, tote, totl, tott)  
×
1015
    use mp, only: sum_allreduce
1016
    use theta_grid, only: ntgrid
1017
    use gs2_layouts, only: g_lo, it_idx, ik_idx, il_idx, ie_idx, is_idx
1018
    use le_grids, only: nesub, w, wl, ng2, jend
1019
    use array_utils, only: zero_array
1020
    implicit none
1021
    complex, dimension (-ntgrid:,:,g_lo%llim_proc:), intent (in) :: g
1022
    complex, dimension (0:,-ntgrid:,:,:,:), intent (out) :: tote, totl
1023
    complex, dimension (0:,-ntgrid:,:,:,:), intent (out), optional :: tott
1024
    complex :: totfac
1025
    integer :: is, il, ie, ik, it, iglo, ig, im, lpesize
1026

1027
    lpesize = nesub
×
1028
    ! carry out legendre transform to get coefficients of
1029
    ! legendre polynomial expansion of g
1030
    call zero_array(tote) ; call zero_array(totl)
×
1031

1032
    !$OMP PARALLEL DO DEFAULT(none) &
1033
    !$OMP PRIVATE(iglo, it, ik, il, ie, is, totfac, im) &
1034
    !$OMP SHARED(g_lo, ntgrid, w, wl, g, lpesize, ng2, lpe, lpl) &
1035
    !$OMP REDUCTION(+: tote, totl) &
1036
    !$OMP SCHEDULE(static)
1037
    do iglo = g_lo%llim_proc, g_lo%ulim_proc
×
1038
       it = it_idx(g_lo, iglo) ; ik = ik_idx(g_lo, iglo)
×
1039
       il = il_idx(g_lo, iglo) ; ie = ie_idx(g_lo, iglo) ; is = is_idx(g_lo, iglo)
×
1040
       do ig = -ntgrid, ntgrid
×
1041
          totfac = w(ie,is) * wl(ig,il) * (g(ig,1,iglo) + g(ig,2,iglo))
×
1042
          do im = 0, lpesize - 1
×
1043
             tote(im, ig, it, ik, is) = tote(im, ig, it, ik, is) &
×
1044
                  + totfac * lpe(ie,im,is) * (2*im+1)
×
1045
          end do
1046
          do im = 0, ng2 - 1
×
1047
             totl(im, ig, it, ik, is) = totl(im, ig, it, ik, is) &
×
1048
                  + totfac * lpl(il,im) * (2*im+1)
×
1049
          end do
1050
       end do
1051
    end do
1052
    !$OMP END PARALLEL DO
1053

1054
    !Now complete velocity integral, bringing back results to proc0
1055
    call sum_allreduce (tote)
×
1056
    call sum_allreduce (totl)
×
1057

1058
    if (present(tott)) then
×
1059
       call zero_array(tott)
×
1060
       !$OMP PARALLEL DO DEFAULT(none) &
1061
       !$OMP PRIVATE(iglo, it, ik, il, ie, is, im) &
1062
       !$OMP SHARED(g_lo, ntgrid, w, wl, g, jend, ng2, lpt) &
1063
       !$OMP REDUCTION(+: tott) &
1064
       !$OMP SCHEDULE(static)
1065
       do iglo = g_lo%llim_proc, g_lo%ulim_proc
×
1066
          it = it_idx(g_lo, iglo) ; ik = ik_idx(g_lo, iglo)
×
1067
          il = il_idx(g_lo, iglo) ; ie = ie_idx(g_lo, iglo) ; is = is_idx(g_lo, iglo)
×
1068
          do ig = -ntgrid, ntgrid
×
1069
             do im = 0, 2 * (jend(ig)-ng2-1)
×
1070
                tott(im, ig, it, ik, is) = tott(im, ig, it, ik, is) + &
×
1071
                     w(ie,is) * wl(ig,il) * (lpt(il,im,ig,1) * g(ig,1,iglo) &
×
1072
                     + lpt(il,im,ig,2) * g(ig,2,iglo)) * (2*im+1)
×
1073
             end do
1074
          end do
1075
       end do
1076
       !$OMP END PARALLEL DO
1077
       call sum_allreduce (tott)
×
1078
    end if
1079
  end subroutine legendre_transform
×
1080

1081
  !> FIXME : Add documentation  
1082
  subroutine legendre_polynomials (llim, ulim, xptsdum, lpdum)
×
1083
    use iso_fortran_env, only: real64
1084
    implicit none
1085
    real, intent (in) :: ulim, llim
1086
    real, dimension (:), intent (in)   :: xptsdum
1087
    real, dimension (:,0:), intent(out) :: lpdum
1088
    real(real64), dimension (:), allocatable :: lp1, lp2, lp3, zshift
×
1089
    integer :: j, mmax
1090

1091
    lpdum = 0.0
×
1092
    mmax = size(xptsdum)
×
1093

1094
    allocate(lp1(mmax),lp2(mmax),lp3(mmax),zshift(mmax))
×
1095

1096
    lp1 = real(1.0,kind(lp1(1)))
×
1097
    lp2 = real(0.0,kind(lp2(1)))
×
1098

1099
    lpdum(:,0) = real(1.0,kind(lpdum))
×
1100

1101
    zshift = real(2.0,kind(zshift))*(xptsdum-llim)/(ulim-llim) - real(1.0,kind(zshift))
×
1102

1103
    do j=1, size(lpdum(1,:))-1
×
1104
       lp3 = lp2
×
1105
       lp2 = lp1
×
1106
       lp1 = ((2*j-1) * zshift * lp2 - (j-1) * lp3) / j
×
1107
       lpdum(:,j) = lp1
×
1108
    end do
1109

1110
    deallocate(lp1,lp2,lp3,zshift)
×
1111
  end subroutine legendre_polynomials
×
1112
end module diagnostics_velocity_space
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc