@@ -380,6 +380,8 @@ MODULE oft_gs
380380!- -----------------------------------------------------------------------------
381381type, extends(cylinv_interp) :: gsinv_interp
382382 LOGICAL :: compute_geom = .FALSE. ! < Needs docs
383+ LOGICAL :: compute_fsa = .FALSE. ! < Compute additional flux surface averages (see @ref gsinv_apply)
384+ real (8 ) :: f_surf = 0.d0 ! < \f$ F = R B_{\phi} \f$ on the surface being traced (required if `compute_fsa`)
383385 real (8 ), pointer , dimension (:) :: uvals = > NULL () ! < Needs docs
384386 class(oft_vector), pointer :: u = > NULL () ! < Field for interpolation
385387 class(oft_scalar_bfem), pointer :: lag_rep = > NULL () ! < Lagrange FE representation
@@ -4338,7 +4340,7 @@ end subroutine psimax_error_grad
43384340!- -----------------------------------------------------------------------------
43394341! > Get q profile for equilibrium
43404342!- -----------------------------------------------------------------------------
4341- subroutine gs_get_qprof (gseq ,nr ,psi_q ,prof ,dl ,rbounds ,zbounds ,ravgs )
4343+ subroutine gs_get_qprof (gseq ,nr ,psi_q ,prof ,dl ,rbounds ,zbounds ,ravgs , fsa_avgs , shape_geo )
43424344class(gs_equil), intent (inout ) :: gseq ! < G-S object
43434345integer (4 ), intent (in ) :: nr ! < Number of flux surfaces to sample
43444346real (8 ), intent (in ) :: psi_q(nr) ! < Locations to sample in normalized flux
@@ -4347,12 +4349,14 @@ subroutine gs_get_qprof(gseq,nr,psi_q,prof,dl,rbounds,zbounds,ravgs)
43474349real (8 ), optional , intent (out ) :: rbounds(2 ,2 ) ! < Radial bounds of surface `psi_q(1)` (should be LCFS)
43484350real (8 ), optional , intent (out ) :: zbounds(2 ,2 ) ! < Vertical bounds of surface `psi_q(1)` (should be LCFS)
43494351real (8 ), optional , intent (out ) :: ravgs(nr,4 ) ! < Flux surface averages <R>, <1/R>, <1/R^2>, and dV/dPsi
4352+ real (8 ), optional , intent (out ) :: fsa_avgs(nr,4 ) ! < Flux surface averages <|grad(psi)|>, <|grad(psi)|^2>, <B_p^2>, <1/B^2>
4353+ real (8 ), optional , intent (out ) :: shape_geo(nr,6 ) ! < Per-surface R_min, R_max, Z_min, Z_max, R(Z_min), R(Z_max)
43504354real (8 ) :: psi_surf,rmax,x1,x2,raxis,zaxis,fpol,qpsi
43514355real (8 ) :: pt(3 ),pt_last(3 ),pt_proj(3 ),f(3 ),psi_tmp(1 ),gop(3 ,3 )
43524356type (oft_lag_brinterp), target :: psi_int
43534357real (8 ), pointer :: ptout(:,:)
43544358real (8 ), parameter :: tol= 1.d-10
4355- integer (4 ) :: i,j,cell
4359+ integer (4 ) :: i,j,cell,imin_r,imax_r,imin_z,imax_z
43564360integer (4 ), parameter :: nlcfs= 1000
43574361logical :: lcfs_all,lcfs_any
43584362type (gsinv_interp), pointer :: field
@@ -4362,6 +4366,8 @@ subroutine gs_get_qprof(gseq,nr,psi_q,prof,dl,rbounds,zbounds,ravgs)
43624366lcfs_all = PRESENT (dl).AND. PRESENT (rbounds).AND. PRESENT (zbounds)
43634367IF (lcfs_any.AND. (.NOT. lcfs_all))CALL oft_abort(' All LCFS arguments must be passed if any are' ,' gs_get_qprof' ,__FILE__)
43644368IF (lcfs_all.AND. (psi_q(1 )>= 0.05d0 ))CALL oft_warn(' LCFS parameters requested but "psi_q(1)" far from LCFS, not projecting' )
4369+ IF (PRESENT (shape_geo).AND. lcfs_any)CALL oft_abort(' "shape_geo" cannot be combined with LCFS arguments' , &
4370+ ' gs_get_qprof' ,__FILE__)
43654371!- --
43664372raxis= gseq% o_point(1 )
43674373zaxis= gseq% o_point(2 )
@@ -4405,11 +4411,17 @@ subroutine gs_get_qprof(gseq,nr,psi_q,prof,dl,rbounds,zbounds,ravgs)
44054411END IF
44064412!- --Trace
44074413call set_tracer(1 )
4408- ! $omp parallel private(psi_surf,pt,pt_proj,ptout,fpol,qpsi,field) firstprivate(pt_last)
4414+ ! $omp parallel private(psi_surf,pt,pt_proj,ptout,fpol,qpsi,field,i,imin_r,imax_r, &
4415+ ! $omp imin_z,imax_z,error_str,lcfs_rz) firstprivate(pt_last)
4416+ NULLIFY(ptout)
44094417ALLOCATE (field)
44104418field% u= >gseq% psi
44114419CALL field% setup(gseq% device% fe_rep)
4412- IF (PRESENT (ravgs))THEN
4420+ IF (PRESENT (fsa_avgs))THEN
4421+ field% compute_geom= .TRUE.
4422+ field% compute_fsa= .TRUE.
4423+ active_tracer% neq= 10
4424+ ELSE IF (PRESENT (ravgs))THEN
44134425 field% compute_geom= .TRUE.
44144426 active_tracer% neq= 6
44154427ELSE
@@ -4421,6 +4433,7 @@ subroutine gs_get_qprof(gseq,nr,psi_q,prof,dl,rbounds,zbounds,ravgs)
44214433active_tracer% raxis= raxis
44224434active_tracer% zaxis= zaxis
44234435active_tracer% inv= .TRUE.
4436+ IF (PRESENT (shape_geo))ALLOCATE (ptout(3 ,active_tracer% maxsteps+1 ))
44244437! $omp do schedule(dynamic,1)
44254438do j= 1 ,nr
44264439 !- -----------------------------------------------------------------------------
@@ -4440,8 +4453,18 @@ subroutine gs_get_qprof(gseq,nr,psi_q,prof,dl,rbounds,zbounds,ravgs)
44404453 CALL gs_psi2r(gseq,psi_surf,pt,psi_int)
44414454 ! !$omp end critical
44424455 pt_last= pt
4443- IF (j== 1.AND .PRESENT (dl))THEN
4444- ALLOCATE (ptout(3 ,active_tracer% maxsteps+1 ))
4456+ !- --Get flux variables. F is constant on the surface, so it must be known
4457+ !- --before tracing when <1/B^2> is accumulated in the tracing ODE.
4458+ IF (gseq% mode== 0 )THEN
4459+ fpol= gseq% ffp_scale* gseq% I% f(psi_surf)+ gseq% I% f_offset
4460+ ELSE
4461+ fpol= SIGN (1.d0 ,gseq% I% f_offset)* SQRT (gseq% ffp_scale* gseq% I% f(psi_surf) + gseq% I% f_offset** 2 )
4462+ END IF
4463+ field% f_surf= fpol
4464+ !- --Record the traced path only where it is consumed: every surface for `shape_geo`,
4465+ !- --but just the first for the LCFS parameters.
4466+ IF (PRESENT (shape_geo).OR. (PRESENT (dl).AND. (j== 1 )))THEN
4467+ IF (.NOT. PRESENT (shape_geo))ALLOCATE (ptout(3 ,active_tracer% maxsteps+1 ))
44454468 CALL tracinginv_fs(gseq% device% fe_rep% mesh,pt(1 :2 ),ptout)
44464469 ELSE
44474470 CALL tracinginv_fs(gseq% device% fe_rep% mesh,pt(1 :2 ))
@@ -4452,10 +4475,10 @@ subroutine gs_get_qprof(gseq,nr,psi_q,prof,dl,rbounds,zbounds,ravgs)
44524475 CALL oft_warn(error_str)
44534476 CYCLE
44544477 end if
4455- IF ((j == 1 ) .AND. PRESENT (dl)) THEN
4456- !- -----------------------------------------------------------------------------
4457- ! Reinterpolate LCFS to get uniform spacing and compute geometric parameters
4458- !- -----------------------------------------------------------------------------
4478+ !- -----------------------------------------------------------------------------
4479+ ! If shape information is requested, reinterpolate LCFS to get uniform spacing for geometric parameters
4480+ !- -----------------------------------------------------------------------------
4481+ IF ( PRESENT (shape_geo) .OR. ( PRESENT (dl) .AND. (j == 1 ))) THEN
44594482 IF (active_tracer% nsteps> nlcfs)THEN
44604483 !- --Allocate and fit spline
44614484 CALL spline_alloc(lcfs_rz,active_tracer% nsteps,2 )
@@ -4474,53 +4497,70 @@ subroutine gs_get_qprof(gseq,nr,psi_q,prof,dl,rbounds,zbounds,ravgs)
44744497 CALL spline_dealloc(lcfs_rz)
44754498 active_tracer% nsteps= nlcfs
44764499 END IF
4477- !- --Extrapolate to real LCFS
4478- IF (psi_q( 1 )< 0.05d0 )THEN
4479- DO i = 1 ,active_tracer % nsteps
4480- pt( 1 : 2 ) = ptout( 2 : 3 ,i)
4481- pt_proj( 1 : 2 ) = pt( 1 : 2 ) - gseq % o_point
4482- pt_proj = pt_proj / SQRT ( SUM (pt_proj( 1 : 2 ) ** 2 ))
4483- CALL gs_psi2pt(gseq,x1,pt,gseq % o_point,pt_proj,psi_int)
4484- ptout(2 : 3 ,i)= pt( 1 : 2 )
4500+ !- --Per-surface extrema, used for elongation and triangularity
4501+ IF (PRESENT (shape_geo) )THEN
4502+ imin_r = 1 ; imax_r = 1 ; imin_z = 1 ; imax_z = 1
4503+ DO i = 2 ,active_tracer % nsteps
4504+ IF (ptout( 2 ,i)<ptout( 2 ,imin_r))imin_r = i
4505+ IF (ptout( 2 ,i)>ptout( 2 ,imax_r))imax_r = i
4506+ IF (ptout( 3 ,i)<ptout( 3 ,imin_z))imin_z = i
4507+ IF ( ptout(3 ,i)>ptout( 3 ,imax_z))imax_z = i
44854508 END DO
4509+ shape_geo(j,1 )= ptout(2 ,imin_r); shape_geo(j,2 )= ptout(2 ,imax_r)
4510+ shape_geo(j,3 )= ptout(3 ,imin_z); shape_geo(j,4 )= ptout(3 ,imax_z)
4511+ shape_geo(j,5 )= ptout(2 ,imin_z); shape_geo(j,6 )= ptout(2 ,imax_z)
44864512 END IF
4487- !- --Compute geometric parameters
4488- dl = 0.d0
4489- rbounds(:,1 )= ptout(2 :3 ,1 ); rbounds(:,2 )= ptout(2 :3 ,1 )
4490- zbounds(:,1 )= ptout(2 :3 ,1 ); zbounds(:,2 )= ptout(2 :3 ,1 )
4491- DO i= 2 ,active_tracer% nsteps
4492- dl = dl + SQRT (SUM ((ptout(2 :3 ,i)- ptout(2 :3 ,i-1 ))** 2 ))
4493- IF (ptout(2 ,i)<rbounds(1 ,1 ))THEN
4494- rbounds(:,1 )= ptout(2 :3 ,i)
4495- ELSE IF (ptout(2 ,i)>rbounds(1 ,2 ))THEN
4496- rbounds(:,2 )= ptout(2 :3 ,i)
4497- END IF
4498- IF (ptout(3 ,i)<zbounds(2 ,1 ))THEN
4499- zbounds(:,1 )= ptout(2 :3 ,i)
4500- ELSE IF (ptout(3 ,i)>zbounds(2 ,2 ))THEN
4501- zbounds(:,2 )= ptout(2 :3 ,i)
4513+ !- --LCFS length and spatial bounds
4514+ IF (PRESENT (dl).AND. (j== 1 ))THEN
4515+ !- --Extrapolate to real LCFS if diverted
4516+ IF (gseq% diverted.AND. (psi_q(1 )<0.05d0 ))THEN
4517+ DO i= 1 ,active_tracer% nsteps
4518+ pt(1 :2 )= ptout(2 :3 ,i)
4519+ pt_proj(1 :2 )= pt(1 :2 )- gseq% o_point
4520+ pt_proj= pt_proj/ SQRT (SUM (pt_proj(1 :2 )** 2 ))
4521+ CALL gs_psi2pt(gseq,x1,pt,gseq% o_point,pt_proj,psi_int)
4522+ ptout(2 :3 ,i)= pt(1 :2 )
4523+ END DO
45024524 END IF
4503- END DO
4504- IF (active_tracer% status/= 1 )dl=- 1.d0
4505- DEALLOCATE (ptout)
4506- END IF
4507- !- --Get flux variables
4508- IF (gseq% mode== 0 )THEN
4509- fpol= gseq% ffp_scale* gseq% I% f(psi_surf)+ gseq% I% f_offset
4510- ELSE
4511- fpol= SIGN (1.d0 ,gseq% I% f_offset)* SQRT (gseq% ffp_scale* gseq% I% f(psi_surf) + gseq% I% f_offset** 2 )
4525+ !- --Compute geometric parameters
4526+ dl = 0.d0
4527+ rbounds(:,1 )= ptout(2 :3 ,1 ); rbounds(:,2 )= ptout(2 :3 ,1 )
4528+ zbounds(:,1 )= ptout(2 :3 ,1 ); zbounds(:,2 )= ptout(2 :3 ,1 )
4529+ DO i= 2 ,active_tracer% nsteps
4530+ dl = dl + SQRT (SUM ((ptout(2 :3 ,i)- ptout(2 :3 ,i-1 ))** 2 ))
4531+ IF (ptout(2 ,i)<rbounds(1 ,1 ))THEN
4532+ rbounds(:,1 )= ptout(2 :3 ,i)
4533+ ELSE IF (ptout(2 ,i)>rbounds(1 ,2 ))THEN
4534+ rbounds(:,2 )= ptout(2 :3 ,i)
4535+ END IF
4536+ IF (ptout(3 ,i)<zbounds(2 ,1 ))THEN
4537+ zbounds(:,1 )= ptout(2 :3 ,i)
4538+ ELSE IF (ptout(3 ,i)>zbounds(2 ,2 ))THEN
4539+ zbounds(:,2 )= ptout(2 :3 ,i)
4540+ END IF
4541+ END DO
4542+ IF (active_tracer% status/= 1 )dl=- 1.d0
4543+ IF (.NOT. PRESENT (shape_geo))DEALLOCATE (ptout)
4544+ END IF
45124545 END IF
45134546 !- --Safety Factor (q)
45144547 qpsi= fpol* active_tracer% v(3 )/ (2 * pi)
45154548 prof(j)= qpsi
45164549 IF (PRESENT (ravgs))THEN
4517- ravgs(j,1 )= active_tracer% v(4 )/ active_tracer% v(2 )
4518- ravgs(j,2 )= active_tracer% v(5 )/ active_tracer% v(2 )
4519- ravgs(j,3 )= active_tracer% v(6 )/ active_tracer% v(2 )
4520- ravgs(j,4 )=- 2.d0 * pi* active_tracer% v(2 ) ! First derivative of FS volume (V')
4550+ ravgs(j,1 )= active_tracer% v(4 )/ active_tracer% v(2 ) ! <R>
4551+ ravgs(j,2 )= active_tracer% v(5 )/ active_tracer% v(2 ) ! <1/R>
4552+ ravgs(j,3 )= active_tracer% v(6 )/ active_tracer% v(2 ) ! <1/R^2>
4553+ ravgs(j,4 )=- 2.d0 * pi* active_tracer% v(2 ) ! First derivative of FS volume (V')
4554+ END IF
4555+ IF (PRESENT (fsa_avgs))THEN
4556+ fsa_avgs(j,1 )= active_tracer% v(7 )/ active_tracer% v(2 ) ! <|grad(psi)|>
4557+ fsa_avgs(j,2 )= active_tracer% v(8 )/ active_tracer% v(2 ) ! <|grad(psi)|^2>
4558+ fsa_avgs(j,3 )= active_tracer% v(9 )/ active_tracer% v(2 ) ! <B_p^2>
4559+ fsa_avgs(j,4 )= active_tracer% v(10 )/ active_tracer% v(2 ) ! <1/B^2>
45214560 END IF
45224561end do
45234562CALL active_tracer% delete
4563+ IF (ASSOCIATED (ptout))DEALLOCATE (ptout)
45244564CALL field% delete ()
45254565DEALLOCATE (field)
45264566! $omp end parallel
@@ -4857,7 +4897,7 @@ subroutine gsinv_apply(self,cell,f,gop,val)
48574897integer (4 ), allocatable :: j(:)
48584898integer (4 ) :: jc
48594899real (8 ) :: rop(3 ),d2op(6 ),pt(3 ),grad(3 ),tmp
4860- real (8 ) :: s,c
4900+ real (8 ) :: s,c,grad_psi2,Bp2,Bt2
48614901!- --Get dofs
48624902allocate (j(self% lag_rep% nce))
48634903call self% lag_rep% ncdofs(cell,j)
@@ -4882,6 +4922,17 @@ subroutine gsinv_apply(self,cell,f,gop,val)
48824922 val(5 )= val(2 )/ pt(1 )
48834923 val(6 )= val(2 )/ (pt(1 )** 2 )
48844924END IF
4925+ IF (self% compute_fsa)THEN
4926+ ! B_p = |grad(psi)|/R and B_t = F/R, with F constant on the surface. Note that
4927+ ! <B^2> is not accumulated here as it follows exactly from <B_p^2> + F^2<1/R^2>.
4928+ grad_psi2 = grad(1 )** 2 + grad(2 )** 2
4929+ Bp2 = grad_psi2/ pt(1 )** 2
4930+ Bt2 = (self% f_surf/ pt(1 ))** 2
4931+ val(7 )= val(2 )* SQRT (grad_psi2) ! <|grad(psi)|>
4932+ val(8 )= val(2 )* grad_psi2 ! <|grad(psi)|^2>
4933+ val(9 )= val(2 )* Bp2 ! <B_p^2>
4934+ val(10 )= val(2 )/ (Bp2+ Bt2) ! <1/B^2>
4935+ END IF
48854936! val(3:8)=val(3:8)
48864937deallocate (j)
48874938end subroutine gsinv_apply
0 commit comments