Skip to content

Commit 96f76b0

Browse files
fs2819Freddie Sheehanhansec
authored
TokaMaker: Add flux surface averaged quantities via new get_fsa() (OpenFUSIONToolkit#325)
Extend `gs_get_qprof` with optional `fsa_avgs`, `shape_geo` and `fpol_out` arguments, accumulated in the same tracing ODE as the existing `ravgs`: - <|grad(psi)|>, <|grad(psi)|^2>, <B_p^2> and <1/B^2> - per-surface R_min/R_max/Z_min/Z_max and R at each Z extremum, refined by a 3-point parabolic fit in the tracing angle - F = R*B_phi at each sampling location --------- Co-authored-by: Freddie Sheehan <f.sheehan@columbia.edu> Co-authored-by: Chris Hansen <christopher.hansen@columbia.edu>
1 parent 15bac08 commit 96f76b0

6 files changed

Lines changed: 242 additions & 50 deletions

File tree

src/physics/grad_shaf.F90

Lines changed: 98 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,8 @@ MODULE oft_gs
380380
!------------------------------------------------------------------------------
381381
type, 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)
43424344
class(gs_equil), intent(inout) :: gseq !< G-S object
43434345
integer(4), intent(in) :: nr !< Number of flux surfaces to sample
43444346
real(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)
43474349
real(8), optional, intent(out) :: rbounds(2,2) !< Radial bounds of surface `psi_q(1)` (should be LCFS)
43484350
real(8), optional, intent(out) :: zbounds(2,2) !< Vertical bounds of surface `psi_q(1)` (should be LCFS)
43494351
real(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)
43504354
real(8) :: psi_surf,rmax,x1,x2,raxis,zaxis,fpol,qpsi
43514355
real(8) :: pt(3),pt_last(3),pt_proj(3),f(3),psi_tmp(1),gop(3,3)
43524356
type(oft_lag_brinterp), target :: psi_int
43534357
real(8), pointer :: ptout(:,:)
43544358
real(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
43564360
integer(4), parameter :: nlcfs=1000
43574361
logical :: lcfs_all,lcfs_any
43584362
type(gsinv_interp), pointer :: field
@@ -4362,6 +4366,8 @@ subroutine gs_get_qprof(gseq,nr,psi_q,prof,dl,rbounds,zbounds,ravgs)
43624366
lcfs_all = PRESENT(dl).AND.PRESENT(rbounds).AND.PRESENT(zbounds)
43634367
IF(lcfs_any.AND.(.NOT.lcfs_all))CALL oft_abort('All LCFS arguments must be passed if any are','gs_get_qprof',__FILE__)
43644368
IF(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
!---
43664372
raxis=gseq%o_point(1)
43674373
zaxis=gseq%o_point(2)
@@ -4405,11 +4411,17 @@ subroutine gs_get_qprof(gseq,nr,psi_q,prof,dl,rbounds,zbounds,ravgs)
44054411
END IF
44064412
!---Trace
44074413
call 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)
44094417
ALLOCATE(field)
44104418
field%u=>gseq%psi
44114419
CALL 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
44154427
ELSE
@@ -4421,6 +4433,7 @@ subroutine gs_get_qprof(gseq,nr,psi_q,prof,dl,rbounds,zbounds,ravgs)
44214433
active_tracer%raxis=raxis
44224434
active_tracer%zaxis=zaxis
44234435
active_tracer%inv=.TRUE.
4436+
IF(PRESENT(shape_geo))ALLOCATE(ptout(3,active_tracer%maxsteps+1))
44244437
!$omp do schedule(dynamic,1)
44254438
do 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
45224561
end do
45234562
CALL active_tracer%delete
4563+
IF(ASSOCIATED(ptout))DEALLOCATE(ptout)
45244564
CALL field%delete()
45254565
DEALLOCATE(field)
45264566
!$omp end parallel
@@ -4857,7 +4897,7 @@ subroutine gsinv_apply(self,cell,f,gop,val)
48574897
integer(4), allocatable :: j(:)
48584898
integer(4) :: jc
48594899
real(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
48624902
allocate(j(self%lag_rep%nce))
48634903
call 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)
48844924
END 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)
48864937
deallocate(j)
48874938
end subroutine gsinv_apply

src/physics/grad_shaf_util.F90

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ MODULE oft_gs_util
3737
!------------------------------------------------------------------------------
3838
type, extends(gsinv_interp) :: sauter_interp
3939
logical :: stage_1 = .FALSE.
40-
real(8) :: f_surf = 0.d0
4140
real(8) :: bmax = -1.d0
4241
real(8) :: mag_axis(2) = 0.d0
4342
contains

src/python/OpenFUSIONToolkit/TokaMaker/_core.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1393,6 +1393,20 @@ def get_q(self,psi=None,psi_pad=0.02,npsi=50,compute_geo=False):
13931393
raise ValueError("Equilibrium object is `None`")
13941394
return self._tMaker_equil.get_q(psi,psi_pad,npsi,compute_geo)
13951395

1396+
def get_fsa(self,psi=None,psi_pad=0.02,npsi=50):
1397+
r'''! Get flux surface averages and per-surface shape parameters
1398+
1399+
See @ref TokaMaker.TokaMaker_equilibrium.get_fsa "get_fsa"
1400+
1401+
@param psi Explicit sampling locations in \f$\hat{\psi}\f$
1402+
@param psi_pad End padding (axis and edge) for uniform sampling (ignored if `psi` is not None)
1403+
@param npsi Number of points for uniform sampling (ignored if `psi` is not None)
1404+
@result Dictionary of flux surface profiles and scalars
1405+
'''
1406+
if self._tMaker_equil is None:
1407+
raise ValueError("Equilibrium object is `None`")
1408+
return self._tMaker_equil.get_fsa(psi,psi_pad,npsi)
1409+
13961410
def sauter_fc(self,psi=None,psi_pad=0.02,npsi=50):
13971411
r'''! Evaluate Sauter trapped particle fractions at specified or uniformly spaced points
13981412
@@ -2764,6 +2778,79 @@ def get_q(self,psi=None,psi_pad=0.02,npsi=50,compute_geo=False):
27642778
else:
27652779
return psi,qvals,ravg_dict,None,None,None
27662780

2781+
def get_fsa(self,psi=None,psi_pad=0.02,npsi=50):
2782+
r'''! Get flux surface averages and per-surface shape parameters
2783+
2784+
Traces each requested surface once and accumulates all quantities in the
2785+
tracing ODE, so every returned profile shares the same quadrature. This
2786+
extends @ref TokaMaker.TokaMaker_equilibrium.get_q "get_q" with the
2787+
averages needed to close a 1D transport formulation.
2788+
2789+
Note \f$ \left< B^2 \right> \f$ is not returned as it follows exactly from
2790+
\f$ \left< B_p^2 \right> + F^2 \left< 1/R^2 \right> \f$, since \f$ F \f$ is
2791+
constant on a flux surface.
2792+
2793+
Poloidal flux is returned in TokaMaker's convention (Wb/rad), where
2794+
\f$ B_p = \left| \nabla \psi \right| / R \f$.
2795+
2796+
@param psi Explicit sampling locations in \f$\hat{\psi}\f$
2797+
@param psi_pad End padding (axis and edge) for uniform sampling (ignored if `psi` is not None)
2798+
@param npsi Number of points for uniform sampling (ignored if `psi` is not None)
2799+
@result Dictionary of flux surface profiles and scalars
2800+
'''
2801+
if psi is None:
2802+
psi = numpy.linspace(psi_pad,1.0-psi_pad,npsi,dtype=numpy.float64)
2803+
if self.psi_convention == 0:
2804+
psi = numpy.ascontiguousarray(numpy.flip(psi), dtype=numpy.float64)
2805+
psi_save = 1.0-psi
2806+
else:
2807+
psi = numpy.ascontiguousarray(psi, dtype=numpy.float64)
2808+
if self.psi_convention == 0:
2809+
psi_save = numpy.copy(psi)
2810+
psi = numpy.ascontiguousarray(1.0-psi, dtype=numpy.float64)
2811+
if self.psi_convention != 0:
2812+
psi_save = psi
2813+
qvals = numpy.zeros((psi.shape[0],), dtype=numpy.float64)
2814+
ravgs = numpy.zeros((4,psi.shape[0]), dtype=numpy.float64)
2815+
fsa_avgs = numpy.zeros((4,psi.shape[0]), dtype=numpy.float64)
2816+
shape_geo = numpy.zeros((6,psi.shape[0]), dtype=numpy.float64)
2817+
error_string = self._oft_env.get_c_errorbuff()
2818+
tokamaker_get_fsa(self._equil_ptr,psi.shape[0],psi,qvals,ravgs,fsa_avgs,shape_geo,error_string)
2819+
if error_string.value != b'':
2820+
raise Exception(error_string.value)
2821+
# F is a flux function, so it comes straight from the source profile rather
2822+
# than the trace. The leading zero picks up the axis value, which cannot be traced.
2823+
fpol = self.get_profiles(psi=numpy.concatenate(([0.0], psi_save)))[1]
2824+
F_axis = fpol[0]
2825+
fpol = numpy.ascontiguousarray(fpol[1:])
2826+
return {
2827+
'psi_norm': psi_save,
2828+
'psi': self.psinorm_to_absolute(psi_save),
2829+
'q': qvals,
2830+
'F': fpol,
2831+
'<R>': ravgs[0,:],
2832+
'<1/R>': ravgs[1,:],
2833+
'<1/R^2>': ravgs[2,:],
2834+
'dV/dPsi': ravgs[3,:],
2835+
'<|grad psi|>': fsa_avgs[0,:],
2836+
'<|grad psi|^2>': fsa_avgs[1,:],
2837+
'<Bp^2>': fsa_avgs[2,:],
2838+
'<1/B^2>': fsa_avgs[3,:],
2839+
'R_min': shape_geo[0,:],
2840+
'R_max': shape_geo[1,:],
2841+
'Z_min': shape_geo[2,:],
2842+
'Z_max': shape_geo[3,:],
2843+
'R_at_Zmin': shape_geo[4,:],
2844+
'R_at_Zmax': shape_geo[5,:],
2845+
'psi_axis': float(self.psinorm_to_absolute(0.0)),
2846+
'psi_boundary': float(self.psinorm_to_absolute(1.0)),
2847+
'R_axis': float(self.o_point[0]),
2848+
'Z_axis': float(self.o_point[1]),
2849+
'F_axis': float(F_axis),
2850+
'F0': float(self.F0),
2851+
'diverted': bool(self.diverted),
2852+
}
2853+
27672854
def trace_surf(self,psi):
27682855
r'''! Trace surface for a given poloidal flux
27692856

src/python/OpenFUSIONToolkit/TokaMaker/_interface.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,11 @@ class tokamaker_settings_cstruct(c_struct):
151151
[c_void_p, c_int,ctypes_numpy_array(numpy.float64,1), ctypes_numpy_array(numpy.float64,1), ctypes_numpy_array(numpy.float64,2),
152152
c_double_ptr, ctypes_numpy_array(numpy.float64,2), ctypes_numpy_array(numpy.float64,2), c_char_p])
153153

154+
# tokamaker_get_fsa(tMaker_equil_ptr,npsi,psi_q,qvals,ravgs,fsa_avgs,shape_geo,error_str)
155+
tokamaker_get_fsa = ctypes_subroutine(oftpy_lib.tokamaker_get_fsa,
156+
[c_void_p, c_int,ctypes_numpy_array(numpy.float64,1), ctypes_numpy_array(numpy.float64,1), ctypes_numpy_array(numpy.float64,2),
157+
ctypes_numpy_array(numpy.float64,2), ctypes_numpy_array(numpy.float64,2), c_char_p])
158+
154159
# tokamaker_sauter_fc(tMaker_equil_ptr,npsi,psi_saut,fc,r_avgs,modb_avgs,error_str)
155160
tokamaker_sauter_fc = ctypes_subroutine(oftpy_lib.tokamaker_sauter_fc,
156161
[c_void_p, c_int,ctypes_numpy_array(numpy.float64,1), ctypes_numpy_array(numpy.float64,1), ctypes_numpy_array(numpy.float64,2),

src/python/wrappers/tokamaker_f.F90

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1227,6 +1227,23 @@ SUBROUTINE tokamaker_get_q(tMaker_equil_ptr,npsi,psi_q,qvals,ravgs,dl,rbounds,zb
12271227
END IF
12281228
END SUBROUTINE tokamaker_get_q
12291229
!---------------------------------------------------------------------------------
1230+
!> Evaluate flux surface averages, per-surface shape parameters, and q
1231+
!---------------------------------------------------------------------------------
1232+
SUBROUTINE tokamaker_get_fsa(tMaker_equil_ptr,npsi,psi_q,qvals,ravgs,fsa_avgs,shape_geo,error_str) BIND(C,NAME="tokamaker_get_fsa")
1233+
TYPE(c_ptr), VALUE, INTENT(in) :: tMaker_equil_ptr !< Pointer to TokaMaker equilibrium object
1234+
INTEGER(c_int), VALUE, INTENT(in) :: npsi !< Number of evaluation points
1235+
REAL(c_double), INTENT(in) :: psi_q(npsi) !< \f$ \psi \f$ values to compute averages at
1236+
REAL(c_double), INTENT(out) :: qvals(npsi) !< q values
1237+
REAL(c_double), INTENT(out) :: ravgs(npsi,4) !< <R>, <1/R>, <1/R^2>, and dV/dPsi
1238+
REAL(c_double), INTENT(out) :: fsa_avgs(npsi,4) !< <|grad(psi)|>, <|grad(psi)|^2>, <B_p^2>, <1/B^2>
1239+
REAL(c_double), INTENT(out) :: shape_geo(npsi,6) !< R_min, R_max, Z_min, Z_max, R(Z_min), R(Z_max)
1240+
CHARACTER(KIND=c_char), INTENT(out) :: error_str(OFT_ERROR_SLEN) !< Error string (empty if no error)
1241+
TYPE(gs_equil), POINTER :: tMaker_equil_obj
1242+
IF(.NOT.tokamaker_equil_ccast(tMaker_equil_ptr,tMaker_equil_obj,error_str))RETURN
1243+
CALL gs_get_qprof(tMaker_equil_obj,npsi,psi_q,qvals,ravgs=ravgs,fsa_avgs=fsa_avgs, &
1244+
shape_geo=shape_geo)
1245+
END SUBROUTINE tokamaker_get_fsa
1246+
!---------------------------------------------------------------------------------
12301247
!> Evaluate Sauter trapped particle fraction and related quantities
12311248
!---------------------------------------------------------------------------------
12321249
SUBROUTINE tokamaker_sauter_fc(tMaker_equil_ptr,npsi,psi_saut,fc,r_avgs,modb_avgs,error_str) BIND(C,NAME="tokamaker_sauter_fc")

0 commit comments

Comments
 (0)